From cf52d77671cb08b3dc3f4eff3408a09837336832 Mon Sep 17 00:00:00 2001 From: Buz Carter Date: Fri, 24 Nov 2023 11:37:21 -0800 Subject: [PATCH 01/10] add Author to page title --- src/buildRecipes.js | 10 +++++----- src/libs/utils.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/buildRecipes.js b/src/buildRecipes.js index de886c4..9e68f0c 100644 --- a/src/buildRecipes.js +++ b/src/buildRecipes.js @@ -90,9 +90,9 @@ const RegExes = Object.freeze({ const LINK_SUB_NAME = ''; -function setHeadMeta(documentHtml, { favicon, ogImgURL, recipeName, titleSuffix }) { +function setHeadMeta(documentHtml, { author, favicon, ogImgURL, recipeName, titleSuffix }) { return documentHtml - .replace(RegExes.PAGE_TITLE, `${recipeName}${titleSuffix || ''}`) + .replace(RegExes.PAGE_TITLE, `${recipeName}${author ? ` by ${author}` : ''}${titleSuffix || ''}`) .replace(Substitutions.META_DATE, ``) .replace(Substitutions.META_OG_IMG, ogImgURL ? `` : '') .replace(Substitutions.META_FAVICON, favicon ? `` : '') @@ -175,7 +175,7 @@ function getHelpSection(helpURLs, name) { function convertRecipe(outputHTML, recipeHTML, opts) { const { - name, + author, name, heroImgURL: image, autoUrlSections, defaultTheme, favicon, useFractionSymbols, helpURLs, includeHelpLinks, shortenURLs, titleSuffix, } = opts; @@ -227,7 +227,7 @@ function convertRecipe(outputHTML, recipeHTML, opts) { outputHTML = sectionMgr.replace(outputHTML); - return setHeadMeta(outputHTML, { favicon, ogImgURL: heroImgURL, recipeName, titleSuffix }) + return setHeadMeta(outputHTML, { author, favicon, ogImgURL: heroImgURL, recipeName, titleSuffix }) .replace(Substitutions.HELP, showHelp ? getHelpSection(helpURLs, name) : '') .replace(Substitutions.HERO_IMG, heroImgURL ? `` : '') .replace(Substitutions.THEME_CSS, `theme-${customizations.style || defaultTheme}`) @@ -267,7 +267,7 @@ export default function buildRecipes(recipeTemplate, options, fileList, images) if (addImageLinks) { html = linkifyImages(html); } - html = prettyHtml(convertRecipe(recipeTemplate, html, { ...options, name, heroImgURL }), { ocd: true }); + html = prettyHtml(convertRecipe(recipeTemplate, html, { ...options, author, heroImgURL, name }), { ocd: true }); writeFile(resolve(outputPath, `${name}.html`), html, { encoding: 'utf8' }, () => { if (fileList.length === ++fileCount) { promResolve(recipeInfo); diff --git a/src/libs/utils.js b/src/libs/utils.js index c0de3cd..3721f76 100644 --- a/src/libs/utils.js +++ b/src/libs/utils.js @@ -23,7 +23,7 @@ const RegExes = { // #endregion // #region Find Author Credit - AUTHOR: /^(?:#{3,6})?\s*(?:recipe )?(?:adapted by|author|by|courtesy(?: of)?|from(?: the)? kitchen of|from|source)\s*[ :-]\s*([A-Z][\w '"]+)/im, + AUTHOR: /^(?:#{3,6})?\s*(?:recipe )?(?:adapted (?:by|from)|author|by|courtesy(?: of)?|from(?: the)? kitchen of|from|source)\s*[ :-]\s*([A-Z][\w '"]+)/im, // #endregion }; From 346cf75fce56a70f48f8ecf4efa2fb64e6f2d234 Mon Sep 17 00:00:00 2001 From: Buz Carter Date: Sat, 18 Nov 2023 14:05:14 -0800 Subject: [PATCH 02/10] stub Search History & Recently Viewed lists --- src/static/scripts/index.js | 4 +++- src/static/scripts/libs/preferences.js | 28 ++++++++++++++++++++++- src/static/scripts/libs/recentlyViewed.js | 25 ++++++++++++++++++++ src/static/scripts/libs/searchBox.js | 16 +++++++++++-- src/static/scripts/libs/utils.js | 8 +++++++ 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/static/scripts/libs/recentlyViewed.js create mode 100644 src/static/scripts/libs/utils.js diff --git a/src/static/scripts/index.js b/src/static/scripts/index.js index 63a456f..1538877 100644 --- a/src/static/scripts/index.js +++ b/src/static/scripts/index.js @@ -1,5 +1,6 @@ -import { init as initViewBtns } from './libs/viewPicker.js'; +import { init as initRecentlyViewed } from './libs/recentlyViewed.js'; import { init as initSearchBox } from './libs/searchBox.js'; +import { init as initViewBtns } from './libs/viewPicker.js'; import { KeyNames, getKey } from './libs/preferences.js'; function run() { @@ -8,6 +9,7 @@ function run() { initSearchBox(search); initViewBtns(view); + initRecentlyViewed(); } run(); diff --git a/src/static/scripts/libs/preferences.js b/src/static/scripts/libs/preferences.js index 062763e..b56f600 100644 --- a/src/static/scripts/libs/preferences.js +++ b/src/static/scripts/libs/preferences.js @@ -3,8 +3,10 @@ const StorageKeys = { }; export const KeyNames = Object.freeze({ - VIEW: 'content', + RECENT: 'recent', SEARCH: 'search', + SEARCH_HISTORY: 'searchHistory', + VIEW: 'content', }); const setAppData = (payload) => localStorage.setItem(StorageKeys.APP_NAME, JSON.stringify(payload)); @@ -34,3 +36,27 @@ export const getKey = (key, defaultValue) => { const value = payload[key]; return value === undefined ? defaultValue : value; }; + +/** + * Add `value` in first position of list named `key`. When list length + * exceeds `maxLength` last item is discarded. + */ +export const updateMRUList = (key, maxLength, value) => { + let list = getKey(key, []); + const index = list.indexOf(value); + if (index === 0) { + return; + } + + if (index > -1) { + list.splice(index, 1); + } + + list.unshift(value); + + if (list.length > maxLength) { + list = list.slice(0, maxLength - 1); + } + + updateKey(key, list); +}; diff --git a/src/static/scripts/libs/recentlyViewed.js b/src/static/scripts/libs/recentlyViewed.js new file mode 100644 index 0000000..029f256 --- /dev/null +++ b/src/static/scripts/libs/recentlyViewed.js @@ -0,0 +1,25 @@ +import { KeyNames, updateMRUList } from './preferences.js'; + +const MAX_LIST_LENGTH = 50; + +const Selectors = { + RECIPE_LIST: '#recipe-list', +}; + +function onClick(e) { + const anchor = e.target.tagName === 'A' + ? e.target + : e.target.closest('a'); + + if (anchor) { + const value = anchor.getAttribute('href'); + if (value) { + updateMRUList(KeyNames.RECENT, MAX_LIST_LENGTH, value.replace(/^.*\/|\.html$/g, '')); + } + } +} + +export function init() { + // event delegation: record all recipe link clicks + document.querySelector(Selectors.RECIPE_LIST).addEventListener('click', onClick); +} diff --git a/src/static/scripts/libs/searchBox.js b/src/static/scripts/libs/searchBox.js index b8bfae9..f9a76d7 100644 --- a/src/static/scripts/libs/searchBox.js +++ b/src/static/scripts/libs/searchBox.js @@ -1,4 +1,5 @@ -import { KeyNames, updateKey } from './preferences.js'; +import { KeyNames, updateKey, updateMRUList } from './preferences.js'; +import { debounce } from './utils.js'; /* eslint-disable key-spacing */ const Styles = { @@ -15,7 +16,10 @@ const Selectors = { const KeyCodes = { ESCAPE: 27, }; - /* eslint-enable key-spacing */ +/* eslint-enable key-spacing */ + +const MAX_LIST_LENGTH = 8; +const HISTORY_DEBOUNCE_DELAY = 2.25 * 1000; const scrub = (value) => value .trim() @@ -37,6 +41,13 @@ function filter(filterText) { }); } +const updateHistory = () => { + const { value } = document.querySelector(Selectors.SEARCH); + if (value.trim()) { + updateMRUList(KeyNames.SEARCH_HISTORY, MAX_LIST_LENGTH, value); + } +}; + const clearInput = () => { const input = document.querySelector(Selectors.SEARCH); input.value = ''; @@ -61,6 +72,7 @@ export function init(initalValue) { updateKey(KeyNames.SEARCH, this.value); filter(this.value); }); + input.addEventListener('keyup', debounce(updateHistory, HISTORY_DEBOUNCE_DELAY)); input.addEventListener('keydown', (e) => e.which === KeyCodes.ESCAPE && clearInput()); diff --git a/src/static/scripts/libs/utils.js b/src/static/scripts/libs/utils.js new file mode 100644 index 0000000..6c72278 --- /dev/null +++ b/src/static/scripts/libs/utils.js @@ -0,0 +1,8 @@ +export const debounce = (func, delay) => { + let timeoutId; + + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => func.apply(this, args), delay); + }; +}; From 31229d5247b808fe5345317ffd489506e53388f9 Mon Sep 17 00:00:00 2001 From: Buz Carter Date: Fri, 24 Nov 2023 16:08:30 -0800 Subject: [PATCH 03/10] add some UI --- src/static/scripts/libs/searchBox.js | 40 +++++++++++++++++----- src/static/styles/recipesIndex.css | 51 +++++++++++++++++++++++++--- src/templates/index.html | 4 ++- 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/static/scripts/libs/searchBox.js b/src/static/scripts/libs/searchBox.js index f9a76d7..b57e95d 100644 --- a/src/static/scripts/libs/searchBox.js +++ b/src/static/scripts/libs/searchBox.js @@ -1,5 +1,4 @@ -import { KeyNames, updateKey, updateMRUList } from './preferences.js'; -import { debounce } from './utils.js'; +import { KeyNames, getKey, updateKey, updateMRUList } from './preferences.js'; /* eslint-disable key-spacing */ const Styles = { @@ -7,10 +6,11 @@ const Styles = { }; const Selectors = { - RECIPE_LIST: '#recipe-list', - RECIPE_ITEMS: '#recipe-list li', - SEARCH: '#filter-field', - CLEAR_BTN: '#clear-filter-btn', + RECIPE_LIST: '#recipe-list', + RECIPE_ITEMS: '#recipe-list li', + SEARCH: '#filter-field', + CLEAR_BTN: '#clear-filter-btn', + SEARCH_HISTORY: '#filter-suggestions', }; const KeyCodes = { @@ -19,7 +19,6 @@ const KeyCodes = { /* eslint-enable key-spacing */ const MAX_LIST_LENGTH = 8; -const HISTORY_DEBOUNCE_DELAY = 2.25 * 1000; const scrub = (value) => value .trim() @@ -41,13 +40,34 @@ function filter(filterText) { }); } +function updateHistoryUL() { + const list = getKey(KeyNames.SEARCH_HISTORY, []); + const listEle = document.querySelector(Selectors.SEARCH_HISTORY); + // TODO: unsafe HTML entities + listEle.innerHTML = list.reduce((acc, value) => `${acc}
  • ${value}
  • `, ''); + listEle.dataset.count = list.length || ''; +} + const updateHistory = () => { const { value } = document.querySelector(Selectors.SEARCH); if (value.trim()) { updateMRUList(KeyNames.SEARCH_HISTORY, MAX_LIST_LENGTH, value); + updateHistoryUL(); } }; +const onHistoryClick = (e) => { + if (e.target.tagName !== 'LI') { + return; + } + + const value = e.target.innerText; + document.querySelector(Selectors.SEARCH).value = value; + updateKey(KeyNames.SEARCH, value); + filter(value); + updateHistory(); +}; + const clearInput = () => { const input = document.querySelector(Selectors.SEARCH); input.value = ''; @@ -72,9 +92,13 @@ export function init(initalValue) { updateKey(KeyNames.SEARCH, this.value); filter(this.value); }); - input.addEventListener('keyup', debounce(updateHistory, HISTORY_DEBOUNCE_DELAY)); input.addEventListener('keydown', (e) => e.which === KeyCodes.ESCAPE && clearInput()); document.querySelector(Selectors.CLEAR_BTN).addEventListener('click', clearInput); + + // wire-up search history + input.addEventListener('focusout', updateHistory); + document.querySelector(Selectors.SEARCH_HISTORY).addEventListener('click', onHistoryClick); + updateHistoryUL(); } diff --git a/src/static/styles/recipesIndex.css b/src/static/styles/recipesIndex.css index 890dfb3..10143b1 100644 --- a/src/static/styles/recipesIndex.css +++ b/src/static/styles/recipesIndex.css @@ -8,10 +8,13 @@ --color-filter-hover: var(--color-neutral-80); --color-filter-focus: var(--color-neutral-50); - --search-input-height: 40px; - --search-icon-length: 18px; + --default-border-radius: 4px; --small-btn-length: 24px; + + --search-input-height: 40px; + --search-icon-length: 18px; + --search-input-max-width: calc(14em + var(--small-btn-length) + var(--search-icon-length)); } .mainTitle { @@ -58,7 +61,7 @@ html body .nav-view__item { .nav-view__btn { background-color: transparent; border: solid 1px var(--color-view-btn-inactive); - border-radius: 4px; + border-radius: var(--default-border-radius); padding: 0; display: inline-block; width: 26px; @@ -120,7 +123,7 @@ html body .nav-view__item { .filter__text-field:not(:placeholder-shown), .filter__text-field:focus { border-color: var(--color-filter-focus); - width: calc(14em + var(--small-btn-length) + var(--search-icon-length)); + width: var(--search-input-max-width); } .filter__icon { @@ -143,7 +146,7 @@ html body .nav-view__item { .filter__cancel-btn { border: solid 1px var(--color-filter-focus); - border-radius: 4px; + border-radius: var(--default-border-radius); cursor: pointer; display: none; height: var(--small-btn-length); @@ -171,6 +174,44 @@ html body .nav-view__item { fill: var(--color-filter-focus); } +.filter__suggestions { + background-color: var(--color-neutral-00); + border-radius: var(--default-border-radius); + border: solid 1px var(--color-filter-hover); + box-shadow: 5px 8px 8px rgba(0,0,0,.2); + display: none; + left: var(--small-btn-length); + overflow: hidden; + padding: 5px 0; + position: absolute; + --suggestions-max-width: calc(var(--search-input-max-width) - 2 * var(--small-btn-length)); + min-width: calc(var(--suggestions-max-width) / 2); + max-width: var(--suggestions-max-width); +} + +.filter__suggestions li { + padding: 6px 16px; + border-bottom: solid 1px var(--color-neutral-20); + text-overflow: ellipsis; + max-width: 100%; + text-wrap: nowrap; + overflow: hidden; + cursor: pointer; +} + +.filter__suggestions li:last-child { + border-bottom: none; +} + +.filter__suggestions li:hover { + color: var(--color-primary); +} + +.filter__suggestions:not([data-count]:empty):hover, +.filter__text-field:focus~.filter__suggestions:not([data-count]:empty) { + display: block; +} + /* oops */ .recipe-list__photo { display: none; diff --git a/src/templates/index.html b/src/templates/index.html index 4e25e1c..3fdb53d 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -32,7 +32,7 @@

    Jump To Recipes

    From d69621fe974e9584ad4f1e17d7e3a157b894d8a2 Mon Sep 17 00:00:00 2001 From: Buz Carter Date: Fri, 24 Nov 2023 17:13:20 -0800 Subject: [PATCH 04/10] Show Recents --- src/static/styles/recipesIndex.css | 12 ++++++++++++ src/templates/index.html | 1 + 2 files changed, 13 insertions(+) diff --git a/src/static/styles/recipesIndex.css b/src/static/styles/recipesIndex.css index 10143b1..c47c3fb 100644 --- a/src/static/styles/recipesIndex.css +++ b/src/static/styles/recipesIndex.css @@ -100,6 +100,18 @@ html body .nav-view__item { width: 24px; } +/* */ +.btn__show-recent { + position: absolute; + top: 10px; + left: 10px; + width: 25px; + height: 25px; + background-color: transparent; + border: none; + cursor: pointer; +} + /* Filter/Search */ .filter__wrap { float: right; diff --git a/src/templates/index.html b/src/templates/index.html index 3fdb53d..77de9a1 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -28,6 +28,7 @@

    Recipe Book

    Jump To Recipes

    {{__letters-index__}} +