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-08-21 05:04:21 +00:00
|
|
|
export function localRehype() {
|
2023-08-19 19:45:02 +00:00
|
|
|
let printed = false;
|
|
|
|
|
|
|
|
return (tree, vfile) => {
|
|
|
|
const needsDropcap = vfile.data.fm.dropcap !== false
|
|
|
|
let dropcapAdded = false;
|
|
|
|
|
|
|
|
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;
|
|
|
|
return SKIP;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
}
|