2023-08-19 19:45:02 +00:00
|
|
|
import { visit, CONTINUE, EXIT, SKIP, } from 'unist-util-visit';
|
|
|
|
import { find } from 'unist-util-find';
|
|
|
|
import { toText } from 'hast-util-to-text';
|
|
|
|
import { makeSlug } from '../lib/utils.js';
|
|
|
|
|
2023-12-27 04:30:09 +00:00
|
|
|
import {writeFileSync} from 'node:fs';
|
|
|
|
import {toHtml} from 'hast-util-to-html';
|
2023-08-19 19:45:02 +00:00
|
|
|
|
|
|
|
|
2023-12-27 04:30:09 +00:00
|
|
|
export function localRehype() {
|
2023-08-19 19:45:02 +00:00
|
|
|
return (tree, vfile) => {
|
|
|
|
const needsDropcap = vfile.data.fm.dropcap !== false
|
|
|
|
let dropcapAdded = false;
|
|
|
|
|
2024-07-11 10:03:34 +00:00
|
|
|
let sidenotesCount = 0;
|
2023-08-19 19:45:02 +00:00
|
|
|
let moduleScript;
|
2023-08-20 23:12:04 +00:00
|
|
|
let imports = new Set();
|
2023-08-19 19:45:02 +00:00
|
|
|
if (needsDropcap) {
|
2023-08-20 23:12:04 +00:00
|
|
|
imports.add("import Dropcap from '$lib/Dropcap.svelte';");
|
2023-08-19 19:45:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
visit(tree, node => {
|
|
|
|
// add slugs to headings
|
|
|
|
if (isHeading(node)) {
|
|
|
|
processHeading(node);
|
2023-08-20 23:12:04 +00:00
|
|
|
imports.add("import Heading from '$lib/Heading.svelte';");
|
2023-08-19 19:45:02 +00:00
|
|
|
return SKIP;
|
|
|
|
}
|
|
|
|
|
|
|
|
// mdsvex adds a <script context="module"> so we just hijack that for our own purposes
|
|
|
|
if (isModuleScript(node)) {
|
|
|
|
moduleScript = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert first letter/word of first paragraph to <Dropcap word="{whatever}">
|
|
|
|
if (needsDropcap && !dropcapAdded && isParagraph(node)) {
|
|
|
|
addDropcap(node);
|
|
|
|
dropcapAdded = true;
|
2024-07-11 10:03:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add `count` prop to each <Sidenote> component
|
|
|
|
if (isSidenote(node)) {
|
|
|
|
// increment the counter first so that the count starts at 1
|
|
|
|
sidenotesCount += 1;
|
|
|
|
addSidenoteCount(node, sidenotesCount);
|
2023-08-19 19:45:02 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// insert our imports at the top of the `<script context="module">` tag
|
2023-08-20 23:12:04 +00:00
|
|
|
if (imports.size > 0) {
|
2023-08-19 19:45:02 +00:00
|
|
|
const script = moduleScript.value;
|
|
|
|
// split the script where the opening tag ends
|
|
|
|
const i = script.indexOf('>');
|
|
|
|
const openingTag = script.slice(0, i + 1);
|
|
|
|
const remainder = script.slice(i + 1);
|
|
|
|
|
|
|
|
// mdvsex uses tabs so we will as well
|
2023-08-20 23:12:04 +00:00
|
|
|
const importScript = Array.from(imports).join('\n\t');
|
2023-08-19 19:45:02 +00:00
|
|
|
|
2023-08-20 23:12:04 +00:00
|
|
|
moduleScript.value = `${openingTag}\n\t${importScript}${remainder}`;
|
2023-08-19 19:45:02 +00:00
|
|
|
}
|
2024-07-11 10:03:34 +00:00
|
|
|
|
|
|
|
// const name = vfile.filename.split('/').findLast(() => true);
|
|
|
|
// writeFileSync(`scratch/${name}.json`, JSON.stringify(tree, undefined, 4));
|
2023-08-19 19:45:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function processHeading(node) {
|
2023-08-20 23:12:04 +00:00
|
|
|
const level = node.tagName.slice(1);
|
|
|
|
node.tagName = 'Heading';
|
|
|
|
node.properties.level = level;
|
2023-08-19 19:45:02 +00:00
|
|
|
node.properties.id = makeSlug(toText(node));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addDropcap(par) {
|
|
|
|
let txtNode = find(par, {type: 'text'});
|
|
|
|
const i = txtNode.value.search(/\s/);
|
|
|
|
const firstWord = txtNode.value.slice(0, i);
|
|
|
|
const remainder = txtNode.value.slice(i);
|
|
|
|
|
|
|
|
par.children.unshift({
|
|
|
|
type: 'raw',
|
|
|
|
value: `<Dropcap word="${firstWord}" />`,
|
|
|
|
});
|
|
|
|
txtNode.value = remainder;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-11 10:03:34 +00:00
|
|
|
function addSidenoteCount(node, count) {
|
|
|
|
// get the index of the closing >
|
|
|
|
const i = node.value.search(/>\s*$/);
|
|
|
|
if (i < 0) {
|
|
|
|
throw new Error('Failed to add counter to element, closing angle bracket not found.');
|
|
|
|
}
|
|
|
|
// splice in the count prop
|
|
|
|
node.value = `${node.value.slice(0, i)} count={${count}}>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-19 19:45:02 +00:00
|
|
|
function isHeading(node) {
|
|
|
|
return node.type === 'element' && node.tagName.match(/h[1-6]/);
|
|
|
|
}
|
|
|
|
|
|
|
|
function isModuleScript(node) {
|
|
|
|
return node.type === 'raw' && node.value.match(/^<script context="module">/);
|
|
|
|
}
|
|
|
|
|
|
|
|
function isParagraph(node) {
|
|
|
|
return node.type === 'element' && node.tagName === 'p';
|
|
|
|
}
|
2024-07-11 10:03:34 +00:00
|
|
|
|
|
|
|
function isSidenote(node) {
|
|
|
|
return node.type === 'raw' && node.value.match(/<\s*Sidenote/);
|
|
|
|
}
|