refactor
This commit is contained in:
@ -1,13 +0,0 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Generates valid dom elements from a string
|
||||
* @param {string} htmlString
|
||||
*/
|
||||
export const generateDomFromString = (htmlString) =>
|
||||
/** @type {HTMLElement} */ (
|
||||
new DOMParser().parseFromString(`<div>${htmlString}</div>`, "text/html")
|
||||
.firstChild
|
||||
);
|
||||
|
||||
export default generateDomFromString;
|
@ -1,51 +1,5 @@
|
||||
//@ts-check
|
||||
|
||||
import { changeTitle } from "./changeTitle.mjs";
|
||||
import { documentMode } from "./documentMode.mjs";
|
||||
import { fetchMarkdown } from "./fetchMarkdown.mjs";
|
||||
import { fetchText } from "./fetchText.mjs";
|
||||
import { generateDomFromString } from "./generateDomFromString.mjs";
|
||||
import { getCurrentHashUrl } from "./getCurrentHashUrl.mjs";
|
||||
import { getElementById } from "./getElementById.mjs";
|
||||
import { isExternalUrl } from "./isExternalUrl.mjs";
|
||||
import { isLocalHost } from "./isLocalHost.mjs";
|
||||
import { isNotNull } from "./isNotNull.mjs";
|
||||
import {
|
||||
onDocumentKeyUp,
|
||||
onDocumentKeyDown,
|
||||
onDocumentKey,
|
||||
} from "./onDocumentKey.mjs";
|
||||
import { parseFileList } from "./parseFileList.mjs";
|
||||
import {
|
||||
querySelectorDoc,
|
||||
querySelectorParent,
|
||||
querySelectorAll,
|
||||
} from "./querySelectorAll.mjs";
|
||||
import { rewriteLocalUrls } from "./rewriteLocalUrls.mjs";
|
||||
import * as templateBlog from "./templateBlogUtils.mjs";
|
||||
import { wait } from "./wait.mjs";
|
||||
import { waitIfLocalHost } from "./waitIfLocalHost.mjs";
|
||||
|
||||
export {
|
||||
changeTitle,
|
||||
documentMode,
|
||||
fetchMarkdown,
|
||||
fetchText,
|
||||
generateDomFromString,
|
||||
getCurrentHashUrl,
|
||||
getElementById,
|
||||
isExternalUrl,
|
||||
isLocalHost,
|
||||
isNotNull,
|
||||
onDocumentKeyUp,
|
||||
onDocumentKeyDown,
|
||||
onDocumentKey,
|
||||
parseFileList,
|
||||
querySelectorDoc,
|
||||
querySelectorParent,
|
||||
querySelectorAll,
|
||||
rewriteLocalUrls,
|
||||
templateBlog,
|
||||
wait,
|
||||
waitIfLocalHost,
|
||||
};
|
||||
export * from './tickle/index.mjs'
|
||||
export * from './utils/index.mjs'
|
||||
export { default as blog } from './templateBlog.mjs'
|
||||
export { default as musings } from './templateMusings.mjs'
|
@ -1,40 +0,0 @@
|
||||
//@ts-check
|
||||
|
||||
import { isNotNull } from "./isNotNull.mjs";
|
||||
|
||||
/**
|
||||
* parses a filelist string. That's a string that looks like
|
||||
* ```md
|
||||
* name dd-mm-yyyy link name
|
||||
* name dd-mm-yyyy
|
||||
* name linkname
|
||||
* name
|
||||
* ```
|
||||
*
|
||||
* @param {string} lines
|
||||
*/
|
||||
export const parseFileList = (lines) =>
|
||||
// @ts-ignore
|
||||
lines
|
||||
.trim()
|
||||
.split(`\n`)
|
||||
.map((line, index) => {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const result = line.match(
|
||||
/(?<name>.+)\.(?<ext>\w{2,3})(?:\s+(?<date>[\d-]+)?(?<title>.+))?/
|
||||
);
|
||||
if (!result) {
|
||||
console.error(`could not parse line number ${index}`);
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
// @ts-ignore
|
||||
groups: { name, ext, date = today, title = name },
|
||||
} = result;
|
||||
const href = `/${name}.${ext}`;
|
||||
const date_unix = new Date(date).getTime();
|
||||
return { name, href, date, title, date_unix, index };
|
||||
})
|
||||
.filter(isNotNull);
|
||||
|
||||
export default parseFileList;
|
@ -1,4 +1,38 @@
|
||||
//@ts-check
|
||||
import { fetchText } from "./utils/fetchText.mjs";
|
||||
import { getElementById } from "./utils/getElementById.mjs";
|
||||
import { onDocumentKeyUp } from "./utils/onDocumentKey.mjs";
|
||||
import { parseFileList } from "./tickle/parseFileList.mjs";
|
||||
import {createMenuEntriesFromFileList} from "./tickle/createMenuEntriesFromFileList.mjs"
|
||||
import { sortFileListLines } from "./tickle/sortFileListLines.mjs"
|
||||
import { mode } from "./tickle/mode.mjs";
|
||||
import {bootstrapRouter} from "./tickle/bootstrapRouter.mjs";
|
||||
|
||||
import blog from "./templateBlogUtils.mjs";
|
||||
blog();
|
||||
/**
|
||||
* Loads the article list, parses it, creates the menu items
|
||||
*/
|
||||
export const bootstrap = () => {
|
||||
|
||||
const [Menu, Body, Source, Burger] = ["Menu", "Body", "Source", "Burger"].map(
|
||||
getElementById
|
||||
);
|
||||
|
||||
Burger.addEventListener("click", mode.menuOpen.toggle);
|
||||
|
||||
mode.loading.on();
|
||||
fetchText("./files.txt").then((lines)=>{
|
||||
const links = parseFileList(lines)
|
||||
const firstHref = links[0].href;
|
||||
sortFileListLines(links)
|
||||
Menu.appendChild(createMenuEntriesFromFileList(links))
|
||||
bootstrapRouter(firstHref, (content, raw)=>{
|
||||
Body.innerHTML = "";
|
||||
Source.innerHTML = raw;
|
||||
Body.appendChild(content);
|
||||
})
|
||||
});
|
||||
|
||||
onDocumentKeyUp("?", mode.sourceEnable.toggle);
|
||||
};
|
||||
|
||||
export default bootstrap;
|
||||
|
@ -1,107 +0,0 @@
|
||||
//@ts-check
|
||||
import { changeTitle } from "./changeTitle.mjs";
|
||||
import { getCurrentHashUrl } from "./getCurrentHashUrl.mjs";
|
||||
import { fetchMarkdown } from "./fetchMarkdown.mjs";
|
||||
import { fetchText } from "./fetchText.mjs";
|
||||
import { parseFileList } from "./parseFileList.mjs";
|
||||
import { documentMode } from "./documentMode.mjs";
|
||||
import { getElementById } from "./getElementById.mjs";
|
||||
import { onDocumentKeyUp } from "./onDocumentKey.mjs";
|
||||
|
||||
/*****************************************************************
|
||||
*
|
||||
* Creating references to the important stuff
|
||||
*
|
||||
****************************************************************/
|
||||
|
||||
/**
|
||||
* The elements we will need
|
||||
*/
|
||||
const [Menu, Body, Source, Burger] = ["Menu", "Body", "Source", "Burger"].map(
|
||||
getElementById
|
||||
);
|
||||
|
||||
const loadingMode = documentMode("loading");
|
||||
const sourceEnableMode = documentMode("source-enabled");
|
||||
const menuOpenMode = documentMode("menu-open");
|
||||
|
||||
/*****************************************************************
|
||||
*
|
||||
* Router
|
||||
*
|
||||
* Things related to main navigation and to loading pages
|
||||
*
|
||||
****************************************************************/
|
||||
|
||||
/**
|
||||
* Listens to hashchange event, and attempts to read the url.
|
||||
* @param {HashChangeEvent} [_evt]
|
||||
*/
|
||||
export const onHashChange = async (_evt) => {
|
||||
const { path, params } = getCurrentHashUrl();
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
loadingMode.on();
|
||||
const { title, raw, content } = await fetchMarkdown(path);
|
||||
changeTitle(title);
|
||||
Body.innerHTML = "";
|
||||
Source.innerHTML = raw;
|
||||
Body.appendChild(content);
|
||||
loadingMode.off();
|
||||
};
|
||||
|
||||
export const loadFileList = () => {
|
||||
loadingMode.on();
|
||||
|
||||
fetchText("./files.txt").then(fillMenuFromFileList);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the file list is obtained (presumably through loading)
|
||||
* parses the file list, then fills the side navigation
|
||||
* If there's no page loaded, it also loads the first page in the list
|
||||
* (the list gets sorted by date, but the first line is the one that gets used)
|
||||
* @param {string} lines
|
||||
*/
|
||||
export const fillMenuFromFileList = (lines) => {
|
||||
const links = parseFileList(lines).sort(
|
||||
({ date_unix: a }, { date_unix: b }) => a - b
|
||||
);
|
||||
if (links.length < 1) {
|
||||
return;
|
||||
}
|
||||
Menu.innerHTML = links
|
||||
.map(({ href, title }) => `<a data-link href="#${href}">${title}</a>`)
|
||||
.join(`\n`);
|
||||
if (!getCurrentHashUrl().path) {
|
||||
// @ts-ignore
|
||||
const href = links.find(({ index }) => index === 0).href;
|
||||
window.location.hash = `#${href}`;
|
||||
} else {
|
||||
onHashChange();
|
||||
}
|
||||
};
|
||||
|
||||
/*****************************************************************
|
||||
*
|
||||
* Bootstrapping
|
||||
*
|
||||
* this is where things actually happen
|
||||
*
|
||||
****************************************************************/
|
||||
|
||||
/**
|
||||
* Loads the article list, parses it, creates the menu items
|
||||
*/
|
||||
export const bootstrap = () => {
|
||||
Burger.addEventListener("click", menuOpenMode.toggle);
|
||||
|
||||
window.addEventListener("hashchange", onHashChange);
|
||||
|
||||
loadFileList();
|
||||
|
||||
onDocumentKeyUp("?", sourceEnableMode.toggle);
|
||||
};
|
||||
|
||||
export default bootstrap;
|
38
modules/templateMusings.mjs
Normal file
38
modules/templateMusings.mjs
Normal file
@ -0,0 +1,38 @@
|
||||
//@ts-check
|
||||
import { fetchText } from "./utils/fetchText.mjs";
|
||||
import { getElementById } from "./utils/getElementById.mjs";
|
||||
import { onDocumentKeyUp } from "./utils/onDocumentKey.mjs";
|
||||
import { parseFileList } from "./tickle/parseFileList.mjs";
|
||||
import {createMenuEntriesFromFileList} from "./tickle/createMenuEntriesFromFileList.mjs"
|
||||
import { sortFileListLines } from "./tickle/sortFileListLines.mjs"
|
||||
import { mode } from "./tickle/mode.mjs";
|
||||
import {bootstrapRouter} from "./tickle/bootstrapRouter.mjs";
|
||||
|
||||
/**
|
||||
* Loads the article list, parses it, creates the menu items
|
||||
*/
|
||||
export const bootstrap = () => {
|
||||
|
||||
const [Menu, Body, Source, Burger] = ["Menu", "Body", "Source", "Burger"].map(
|
||||
getElementById
|
||||
);
|
||||
|
||||
Burger.addEventListener("click", mode.menuOpen.toggle);
|
||||
|
||||
mode.loading.on();
|
||||
fetchText("./files.txt").then((lines)=>{
|
||||
const links = parseFileList(lines)
|
||||
const firstHref = links[0].href;
|
||||
sortFileListLines(links)
|
||||
Menu.appendChild(createMenuEntriesFromFileList(links))
|
||||
bootstrapRouter(firstHref, (content, raw)=>{
|
||||
Body.innerHTML = "";
|
||||
Source.innerHTML = raw;
|
||||
Body.appendChild(content);
|
||||
})
|
||||
});
|
||||
|
||||
onDocumentKeyUp("?", mode.sourceEnable.toggle);
|
||||
};
|
||||
|
||||
export default bootstrap;
|
41
modules/tickle/bootstrapRouter.mjs
Normal file
41
modules/tickle/bootstrapRouter.mjs
Normal file
@ -0,0 +1,41 @@
|
||||
//@ts-check
|
||||
|
||||
import { changeTitle } from "../utils/changeTitle.mjs";
|
||||
import { getCurrentHashUrl } from "../utils/getCurrentHashUrl.mjs";
|
||||
import { fetchMarkdown } from "../utils/fetchMarkdown.mjs";
|
||||
import { rewriteLocalUrls } from "../utils/rewriteLocalUrls.mjs";
|
||||
import { hasNoHashUrl } from "../utils/getCurrentHashUrl.mjs";
|
||||
import { mode } from "./mode.mjs";
|
||||
|
||||
/**
|
||||
* Sets up a listener for hashchange events.
|
||||
* Loads the provided default Href if none is set by the user
|
||||
* @param {string} defaultHref
|
||||
* @param {(content: DocumentFragment, raw: string) => void} onPageLoaded
|
||||
*/
|
||||
export const bootstrapRouter = async (defaultHref, onPageLoaded) => {
|
||||
/**
|
||||
* Listens to hashchange event, and attempts to read the url.
|
||||
* @param {HashChangeEvent} [_evt]
|
||||
*/
|
||||
const onHashChange = async (_evt) => {
|
||||
const { path } = getCurrentHashUrl();
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
mode.loading.on();
|
||||
const { title, raw, content } = await fetchMarkdown(path);
|
||||
changeTitle(title);
|
||||
rewriteLocalUrls(content);
|
||||
onPageLoaded(content, raw)
|
||||
mode.loading.off();
|
||||
};
|
||||
|
||||
window.addEventListener("hashchange", onHashChange);
|
||||
|
||||
if (hasNoHashUrl()) {
|
||||
window.location.hash = `#${defaultHref}`;
|
||||
} else {
|
||||
onHashChange();
|
||||
}
|
||||
};
|
18
modules/tickle/createMenuEntriesFromFileList.mjs
Normal file
18
modules/tickle/createMenuEntriesFromFileList.mjs
Normal file
@ -0,0 +1,18 @@
|
||||
//@ts-check
|
||||
import { makeTemplate } from "./utils/print.mjs";
|
||||
import { generateDomFromString } from "./utils/generateDomFromString.mjs";
|
||||
/**
|
||||
* @typedef {import("./parseFileList.mjs").ParsedLine} ParsedLine
|
||||
*/
|
||||
|
||||
const defaultTemplate = `<a data-link href="#{{href}}">{{title}}</a>`;
|
||||
|
||||
/**
|
||||
* Creates menu entries from a list of links.
|
||||
* @param {ParsedLine[]} links
|
||||
* @param {string} template a template for each link, where `{{href}}` is replaced by the href
|
||||
* and `{{title}}` is replaced by the title.
|
||||
* all the keys parsed in `parseFileList.mjs` can be used.
|
||||
*/
|
||||
export const createMenuEntriesFromFileList = (links, template = defaultTemplate) =>
|
||||
generateDomFromString(links.map(makeTemplate(template)).join("\n"));
|
13
modules/tickle/index.mjs
Normal file
13
modules/tickle/index.mjs
Normal file
@ -0,0 +1,13 @@
|
||||
//@ts-check
|
||||
import { bootstrapRouter } from "./bootstrapRouter.mjs";
|
||||
import { createMenuEntriesFromFileList } from "./createMenuEntriesFromFileList.mjs";
|
||||
import { parseFileList, parseFileListLine } from "./parseFileList.mjs";
|
||||
import { sortFileListLines } from "./sortFileListLines.mjs";
|
||||
|
||||
export {
|
||||
bootstrapRouter,
|
||||
createMenuEntriesFromFileList,
|
||||
parseFileList,
|
||||
parseFileListLine,
|
||||
sortFileListLines,
|
||||
};
|
8
modules/tickle/mode.mjs
Normal file
8
modules/tickle/mode.mjs
Normal file
@ -0,0 +1,8 @@
|
||||
//@ts-check
|
||||
import { documentMode } from "./utils/documentMode.mjs";
|
||||
|
||||
export const mode = {
|
||||
loading: documentMode("loading"),
|
||||
sourceEnable: documentMode("source-enabled"),
|
||||
menuOpen: documentMode("menu-open"),
|
||||
};
|
59
modules/tickle/parseFileList.mjs
Normal file
59
modules/tickle/parseFileList.mjs
Normal file
@ -0,0 +1,59 @@
|
||||
//@ts-check
|
||||
|
||||
import { today } from "./utils/today.mjs";
|
||||
|
||||
/**
|
||||
* @typedef {Exclude<ReturnType<typeof parseFileListLine>, null>} ParsedLine
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parses a single line of a file list
|
||||
* @param {string} line
|
||||
* @param {number} index
|
||||
* @returns
|
||||
*/
|
||||
export const parseFileListLine = (line, index) => {
|
||||
const result = line.match(
|
||||
/(?<name>.+)\.(?<ext>\w{2,3})(?:\s+(?<date>[\d-]+)?(?<title>.+))?/
|
||||
);
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
//@ts-ignore
|
||||
groups: { name, ext, date = today, title = name },
|
||||
} = result;
|
||||
const href = `/${name}.${ext}`;
|
||||
const date_unix = new Date(date).getTime();
|
||||
return { name, href, date, title, date_unix, index };
|
||||
};
|
||||
|
||||
/**
|
||||
* parses a filelist string. That's a string that looks like
|
||||
* ```md
|
||||
* name dd-mm-yyyy link name
|
||||
* name dd-mm-yyyy
|
||||
* name linkname
|
||||
* name
|
||||
* ```
|
||||
* This is very specific to Tickle and unlikely to be very useful anywhere else.
|
||||
* @param {string} lines
|
||||
*/
|
||||
export const parseFileList = (lines) =>
|
||||
// @ts-ignore
|
||||
lines
|
||||
.trim()
|
||||
.split(`\n`)
|
||||
.reduce((/** @type {ParsedLine[]}*/ arr, line, index) => {
|
||||
const result = parseFileListLine(line.trim(), index);
|
||||
if (!result) {
|
||||
console.error(`could not parse line number ${index}`);
|
||||
return arr;
|
||||
}
|
||||
arr.push(result);
|
||||
return arr;
|
||||
}, []);
|
||||
|
||||
parseFileList.parseLine = parseFileListLine;
|
||||
|
||||
export default parseFileList;
|
15
modules/tickle/sortFileListLines.mjs
Normal file
15
modules/tickle/sortFileListLines.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* @typedef {import("./parseFileList.mjs").ParsedLine} ParsedLine
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sorts a list of parsed lines by a given property in-place
|
||||
* @param {ParsedLine[]} lines
|
||||
* @param {keyof ParsedLine} property
|
||||
*/
|
||||
export const sortFileListLines = (lines, property = 'date_unix') =>
|
||||
lines.sort(({ [property]: a }, { [property]: b }) => a - b);
|
||||
|
||||
export default sortFileListLines
|
@ -7,6 +7,8 @@
|
||||
* changeTitle.title = "My Site"
|
||||
* changeTitle("Home") // produces "Home | My Site"
|
||||
* ```
|
||||
* if not `title` is passed, the document title (as found when running the function
|
||||
* the first time) will be used.
|
||||
* @param {string} title
|
||||
* @param {string} mainTitle
|
||||
* @returns
|
@ -8,6 +8,7 @@ export const documentMode = (name) => ({
|
||||
on: () => document.body.classList.add(`is-${name}`),
|
||||
off: () => document.body.classList.remove(`is-${name}`),
|
||||
toggle: () => document.body.classList.toggle(`is-${name}`),
|
||||
has: () => document.body.classList.contains(`is-${name}`),
|
||||
});
|
||||
|
||||
export default documentMode;
|
35
modules/utils/documentState.mjs
Normal file
35
modules/utils/documentState.mjs
Normal file
@ -0,0 +1,35 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Creates a document state object that can toggle between exclusive states.
|
||||
* All passed states' css classnames will be prepended with `mode-`.
|
||||
* @param {string[]} states
|
||||
*/
|
||||
export const documentState = (states) => {
|
||||
const all = states.map((state) => `mode-${state}`);
|
||||
|
||||
/**
|
||||
* @param {any} state
|
||||
* @returns {state is number}
|
||||
*/
|
||||
const isValidIndex = (state) =>
|
||||
typeof state === "number" && state >= 0 && state < all.length;
|
||||
|
||||
/**
|
||||
* @param {number} state
|
||||
*/
|
||||
const is = (state) =>
|
||||
isValidIndex(state) && document.body.classList.contains(all[state]);
|
||||
|
||||
/**
|
||||
* @param {number|undefined|null|false} state
|
||||
*/
|
||||
const set = (state = false) => {
|
||||
document.body.classList.remove(...all);
|
||||
isValidIndex(state) && document.body.classList.add(all[state]);
|
||||
};
|
||||
|
||||
return { set, is };
|
||||
};
|
||||
|
||||
export default documentState;
|
@ -2,24 +2,21 @@
|
||||
|
||||
import { fetchText } from "./fetchText.mjs";
|
||||
import { waitIfLocalHost } from "./waitIfLocalHost.mjs";
|
||||
import { rewriteLocalUrls } from "./rewriteLocalUrls.mjs";
|
||||
import { generateDomFromString } from "./generateDomFromString.mjs";
|
||||
import {getFirstTitleContent} from "./getFirstTitleContent.mjs";
|
||||
// @ts-ignore
|
||||
import { micromark } from "https://esm.sh/micromark@3?bundle";
|
||||
|
||||
/**
|
||||
* Loads and parses a markdown document. Makes use of micromark
|
||||
* Loads and parses a markdown document. Makes use of micromark.
|
||||
* @param {string} path the path to load
|
||||
*/
|
||||
export const fetchMarkdown = (path) =>
|
||||
fetchText(path)
|
||||
.then(waitIfLocalHost())
|
||||
.then((raw) => {
|
||||
const content = rewriteLocalUrls(generateDomFromString(micromark(raw)));
|
||||
const firstTitleElement = content.querySelector("h1");
|
||||
const title = firstTitleElement
|
||||
? firstTitleElement.textContent
|
||||
: path.replace(/\.\w{2, 4}$/, "");
|
||||
const content = generateDomFromString(micromark(raw));
|
||||
const title = getFirstTitleContent(content) || path.replace(/\.\w{2, 4}$/, "");
|
||||
return { title, raw, content };
|
||||
});
|
||||
|
15
modules/utils/generateDomFromString.mjs
Normal file
15
modules/utils/generateDomFromString.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
//@ts-check
|
||||
|
||||
/**
|
||||
* Generates valid dom elements from a string
|
||||
* @param {string} htmlString
|
||||
*/
|
||||
export const generateDomFromString = (htmlString) =>{
|
||||
const children = new DOMParser().parseFromString(`<div>${htmlString}</div>`, "text/html")
|
||||
.children
|
||||
const fragment = document.createDocumentFragment()
|
||||
fragment.append(...children)
|
||||
return fragment
|
||||
;}
|
||||
|
||||
export default generateDomFromString;
|
@ -13,4 +13,11 @@ export const getCurrentHashUrl = () => {
|
||||
return { path, params };
|
||||
};
|
||||
|
||||
export const hasCurrentHashUrl = () => getCurrentHashUrl().path !== "";
|
||||
|
||||
export const hasNoHashUrl = () => getCurrentHashUrl().path === "";
|
||||
|
||||
getCurrentHashUrl.hasCurrentHashUrl = hasCurrentHashUrl;
|
||||
getCurrentHashUrl.hasNoHashUrl = hasNoHashUrl;
|
||||
|
||||
export default getCurrentHashUrl;
|
12
modules/utils/getFirstTitleContent.mjs
Normal file
12
modules/utils/getFirstTitleContent.mjs
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Returns the first title content in the document, if there is one.
|
||||
* @param {Node} content
|
||||
* @returns
|
||||
*/
|
||||
export const getFirstTitleContent = (content = document) => {
|
||||
/** @type {HTMLHeadElement} */
|
||||
const firstTitleElement = content.querySelector("h1");
|
||||
return firstTitleElement ? firstTitleElement.textContent || "" : "";
|
||||
};
|
||||
|
||||
export default getFirstTitleContent;
|
17
modules/utils/html.mjs
Normal file
17
modules/utils/html.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Does exactly the same as simply using ``, but allows to
|
||||
* neatly highlight the html string in editors that support it.
|
||||
* Packages the resulting string as a template
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {...any} expressions
|
||||
*/
|
||||
export function html(strings, ...expressions){
|
||||
const parsed = strings.reduce((previous, current, i) => {
|
||||
return previous + current + (expressions[i] ? expressions[i] : '')
|
||||
}, '')
|
||||
const template = document.createElement("template")
|
||||
template.innerHTML = parsed
|
||||
return template
|
||||
}
|
||||
|
||||
export default html
|
17
modules/utils/identity.mjs
Normal file
17
modules/utils/identity.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} [value]
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
export const awaitedIdentity = (value) => Promise.resolve(value);
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} [value]
|
||||
* @returns {T}
|
||||
*/
|
||||
export const identity = (value) => value
|
||||
|
||||
identity.awaited = awaitedIdentity;
|
||||
|
||||
export default identity;
|
69
modules/utils/index.mjs
Normal file
69
modules/utils/index.mjs
Normal file
@ -0,0 +1,69 @@
|
||||
//@ts-check
|
||||
|
||||
import { changeTitle } from "./changeTitle.mjs";
|
||||
import { documentMode } from "./documentMode.mjs";
|
||||
import { documentState } from "./documentState.mjs";
|
||||
import { fetchMarkdown } from "./fetchMarkdown.mjs";
|
||||
import { fetchText } from "./fetchText.mjs";
|
||||
import { generateDomFromString } from "./generateDomFromString.mjs";
|
||||
import {
|
||||
getCurrentHashUrl,
|
||||
hasCurrentHashUrl,
|
||||
hasNoHashUrl,
|
||||
} from "./getCurrentHashUrl.mjs";
|
||||
import { getElementById } from "./getElementById.mjs";
|
||||
import { getFirstTitleContent } from "./getFirstTitleContent.mjs";
|
||||
import { identity, awaitedIdentity } from "./identity.mjs";
|
||||
import { html } from "./html.mjs";
|
||||
import { isExternalUrl } from "./isExternalUrl.mjs";
|
||||
import { isLocalHost } from "./isLocalHost.mjs";
|
||||
import { isNotNull } from "./isNotNull.mjs";
|
||||
import { not } from "./not.mjs";
|
||||
import {
|
||||
onDocumentKeyUp,
|
||||
onDocumentKeyDown,
|
||||
onDocumentKey,
|
||||
} from "./onDocumentKey.mjs";
|
||||
import { print, makeTemplate } from "./print.mjs";
|
||||
import {
|
||||
querySelectorDoc,
|
||||
querySelectorParent,
|
||||
querySelectorAll,
|
||||
} from "./querySelectorAll.mjs";
|
||||
import { rewriteLocalUrls } from "./rewriteLocalUrls.mjs";
|
||||
import { today } from "./today.mjs";
|
||||
import { wait } from "./wait.mjs";
|
||||
import { waitIfLocalHost } from "./waitIfLocalHost.mjs";
|
||||
|
||||
export {
|
||||
changeTitle,
|
||||
documentMode,
|
||||
documentState,
|
||||
fetchMarkdown,
|
||||
fetchText,
|
||||
generateDomFromString,
|
||||
getCurrentHashUrl,
|
||||
hasCurrentHashUrl,
|
||||
hasNoHashUrl,
|
||||
getElementById,
|
||||
getFirstTitleContent,
|
||||
html,
|
||||
identity,
|
||||
awaitedIdentity,
|
||||
isExternalUrl,
|
||||
isLocalHost,
|
||||
isNotNull,
|
||||
not,
|
||||
onDocumentKeyUp,
|
||||
onDocumentKeyDown,
|
||||
onDocumentKey,
|
||||
print,
|
||||
makeTemplate,
|
||||
querySelectorDoc,
|
||||
querySelectorParent,
|
||||
querySelectorAll,
|
||||
rewriteLocalUrls,
|
||||
today,
|
||||
wait,
|
||||
waitIfLocalHost,
|
||||
};
|
9
modules/utils/not.mjs
Normal file
9
modules/utils/not.mjs
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Inverter.
|
||||
* Easier to read than "!"
|
||||
* @param {any} a
|
||||
* @returns
|
||||
*/
|
||||
export const not = a => !a
|
||||
|
||||
export default not
|
19
modules/utils/print.mjs
Normal file
19
modules/utils/print.mjs
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Mini-mustache templating system. Simply replaces all occurrences of {{key}} with the value of the key.
|
||||
* @param {string} str
|
||||
* @param {Record<string, any>} replacements
|
||||
*/
|
||||
export const print = (str, replacements) =>
|
||||
str.replace(/{{(.*?)}}/g, (_match, key) => replacements[key] || '')
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {(replacements: Record<string, any>) => string}
|
||||
*/
|
||||
export const makeTemplate = (str) => print.bind(null, str)
|
||||
|
||||
print.makeTemplate = makeTemplate
|
||||
|
||||
export default print
|
@ -4,13 +4,13 @@ import isExternalUrl from "./isExternalUrl.mjs";
|
||||
import { querySelectorParent } from "./querySelectorAll.mjs";
|
||||
|
||||
/**
|
||||
* Makes sure urls to local pages get passed through the routing system
|
||||
* @param {HTMLElement} container the element containing links to find
|
||||
* Makes sure urls to local pages are prepended with #/
|
||||
* @param {ParentNode} container the element containing links to find
|
||||
*/
|
||||
export const rewriteLocalUrls = (container) => {
|
||||
querySelectorParent(container, "a").forEach((a) => {
|
||||
const href = a.getAttribute("href");
|
||||
if (href && !isExternalUrl(href)) {
|
||||
if (href && !isExternalUrl(href) && !href.startsWith("#")) {
|
||||
a.setAttribute("href", "#/" + href.replace(/^\.?\//, ""));
|
||||
}
|
||||
});
|
3
modules/utils/today.mjs
Normal file
3
modules/utils/today.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
export const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
export default today
|
@ -1,23 +1,16 @@
|
||||
//@ts-check
|
||||
|
||||
import wait from "./wait.mjs";
|
||||
import { awaitedIdentity } from "./identity.mjs";
|
||||
import isLocalHost from "./isLocalHost.mjs";
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {T} [value]
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const identity = (value) => Promise.resolve(value);
|
||||
|
||||
/**
|
||||
* Waits the specified amount of time before returning the value
|
||||
* @param {number} _durationMs Duration, in milliseconds. Defaults to 1 second
|
||||
* @returns
|
||||
*/
|
||||
const fakeWait = (_durationMs = 1000) => identity;
|
||||
const fakeWait = (_durationMs = 1000) => awaitedIdentity;
|
||||
|
||||
import isLocalHost from "./isLocalHost.mjs";
|
||||
|
||||
/**
|
||||
* useful to check for transitions while developing styles, if the loading screen
|
Reference in New Issue
Block a user