279 lines
8.8 KiB
JavaScript
279 lines
8.8 KiB
JavaScript
import { importTemplate, intersectOnce, loadCSS, stylesheetReady, once } from './common';
|
|
import { fromEvent } from 'rxjs';
|
|
import { concatMap } from 'rxjs/operators';
|
|
import { createElement } from 'create-element-x/library';
|
|
import tippy from 'tippy.js';
|
|
|
|
// import LANG from './languages.json';
|
|
|
|
const toggleClass = (element, ...cls) => {
|
|
element.classList.remove(...cls);
|
|
void element.offsetWidth;
|
|
element.classList.add(...cls);
|
|
};
|
|
|
|
(async () => {
|
|
await Promise.all([
|
|
...('animate' in Element.prototype ? [] : [import(/* webpackChunkName: "webanimations" */ 'web-animations-js')]),
|
|
...('IntersectionObserver' in window
|
|
? []
|
|
: [import(/* webpackChunkName: "intersection-observer" */ 'intersection-observer')]),
|
|
]);
|
|
|
|
await stylesheetReady;
|
|
|
|
const FN_SEL = "li[id^='fn:']";
|
|
const FN_LINK_SEL = "a[href^='#fn:']";
|
|
const HORIZONTAL_SCROLL_SEL =
|
|
'pre, table:not(.highlight), .katex-display, .break-layout, mjx-container[jax="CHTML"][display="true"]';
|
|
const CODE_BLOCK_SEL = 'pre.highlight > code';
|
|
const CODE_TITLE_RE = /(?:title|file):\s*['"`](([^'"`\\]|\\.)*)['"`]/iu;
|
|
const HEADING_SELECTOR = 'h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]';
|
|
|
|
const IMG_FADE_DURATION = 500;
|
|
const IMG_KEYFRAMES = [{ opacity: 0 }, { opacity: 1 }];
|
|
const IMG_SETTINGS = {
|
|
fill: 'forwards',
|
|
duration: IMG_FADE_DURATION,
|
|
easing: 'ease',
|
|
};
|
|
|
|
const pushStateEl = document.querySelector('hy-push-state');
|
|
|
|
const CODE_LINE_HEIGHT = 1.5;
|
|
|
|
/** @param {(param0: HTMLElement|null) => void} fn
|
|
* @param {any=} opts */
|
|
function ready(fn, opts) {
|
|
if (pushStateEl && !window._noPushState) {
|
|
pushStateEl.addEventListener(
|
|
'hy-push-state-ready',
|
|
({
|
|
detail: {
|
|
replaceEls: [main],
|
|
},
|
|
}) => fn(main),
|
|
opts,
|
|
);
|
|
}
|
|
fn(document.getElementById('_main'));
|
|
}
|
|
|
|
/** @param {(param0: HTMLElement|null) => void} fn
|
|
* @param {any=} opts */
|
|
function load(fn, opts) {
|
|
if (pushStateEl && !window._noPushState) {
|
|
pushStateEl.addEventListener('hy-push-state-load', () => fn(document.getElementById('_main')), opts);
|
|
}
|
|
fn(document.getElementById('_main'));
|
|
}
|
|
|
|
let init = true;
|
|
|
|
ready((main) => {
|
|
if (!main) return;
|
|
|
|
tippy(main.querySelectorAll('.post-date > .ellipsis'), {
|
|
trigger: 'click',
|
|
touch: true,
|
|
interactive: true,
|
|
allowHTML: true,
|
|
maxWidth: 'none',
|
|
placement: 'bottom-start',
|
|
offset: 0,
|
|
content: (el) => el.innerHTML,
|
|
onTrigger(instance, event) {
|
|
if (event.target.tagName === 'A') {
|
|
instance._hideOnce = true;
|
|
}
|
|
},
|
|
onShow(instance) {
|
|
if (instance._hideOnce) {
|
|
return (instance._hideOnce = false);
|
|
}
|
|
},
|
|
});
|
|
|
|
tippy(main.querySelectorAll('abbr[title]'), {
|
|
trigger: 'click',
|
|
touch: true,
|
|
maxWidth: 500,
|
|
content: (el) => el.getAttribute('title'),
|
|
});
|
|
|
|
tippy(main.querySelectorAll('.sidebar-social [title]'), {
|
|
touch: true,
|
|
content: (el) => el.getAttribute('title'),
|
|
});
|
|
|
|
main.querySelectorAll(HEADING_SELECTOR).forEach((h) => {
|
|
const df = importTemplate('_permalink-template');
|
|
const a = df.querySelector('.permalink');
|
|
a.href = `#${h.id}`;
|
|
h.appendChild(df);
|
|
});
|
|
|
|
const toc = main.querySelector('#markdown-toc');
|
|
if (toc) toc.classList.add('toc-hide');
|
|
|
|
if ('clipboard' in navigator && 'ClipboardItem' in window) {
|
|
Array.from(main.querySelectorAll(CODE_BLOCK_SEL)).forEach((el) => {
|
|
const container = el?.parentNode?.parentNode;
|
|
const writeText = async () => {
|
|
await navigator.clipboard.write([
|
|
new ClipboardItem({ 'text/plain': new Blob([el.textContent], { type: 'text/plain' }) }),
|
|
]);
|
|
toggleClass(copyBtn, 'copy-success');
|
|
};
|
|
const copyBtn = createElement(
|
|
'button',
|
|
{ onClick: writeText },
|
|
createElement('small', { class: 'icon-copy', title: 'Copy' }),
|
|
createElement('small', { class: 'icon-checkmark', title: 'Done' }),
|
|
);
|
|
container?.appendChild(copyBtn);
|
|
});
|
|
}
|
|
|
|
Array.from(main.querySelectorAll(CODE_BLOCK_SEL))
|
|
.map((code) => code.children[0])
|
|
.forEach((el) => {
|
|
const result = CODE_TITLE_RE.exec(el?.innerText);
|
|
if (!result) return;
|
|
const [, fileName] = result;
|
|
|
|
const code = el.parentNode;
|
|
|
|
// Remove the first line
|
|
const child0 = el.childNodes[0];
|
|
const nli = child0.wholeText.indexOf('\n');
|
|
if (nli > -1) {
|
|
const restNode = child0.splitText(nli);
|
|
code.insertBefore(restNode, code.firstChild);
|
|
}
|
|
|
|
// Remove element before making changes
|
|
code.removeChild(el);
|
|
|
|
// Remove newline
|
|
code.childNodes[0].splitText(1);
|
|
code.removeChild(code.childNodes[0]);
|
|
|
|
const container = code.parentNode.parentNode;
|
|
|
|
// Language
|
|
// const highlighter = container.parentNode;
|
|
// const [, lang] = highlighter.classList.value.match(/language-(\w*)/) ?? [];
|
|
// const language = LANG[lang];
|
|
|
|
const header = createElement(
|
|
'div',
|
|
{ class: 'pre-header break-layout' },
|
|
createElement('span', {}, createElement('small', { class: 'icon-file-empty' }), ' ', fileName),
|
|
// !language ? null : createElement('small', { class: 'fr lang' }, language),
|
|
);
|
|
|
|
container.insertBefore(header, container.firstChild);
|
|
});
|
|
|
|
if ('complete' in HTMLImageElement.prototype) {
|
|
main.querySelectorAll('img[width][height][loading=lazy]').forEach((el) => {
|
|
if (init && el.complete) return;
|
|
el.style.opacity = '0';
|
|
// TODO: replace with loading spinner
|
|
el.addEventListener('load', () => el.animate(IMG_KEYFRAMES, IMG_SETTINGS), { once: true });
|
|
});
|
|
init = false;
|
|
}
|
|
|
|
// main.querySelectorAll(pushStateEl.linkSelector).forEach(anchor => {
|
|
// caches.match(anchor.href).then(m => {
|
|
// if (m) requestAnimationFrame(() => anchor.classList.add("visited"));
|
|
// });
|
|
// });
|
|
});
|
|
|
|
/** @type {Promise<{}>|null} */
|
|
let katexPromise = null;
|
|
|
|
load(() => {
|
|
const main = document.getElementById('_main');
|
|
if (!main) return;
|
|
|
|
const toc = main.querySelector('#markdown-toc');
|
|
if (toc) {
|
|
toc.classList.remove('toc-hide');
|
|
toc.classList.add('toc-show');
|
|
}
|
|
|
|
main.querySelectorAll(FN_SEL).forEach((li) => (li.tabIndex = 0));
|
|
|
|
main
|
|
.querySelectorAll(FN_LINK_SEL)
|
|
.forEach((a) =>
|
|
a.addEventListener('click', (e) =>
|
|
document.getElementById(e.currentTarget.getAttribute('href').substr(1))?.focus(),
|
|
),
|
|
);
|
|
|
|
main
|
|
.querySelectorAll(HORIZONTAL_SCROLL_SEL)
|
|
.forEach((el) =>
|
|
el.addEventListener('touchstart', (e) => el.scrollLeft > 0 && e.stopPropagation(), { passive: false }),
|
|
);
|
|
|
|
// Array.from(main.querySelectorAll(CODE_BLOCK_SEL)).forEach((code) => {
|
|
// Array.from(code.querySelectorAll('span[class^="c"]'))
|
|
// .filter((c1) => c1.innerText.includes('!!'))
|
|
// .forEach((c1) => {
|
|
// const [, n] = c1.innerText.match(/!!\s*(\d+)/) || [, '1'];
|
|
// const hl = createElement('span', { class: '__hl', style: `height: ${Number(n) * CODE_LINE_HEIGHT}em` });
|
|
// c1.innerText = c1.innerText.replace(`!!${n}`, '!!');
|
|
// const hasContent = c1.innerText?.match(/[\p{L}|\d]/u);
|
|
// if (!hasContent) {
|
|
// c1.parentElement?.replaceChild(hl, c1);
|
|
// } else {
|
|
// c1.innerText = c1.innerText.replace('!!', '');
|
|
// c1.parentElement?.insertBefore(hl, c1);
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
const katexHref = document.getElementById('_katexPreload')?.href;
|
|
if (!katexPromise && katexHref) {
|
|
intersectOnce(main.querySelectorAll('.katex'), { rootMargin: '1440px' }).then(() => {
|
|
katexPromise = loadCSS(katexHref);
|
|
});
|
|
}
|
|
});
|
|
|
|
const mathJaxEl = document.getElementById('_MathJax');
|
|
if (pushStateEl && mathJaxEl) {
|
|
const mathJax2To3 = ({
|
|
detail: {
|
|
replaceEls: [mainEl],
|
|
},
|
|
}) => {
|
|
mainEl.querySelectorAll('script[type="math/tex; mode=display"]').forEach((el) => {
|
|
el.outerHTML = el.innerText.replace('% <![CDATA[', '\\[').replace('%]]>', '\\]');
|
|
});
|
|
mainEl.querySelectorAll('script[type="math/tex"]').forEach((el) => {
|
|
el.outerHTML = `\\(${el.innerText}\\)`;
|
|
});
|
|
};
|
|
|
|
mathJax2To3({ detail: { replaceEls: [document] } });
|
|
|
|
if (!('MathJax' in window)) await once(mathJaxEl, 'load');
|
|
|
|
await MathJax.typesetPromise();
|
|
|
|
if (!window._noPushState) {
|
|
pushStateEl.addEventListener('ready', mathJax2To3);
|
|
fromEvent(pushStateEl, 'after')
|
|
.pipe(concatMap(() => MathJax.typesetPromise()))
|
|
.subscribe();
|
|
}
|
|
}
|
|
})();
|