diff --git a/app/package.json b/app/package.json index 07e4406..567fbe7 100644 --- a/app/package.json +++ b/app/package.json @@ -40,6 +40,7 @@ "@storybook/react-vite": "^10.6.0", "@tailwindcss/vite": "^4.3.3", "@types/electron-squirrel-startup": "^1.0.2", + "@types/mdast": "^4.0.4", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.7", "@typescript-eslint/eslint-plugin": "^5.62.0", @@ -49,6 +50,9 @@ "eslint": "^8.57.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-storybook": "^10.6.0", + "mdast-util-from-markdown": "^2.1.0", + "micromark-util-types": "^2.0.3", + "remark-parse": "^11.0.0", "storybook": "^10.6.0", "tailwindcss": "^4.3.3", "typescript": "^5.9.3", @@ -60,9 +64,15 @@ "@pbnotes/pages": "*", "electron-squirrel-startup": "^1.0.1", "katex": "^0.16.47", + "mdast-util-gfm-strikethrough": "^2.0.1", + "micromark-extension-gfm-strikethrough": "^2.1.0", + "micromark-util-chunked": "^2.0.1", + "micromark-util-classify-character": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", "react": "^19.2.8", "react-dom": "^19.2.8", "react-markdown": "^10.1.0", - "rehype-katex": "^7.0.1" + "rehype-katex": "^7.0.1", + "unified": "^11.0.5" } } diff --git a/app/src/renderer/project/Block/Block.tsx b/app/src/renderer/project/Block/Block.tsx index 7649fef..777c311 100644 --- a/app/src/renderer/project/Block/Block.tsx +++ b/app/src/renderer/project/Block/Block.tsx @@ -13,12 +13,16 @@ import Markdown from 'react-markdown'; import type { Components } from 'react-markdown'; import rehypeKatex from 'rehype-katex'; import { IMAGE_SCHEME } from '../../../shared/pages'; -import { remarkPlugins } from '@pbnotes/pages'; +import { remarkPlugins as reading } from '@pbnotes/pages'; +import { remarkHighlight, remarkStrikethrough } from './marks/marks'; import { bracketEdit } from './brackets/brackets'; import type { Edit } from './brackets/brackets'; import { completeLink, linkQuery } from './linkQuery/linkQuery'; import type { LinkQuery } from './linkQuery/linkQuery'; +/** Read the way `@pbnotes/pages` reads a block, plus marks that only draw. */ +const remarkPlugins = [...reading, remarkStrikethrough, remarkHighlight]; + /** * KaTeX throws on malformed TeX by default, which would take the whole page * down over a half-typed formula. Bad math renders as flagged source instead; diff --git a/app/src/renderer/project/Block/marks/marks.test.ts b/app/src/renderer/project/Block/marks/marks.test.ts new file mode 100644 index 0000000..6f1634e --- /dev/null +++ b/app/src/renderer/project/Block/marks/marks.test.ts @@ -0,0 +1,55 @@ +/** Run with: npm test */ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import { remarkPlugins } from '@pbnotes/pages'; +import { remarkHighlight, remarkStrikethrough } from './marks.ts'; + +type Node = { + type: string; + value?: string; + children?: Node[]; + data?: { hProperties?: { href?: string } }; +}; + +const md = unified() + .use(remarkParse) + .use([...remarkPlugins, remarkStrikethrough, remarkHighlight]); +const parse = (source: string) => md.runSync(md.parse(source)) as Node; + +/** The text a node reads as, formatting dropped. */ +const read = (node: Node): string => + node.value ?? (node.children ?? []).map(read).join(''); + +/** Every node of a type, in document order. */ +const all = (node: Node, type: string): Node[] => [ + ...(node.type === type ? [node] : []), + ...(node.children ?? []).flatMap((child) => all(child, type)), +]; + +const pages = (node: Node) => + all(node, 'wikilink').map((link) => link.data?.hProperties?.href?.slice(1)); + +/** A link struck through is still a link; tildes in code, math or alone stay + what was typed. */ +test('~~strikethrough~~ wraps its words, links and all', () => { + const tree = parse('~~done [[Mira]]~~ `~~a~~` $~~b~~$ ~c~'); + assert.deepEqual(all(tree, 'delete').map(read), ['done [[Mira]]']); + assert.deepEqual(pages(tree), ['Mira']); + assert.match(read(tree), /~~a~~.*~~b~~.*~c~/); +}); + +/** Both spellings highlight, links inside still count; code, math, single + markers, spaced markers and mismatched pairs stay what was typed. */ +test('==highlight== and ^^highlight^^ wrap their words, links and all', () => { + const tree = parse( + '==done [[Mira]]== ^^#Ada^^ `==a==` $==b==$ a = b 2^3 x == y == z ==c^^ ===d===', + ); + assert.deepEqual(all(tree, 'highlight').map(read), ['done [[Mira]]', '#Ada']); + assert.deepEqual(pages(tree), ['Mira', 'Ada']); + assert.match( + read(tree), + /==a==.*==b==.*a = b 2\^3 x == y == z ==c\^\^ ===d===/, + ); +}); diff --git a/app/src/renderer/project/Block/marks/marks.ts b/app/src/renderer/project/Block/marks/marks.ts new file mode 100644 index 0000000..416386e --- /dev/null +++ b/app/src/renderer/project/Block/marks/marks.ts @@ -0,0 +1,193 @@ +/** + * Marks drawn over a block's words: `~~struck~~`, `==highlighted==` and + * `^^highlighted^^`. Rendering only — nothing in `@pbnotes/pages` reads + * them, so they ride alongside its plugins here instead of inside them. + */ +import type { RootContent } from 'mdast'; +import type { Processor } from 'unified'; +import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'; +import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'; +import type { Extension as FromMarkdownExtension } from 'mdast-util-from-markdown'; +import type { + Code, + Construct, + Event, + Resolver, + State, + Token, + Tokenizer, +} from 'micromark-util-types'; +import { splice } from 'micromark-util-chunked'; +import { classifyCharacter } from 'micromark-util-classify-character'; +import { resolveAll } from 'micromark-util-resolve-all'; + +/** + * `~~foo~~`, and only that piece of GFM: `remark-gfm` would also bring + * tables, task lists, footnotes and bare-URL autolinks, each of which changes + * how existing notes look. A single `~` stays text, so `~5 min` is safe. + * Rendering needs nothing more — the `delete` node already becomes ``. + */ +export function remarkStrikethrough(this: Processor) { + const data = this.data(); + (data.micromarkExtensions ??= []).push( + gfmStrikethrough({ singleTilde: false }), + ); + (data.fromMarkdownExtensions ??= []).push(gfmStrikethroughFromMarkdown()); +} + +declare module 'micromark-util-types' { + interface TokenTypeMap { + highlight: 'highlight'; + highlightSequence: 'highlightSequence'; + highlightSequenceTemporary: 'highlightSequenceTemporary'; + highlightText: 'highlightText'; + } +} + +const EQUALS = 61; +const CARET = 94; + +/** + * `==foo==`, Obsidian's highlight, and `^^foo^^`, Roam's: one node, drawn as + * ``. The tokenizer is GFM strikethrough's with the tilde swapped out — + * the same flanking rules, so `a == b` and `x ^^ y` stay text, and the same + * doubled marker, so `a = b` and `2^3` do too. What it adds is that a closer + * has to be the marker its opener was: `==a^^` highlights nothing. + * + * ponytail: a bare URL right before the closer, `==see https://a.com==`, + * takes the `==` into the URL and nothing is highlighted — the autolink + * extension's list of trailing punctuation to drop is fixed and has `~` but + * not `=` or `^`. A space before the closer does not help either. Fork that + * tokenizer's trail check if anyone highlights links. + */ +export function remarkHighlight(this: Processor) { + const tokenizer: Construct = { + name: 'highlight', + tokenize: tokenizeHighlight, + resolveAll: resolveAllHighlight, + }; + const data = this.data(); + (data.micromarkExtensions ??= []).push({ + text: { [EQUALS]: tokenizer, [CARET]: tokenizer }, + insideSpan: { null: [tokenizer] }, + attentionMarkers: { null: [EQUALS, CARET] }, + }); + (data.fromMarkdownExtensions ??= []).push({ + canContainEols: ['highlight'], + enter: { + highlight(token) { + const node = { + type: 'highlight', + children: [] as RootContent[], + data: { hName: 'mark' }, + }; + this.enter(node as never, token); + }, + }, + exit: { + highlight(token) { + this.exit(token); + }, + }, + } satisfies FromMarkdownExtension); +} + +/** Pair each closer with the nearest opener spelled the same way. */ +const resolveAllHighlight: Resolver = (events, context) => { + for (let index = 0; index < events.length; index++) { + const close = events[index][1]; + if ( + events[index][0] !== 'enter' || + close.type !== 'highlightSequenceTemporary' || + !close._close + ) { + continue; + } + let open = index; + while (open--) { + const opener = events[open][1]; + if ( + events[open][0] === 'exit' && + opener.type === 'highlightSequenceTemporary' && + opener._open && + context.sliceSerialize(opener) === context.sliceSerialize(close) + ) { + opener.type = 'highlightSequence'; + close.type = 'highlightSequence'; + const highlight: Token = { + type: 'highlight', + start: { ...opener.start }, + end: { ...close.end }, + }; + const text: Token = { + type: 'highlightText', + start: { ...opener.end }, + end: { ...close.start }, + }; + const next: Event[] = [ + ['enter', highlight, context], + ['enter', opener, context], + ['exit', opener, context], + ['enter', text, context], + ]; + const insideSpan = context.parser.constructs.insideSpan.null; + if (insideSpan) { + splice( + next, + next.length, + 0, + resolveAll(insideSpan, events.slice(open + 1, index), context), + ); + } + splice(next, next.length, 0, [ + ['exit', text, context], + ['enter', close, context], + ['exit', close, context], + ['exit', highlight, context], + ]); + splice(events, open - 1, index - open + 3, next); + index = open + next.length - 2; + break; + } + } + } + for (const [, token] of events) { + if (token.type === 'highlightSequenceTemporary') token.type = 'data'; + } + return events; +}; + +/** Exactly two of one marker; whether they open, close or both is down to + what is either side of them, as with `~~`. */ +const tokenizeHighlight: Tokenizer = function (effects, ok, nok) { + const previous = this.previous; + const events = this.events; + let marker: Code = null; + let size = 0; + const more: State = (code) => { + if (code === marker) { + if (size > 1) return nok(code); + effects.consume(code); + size++; + return more; + } + if (size < 2) return nok(code); + const token = effects.exit('highlightSequenceTemporary'); + const before = classifyCharacter(previous); + const after = classifyCharacter(code); + token._open = !after || (after === 2 && Boolean(before)); + token._close = !before || (before === 2 && Boolean(after)); + return ok(code); + }; + return (code) => { + marker = code; + if ( + previous === marker && + events[events.length - 1][1].type !== 'characterEscape' + ) { + return nok(code); + } + effects.enter('highlightSequenceTemporary'); + return more(code); + }; +}; diff --git a/package-lock.json b/package-lock.json index 0b876a3..a46d141 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,10 +21,16 @@ "@pbnotes/pages": "*", "electron-squirrel-startup": "^1.0.1", "katex": "^0.16.47", + "mdast-util-gfm-strikethrough": "^2.0.1", + "micromark-extension-gfm-strikethrough": "^2.1.0", + "micromark-util-chunked": "^2.0.1", + "micromark-util-classify-character": "^2.0.1", + "micromark-util-resolve-all": "^2.0.1", "react": "^19.2.8", "react-dom": "^19.2.8", "react-markdown": "^10.1.0", - "rehype-katex": "^7.0.1" + "rehype-katex": "^7.0.1", + "unified": "^11.0.5" }, "devDependencies": { "@electron-forge/cli": "^7.11.2", @@ -44,6 +50,7 @@ "@storybook/react-vite": "^10.6.0", "@tailwindcss/vite": "^4.3.3", "@types/electron-squirrel-startup": "^1.0.2", + "@types/mdast": "^4.0.4", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.7", "@typescript-eslint/eslint-plugin": "^5.62.0", @@ -53,6 +60,9 @@ "eslint": "^8.57.1", "eslint-plugin-import": "^2.32.0", "eslint-plugin-storybook": "^10.6.0", + "mdast-util-from-markdown": "^2.1.0", + "micromark-util-types": "^2.0.3", + "remark-parse": "^11.0.0", "storybook": "^10.6.0", "tailwindcss": "^4.3.3", "typescript": "^5.9.3", @@ -11478,9 +11488,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz", + "integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -12193,9 +12203,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", "funding": [ { "type": "GitHub Sponsors", @@ -16672,20 +16682,13 @@ "license": "MIT", "dependencies": { "mdast-util-gfm-autolink-literal": "^2.0.1", - "mdast-util-gfm-strikethrough": "^2.0.1", "micromark-extension-gfm-autolink-literal": "^2.1.0", - "micromark-extension-gfm-strikethrough": "^2.1.0", - "micromark-util-chunked": "^2.0.1", - "micromark-util-classify-character": "^2.0.1", - "micromark-util-resolve-all": "^2.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "unified": "^11.0.5" }, "devDependencies": { - "@types/mdast": "^4.0.4", - "mdast-util-from-markdown": "^2.0.2", - "micromark-util-types": "^2.0.2" + "@types/mdast": "^4.0.4" } } } diff --git a/packages/pages/package.json b/packages/pages/package.json index d504a9a..4fda75d 100644 --- a/packages/pages/package.json +++ b/packages/pages/package.json @@ -11,20 +11,13 @@ }, "dependencies": { "mdast-util-gfm-autolink-literal": "^2.0.1", - "mdast-util-gfm-strikethrough": "^2.0.1", "micromark-extension-gfm-autolink-literal": "^2.1.0", - "micromark-extension-gfm-strikethrough": "^2.1.0", - "micromark-util-chunked": "^2.0.1", - "micromark-util-classify-character": "^2.0.1", - "micromark-util-resolve-all": "^2.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "unified": "^11.0.5" }, "devDependencies": { - "@types/mdast": "^4.0.4", - "mdast-util-from-markdown": "^2.0.2", - "micromark-util-types": "^2.0.2" + "@types/mdast": "^4.0.4" }, "license": "MIT" } diff --git a/packages/pages/src/wikilink/wikilink.test.ts b/packages/pages/src/wikilink/wikilink.test.ts index 1319943..6a465b6 100644 --- a/packages/pages/src/wikilink/wikilink.test.ts +++ b/packages/pages/src/wikilink/wikilink.test.ts @@ -186,7 +186,7 @@ test('a note with none of them is untouched', () => { ); }); -/** Read the way a note is read, strikethrough included. */ +/** Read the way a note is read, URLs and math included. */ const full = unified().use(remarkParse).use(remarkPlugins); const parseNote = (source: string) => full.runSync(full.parse(source)) as Node; @@ -196,15 +196,6 @@ const all = (node: Node, type: string): Node[] => [ ...(node.children ?? []).flatMap((child) => all(child, type)), ]; -/** A link struck through is still a link; tildes in code, math or alone stay - what was typed. */ -test('~~strikethrough~~ wraps its words, links and all', () => { - const tree = parseNote('~~done [[Mira]]~~ `~~a~~` $~~b~~$ ~c~'); - assert.deepEqual(all(tree, 'delete').map(read), ['done [[Mira]]']); - assert.deepEqual(pages(tree), ['Mira']); - assert.match(read(tree), /~~a~~.*~~b~~.*~c~/); -}); - /** The URLs a note links out to, in document order. */ const urls = (node: Node): string[] => all(node, 'link').map((link) => (link as { url?: string }).url ?? ''); @@ -239,17 +230,3 @@ test('a URL on its own is left as micromark made it', () => { ['text', 'link', 'text'], ); }); - -/** Both spellings highlight, links inside still count; code, math, single - markers, spaced markers and mismatched pairs stay what was typed. */ -test('==highlight== and ^^highlight^^ wrap their words, links and all', () => { - const tree = parseNote( - '==done [[Mira]]== ^^#Ada^^ `==a==` $==b==$ a = b 2^3 x == y == z ==c^^ ===d===', - ); - assert.deepEqual(all(tree, 'highlight').map(read), ['done [[Mira]]', '#Ada']); - assert.deepEqual(pages(tree), ['Mira', 'Ada']); - assert.match( - read(tree), - /==a==.*==b==.*a = b 2\^3 x == y == z ==c\^\^ ===d===/, - ); -}); diff --git a/packages/pages/src/wikilink/wikilink.ts b/packages/pages/src/wikilink/wikilink.ts index 472d8e0..ee4ae6a 100644 --- a/packages/pages/src/wikilink/wikilink.ts +++ b/packages/pages/src/wikilink/wikilink.ts @@ -1,23 +1,8 @@ import type { Link, Root, RootContent, Text } from 'mdast'; import type { Processor } from 'unified'; import remarkMath from 'remark-math'; -import { gfmStrikethrough } from 'micromark-extension-gfm-strikethrough'; -import { gfmStrikethroughFromMarkdown } from 'mdast-util-gfm-strikethrough'; import { gfmAutolinkLiteral } from 'micromark-extension-gfm-autolink-literal'; import { gfmAutolinkLiteralFromMarkdown } from 'mdast-util-gfm-autolink-literal'; -import type { Extension as FromMarkdownExtension } from 'mdast-util-from-markdown'; -import type { - Code, - Construct, - Event, - Resolver, - State, - Token, - Tokenizer, -} from 'micromark-util-types'; -import { splice } from 'micromark-util-chunked'; -import { classifyCharacter } from 'micromark-util-classify-character'; -import { resolveAll } from 'micromark-util-resolve-all'; /** * A hashtag and a link are the same idea here, so there is one plugin and @@ -191,177 +176,6 @@ function linkFor(match: RegExpExecArray): RootContent { /** The text a link reads as. A bare URL's label is one text node. */ const read = (node: Link) => (node.children[0] as Text).value; -/** - * `~~foo~~`, and only that piece of GFM: `remark-gfm` would also bring - * tables, task lists, footnotes and bare-URL autolinks, each of which changes - * how existing notes look. A single `~` stays text, so `~5 min` is safe. - * Rendering needs nothing more — the `delete` node already becomes ``. - */ -function remarkStrikethrough(this: Processor) { - const data = this.data(); - (data.micromarkExtensions ??= []).push( - gfmStrikethrough({ singleTilde: false }), - ); - (data.fromMarkdownExtensions ??= []).push(gfmStrikethroughFromMarkdown()); -} - -declare module 'micromark-util-types' { - interface TokenTypeMap { - highlight: 'highlight'; - highlightSequence: 'highlightSequence'; - highlightSequenceTemporary: 'highlightSequenceTemporary'; - highlightText: 'highlightText'; - } -} - -const EQUALS = 61; -const CARET = 94; - -/** - * `==foo==`, Obsidian's highlight, and `^^foo^^`, Roam's: one node, drawn as - * ``. The tokenizer is GFM strikethrough's with the tilde swapped out — - * the same flanking rules, so `a == b` and `x ^^ y` stay text, and the same - * doubled marker, so `a = b` and `2^3` do too. What it adds is that a closer - * has to be the marker its opener was: `==a^^` highlights nothing. - * - * ponytail: a bare URL right before the closer, `==see https://a.com==`, - * takes the `==` into the URL and nothing is highlighted — the autolink - * extension's list of trailing punctuation to drop is fixed and has `~` but - * not `=` or `^`. A space before the closer does not help either. Fork that - * tokenizer's trail check if anyone highlights links. - */ -function remarkHighlight(this: Processor) { - const tokenizer: Construct = { - name: 'highlight', - tokenize: tokenizeHighlight, - resolveAll: resolveAllHighlight, - }; - const data = this.data(); - (data.micromarkExtensions ??= []).push({ - text: { [EQUALS]: tokenizer, [CARET]: tokenizer }, - insideSpan: { null: [tokenizer] }, - attentionMarkers: { null: [EQUALS, CARET] }, - }); - (data.fromMarkdownExtensions ??= []).push({ - canContainEols: ['highlight'], - enter: { - highlight(token) { - const node = { - type: 'highlight', - children: [] as RootContent[], - data: { hName: 'mark' }, - }; - this.enter(node as never, token); - }, - }, - exit: { - highlight(token) { - this.exit(token); - }, - }, - } satisfies FromMarkdownExtension); -} - -/** Pair each closer with the nearest opener spelled the same way. */ -const resolveAllHighlight: Resolver = (events, context) => { - for (let index = 0; index < events.length; index++) { - const close = events[index][1]; - if ( - events[index][0] !== 'enter' || - close.type !== 'highlightSequenceTemporary' || - !close._close - ) { - continue; - } - let open = index; - while (open--) { - const opener = events[open][1]; - if ( - events[open][0] === 'exit' && - opener.type === 'highlightSequenceTemporary' && - opener._open && - context.sliceSerialize(opener) === context.sliceSerialize(close) - ) { - opener.type = 'highlightSequence'; - close.type = 'highlightSequence'; - const highlight: Token = { - type: 'highlight', - start: { ...opener.start }, - end: { ...close.end }, - }; - const text: Token = { - type: 'highlightText', - start: { ...opener.end }, - end: { ...close.start }, - }; - const next: Event[] = [ - ['enter', highlight, context], - ['enter', opener, context], - ['exit', opener, context], - ['enter', text, context], - ]; - const insideSpan = context.parser.constructs.insideSpan.null; - if (insideSpan) { - splice( - next, - next.length, - 0, - resolveAll(insideSpan, events.slice(open + 1, index), context), - ); - } - splice(next, next.length, 0, [ - ['exit', text, context], - ['enter', close, context], - ['exit', close, context], - ['exit', highlight, context], - ]); - splice(events, open - 1, index - open + 3, next); - index = open + next.length - 2; - break; - } - } - } - for (const [, token] of events) { - if (token.type === 'highlightSequenceTemporary') token.type = 'data'; - } - return events; -}; - -/** Exactly two of one marker; whether they open, close or both is down to - what is either side of them, as with `~~`. */ -const tokenizeHighlight: Tokenizer = function (effects, ok, nok) { - const previous = this.previous; - const events = this.events; - let marker: Code = null; - let size = 0; - const more: State = (code) => { - if (code === marker) { - if (size > 1) return nok(code); - effects.consume(code); - size++; - return more; - } - if (size < 2) return nok(code); - const token = effects.exit('highlightSequenceTemporary'); - const before = classifyCharacter(previous); - const after = classifyCharacter(code); - token._open = !after || (after === 2 && Boolean(before)); - token._close = !before || (before === 2 && Boolean(after)); - return ok(code); - }; - return (code) => { - marker = code; - if ( - previous === marker && - events[events.length - 1][1].type !== 'characterEscape' - ) { - return nok(code); - } - effects.enter('highlightSequenceTemporary'); - return more(code); - }; -}; - /** * Bare URLs, `https://example.com` and `www.example.com`, the way GitHub * links them: trailing punctuation left out, parentheses balanced, code and @@ -377,22 +191,20 @@ function remarkAutolinkLiteral(this: Processor) { /** * The remark plugins a note is read with, in the order they run. Math * first: `$…$` becomes a node without text children, so a `#` inside a - * formula never becomes a link. Strikethrough and highlight are syntax, like - * math; the walk then descends into a `delete` or a `mark` as into any other - * formatting. Bare URLs come before the walk too, which takes them back apart - * where they cut into a link. + * formula never becomes a link. Bare URLs come before the walk too, which + * takes them back apart where they cut into a link. * * Exported because whoever renders a block has to read it the same way * `reading.ts` does to find the pages it links to, which is how a page lists * what links to it. Two parsers that have to agree on what a link is would be * one more than anyone can keep in step — a `#foo` in a code span would - * render as text and count as a reference. + * render as text and count as a reference. A renderer may add marks that + * only draw (strikethrough, highlight); the walk descends into those as into + * any other formatting. */ export const remarkPlugins = [ remarkMath, - remarkStrikethrough, remarkAutolinkLiteral, - remarkHighlight, remarkWikilink, ];