Skip to content

Commit

Permalink
added support for loading google doc page
Browse files Browse the repository at this point in the history
  • Loading branch information
xdamman committed Dec 10, 2020
1 parent 8971cb1 commit d3da811
Show file tree
Hide file tree
Showing 18 changed files with 12,803 additions and 4,339 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,5 @@ dist

.now
.DS_Store

.vercel
16 changes: 16 additions & 0 deletions components/ErrorNotPublished.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const ErrorNotPublished = ({ googleDocId }) => (
<div className="mx-auto w-4/6">
<h2>This Google Doc hasn't been published yet by the author</h2>
<p>
<a
href={`https://docs.google.com/document/d/${googleDocId}/edit`}
target="_blank"
>
Open the document
</a>{" "}
then go to the file menu and click on "Publish to the web".
</p>
</div>
);

export default ErrorNotPublished;
16 changes: 16 additions & 0 deletions components/Footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const Footer = ({ googleDocId }) => (
<div className="footer mt-8 border-t border-gray-300 flex flex-row justify-between items-center w-screen max-w-screen-md mx-auto p-3">
<div></div>
<div>
<a
href={`https://docs.google.com/document/d/${googleDocId}/edit`}
target="_blank"
className="text-gray-600"
>
Edit Page 📝
</a>
</div>
</div>
);

export default Footer;
30 changes: 30 additions & 0 deletions components/RenderGoogleDoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import Image from "next/image";
import ReactDOM from "react-dom";

class RenderGoogleDoc extends React.Component {
prepare(ref) {
if (!ref || !ref.querySelectorAll) return;
const images = Array.from(ref.querySelectorAll("img"));
images.forEach((img) => {
ReactDOM.render(
<Image
src={img.src}
width={img.width}
height={img.height}
layout="intrinsic"
/>,
img.parentNode
);
});
}

render() {
const { html } = this.props;
return (
<div ref={this.prepare} dangerouslySetInnerHTML={{ __html: html }} />
);
}
}

export default RenderGoogleDoc;
26 changes: 26 additions & 0 deletions components/YoutubeEmbed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default ({ id }) => {
return (
<div
className="video full-width"
style={{
position: "relative",
paddingBottom: "56.25%" /* 16:9 */,
paddingTop: 0.25,
height: 0,
}}
>
<iframe
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
}}
src={`https://www.youtube.com/embed/${id}`}
frameBorder="0"
allowFullScreen
/>
</div>
);
};
108 changes: 108 additions & 0 deletions lib/googledoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import fetch from "node-fetch";
import cheerio from "cheerio";
import { renderToString } from "react-dom/server";
import XMLToReact from "@condenast/xml-to-react";
import YouTubeEmbed from "../components/YouTubeEmbed";

function cleanHTML(html) {
return html
.replace(
/<a [^>]*>https?:\/\/(www\.)?(youtu.be\/|youtube.com\/(embed\/|watch\?v=))([a-z0-9_-]{11})[^<]*<\/a>/gi,
(match, p1, p2, p3, p4) => `<YouTube id="${p4}" />`
)
.replace(/<img ([^>]+)>/gi, "<img $1 />")
.replace(/ (alt|title)=""/gi, "")
.replace(/<hr style="page-break[^"]+">/gi, '<div class="pagebreak" />');
}

function convertDomToReactTree(xml) {
function Image({ children, i, src, style }) {
const size = style.match(/width: ([0-9]+).*height: ([0-9]+)/i);
const img = <img src={src} width={size[1]} height={size[2]} />;
if (size[1] > 500) {
return <center>{img}</center>;
}
return img;
}

const xmlToReact = new XMLToReact({
html: () => ({ type: "html" }),
body: () => ({ type: "body" }),
// style: (attrs) => ({ type: "style", props: attrs }),
div: (attrs) => ({ type: "div", props: { className: attrs.class } }),
span: (attrs) => ({ type: "span", props: { className: attrs.class } }),
a: (attrs) => ({
type: "a",
props: { className: attrs.class, href: attrs.href },
}),
p: (attrs) => ({ type: "p", props: { className: attrs.class } }),
h1: (attrs) => ({ type: "h1", props: { className: attrs.class } }),
h2: (attrs) => ({ type: "h2", props: { className: attrs.class } }),
h3: (attrs) => ({ type: "h3", props: { className: attrs.class } }),
h4: (attrs) => ({ type: "h4", props: { className: attrs.class } }),
ul: (attrs) => ({ type: "ul", props: { className: attrs.class } }),
ol: (attrs) => ({ type: "ol", props: { className: attrs.class } }),
li: (attrs) => ({ type: "li", props: { className: attrs.class } }),
YouTube: (attrs) => ({ type: YouTubeEmbed, props: { id: attrs.id } }),
img: (attrs) => ({ type: Image, props: attrs }),
});
return xmlToReact.convert(xml);
}

const processHTML = (htmlText) => {
if (htmlText.indexOf("#email-display") !== -1) {
return "not_published";
}

const $ = cheerio.load(cleanHTML(htmlText), { xmlMode: true });
const styles = $("#contents style").html();
const newStyles =
typeof styles === "string"
? styles.replace(/([^{]+){([^}]+)}/gi, (matches, selector, style) => {
if (selector.match(/\.c[0-9]+/)) {
// return matches;
return matches
.replace(/font-family:[^;}]+;?/gi, "")
.replace(/line-height:[^;}]+;?/gi, "")
.replace(
/(margin|padding)(-(top|right|bottom|left))?:[^};]+\;?/gi,
""
);
} else return "";
})
: "";

$("a").each((i, e) => {
const href = $(e).attr("href");
let newValue = decodeURIComponent(
$(e)
.attr("href")
.replace("https://www.google.com/url?q=", "")
.replace(/&sa=D&ust=[0-9]+&usg=.{28}/, "")
);

if (href) {
const matches = href.match(
/https:\/\/docs.google.com\/document\/d\/(.{44})/i
);
if (href.indexOf("docs.google.com/document/d/") !== -1) {
newValue = `/${matches[1]}`;
}
$(e).attr("href", newValue);
}
});
const xml = $("<div/>").append($("#contents")).html();

const tree = convertDomToReactTree(xml);
return `<style>${newStyles}</style>${renderToString(tree)}`;
};

export async function getHTMLFromGoogleDocId(docid) {
const googledoc = `https://docs.google.com/document/d/${docid}`;
const res = await fetch(`${googledoc}/pub`);
console.log(">>> loading google doc", googledoc);

const htmlText = await res.text();
const html = processHTML(htmlText);
return html;
}
16 changes: 13 additions & 3 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
module.exports = {
env: {
OC_GRAPHQL_API: 'https://api.opencollective.com/graphql/v1/'
}
};
OC_GRAPHQL_API: "https://api.opencollective.com/graphql/v1/",
},
images: {
domains: [
"lh1.googleusercontent.com",
"lh2.googleusercontent.com",
"lh3.googleusercontent.com",
"lh4.googleusercontent.com",
"lh5.googleusercontent.com",
"lh6.googleusercontent.com",
],
},
};
Loading

0 comments on commit d3da811

Please sign in to comment.