mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
Add callouts support
Adds a colour palette Adds homemade remark-callouts package Adds tabler icons dependency
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
import { visit } from "unist-util-visit";
|
||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||
// @ts-ignore
|
||||
import type { Plugin } from "unified";
|
||||
import type { Node, Data, Parent } from "unist";
|
||||
import type { Blockquote, Heading, Text, BlockContent } from "mdast";
|
||||
import { parse } from "svg-parser";
|
||||
// import { calloutTypes } from "./calloutTypes";
|
||||
|
||||
// escape regex special characters
|
||||
function escapeRegExp(s: string) {
|
||||
return s.replace(new RegExp(`[-[\\]{}()*+?.\\\\^$|/]`, "g"), "\\$&");
|
||||
}
|
||||
|
||||
import tok from "./tokeniseCallout"
|
||||
|
||||
function trim(str: string){
|
||||
return str.replace(/^\s*|\s*$/g, '');
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
// classNameMaps: {
|
||||
// block: ClassNameMap;
|
||||
// title: ClassNameMap;
|
||||
// };
|
||||
// dataMaps: {
|
||||
// block: (data: Data) => Data;
|
||||
// title: (data: Data) => Data;
|
||||
// };
|
||||
// types: { [index: string]: string | object };
|
||||
}
|
||||
|
||||
export const defaultConfig: Config = {
|
||||
// classNameMaps: {
|
||||
// block: "callout",
|
||||
// title: "callout-title",
|
||||
// },
|
||||
// dataMaps: {
|
||||
// block: (data) => data,
|
||||
// title: (data) => data,
|
||||
// },
|
||||
// types: { ...calloutTypes },
|
||||
};
|
||||
|
||||
type ClassNames = string | string[];
|
||||
type ClassNameMap = ClassNames | ((title: string) => ClassNames);
|
||||
function formatClassNameMap(gen: ClassNameMap) {
|
||||
return (title: string) => {
|
||||
const classNames = typeof gen == "function" ? gen(title) : gen;
|
||||
return typeof classNames == "object" ? classNames.join(" ") : classNames;
|
||||
};
|
||||
}
|
||||
|
||||
export const callouts: Plugin = function (providedConfig?: Partial<Config>) {
|
||||
const config: Config = { ...defaultConfig, ...providedConfig };
|
||||
// const defaultKeywords: string = Object.keys(config.types)
|
||||
// .map(escapeRegExp)
|
||||
// .join("|");
|
||||
// @ts-ignore
|
||||
const Parser = this.Parser
|
||||
console.log(Parser)
|
||||
|
||||
const blockTokenizers = Parser.prototype.blockTokenizers
|
||||
const blockMethods = Parser.prototype.blockMethods
|
||||
console.log(blockMethods, blockTokenizers)
|
||||
|
||||
|
||||
blockTokenizers.callout = tok
|
||||
// console.log(blockTokenizers.blockquote.toString())
|
||||
var insertPoint = blockMethods.indexOf('fencedCode') + 1
|
||||
blockMethods.splice(insertPoint, 0, 'callout')
|
||||
|
||||
// @ts-ignore
|
||||
const Compiler = this.Compiler
|
||||
|
||||
if (Compiler != null) {
|
||||
const visitors = Compiler.prototype.visitors
|
||||
if (visitors) {
|
||||
visitors.callout = function (node: { keyword: string; title: string; }) {
|
||||
var values = this.block(node).split('\n');
|
||||
var result = [];
|
||||
var length = values.length;
|
||||
var index = -1;
|
||||
var value;
|
||||
|
||||
while (++index < length) {
|
||||
value = values[index];
|
||||
result[index] = (value ? ' ' : '') + value;
|
||||
}
|
||||
|
||||
return '> [!'+node.keyword+'] ' + node.title + '\n' + result.join('\n>');
|
||||
}
|
||||
}
|
||||
}
|
||||
// return function (tree) {
|
||||
// visit(tree, (node: Node, index, parent: Parent) => {
|
||||
// // Filter required elems
|
||||
// if (node.type !== "blockquote") return;
|
||||
|
||||
// /** add breaks to text without needing spaces or escapes (turns enters into <br>)
|
||||
// * code taken directly from remark-breaks,
|
||||
// * see https://github.com/remarkjs/remark-breaks for more info on what this does.
|
||||
// */
|
||||
// // visit(node, "text", (node: Text, index: number, parent: Parent) => {
|
||||
// // const result = [];
|
||||
// // let start = 0;
|
||||
|
||||
// // find.lastIndex = 0;
|
||||
|
||||
// // let match = find.exec(node.value);
|
||||
|
||||
// // while (match) {
|
||||
// // const position = match.index;
|
||||
|
||||
// // if (start !== position) {
|
||||
// // result.push({
|
||||
// // type: "text",
|
||||
// // value: node.value.slice(start, position),
|
||||
// // });
|
||||
// // }
|
||||
|
||||
// // result.push({ type: "break" });
|
||||
// // start = position + match[0].length;
|
||||
// // match = find.exec(node.value);
|
||||
// // }
|
||||
|
||||
// // if (result.length > 0 && parent && typeof index === "number") {
|
||||
// // if (start < node.value.length) {
|
||||
// // result.push({ type: "text", value: node.value.slice(start) });
|
||||
// // }
|
||||
|
||||
// // parent.children.splice(index, 1, ...result);
|
||||
// // return index + result.length;
|
||||
// // }
|
||||
// // });
|
||||
|
||||
// /** add classnames to headings within blockquotes,
|
||||
// * mainly to identify when using other plugins that
|
||||
// * might interfere. for eg, rehype-auto-link-headings.
|
||||
// */
|
||||
// visit(node, "heading", (node) => {
|
||||
// const heading = node as Heading;
|
||||
// heading.data = {
|
||||
// hProperties: {
|
||||
// className: "blockquote-heading",
|
||||
// },
|
||||
// };
|
||||
// });
|
||||
|
||||
// // wrap blockquote in a div
|
||||
// const wrapper = {
|
||||
// ...node,
|
||||
// type: "element",
|
||||
// tagName: "div",
|
||||
// data: {
|
||||
// hProperties: {},
|
||||
// },
|
||||
// children: [node],
|
||||
// };
|
||||
|
||||
// parent.children.splice(Number(index), 1, wrapper);
|
||||
|
||||
// const blockquote = wrapper.children[0] as Blockquote;
|
||||
|
||||
// blockquote.data = {
|
||||
// hProperties: {
|
||||
// className: "blockquote",
|
||||
// },
|
||||
// };
|
||||
// console.log(blockquote)
|
||||
|
||||
// // check for callout syntax starts here
|
||||
// if (
|
||||
// blockquote.children.length <= 0 ||
|
||||
// blockquote.children[0].type !== "paragraph"
|
||||
// )
|
||||
// return;
|
||||
// const paragraph = blockquote.children[0];
|
||||
// console.log(paragraph)
|
||||
|
||||
// if (
|
||||
// paragraph.children.length <= 0 ||
|
||||
// paragraph.children[0].type !== "text"
|
||||
// )
|
||||
// return;
|
||||
|
||||
// const [t, ...rest] = paragraph.children;
|
||||
|
||||
// const regex = new RegExp(
|
||||
// `^\\[!(?<keyword>(.*?))\\][\t\f ]?(?<title>.*?)$`,
|
||||
// "gi"
|
||||
// );
|
||||
// const m = regex.exec(t.value);
|
||||
|
||||
// // if no callout syntax, forget about it.
|
||||
// if (!m) return;
|
||||
|
||||
// const [key, title] = [m.groups?.keyword, m.groups?.title];
|
||||
|
||||
// // if there's nothing inside the brackets, is it really a callout ?
|
||||
// if (!key) return;
|
||||
|
||||
// const keyword = key.toLowerCase();
|
||||
// // const isOneOfKeywords: boolean = new RegExp(defaultKeywords).test(
|
||||
// // keyword
|
||||
// // );
|
||||
|
||||
// if (title) {
|
||||
// const mdast = fromMarkdown(title.trim()).children[0];
|
||||
// if (mdast.type === "heading") {
|
||||
// mdast.data = {
|
||||
// ...mdast.data,
|
||||
// hProperties: {
|
||||
// className: "blockquote-heading",
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// blockquote.children.unshift(mdast as BlockContent);
|
||||
// } else {
|
||||
// t.value =
|
||||
// typeof keyword.charAt(0) === "string"
|
||||
// ? keyword.charAt(0).toUpperCase() + keyword.slice(1)
|
||||
// : keyword;
|
||||
// }
|
||||
|
||||
// const entry: { [index: string]: string } = {};
|
||||
|
||||
// // if (isOneOfKeywords) {
|
||||
// // if (typeof config?.types[keyword] === "string") {
|
||||
// // const e = String(config?.types[keyword]);
|
||||
// // Object.assign(entry, config?.types[e]);
|
||||
// // } else {
|
||||
// // Object.assign(entry, config?.types[keyword]);
|
||||
// // }
|
||||
// // } else {
|
||||
// // Object.assign(entry, config?.types["note"]);
|
||||
// // }
|
||||
|
||||
// let parsedSvg;
|
||||
|
||||
// if (entry && entry.svg) {
|
||||
// parsedSvg = parse(entry.svg);
|
||||
// }
|
||||
|
||||
// // create icon and title node wrapped in div
|
||||
// const titleNode: object = {
|
||||
// type: "element",
|
||||
// children: [
|
||||
// {
|
||||
// type: "element",
|
||||
// tagName: "span",
|
||||
// data: {
|
||||
// hName: "span",
|
||||
// hProperties: {
|
||||
// style: `color:${entry?.color}`,
|
||||
// className: "callout-icon",
|
||||
// },
|
||||
// hChildren: parsedSvg?.children ? parsedSvg.children : [],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// type: "element",
|
||||
// children: title ? [blockquote.children[0]] : [t],
|
||||
// data: {
|
||||
// hName: "strong",
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// data: {
|
||||
// ...blockquote.children[0]?.data,
|
||||
// hProperties: {
|
||||
// className: `${formatClassNameMap(config.classNameMaps.title)(
|
||||
// keyword
|
||||
// )} ${"note"}`,
|
||||
// style: `background-color: ${entry?.color}1a;`,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
// // remove the callout paragraph from the content body
|
||||
// if (title) {
|
||||
// blockquote.children.shift();
|
||||
// }
|
||||
|
||||
// if (rest.length > 0) {
|
||||
// rest.shift();
|
||||
// paragraph.children = rest;
|
||||
// } else {
|
||||
// blockquote.children.shift();
|
||||
// }
|
||||
|
||||
// // wrap blockquote content in div
|
||||
// const contentNode: object = {
|
||||
// type: "element",
|
||||
// children: blockquote.children,
|
||||
// data: {
|
||||
// hProperties: {
|
||||
// className: "callout-content",
|
||||
// style:
|
||||
// parent.type !== "root"
|
||||
// ? `border-right:1px solid ${entry?.color}33;
|
||||
// border-bottom:1px solid ${entry?.color}33;`
|
||||
// : "",
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
// if (blockquote.children.length > 0)
|
||||
// blockquote.children = [contentNode] as BlockContent[];
|
||||
// blockquote.children.unshift(titleNode as BlockContent);
|
||||
|
||||
// // Add classes for the callout block
|
||||
// blockquote.data = config.dataMaps.block({
|
||||
// ...blockquote.data,
|
||||
// hProperties: {
|
||||
// className: formatClassNameMap(config.classNameMaps.block)(
|
||||
// keyword.toLowerCase()
|
||||
// ),
|
||||
// style: `border-left-color:${entry?.color};`,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
};
|
||||
|
||||
export default callouts;
|
||||
@@ -0,0 +1,245 @@
|
||||
'use strict';
|
||||
|
||||
import trim from "trim";
|
||||
|
||||
|
||||
export default blockquote;
|
||||
|
||||
var C_NEWLINE = '\n';
|
||||
var C_TAB = '\t';
|
||||
var C_SPACE = ' ';
|
||||
var C_GT = '>';
|
||||
// TODO:
|
||||
// - Grow/shrink support
|
||||
// - Customise AST output
|
||||
|
||||
/* Tokenise a blockquote. */
|
||||
function blockquote(eat: { (arg0: string): any; (arg0: string): any; now: any; }, value: string, silent: boolean) {
|
||||
var self = this;
|
||||
var offsets = self.offset;
|
||||
var tokenizers = self.blockTokenizers;
|
||||
var interruptors = [
|
||||
['indentedCode', { commonmark: true }],
|
||||
['fencedCode', { commonmark: true }],
|
||||
['atxHeading', { commonmark: true }],
|
||||
['setextHeading', { commonmark: true }],
|
||||
['thematicBreak', { commonmark: true }],
|
||||
['html', { commonmark: true }],
|
||||
['list', { commonmark: true }],
|
||||
['definition', { commonmark: false }],
|
||||
['footnote', { commonmark: false }]
|
||||
];
|
||||
var now = eat.now();
|
||||
var currentLine = now.line;
|
||||
var length = value.length;
|
||||
var values = [];
|
||||
var contents = [];
|
||||
var indents = [];
|
||||
var add;
|
||||
var index = 0;
|
||||
var character;
|
||||
var rest;
|
||||
var nextIndex;
|
||||
var content;
|
||||
var line;
|
||||
var startIndex;
|
||||
var prefixed;
|
||||
var exit;
|
||||
|
||||
while (index < length) {
|
||||
character = value.charAt(index);
|
||||
|
||||
if (character !== C_SPACE && character !== C_TAB) {
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (value.charAt(index) !== C_GT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const regex = /^>\s*\[!(?<keyword>(.*?))\][\t\f ]?(?<title>.*?)$/i;
|
||||
nextIndex = value.indexOf(C_NEWLINE, index);
|
||||
content = value.slice(index, nextIndex);
|
||||
const m = regex.exec(content); // value.slice(index)
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
if (!m.groups?.keyword) return;
|
||||
|
||||
if (silent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index = 0;
|
||||
let titleLine = true
|
||||
while (index < length) {
|
||||
nextIndex = value.indexOf(C_NEWLINE, index);
|
||||
startIndex = index;
|
||||
prefixed = false;
|
||||
|
||||
if (nextIndex === -1) {
|
||||
nextIndex = length;
|
||||
}
|
||||
|
||||
while (index < length) {
|
||||
character = value.charAt(index);
|
||||
|
||||
if (character !== C_SPACE && character !== C_TAB) {
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
if (value.charAt(index) === C_GT) {
|
||||
index++;
|
||||
prefixed = true;
|
||||
|
||||
if (value.charAt(index) === C_SPACE) {
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
index = startIndex;
|
||||
}
|
||||
|
||||
// regex.lastIndex = index
|
||||
|
||||
content = value.slice(index, nextIndex);
|
||||
|
||||
if (!prefixed && !trim(content)) {
|
||||
index = startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!prefixed) {
|
||||
rest = value.slice(index);
|
||||
|
||||
/* Check if the following code contains a possible
|
||||
* block. */
|
||||
if (interrupt(interruptors, tokenizers, self, [eat, rest, true])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
line = startIndex === index ? content : value.slice(startIndex, nextIndex);
|
||||
|
||||
indents.push(index - startIndex);
|
||||
values.push(line);
|
||||
if (titleLine) {
|
||||
titleLine = false
|
||||
} else {
|
||||
contents.push(content);
|
||||
}
|
||||
|
||||
index = nextIndex + 1;
|
||||
}
|
||||
|
||||
index = -1;
|
||||
length = indents.length;
|
||||
add = eat(values.join(C_NEWLINE));
|
||||
|
||||
while (++index < length) {
|
||||
offsets[currentLine] = (offsets[currentLine] || 0) + indents[index];
|
||||
currentLine++;
|
||||
}
|
||||
|
||||
exit = self.enterBlock();
|
||||
const title = self.tokenizeInline(m.groups?.title, now);
|
||||
contents = self.tokenizeBlock(contents.join(C_NEWLINE), now);
|
||||
exit();
|
||||
console.log(title,)
|
||||
return add({
|
||||
type: 'callout',
|
||||
children: [{
|
||||
type: "heading",
|
||||
children: title,
|
||||
data: { hName: 'svelte:fragment',
|
||||
hProperties: {
|
||||
slot: "title"
|
||||
} }
|
||||
}, {
|
||||
type: "block",
|
||||
children: contents,
|
||||
data: { hName: 'svelte:fragment',
|
||||
hProperties: {
|
||||
slot: "body"
|
||||
} }
|
||||
}],
|
||||
keyword: m.groups?.keyword,
|
||||
data: {
|
||||
hName: 'Components.Callout',
|
||||
hProperties: {
|
||||
"calloutType": m.groups?.keyword
|
||||
},
|
||||
}
|
||||
});
|
||||
// return add({
|
||||
// type: 'callout',
|
||||
// children: [{
|
||||
// type: "heading",
|
||||
// children: title,
|
||||
// data: { hName: 'div',
|
||||
// hProperties: {
|
||||
// className: "callout-title"
|
||||
// } }
|
||||
// }, {
|
||||
// type: "block",
|
||||
// children: contents,
|
||||
// data: { hName: 'div',
|
||||
// hProperties: {
|
||||
// className: "callout-content"
|
||||
// } }
|
||||
// }],
|
||||
// keyword: m.groups?.keyword,
|
||||
// data: {
|
||||
// hName: 'div',
|
||||
// hProperties: {
|
||||
// className: "callout",
|
||||
// "data-callout": m.groups?.keyword
|
||||
// },
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
function interrupt(interruptors, tokenizers, ctx, params) {
|
||||
var bools = ['pedantic', 'commonmark'];
|
||||
var count = bools.length;
|
||||
var length = interruptors.length;
|
||||
var index = -1;
|
||||
var interruptor;
|
||||
var config;
|
||||
var fn;
|
||||
var offset;
|
||||
var bool;
|
||||
var ignore;
|
||||
|
||||
while (++index < length) {
|
||||
interruptor = interruptors[index];
|
||||
config = interruptor[1] || {};
|
||||
fn = interruptor[0];
|
||||
offset = -1;
|
||||
ignore = false;
|
||||
|
||||
while (++offset < count) {
|
||||
bool = bools[offset];
|
||||
|
||||
if (config[bool] !== undefined && config[bool] !== ctx.options[bool]) {
|
||||
ignore = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ignore) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tokenizers[fn].apply(ctx, params)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user