Skip to content

Commit

Permalink
Merge pull request #9 from buzcarter/feature/user_added_image_support
Browse files Browse the repository at this point in the history
Feature/user added image support
  • Loading branch information
buzcarter authored Nov 11, 2023
2 parents 593bb07 + 9cc4178 commit efe7653
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 38 deletions.
6 changes: 3 additions & 3 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ function main(configs) {
readFile(resolve(templatesPath, 'index.html'), { encoding: 'utf8' }),
readFile(resolve(templatesPath, 'recipe.html'), { encoding: 'utf8' }),
])
.then(([markdownFiles, images, indexTemplate, recipeTemplate]) => {
.then(async ([markdownFiles, images, indexTemplate, recipeTemplate]) => {
markdownFiles = filterByExtension(markdownFiles, recipesPath, ['.md']);
images = filterByExtension(images, imagesPath, ['.jpg', '.jpeg', '.png', '.webp', '.avif']);

Expand All @@ -165,8 +165,8 @@ function main(configs) {
makeThumbnails(outputPath, images);
images = swapJpegForAvif(images);

buildRecipes(recipeTemplate, options, markdownFiles, images);
buildRecipeIndex(indexTemplate, options, markdownFiles, images);
const recipeInfo = await buildRecipes(recipeTemplate, options, markdownFiles, images);
buildRecipeIndex(indexTemplate, options, markdownFiles, images, recipeInfo);

const endTime = new Date();
console.log(`Processed ${markdownFiles.length} recipes in ${endTime - startTime}ms`);
Expand Down
12 changes: 12 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ module.exports = {
*/
useSmartQuotes: true,

/**
* **experimental**
* when enabled wraps `<img />` tags in link to open in new tab
*/
addImageLinks: true,

/**
* **experimental**
* when enabled looks in raw file for author's name
*/
findAuthor: true,

defaultTheme: 'default',

/**
Expand Down
13 changes: 9 additions & 4 deletions src/buildRecipeIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ const Substitutions = {
// Head's meta-tags
META_FAVICON: '{{__favIcon__}}',
META_DATE: '{{__metaDateGenerated__}}',
META_OG_IMG: '{{__metaOGImage__}}',
META_OG_IMG: '{{__metaOGImage__}}',
THEME_CSS: '{{__theme__}}',
// kickoff the on-page script
STARTUP_JS: '{{__startup__}}',
};

const Styles = Object.freeze({
AUTHOR: 'recipe-list__author',
ITEM: 'recipe-list__item',
LINK: 'recipe-list__item-link',
NAME: 'recipe-list__name',
PHOTO: 'recipe-list__photo',
});
Expand All @@ -30,7 +32,7 @@ const RegExes = {
* and generate table of contents, plus a quick-nav list
* at the top
*/
function buildRecipeIndex(indexTemplate, { defaultTheme, favicon, outputPath, initialIndexView }, fileList, images) {
function buildRecipeIndex(indexTemplate, { defaultTheme, favicon, outputPath, initialIndexView }, fileList, images, recipeInfo) {
// create anchor and name from url
let lettersIndex = '';
// create list of recipes
Expand All @@ -55,14 +57,17 @@ function buildRecipeIndex(indexTemplate, { defaultTheme, favicon, outputPath, in
if (!ogImgURL && image) {
ogImgURL = `images/${image.fileName}`;
}
const author = recipeInfo.find(({ name: infoName }) => infoName === name)?.author || '';
const imgPath = image
? `images/thumbnails/${image.name}.jpg`
: 'images/placeholder.svg';
recipeItems += `
<li${isNewLetter ? ` id="${firstLetter}"` : ''} class="${Styles.ITEM}">
<a href="${name}.html">
<a href="${name}.html" class="${Styles.LINK}">
<span class="${Styles.PHOTO}"><img src="${imgPath}" role="presentation"></span>
<span class="${Styles.NAME}">${displayName}</span>
<span class="${Styles.NAME}">${displayName}
${author ? `<em class="${Styles.AUTHOR}">${author}</em>` : ''}
</span>
</a>
</li>
`.replace(RegExes.END_SPACES, '');
Expand Down
59 changes: 37 additions & 22 deletions src/buildRecipes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const { resolve } = require('path');
const { readFile, writeFile } = require('fs');
const showdown = require('showdown');
const prettyHtml = require('pretty');
const { linkify, shorten, replaceFractions, replaceQuotes } = require('./libs/utils');
const { linkify, shorten, replaceFractions, replaceQuotes, linkifyImages, getAuthor } = require('./libs/utils');
const SectionMgr = require('./libs/SectionManager');

/* eslint-disable key-spacing */
Expand Down Expand Up @@ -132,7 +132,7 @@ const getInlineCss = heroImgURL => !heroImgURL
: `
<style>
:root {
--hero-image-url: url("../${heroImgURL}");
--hero-image-url: url("${heroImgURL}");
}
</style>
`;
Expand Down Expand Up @@ -169,10 +169,12 @@ function getHelpSection(helpURLs, name) {
`;
}

function convertRecipe(outputHTML, recipeHTML, config, name, image) {
function convertRecipe(outputHTML, recipeHTML, opts) {
const {
name,
heroImgURL: image,
autoUrlSections, defaultTheme, favicon, useFractionSymbols, helpURLs, includeHelpLinks, shortenURLs, titleSuffix,
} = config;
} = opts;
let recipeName = '';

const sectionMgr = new SectionMgr({ definedTypes: SectionTypes, defaultType: SectionTypes.NOTES });
Expand Down Expand Up @@ -235,24 +237,37 @@ function convertRecipe(outputHTML, recipeHTML, config, name, image) {
}

function buildRecipes(recipeTemplate, options, fileList, images) {
const { outputPath, useSmartQuotes } = options;

const converter = new showdown.Converter();

fileList.forEach(({ file: path, name }) => {
readFile(path, { encoding: 'utf8' }, (err, markdown) => {
if (err) {
// eslint-disable-next-line no-console
console.error(err);
return;
}
const heroImgURL = images.find(i => i.name === name);
if (useSmartQuotes) {
markdown = replaceQuotes(markdown);
}
let html = converter.makeHtml(markdown);
html = prettyHtml(convertRecipe(recipeTemplate, html, options, name, heroImgURL), { ocd: true });
writeFile(resolve(outputPath, `${name}.html`), html, { encoding: 'utf8'}, () => null);
return new Promise((promResolve) => {
let fileCount = 0;
const { addImageLinks, findAuthor, outputPath, useSmartQuotes } = options;
const converter = new showdown.Converter();
const recipeInfo = [];
fileList.forEach(({ file: path, name }) => {
readFile(path, { encoding: 'utf8' }, (err, markdown) => {
if (err) {
// eslint-disable-next-line no-console
console.error(err);
return;
}
const heroImgURL = images.find(i => i.name === name);
if (useSmartQuotes) {
markdown = replaceQuotes(markdown);
}
const author = findAuthor ? getAuthor(markdown) : '';
if (author) {
recipeInfo.push({ name, author });
}
let html = converter.makeHtml(markdown);
if (addImageLinks) {
html = linkifyImages(html);
}
html = prettyHtml(convertRecipe(recipeTemplate, html, { ...options, name, heroImgURL }), { ocd: true });
writeFile(resolve(outputPath, `${name}.html`), html, { encoding: 'utf8'}, () => {
if (fileList.length === ++fileCount) {
promResolve(recipeInfo);
}
});
});
});
});
}
Expand Down
78 changes: 78 additions & 0 deletions src/libs/__tests__/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,82 @@ describe('buildRecipes', () => {
expect(result).toBe(expectedResult);
});
});

describe('getAuthor', () => {
const { getAuthor } = utils;

it('should happy path', () => {
const Tests = [{
value: ``,
expectedResult: '',
}, {
value: `
by Amanda Berry
`,
expectedResult: 'Amanda Berry',
}, {
value: `
photo by Demples
This is from the kitchen of Harriet McCormmick hereself
from the kitchen of Aunt Bertha (my favorite auntie)
`,
expectedResult: 'Aunt Bertha',
}, {
value: `
courtesy of: Jenny
`,
expectedResult: 'Jenny',
}, {
value: `
courtesy of Derek
`,
expectedResult: 'Derek',
}, {
value: `BY Todd`,
expectedResult: 'Todd',
}, {
value: `BY the New York Times Staff`,
expectedResult: '',
}, {
value: `From the time my Unkle James was paroled`,
expectedResult: '',
}, {
value: `
courtesy of : Gavin
`,
expectedResult: 'Gavin',
}, {
value: `
courtesy of:Auntie Jim
`,
expectedResult: 'Auntie Jim',
}, {
value: `
from Mellisa Clark at the New York Times
`,
expectedResult: 'Mellisa Clark at the New York Times',
}, {
value: `
by Jeff "Handsy" Smith aka "The Frugal Gourmet" (WBEZ Chicago)
`,
expectedResult: 'Jeff "Handsy" Smith aka "The Frugal Gourmet"',
}, {
value: `
# Positively-the-Absolutely-Best-Chocolate-Chip Cookies
### From Maida Heatter
* Yield: **50** cookies.
`,
expectedResult: 'Maida Heatter'
}];

Tests.forEach(({ value, expectedResult}) => {
const result = getAuthor(value);
expect(result).toBe(expectedResult);
});
});
});
});
35 changes: 35 additions & 0 deletions src/libs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const RegExes = {
EMAIL: /(([a-zA-Z0-9\-_.])+@[a-zA-Z_]+?(\.[a-zA-Z]{2,6})+)/gim,
// #endregion

// #region LInkify Images
IMG_TAG: /<img\s+.*?src="([^"]+?)"[^>]+?>/,
IMG_ALT_PROP: /\balt="([^"]+?)"/,
// #endregion

// #region Shorten
textReg: /(?<=>)(https?:\/\/.*?)(?=<\/a>)/,
simpleDomain: /((?:[\w-]+\.)+[\w-]+)/,
Expand All @@ -16,8 +21,17 @@ const RegExes = {
// #region replaceFractions
FRACTIONS: /(1\/[2-9]|2\/[35]|3\/[458]|4\/5|5\/[68])|7\/8/g,
// #endregion

// #region Find Author Credit
AUTHOR: /^(?:#{3,6})?\s*(?:by|courtesy of|from(?: the)? kitchen of|from)\s*[ :-]\s*([A-Z][\w "]+)/im
// #endregion
};

const Styles = Object.freeze({
LINKED_IMG: 'img-link',
JS_LINKED_IMG: 'js-img-link',
});

const FractionsHash = Object.freeze({
'1/2': '½',
'1/3': '⅓',
Expand Down Expand Up @@ -46,6 +60,25 @@ const linkify = value => value
.replace(RegExes.URL_WITH_WWW, '$1<a href="http://$2">$2</a>')
.replace(RegExes.EMAIL, '<a href="mailto:$1">$1</a>');

/**
* Violating one of my goals to keep this "pure" -- all front-end
* aganostic, but should apply a class for future me to tinker.
*/
const linkifyImages = text => text
.replace(new RegExp(RegExes.IMG_TAG, 'gm'), (imgTag) => {
const [, src] = imgTag.match(RegExes.IMG_TAG);
const [, alt] = imgTag.match(RegExes.IMG_ALT_PROP) || [];
return `<a href="${src}" target="_blank" class="${Styles.LINKED_IMG} ${Styles.JS_LINKED_IMG}" title="${alt || ''}">${imgTag}</a>`;
});

const getAuthor = (text) => {
let [, author] = `${text}`.match(RegExes.AUTHOR) || [];
if (!author) {
return '';
}
author = author.trim();
return /[A-Z]/.test(author[0]) ? author : '';
};
/**
* Replaces complete URL with only the domain, i.e. strips
* off path & protocol.
Expand All @@ -65,7 +98,9 @@ const replaceFractions = value => value
const replaceQuotes = value => value.replace(/(?<!=)"([^"\n>]+)"(?=[\s<])/g, '&ldquo;$1&rdquo;');

module.exports = {
getAuthor,
linkify,
linkifyImages,
replaceFractions,
replaceQuotes,
shorten,
Expand Down
Loading

0 comments on commit efe7653

Please sign in to comment.