Integrate flowershow packages (#923)
* [packages][m]: mv @flowershow/core package here * [packages/core][xs]: rename to @portaljs/core * [package.json][xs]: setup npm workspaces * [packages/core][xs]:replace deprecated rollup executor * [core/package.json][s]: fix mermaid versions * [core/tsconfig][xs]: rm extends * [core/jest.config][xs]: rm coverageDirectory * [core/package.json][xs]: install core-js * [packages.json][s]:use same version for all nrwl packages * [core/.eslintrc][xs]: adjust ignorePatterns * [core/project.json][xs]: rm publish targets * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-wiki-link here * [packages][m]: mv @flowershow/remark-embed here * [remark-callouts/project.json][xs]: adjst test pattern * [package.json][s]: install missing deps * [remark-callouts][xs]: adjst fields in package.json * [remark-callouts][s]: rm pubish targets and adjst build executor * [remark-embed/jest.config][xs]: rm unknown option coverageDirectory * [remark-embed][xs]: rm publish targets * [remark-embed][s]: rename to @portaljs/remark-embed * [remark-wiki-link/eslintrc][xs]:adjst ignorePatterns * [package.json][xs]: install missing deps * [remark-wiki-link/test][xs]:specify format - also temporarily force any type on htmlExtension * [remark-wiki-link/README][xs]: replace @flowershow with @portaljs * [remark-wiki-link][xs]:rm old changelog * [remark-wiki-link][xs]: adjst package.json * [remark-wiki-link/project.json][xs]: rm publish targets * [core][s]: rm old changelog * [core/README][xs]:correct scope name * [remark-callouts/README][xs]: add @portaljs to pckg name * [remark-embed/README][xs]: add @portaljs to pckg name * [package-lock.json][xs]: refresh after rebasing on main
This commit is contained in:
172
packages/remark-wiki-link/src/lib/fromMarkdown.ts
Normal file
172
packages/remark-wiki-link/src/lib/fromMarkdown.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { isSupportedFileFormat } from "./isSupportedFileFormat";
|
||||
|
||||
const defaultWikiLinkResolver = (target: string) => {
|
||||
// for [[#heading]] links
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
let permalink = target.replace(/\/index$/, "");
|
||||
// TODO what to do with [[index]] link?
|
||||
if (permalink.length === 0) {
|
||||
permalink = "/";
|
||||
}
|
||||
return [permalink];
|
||||
};
|
||||
|
||||
export interface FromMarkdownOptions {
|
||||
pathFormat?:
|
||||
| "raw" // default; use for regular relative or absolute paths
|
||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
||||
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
|
||||
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
|
||||
newClassName?: string; // class name to add to links that don't have a matching permalink
|
||||
wikiLinkClassName?: string; // class name to add to all wiki links
|
||||
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
|
||||
}
|
||||
|
||||
// mdas-util-from-markdown extension
|
||||
// https://github.com/syntax-tree/mdast-util-from-markdown#extension
|
||||
function fromMarkdown(opts: FromMarkdownOptions = {}) {
|
||||
const pathFormat = opts.pathFormat || "raw";
|
||||
const permalinks = opts.permalinks || [];
|
||||
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
|
||||
const newClassName = opts.newClassName || "new";
|
||||
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
|
||||
const defaultHrefTemplate = (permalink: string) => permalink;
|
||||
|
||||
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
|
||||
|
||||
function top(stack) {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
function enterWikiLink(token) {
|
||||
this.enter(
|
||||
{
|
||||
type: "wikiLink",
|
||||
data: {
|
||||
isEmbed: token.isType === "embed",
|
||||
target: null, // the target of the link, e.g. "Foo Bar#Heading" in "[[Foo Bar#Heading]]"
|
||||
alias: null, // the alias of the link, e.g. "Foo" in "[[Foo Bar|Foo]]"
|
||||
permalink: null, // TODO shouldn't this be named just "link"?
|
||||
exists: null, // TODO is this even needed here?
|
||||
// fields for mdast-util-to-hast (used e.g. by remark-rehype)
|
||||
hName: null,
|
||||
hProperties: null,
|
||||
hChildren: null,
|
||||
},
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
|
||||
function exitWikiLinkTarget(token) {
|
||||
const target = this.sliceSerialize(token);
|
||||
const current = top(this.stack);
|
||||
current.data.target = target;
|
||||
}
|
||||
|
||||
function exitWikiLinkAlias(token) {
|
||||
const alias = this.sliceSerialize(token);
|
||||
const current = top(this.stack);
|
||||
current.data.alias = alias;
|
||||
}
|
||||
|
||||
function exitWikiLink(token) {
|
||||
const wikiLink = this.exit(token);
|
||||
const {
|
||||
data: { isEmbed, target, alias },
|
||||
} = wikiLink;
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
|
||||
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
||||
|
||||
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
||||
|
||||
const matchingPermalink = permalinks.find((e) => {
|
||||
return possibleWikiLinkPermalinks.find((p) => {
|
||||
if (pathFormat === "obsidian-short") {
|
||||
if (e === p || e.endsWith(p)) {
|
||||
return true;
|
||||
}
|
||||
} else if (pathFormat === "obsidian-absolute") {
|
||||
if (e === "/" + p) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (e === p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// TODO this is ugly
|
||||
const link =
|
||||
matchingPermalink ||
|
||||
(pathFormat === "obsidian-absolute"
|
||||
? "/" + possibleWikiLinkPermalinks[0]
|
||||
: possibleWikiLinkPermalinks[0]) ||
|
||||
"";
|
||||
|
||||
wikiLink.data.exists = !!matchingPermalink;
|
||||
wikiLink.data.permalink = link;
|
||||
|
||||
// remove leading # if the target is a heading on the same page
|
||||
const displayName = alias || target.replace(/^#/, "");
|
||||
const headingId = heading.replace(/\s+/, "-").toLowerCase();
|
||||
let classNames = wikiLinkClassName;
|
||||
if (!matchingPermalink) {
|
||||
classNames += " " + newClassName;
|
||||
}
|
||||
|
||||
if (isEmbed) {
|
||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||
if (!isSupportedFormat) {
|
||||
wikiLink.data.hName = "p";
|
||||
wikiLink.data.hChildren = [
|
||||
{
|
||||
type: "text",
|
||||
value: `![[${target}]]`,
|
||||
},
|
||||
];
|
||||
} else if (format === "pdf") {
|
||||
wikiLink.data.hName = "iframe";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
width: "100%",
|
||||
src: `${hrefTemplate(link)}#toolbar=0`,
|
||||
};
|
||||
} else {
|
||||
wikiLink.data.hName = "img";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
src: hrefTemplate(link),
|
||||
alt: displayName,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
wikiLink.data.hName = "a";
|
||||
wikiLink.data.hProperties = {
|
||||
className: classNames,
|
||||
href: hrefTemplate(link) + headingId,
|
||||
};
|
||||
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enter: {
|
||||
wikiLink: enterWikiLink,
|
||||
},
|
||||
exit: {
|
||||
wikiLinkTarget: exitWikiLinkTarget,
|
||||
wikiLinkAlias: exitWikiLinkAlias,
|
||||
wikiLink: exitWikiLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { fromMarkdown };
|
||||
146
packages/remark-wiki-link/src/lib/html.ts
Normal file
146
packages/remark-wiki-link/src/lib/html.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { isSupportedFileFormat } from "./isSupportedFileFormat";
|
||||
|
||||
const defaultWikiLinkResolver = (target: string) => {
|
||||
// for [[#heading]] links
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
let permalink = target.replace(/\/index$/, "");
|
||||
// TODO what to do with [[index]] link?
|
||||
if (permalink.length === 0) {
|
||||
permalink = "/";
|
||||
}
|
||||
return [permalink];
|
||||
};
|
||||
|
||||
export interface HtmlOptions {
|
||||
pathFormat?:
|
||||
| "raw" // default; use for regular relative or absolute paths
|
||||
| "obsidian-absolute" // use for Obsidian-style absolute paths (with no leading slash)
|
||||
| "obsidian-short"; // use for Obsidian-style shortened paths (shortest path possible)
|
||||
permalinks?: string[]; // list of permalinks to match possible permalinks of a wiki link against
|
||||
wikiLinkResolver?: (name: string) => string[]; // function to resolve wiki links to an array of possible permalinks
|
||||
newClassName?: string; // class name to add to links that don't have a matching permalink
|
||||
wikiLinkClassName?: string; // class name to add to all wiki links
|
||||
hrefTemplate?: (permalink: string) => string; // function to generate the href attribute of a link
|
||||
}
|
||||
|
||||
// Micromark HtmlExtension
|
||||
// https://github.com/micromark/micromark#htmlextension
|
||||
function html(opts: HtmlOptions = {}) {
|
||||
const pathFormat = opts.pathFormat || "raw";
|
||||
const permalinks = opts.permalinks || [];
|
||||
const wikiLinkResolver = opts.wikiLinkResolver || defaultWikiLinkResolver;
|
||||
const newClassName = opts.newClassName || "new";
|
||||
const wikiLinkClassName = opts.wikiLinkClassName || "internal";
|
||||
const defaultHrefTemplate = (permalink: string) => permalink;
|
||||
const hrefTemplate = opts.hrefTemplate || defaultHrefTemplate;
|
||||
|
||||
function top(stack) {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
function enterWikiLink() {
|
||||
let stack = this.getData("wikiLinkStack");
|
||||
if (!stack) this.setData("wikiLinkStack", (stack = []));
|
||||
|
||||
stack.push({});
|
||||
}
|
||||
|
||||
function exitWikiLinkTarget(token) {
|
||||
const target = this.sliceSerialize(token);
|
||||
const current = top(this.getData("wikiLinkStack"));
|
||||
current.target = target;
|
||||
}
|
||||
|
||||
function exitWikiLinkAlias(token) {
|
||||
const alias = this.sliceSerialize(token);
|
||||
const current = top(this.getData("wikiLinkStack"));
|
||||
current.alias = alias;
|
||||
}
|
||||
|
||||
function exitWikiLink(token) {
|
||||
const wikiLink = this.getData("wikiLinkStack").pop();
|
||||
const { target, alias } = wikiLink;
|
||||
const isEmbed = token.isType === "embed";
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const wikiLinkWithHeadingPattern = /([\w\s\/\.-]*)(#.*)?/;
|
||||
const [, path, heading = ""] = target.match(wikiLinkWithHeadingPattern);
|
||||
|
||||
const possibleWikiLinkPermalinks = wikiLinkResolver(path);
|
||||
|
||||
const matchingPermalink = permalinks.find((e) => {
|
||||
return possibleWikiLinkPermalinks.find((p) => {
|
||||
if (pathFormat === "obsidian-short") {
|
||||
if (e === p || e.endsWith(p)) {
|
||||
return true;
|
||||
}
|
||||
} else if (pathFormat === "obsidian-absolute") {
|
||||
if (e === "/" + p) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (e === p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// TODO this is ugly
|
||||
const link =
|
||||
matchingPermalink ||
|
||||
(pathFormat === "obsidian-absolute"
|
||||
? "/" + possibleWikiLinkPermalinks[0]
|
||||
: possibleWikiLinkPermalinks[0]) ||
|
||||
"";
|
||||
|
||||
// remove leading # if the target is a heading on the same page
|
||||
const displayName = alias || target.replace(/^#/, "");
|
||||
// replace spaces with dashes and lowercase headings
|
||||
const headingId = heading.replace(/\s+/, "-").toLowerCase();
|
||||
let classNames = wikiLinkClassName;
|
||||
if (!matchingPermalink) {
|
||||
classNames += " " + newClassName;
|
||||
}
|
||||
|
||||
if (isEmbed) {
|
||||
const [isSupportedFormat, format] = isSupportedFileFormat(target);
|
||||
if (!isSupportedFormat) {
|
||||
this.raw(`![[${target}]]`);
|
||||
} else if (format === "pdf") {
|
||||
this.tag(
|
||||
`<iframe width="100%" src="${hrefTemplate(
|
||||
link
|
||||
)}#toolbar=0" class="${classNames}" />`
|
||||
);
|
||||
} else {
|
||||
this.tag(
|
||||
`<img src="${hrefTemplate(
|
||||
link
|
||||
)}" alt="${displayName}" class="${classNames}" />`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.tag(
|
||||
`<a href="${hrefTemplate(link + headingId)}" class="${classNames}">`
|
||||
);
|
||||
this.raw(displayName);
|
||||
this.tag("</a>");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
enter: {
|
||||
wikiLink: enterWikiLink,
|
||||
},
|
||||
exit: {
|
||||
wikiLinkTarget: exitWikiLinkTarget,
|
||||
wikiLinkAlias: exitWikiLinkAlias,
|
||||
wikiLink: exitWikiLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { html };
|
||||
28
packages/remark-wiki-link/src/lib/isSupportedFileFormat.ts
Normal file
28
packages/remark-wiki-link/src/lib/isSupportedFileFormat.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// TODO why only these?
|
||||
export const supportedFileFormats = [
|
||||
"webp",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"gif",
|
||||
"bmp",
|
||||
"svg",
|
||||
"apng",
|
||||
"png",
|
||||
"avif",
|
||||
"ico",
|
||||
"pdf",
|
||||
];
|
||||
|
||||
export const isSupportedFileFormat = (filePath: string): [boolean, string] => {
|
||||
const fileExtensionPattern = /\.([0-9a-z]{1,4})$/i;
|
||||
const match = filePath.match(fileExtensionPattern);
|
||||
|
||||
if (!match) {
|
||||
return [false, null];
|
||||
}
|
||||
|
||||
const [, extension] = match;
|
||||
const isSupported = supportedFileFormats.includes(extension);
|
||||
|
||||
return [isSupported, extension];
|
||||
};
|
||||
42
packages/remark-wiki-link/src/lib/remarkWikiLink.ts
Normal file
42
packages/remark-wiki-link/src/lib/remarkWikiLink.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { toMarkdown } from "mdast-util-wiki-link";
|
||||
import { syntax, SyntaxOptions } from "./syntax";
|
||||
import { fromMarkdown, FromMarkdownOptions } from "./fromMarkdown";
|
||||
|
||||
let warningIssued = false;
|
||||
|
||||
type RemarkWikiLinkOptions = FromMarkdownOptions & SyntaxOptions;
|
||||
|
||||
function remarkWikiLink(opts: RemarkWikiLinkOptions = {}) {
|
||||
const data = this.data(); // this is a reference to the processor
|
||||
|
||||
function add(field, value) {
|
||||
if (data[field]) data[field].push(value);
|
||||
else data[field] = [value];
|
||||
}
|
||||
|
||||
if (
|
||||
!warningIssued &&
|
||||
((this.Parser &&
|
||||
this.Parser.prototype &&
|
||||
this.Parser.prototype.blockTokenizers) ||
|
||||
(this.Compiler &&
|
||||
this.Compiler.prototype &&
|
||||
this.Compiler.prototype.visitors))
|
||||
) {
|
||||
warningIssued = true;
|
||||
console.warn(
|
||||
"[remark-wiki-link] Warning: please upgrade to remark 13 to use this plugin"
|
||||
);
|
||||
}
|
||||
|
||||
// add extensions to packages used by remark-parse
|
||||
// micromark extensions
|
||||
add("micromarkExtensions", syntax(opts));
|
||||
// mdast-util-from-markdown extensions
|
||||
add("fromMarkdownExtensions", fromMarkdown(opts));
|
||||
// mdast-util-to-markdown extensions
|
||||
add("toMarkdownExtensions", toMarkdown(opts));
|
||||
}
|
||||
|
||||
export default remarkWikiLink;
|
||||
export { remarkWikiLink };
|
||||
168
packages/remark-wiki-link/src/lib/syntax.ts
Normal file
168
packages/remark-wiki-link/src/lib/syntax.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
// Adjusted copy of https://github.com/landakram/micromark-extension-wiki-link/blob/master/src/index.js
|
||||
import { codes } from "micromark-util-symbol/codes.js";
|
||||
|
||||
export interface SyntaxOptions {
|
||||
aliasDivider?: string;
|
||||
}
|
||||
|
||||
function isEndOfLineOrFile(code: number) {
|
||||
return (
|
||||
code === codes.carriageReturnLineFeed ||
|
||||
code === codes.carriageReturn ||
|
||||
code === codes.lineFeed ||
|
||||
code === codes.eof
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Token types:
|
||||
* - `wikiLink`:
|
||||
* - `wikiLinkMarker`: The opening and closing brackets
|
||||
* - `wikiLinkData`: The data between the brackets
|
||||
* - `wikiLinkTarget`: The target of the link (the part before the alias divider)
|
||||
* - `wikiLinkAliasMarker`: The alias divider
|
||||
* - `wikiLinkAlias`: The alias of the link (the part after the alias divider)
|
||||
* */
|
||||
|
||||
function wikiLink(opts: SyntaxOptions = {}) {
|
||||
const aliasDivider = opts.aliasDivider || "|";
|
||||
|
||||
const aliasMarker = aliasDivider.charCodeAt(0);
|
||||
const startMarker = codes.leftSquareBracket;
|
||||
const embedStartMarker = codes.exclamationMark;
|
||||
const endMarker = codes.rightSquareBracket;
|
||||
|
||||
function tokenize(effects, ok, nok) {
|
||||
let data = false;
|
||||
let alias = false;
|
||||
|
||||
let startMarkerCount = 0;
|
||||
let endMarkerCount = 0;
|
||||
|
||||
return start;
|
||||
|
||||
// recognize the start of a wiki link
|
||||
function start(code: number) {
|
||||
if (code === startMarker) {
|
||||
effects.enter("wikiLink");
|
||||
effects.enter("wikiLinkMarker");
|
||||
|
||||
return consumeStart(code);
|
||||
} else if (code === embedStartMarker) {
|
||||
effects.enter("wikiLink", { isType: "embed" });
|
||||
effects.enter("wikiLinkMarker", { isType: "embed" });
|
||||
|
||||
return consumeStart(code);
|
||||
} else {
|
||||
return nok(code);
|
||||
}
|
||||
}
|
||||
function consumeStart(code: number) {
|
||||
// when coursor is at the first character after the start marker `[[`
|
||||
if (startMarkerCount === 2) {
|
||||
effects.exit("wikiLinkMarker");
|
||||
return consumeData(code);
|
||||
}
|
||||
|
||||
if (code === startMarker || code === embedStartMarker) {
|
||||
if (code === startMarker) {
|
||||
startMarkerCount++;
|
||||
}
|
||||
effects.consume(code);
|
||||
return consumeStart;
|
||||
} else {
|
||||
return nok(code);
|
||||
}
|
||||
}
|
||||
|
||||
function consumeData(code: number) {
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
effects.enter("wikiLinkData");
|
||||
effects.enter("wikiLinkTarget");
|
||||
return consumeTarget(code);
|
||||
}
|
||||
|
||||
function consumeTarget(code: number) {
|
||||
if (code === aliasMarker) {
|
||||
if (!data) return nok(code);
|
||||
effects.exit("wikiLinkTarget");
|
||||
effects.enter("wikiLinkAliasMarker");
|
||||
return consumeAliasMarker(code);
|
||||
}
|
||||
|
||||
if (code === endMarker) {
|
||||
if (!data) return nok(code);
|
||||
effects.exit("wikiLinkTarget");
|
||||
effects.exit("wikiLinkData");
|
||||
effects.enter("wikiLinkMarker");
|
||||
return consumeEnd(code);
|
||||
}
|
||||
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
data = true;
|
||||
effects.consume(code);
|
||||
|
||||
return consumeTarget;
|
||||
}
|
||||
|
||||
function consumeAliasMarker(code) {
|
||||
effects.consume(code);
|
||||
effects.exit("wikiLinkAliasMarker");
|
||||
effects.enter("wikiLinkAlias");
|
||||
return consumeAlias(code);
|
||||
}
|
||||
|
||||
function consumeAlias(code: number) {
|
||||
if (code === endMarker) {
|
||||
if (!alias) return nok(code);
|
||||
effects.exit("wikiLinkAlias");
|
||||
effects.exit("wikiLinkData");
|
||||
effects.enter("wikiLinkMarker");
|
||||
return consumeEnd(code);
|
||||
}
|
||||
|
||||
if (isEndOfLineOrFile(code)) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
alias = true;
|
||||
effects.consume(code);
|
||||
|
||||
return consumeAlias;
|
||||
}
|
||||
|
||||
function consumeEnd(code: number) {
|
||||
if (endMarkerCount === 2) {
|
||||
effects.exit("wikiLinkMarker");
|
||||
effects.exit("wikiLink");
|
||||
return ok(code);
|
||||
}
|
||||
|
||||
if (code !== endMarker) {
|
||||
return nok(code);
|
||||
}
|
||||
|
||||
effects.consume(code);
|
||||
endMarkerCount++;
|
||||
|
||||
return consumeEnd;
|
||||
}
|
||||
}
|
||||
|
||||
const wikiLinkConstruct = { tokenize };
|
||||
|
||||
return {
|
||||
text: {
|
||||
[codes.leftSquareBracket]: wikiLinkConstruct,
|
||||
[codes.exclamationMark]: wikiLinkConstruct,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { wikiLink as syntax };
|
||||
Reference in New Issue
Block a user