diff --git a/custom/favicon.ico b/custom/favicon.ico index ed0917c..0f98af1 100644 Binary files a/custom/favicon.ico and b/custom/favicon.ico differ diff --git a/examples/roni-rony-rone/.babelrc b/examples/roni-rony-rone/.babelrc new file mode 100644 index 0000000..2b7bafa --- /dev/null +++ b/examples/roni-rony-rone/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@babel/preset-env", "@babel/preset-react"] +} diff --git a/examples/roni-rony-rone/.eslintignore b/examples/roni-rony-rone/.eslintignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/examples/roni-rony-rone/.eslintignore @@ -0,0 +1 @@ +dist diff --git a/examples/roni-rony-rone/.eslintrc.json b/examples/roni-rony-rone/.eslintrc.json new file mode 100644 index 0000000..72419cc --- /dev/null +++ b/examples/roni-rony-rone/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["airbnb", "prettier"], + "env": { + "browser": true + }, + "rules": { + "no-underscore-dangle": 0, + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }] + } +} diff --git a/examples/roni-rony-rone/.nvmrc b/examples/roni-rony-rone/.nvmrc new file mode 100644 index 0000000..a62187b --- /dev/null +++ b/examples/roni-rony-rone/.nvmrc @@ -0,0 +1 @@ +v10.16 diff --git a/examples/roni-rony-rone/build.js b/examples/roni-rony-rone/build.js new file mode 100644 index 0000000..412f906 --- /dev/null +++ b/examples/roni-rony-rone/build.js @@ -0,0 +1,172 @@ +/* eslint-disable no-console */ + +import formatAirtableRowData from "./src/utils/formatAirtableRowData"; + +require("dotenv").config(); + +const React = require("react"); +const fs = require("fs"); +const Airtable = require("airtable"); +const https = require("https"); + +const trimFieldOrder = require("./src/utils/trimFieldOrder"); +const Index = require("./src/components/Index").default; +const RowPage = require("./src/components/RowPage").default; +const tableHasPublishedColumn = require("./src/utils/tableHasPublishedColumn") + .default; + +process.env.FIELD_ORDER = trimFieldOrder(process.env.FIELD_ORDER); +process.env.HOMEPAGE_FIELD_ORDER = trimFieldOrder( + process.env.HOMEPAGE_FIELD_ORDER +); + +const renderAsHTMLPage = require(`./src/utils/renderAsHTMLPage`).default; + +const { AIRTABLE_API_KEY, BASE_ID, TABLE_ID, VIEW } = process.env; + +const base = new Airtable({ apiKey: AIRTABLE_API_KEY }).base(BASE_ID); + +const allRows = [[]]; + +const downloadFile = (url, filepath, onSuccess, onError) => { + const file = fs.createWriteStream(filepath); + https + .get(url, response => { + response.pipe(file); + file.on("finish", () => { + file.close(); + onSuccess && onSuccess(); + }); + }) + .on("error", fileErr => { + console.log(fileErr); + fs.unlink(filepath, error => onError && onError(error)); + }); +}; + +// used to make sure multiple pages aren't created for same slug +const alreadySeenSlugs = {}; + +const alreadyDownloadedAttachments = {}; + +let currentPage = 0; +let recordsOnCurrentPage = 0; +tableHasPublishedColumn(base, includePublished => + base(TABLE_ID) + .select({ + view: VIEW, + ...(includePublished ? { filterByFormula: "{Published}" } : {}) + }) + .eachPage( + function page(records, fetchNextPage) { + records.forEach(row => { + if (!allRows[currentPage]) { + allRows.push([]); + } + const formattedRow = formatAirtableRowData(row); + + const attachmentFields = formattedRow.fields.filter( + field => + Array.isArray(field.value) && + field.value[0] && + field.value[0].size + ); + + attachmentFields.forEach(attachmentField => { + attachmentField.value.forEach(attachment => { + if (!alreadyDownloadedAttachments[attachment.url]) { + const newUrl = `/assets/${attachment.id}-${attachment.filename}`; + downloadFile(attachment.url, `dist${newUrl}`); + alreadyDownloadedAttachments[attachment.url] = true; + attachment.url = newUrl; + } + }); + }); + + const slugFieldValue = row.fields.Slug; + const slug = + slugFieldValue !== undefined && !alreadySeenSlugs[slugFieldValue] + ? slugFieldValue + : formattedRow.id; + alreadySeenSlugs[slug] = true; + + const pageTitle = row.fields[process.env.PAGE_TITLE_COLUMN]; + + const filepath = `dist/${slug}.html`; + allRows[currentPage].push(formattedRow); + recordsOnCurrentPage += 1; + if (recordsOnCurrentPage >= 10) { + recordsOnCurrentPage = 0; + currentPage += 1; + } + + // write individual resource page files + fs.writeFile( + filepath, + renderAsHTMLPage(, pageTitle), + error => { + if (error) { + console.error(`Error writing ${filepath}`); + } else { + console.log(`${filepath} written`); + } + } + ); + }); + + // calls page function again while there are still pages left + fetchNextPage(); + }, + err => { + if (err) { + console.log(err); + } + + const writeFile = (idx, filepath, pagination) => + fs.writeFile( + filepath, + renderAsHTMLPage( + + ), + error => { + if (error) { + console.error(`Error writing ${filepath}`); + } + } + ); + + allRows.forEach((row, idx) => { + const pageFilepath = `dist/page/${idx + 1}.html`; + const indexFilepath = `dist/index.html`; + const pagination = { + back: idx > 0 ? `/page/${idx}.html` : null, + next: idx < allRows.length - 1 ? `/page/${idx + 2}.html` : null + }; + if (idx === 0) { + // write index page at / + writeFile(idx, indexFilepath, pagination); + } + // write page files for pagination + writeFile(idx, pageFilepath, pagination); + }); + } + ) +); + +fs.copyFile("public/default.css", "dist/main.css", () => + fs.readFile("custom/styles.css", "utf-8", (err, data) => { + fs.writeFile("dist/main.css", data, { flag: "a" }, error => { + if (error) { + console.error("Error writing custom CSS to dist/main.css"); + } else { + console.log("custom CSS appended to /dist/main.css"); + } + }); + }) +); + +fs.copyFile("custom/favicon.ico", "dist/favicon.ico", err => { + if (err) { + console.log("No favicon.ico file found"); + } +}); diff --git a/examples/roni-rony-rone/custom/favicon.ico b/examples/roni-rony-rone/custom/favicon.ico new file mode 100644 index 0000000..0f98af1 Binary files /dev/null and b/examples/roni-rony-rone/custom/favicon.ico differ diff --git a/examples/roni-rony-rone/custom/renderers/Author.js b/examples/roni-rony-rone/custom/renderers/Author.js new file mode 100644 index 0000000..0b41404 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/Author.js @@ -0,0 +1,12 @@ +import * as React from "react"; + +const Author = ({ value, name }) => { + const authors = value.join(", "); + return ( +
+

