@glw907/cairn-cms 0.51.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/components/EditPage.svelte +38 -2
- package/dist/components/EditorToolbar.svelte +60 -6
- package/dist/components/EditorToolbar.svelte.d.ts +10 -1
- package/dist/components/MarkdownEditor.svelte +114 -24
- package/dist/components/MarkdownEditor.svelte.d.ts +4 -0
- package/dist/components/cairn-admin.css +27 -11
- package/dist/components/editor-highlight.d.ts +2 -1
- package/dist/components/editor-highlight.js +60 -19
- package/dist/components/editor-modes.d.ts +26 -0
- package/dist/components/editor-modes.js +92 -0
- package/dist/components/fonts/iAWriterMono-OFL.txt +100 -0
- package/dist/components/fonts/ia-writer-mono-latin-400-italic.woff2 +0 -0
- package/dist/components/fonts/ia-writer-mono-latin-400-normal.woff2 +0 -0
- package/dist/components/fonts/ia-writer-mono-latin-700-italic.woff2 +0 -0
- package/dist/components/fonts/ia-writer-mono-latin-700-normal.woff2 +0 -0
- package/dist/components/markdown-directives.d.ts +48 -7
- package/dist/components/markdown-directives.js +89 -13
- package/package.json +1 -1
- package/src/lib/components/EditPage.svelte +38 -2
- package/src/lib/components/EditorToolbar.svelte +60 -6
- package/src/lib/components/MarkdownEditor.svelte +114 -24
- package/src/lib/components/cairn-admin.css +62 -31
- package/src/lib/components/editor-highlight.ts +72 -20
- package/src/lib/components/editor-modes.ts +106 -0
- package/src/lib/components/fonts/iAWriterMono-OFL.txt +100 -0
- package/src/lib/components/fonts/ia-writer-mono-latin-400-italic.woff2 +0 -0
- package/src/lib/components/fonts/ia-writer-mono-latin-400-normal.woff2 +0 -0
- package/src/lib/components/fonts/ia-writer-mono-latin-700-italic.woff2 +0 -0
- package/src/lib/components/fonts/ia-writer-mono-latin-700-normal.woff2 +0 -0
- package/src/lib/components/markdown-directives.ts +113 -13
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// The iA writing modes: focus mode dims every line outside the caret's paragraph, and typewriter
|
|
2
|
+
// scroll holds the cursor line at vertical center while typing. Client-only, like editor-highlight:
|
|
3
|
+
// MarkdownEditor reaches this module through a dynamic import, so the static @codemirror imports
|
|
4
|
+
// here never enter a server bundle (guarded by the editor-boundary test).
|
|
5
|
+
import {
|
|
6
|
+
Decoration,
|
|
7
|
+
EditorView,
|
|
8
|
+
ViewPlugin,
|
|
9
|
+
type DecorationSet,
|
|
10
|
+
type ViewUpdate,
|
|
11
|
+
} from '@codemirror/view';
|
|
12
|
+
import { RangeSetBuilder, type Extension } from '@codemirror/state';
|
|
13
|
+
|
|
14
|
+
/** An inclusive 0-based line range. */
|
|
15
|
+
export interface LineRange {
|
|
16
|
+
fromLine: number;
|
|
17
|
+
toLine: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isBlank = (line: string | undefined) => !line || /^\s*$/.test(line);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The contiguous non-blank block around the caret line, the unit focus mode keeps at full ink.
|
|
24
|
+
* On a blank line the caret stands alone; the walk clamps at the document edges, and an
|
|
25
|
+
* out-of-range caret clamps into the document first.
|
|
26
|
+
*/
|
|
27
|
+
export function paragraphRange(lines: string[], caretLine: number): LineRange {
|
|
28
|
+
const last = Math.max(lines.length - 1, 0);
|
|
29
|
+
const caret = Math.min(Math.max(caretLine, 0), last);
|
|
30
|
+
if (isBlank(lines[caret])) return { fromLine: caret, toLine: caret };
|
|
31
|
+
let fromLine = caret;
|
|
32
|
+
while (fromLine > 0 && !isBlank(lines[fromLine - 1])) fromLine--;
|
|
33
|
+
let toLine = caret;
|
|
34
|
+
while (toLine < last && !isBlank(lines[toLine + 1])) toLine++;
|
|
35
|
+
return { fromLine, toLine };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const dimLine = Decoration.line({ class: 'cm-cairn-focus-dim' });
|
|
39
|
+
|
|
40
|
+
// The line cache mirrors editor-highlight's: one full-document read per doc change, so a caret
|
|
41
|
+
// move or scroll rebuilds the viewport decorations from the cached array.
|
|
42
|
+
function docLines(view: EditorView): string[] {
|
|
43
|
+
const doc = view.state.doc;
|
|
44
|
+
const lines: string[] = [];
|
|
45
|
+
for (let n = 1; n <= doc.lines; n++) lines.push(doc.line(n).text);
|
|
46
|
+
return lines;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildFocusDecorations(view: EditorView, lines: string[]): DecorationSet {
|
|
50
|
+
const builder = new RangeSetBuilder<Decoration>();
|
|
51
|
+
const caretLine = view.state.doc.lineAt(view.state.selection.main.head).number - 1;
|
|
52
|
+
const paragraph = paragraphRange(lines, caretLine);
|
|
53
|
+
for (const { from, to } of view.visibleRanges) {
|
|
54
|
+
for (let pos = from; pos <= to; ) {
|
|
55
|
+
const line = view.state.doc.lineAt(pos);
|
|
56
|
+
const n = line.number - 1;
|
|
57
|
+
if (n < paragraph.fromLine || n > paragraph.toLine) builder.add(line.from, line.from, dimLine);
|
|
58
|
+
pos = line.to + 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return builder.finish();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Focus mode: a line class (`cm-cairn-focus-dim`) on every line outside the caret's paragraph.
|
|
66
|
+
* The class only marks the lines; the dim ink itself lives in the editor theme so the per-theme
|
|
67
|
+
* `--cairn-focus-dim-ink` variable resolves it.
|
|
68
|
+
*/
|
|
69
|
+
export function focusMode(): Extension {
|
|
70
|
+
return ViewPlugin.fromClass(
|
|
71
|
+
class {
|
|
72
|
+
decorations: DecorationSet;
|
|
73
|
+
lines: string[];
|
|
74
|
+
constructor(view: EditorView) {
|
|
75
|
+
this.lines = docLines(view);
|
|
76
|
+
this.decorations = buildFocusDecorations(view, this.lines);
|
|
77
|
+
}
|
|
78
|
+
update(update: ViewUpdate) {
|
|
79
|
+
if (update.docChanged) this.lines = docLines(update.view);
|
|
80
|
+
if (update.docChanged || update.viewportChanged || update.selectionSet)
|
|
81
|
+
this.decorations = buildFocusDecorations(update.view, this.lines);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{ decorations: (v) => v.decorations },
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Typewriter scroll: on every doc change, recenter the selection head vertically. An update
|
|
90
|
+
* listener may not dispatch while its update runs, so the recenter is queued as a microtask:
|
|
91
|
+
* it fires as soon as the update finishes, still ahead of the next paint, where an animation
|
|
92
|
+
* frame would trail the edit by a frame. The isConnected guard skips a view destroyed in the
|
|
93
|
+
* queue window.
|
|
94
|
+
*/
|
|
95
|
+
export function typewriterScroll(): Extension {
|
|
96
|
+
return EditorView.updateListener.of((update) => {
|
|
97
|
+
if (!update.docChanged) return;
|
|
98
|
+
const view = update.view;
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
if (!view.dom.isConnected) return;
|
|
101
|
+
view.dispatch({
|
|
102
|
+
effects: EditorView.scrollIntoView(view.state.selection.main.head, { y: 'center' }),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# iA Writer Typeface
|
|
2
|
+
|
|
3
|
+
Copyright © 2018 Information Architects Inc. with Reserved Font Name "iA Writer"
|
|
4
|
+
|
|
5
|
+
# Based on IBM Plex Typeface
|
|
6
|
+
|
|
7
|
+
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
|
|
8
|
+
|
|
9
|
+
# License
|
|
10
|
+
|
|
11
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
12
|
+
This license is copied below, and is also available with a FAQ at:
|
|
13
|
+
http://scripts.sil.org/OFL
|
|
14
|
+
|
|
15
|
+
-----------------------------------------------------------
|
|
16
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
17
|
+
-----------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
PREAMBLE
|
|
20
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
21
|
+
development of collaborative font projects, to support the font creation
|
|
22
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
23
|
+
open framework in which fonts may be shared and improved in partnership
|
|
24
|
+
with others.
|
|
25
|
+
|
|
26
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
27
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
28
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
29
|
+
redistributed and/or sold with any software provided that any reserved
|
|
30
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
31
|
+
however, cannot be released under any other type of license. The
|
|
32
|
+
requirement for fonts to remain under this license does not apply
|
|
33
|
+
to any document created using the fonts or their derivatives.
|
|
34
|
+
|
|
35
|
+
DEFINITIONS
|
|
36
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
37
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
38
|
+
include source files, build scripts and documentation.
|
|
39
|
+
|
|
40
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
41
|
+
copyright statement(s).
|
|
42
|
+
|
|
43
|
+
"Original Version" refers to the collection of Font Software components as
|
|
44
|
+
distributed by the Copyright Holder(s).
|
|
45
|
+
|
|
46
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
47
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
48
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
49
|
+
new environment.
|
|
50
|
+
|
|
51
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
52
|
+
writer or other person who contributed to the Font Software.
|
|
53
|
+
|
|
54
|
+
PERMISSION & CONDITIONS
|
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
56
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
57
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
58
|
+
Software, subject to the following conditions:
|
|
59
|
+
|
|
60
|
+
1) Neither the Font Software nor any of its individual components,
|
|
61
|
+
in Original or Modified Versions, may be sold by itself.
|
|
62
|
+
|
|
63
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
64
|
+
redistributed and/or sold with any software, provided that each copy
|
|
65
|
+
contains the above copyright notice and this license. These can be
|
|
66
|
+
included either as stand-alone text files, human-readable headers or
|
|
67
|
+
in the appropriate machine-readable metadata fields within text or
|
|
68
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
69
|
+
|
|
70
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
71
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
72
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
73
|
+
presented to the users.
|
|
74
|
+
|
|
75
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
76
|
+
Software shall not be used to promote, endorse or advertise any
|
|
77
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
78
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
79
|
+
permission.
|
|
80
|
+
|
|
81
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
82
|
+
must be distributed entirely under this license, and must not be
|
|
83
|
+
distributed under any other license. The requirement for fonts to
|
|
84
|
+
remain under this license does not apply to any document created
|
|
85
|
+
using the Font Software.
|
|
86
|
+
|
|
87
|
+
TERMINATION
|
|
88
|
+
This license becomes null and void if any of the above conditions are
|
|
89
|
+
not met.
|
|
90
|
+
|
|
91
|
+
DISCLAIMER
|
|
92
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
93
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
94
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
95
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
96
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
97
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
98
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
99
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
100
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
// A container fence: three or more colons, then an optional name, an optional [label], and
|
|
6
6
|
// optional {attrs}, in remark-directive order. The name is captured so the depth scan below can
|
|
7
|
-
// tell an opener (named) from a closer (bare colons)
|
|
8
|
-
//
|
|
9
|
-
|
|
7
|
+
// tell an opener (named) from a closer (bare colons), and the d flag records each group's span
|
|
8
|
+
// so fenceTokens can split the line without re-parsing. Matching is tolerant of stray
|
|
9
|
+
// whitespace, the same posture as the leaf form: a slightly off fence should still read as
|
|
10
|
+
// machinery.
|
|
11
|
+
const FENCE = /^\s{0,3}(:{3,})\s*([\w-]*)\s*(\[[^\]]*\])?\s*(\{[^}]*\})?\s*$/d;
|
|
10
12
|
const LEAF = /^\s{0,3}::[\w-]+(\[[^\]]*\])?(\{[^}]*\})?\s*$/;
|
|
11
13
|
const INLINE = /(?<![:\w]):[\w-]+\[[^\]]*\](\{[^}]*\})?/g;
|
|
12
14
|
|
|
@@ -22,17 +24,28 @@ export function directiveLineKind(line: string): 'fence' | 'leaf' | null {
|
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/** One pass over the document: each line's container depth alongside its fence role. */
|
|
28
|
+
export interface FenceScan {
|
|
29
|
+
/** The 1-based container depth per line, or null outside any container. */
|
|
30
|
+
depths: (number | null)[];
|
|
31
|
+
/** Whether a line opened or closed a container, or null for everything else. A fence-shaped
|
|
32
|
+
* line the code-block tracking disowned is null too, so the role array is the one source of
|
|
33
|
+
* truth for pairing and no caller re-parses a line the scan already judged. */
|
|
34
|
+
roles: ('opener' | 'closer' | null)[];
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
38
|
+
* Scan the document's container structure in one pass. A named fence opens a container; a bare
|
|
39
|
+
* fence closes the most recent one (colon counts are not trusted for pairing, since authors
|
|
40
|
+
* vary them). An opener and its closer share the opener's depth, and a line between them
|
|
41
|
+
* carries the depth of its innermost container. Lines inside a fenced code block are plain
|
|
42
|
+
* content, so a documented ::: example cannot open a phantom container running to end of
|
|
43
|
+
* document. Author errors are tolerated: an unmatched closer reads as depth 1 and the count
|
|
44
|
+
* never goes below zero.
|
|
33
45
|
*/
|
|
34
|
-
export function
|
|
46
|
+
export function fenceScan(lines: string[]): FenceScan {
|
|
35
47
|
const depths: (number | null)[] = [];
|
|
48
|
+
const roles: ('opener' | 'closer' | null)[] = [];
|
|
36
49
|
let open = 0;
|
|
37
50
|
// The marker character that opened the current code block, or null outside one. Only a line
|
|
38
51
|
// opening with the same character closes it, so tildes inside a backtick block stay literal.
|
|
@@ -43,24 +56,111 @@ export function fenceDepths(lines: string[]): (number | null)[] {
|
|
|
43
56
|
if (codeMarker === null) codeMarker = code[1][0];
|
|
44
57
|
else if (code[1][0] === codeMarker) codeMarker = null;
|
|
45
58
|
depths.push(open > 0 ? open : null);
|
|
59
|
+
roles.push(null);
|
|
46
60
|
continue;
|
|
47
61
|
}
|
|
48
62
|
if (codeMarker !== null) {
|
|
49
63
|
depths.push(open > 0 ? open : null);
|
|
64
|
+
roles.push(null);
|
|
50
65
|
continue;
|
|
51
66
|
}
|
|
52
67
|
const fence = FENCE.exec(line);
|
|
53
68
|
if (!fence) {
|
|
54
69
|
depths.push(open > 0 ? open : null);
|
|
55
|
-
|
|
70
|
+
roles.push(null);
|
|
71
|
+
} else if (fence[2]) {
|
|
56
72
|
open += 1;
|
|
57
73
|
depths.push(open);
|
|
74
|
+
roles.push('opener');
|
|
58
75
|
} else {
|
|
59
76
|
depths.push(Math.max(open, 1));
|
|
77
|
+
roles.push('closer');
|
|
60
78
|
if (open > 0) open -= 1;
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
|
-
return depths;
|
|
81
|
+
return { depths, roles };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The depth half of {@link fenceScan}, for callers that need no roles. */
|
|
85
|
+
export function fenceDepths(lines: string[]): (number | null)[] {
|
|
86
|
+
return fenceScan(lines).depths;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The inclusive line span of one directive container. */
|
|
90
|
+
export interface ContainerRange {
|
|
91
|
+
fromLine: number;
|
|
92
|
+
toLine: number;
|
|
93
|
+
depth: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The innermost container around a caret line, as an inclusive line range, or null outside any
|
|
98
|
+
* container. Works from the cached scan without re-parsing: the caret line's own depth names
|
|
99
|
+
* the container (fence rows carry the depth they delimit, so a caret on a fence belongs to
|
|
100
|
+
* that fence's container), and within a container the only same-depth real fences are its
|
|
101
|
+
* opener and closer (nested containers sit deeper, siblings sit outside), so the nearest
|
|
102
|
+
* opener above and the nearest closer below bound the range. The scan's roles already disown a
|
|
103
|
+
* fence-shaped line inside a code block, so a documented example can never clip the range. An
|
|
104
|
+
* unclosed container runs to the document end.
|
|
105
|
+
*/
|
|
106
|
+
export function caretContainerRange(scan: FenceScan, caretLine: number): ContainerRange | null {
|
|
107
|
+
const { depths, roles } = scan;
|
|
108
|
+
const depth = depths[caretLine] ?? null;
|
|
109
|
+
if (depth === null) return null;
|
|
110
|
+
let fromLine = caretLine;
|
|
111
|
+
for (let i = caretLine; i >= 0; i--) {
|
|
112
|
+
if (depths[i] === depth && roles[i] === 'opener') {
|
|
113
|
+
fromLine = i;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
let toLine = depths.length - 1;
|
|
118
|
+
for (let i = caretLine; i < depths.length; i++) {
|
|
119
|
+
if (depths[i] === depth && roles[i] === 'closer') {
|
|
120
|
+
toLine = i;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { fromLine, toLine, depth };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** One span of a fence line, in line-local offsets: machinery (`mark`) or meaning (`label`). */
|
|
128
|
+
export interface FenceToken {
|
|
129
|
+
from: number;
|
|
130
|
+
to: number;
|
|
131
|
+
kind: 'mark' | 'label';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Split a fence line into machinery and meaning. The colon run, the label's brackets, and the
|
|
136
|
+
* whole {attrs} group are machinery; the directive name and the label text are meaning, the
|
|
137
|
+
* parts an editor reads. A bare closer is a single machinery span, and a non-fence line yields
|
|
138
|
+
* no spans at all.
|
|
139
|
+
*/
|
|
140
|
+
export function fenceTokens(line: string): FenceToken[] {
|
|
141
|
+
const m = FENCE.exec(line);
|
|
142
|
+
if (!m?.indices) return [];
|
|
143
|
+
// A group's span exists whenever the group matched: group 1 (the colons) always does on a
|
|
144
|
+
// fence, and the optional groups are read only behind their own m[n] guard.
|
|
145
|
+
const indices = m.indices;
|
|
146
|
+
const out: FenceToken[] = [];
|
|
147
|
+
const [colonFrom, colonTo] = indices[1]!;
|
|
148
|
+
out.push({ from: colonFrom, to: colonTo, kind: 'mark' });
|
|
149
|
+
if (m[2]) {
|
|
150
|
+
const [from, to] = indices[2]!;
|
|
151
|
+
out.push({ from, to, kind: 'label' });
|
|
152
|
+
}
|
|
153
|
+
if (m[3]) {
|
|
154
|
+
const [from, to] = indices[3]!;
|
|
155
|
+
out.push({ from, to: from + 1, kind: 'mark' });
|
|
156
|
+
if (to - from > 2) out.push({ from: from + 1, to: to - 1, kind: 'label' });
|
|
157
|
+
out.push({ from: to - 1, to, kind: 'mark' });
|
|
158
|
+
}
|
|
159
|
+
if (m[4]) {
|
|
160
|
+
const [from, to] = indices[4]!;
|
|
161
|
+
out.push({ from, to, kind: 'mark' });
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
64
164
|
}
|
|
65
165
|
|
|
66
166
|
/** Inline directive ranges (`:name[...]{...}`) within a line of text. */
|