An Investigation by {authors}

+
+ ); +}; + +export default Author; diff --git a/examples/roni-rony-rone/custom/renderers/GoogleMap.js b/examples/roni-rony-rone/custom/renderers/GoogleMap.js new file mode 100644 index 0000000..fc8a228 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/GoogleMap.js @@ -0,0 +1,12 @@ +import * as React from "react"; + +const GoogleMap = ({ value, name }) => { + return ( +
+ ); +}; + +export default GoogleMap; diff --git a/examples/roni-rony-rone/custom/renderers/LedeImage.js b/examples/roni-rony-rone/custom/renderers/LedeImage.js new file mode 100644 index 0000000..da12083 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/LedeImage.js @@ -0,0 +1,10 @@ +import * as React from "react"; + +const LedeImage = attachment => { + const src = attachment.value[0].url; + return ( +
+ ); +}; + +export default LedeImage; diff --git a/examples/roni-rony-rone/custom/renderers/LedeThumb.js b/examples/roni-rony-rone/custom/renderers/LedeThumb.js new file mode 100644 index 0000000..875c463 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/LedeThumb.js @@ -0,0 +1,10 @@ +import * as React from "react"; + +const LedeThumb = attachment => { + const src = attachment.value[0].url; + return ( +
+ ); +}; + +export default LedeThumb; diff --git a/examples/roni-rony-rone/custom/renderers/LiftoffHero.js b/examples/roni-rony-rone/custom/renderers/LiftoffHero.js new file mode 100644 index 0000000..a3ac2a5 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/LiftoffHero.js @@ -0,0 +1,100 @@ +import * as React from "react"; + +const LiftoffHero = () => { + return ( +
+

{process.env.HEADER_TITLE}

+

The Pizza Examiners

+
+ + + + + + + + + + + + + + + + + +
+

+ Top-notch, totally-not-made-up pizza reviews from the heart of the Big + Apple. +

+ +

+ Built with{" "} + + Liftoff + {" "} + by your friends at{" "} + + Postlight + + . +

+
+ ); +}; + +export default LiftoffHero; diff --git a/examples/roni-rony-rone/custom/renderers/RRRSoundtrack.js b/examples/roni-rony-rone/custom/renderers/RRRSoundtrack.js new file mode 100644 index 0000000..bc6c8d3 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/RRRSoundtrack.js @@ -0,0 +1,16 @@ +import * as React from "react"; + +const RRRSountrack = ({ value, name }) => { + const className = name.replace(" ", ""); + return ( +
+

+ RRR + SOUNDTRACK +

+

{value}

+
+ ); +}; + +export default RRRSountrack; diff --git a/examples/roni-rony-rone/custom/renderers/Rating.js b/examples/roni-rony-rone/custom/renderers/Rating.js new file mode 100644 index 0000000..dc6066f --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/Rating.js @@ -0,0 +1,58 @@ +import * as React from "react"; + +const FullStar = ( + + + +); + +const EmptyStar = ( + + + +); + +const HalfStar = ( + + + +); + +const Rating = ({ name, value }) => { + const stars = []; + for (let n = 0; n < 5; n++) { + if (n >= value) { + stars.push( +
+ {EmptyStar} +
+ ); + } else { + stars.push( +
+ {FullStar} +
+ ); + } + } + return ( +
+

+ THE RRR + EVIEW: +

+
{stars}
+
+ ); +}; + +export default Rating; diff --git a/examples/roni-rony-rone/custom/renderers/ReviewCard.js b/examples/roni-rony-rone/custom/renderers/ReviewCard.js new file mode 100644 index 0000000..1c395aa --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/ReviewCard.js @@ -0,0 +1,7 @@ +import * as React from "react"; + +const ReviewCard = ({ value, name }) => { + return null; +}; + +export default ReviewCard; diff --git a/examples/roni-rony-rone/custom/renderers/SpotifyPlaylist.js b/examples/roni-rony-rone/custom/renderers/SpotifyPlaylist.js new file mode 100644 index 0000000..0ee9ba9 --- /dev/null +++ b/examples/roni-rony-rone/custom/renderers/SpotifyPlaylist.js @@ -0,0 +1,11 @@ +import * as React from "react"; + +const SpotifySoundtrack = ({ value, name }) => { + return ( +
+