@aicayzer/inkkit 0.0.8 → 0.0.10
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/README.md +5 -3
- package/dist/callouts.js +2 -1
- package/dist/clipboard.d.ts +3 -3
- package/dist/clipboard.js +64 -25
- package/dist/dialect.d.ts +1 -1
- package/dist/dialect.js +36 -2
- package/dist/editor.d.ts +27 -1
- package/dist/editor.js +478 -35
- package/dist/files.d.ts +40 -0
- package/dist/files.js +133 -0
- package/dist/images.d.ts +20 -4
- package/dist/images.js +242 -25
- package/dist/index.d.ts +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +8 -0
- package/dist/linked-syntax.d.ts +48 -0
- package/dist/linked-syntax.js +308 -0
- package/dist/literals.js +40 -1
- package/dist/portable-files.d.ts +10 -0
- package/dist/portable-files.js +36 -0
- package/dist/preserve.js +7 -0
- package/dist/print.d.ts +2 -2
- package/dist/print.js +49 -17
- package/dist/search.d.ts +2 -1
- package/dist/search.js +53 -22
- package/dist/source.d.ts +0 -1
- package/dist/source.js +0 -60
- package/dist/style.css +39 -0
- package/dist/text-ranges.d.ts +26 -0
- package/dist/text-ranges.js +233 -0
- package/dist/types.d.ts +84 -2
- package/dist/wiki-view.d.ts +21 -0
- package/dist/wiki-view.js +78 -0
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
2
|
+
import type { Literal } from 'mdast';
|
|
3
|
+
import type { FileReference, WikiLinkReference } from './types.js';
|
|
4
|
+
interface WikiLink extends Literal {
|
|
5
|
+
type: 'inkkitWikiLink';
|
|
6
|
+
raw: string;
|
|
7
|
+
target: string;
|
|
8
|
+
fragment: string | null;
|
|
9
|
+
label: string;
|
|
10
|
+
inTable: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare module 'mdast' {
|
|
13
|
+
interface PhrasingContentMap {
|
|
14
|
+
inkkitWikiLink: WikiLink;
|
|
15
|
+
}
|
|
16
|
+
interface RootContentMap {
|
|
17
|
+
inkkitWikiLink: WikiLink;
|
|
18
|
+
}
|
|
19
|
+
interface Image {
|
|
20
|
+
inkkitFileRaw?: string;
|
|
21
|
+
inkkitFileTarget?: string;
|
|
22
|
+
inkkitFileFragment?: string | null;
|
|
23
|
+
inkkitFileWidth?: number | null;
|
|
24
|
+
inkkitFileInTable?: boolean;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
declare module 'micromark-util-types' {
|
|
28
|
+
interface TokenTypeMap {
|
|
29
|
+
inkkitLinkedSyntax: 'inkkitLinkedSyntax';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export interface LinkedSyntax {
|
|
33
|
+
raw: string;
|
|
34
|
+
target: string;
|
|
35
|
+
fragment: string | null;
|
|
36
|
+
label: string;
|
|
37
|
+
width: number | null;
|
|
38
|
+
embed: boolean;
|
|
39
|
+
inTable: boolean;
|
|
40
|
+
}
|
|
41
|
+
export declare function parseLinkedSyntax(raw: string, inTable?: boolean): LinkedSyntax | undefined;
|
|
42
|
+
export declare function wikiMarkdown(node: ProseNode, inTable?: boolean): string;
|
|
43
|
+
export declare function wikiReference(node: ProseNode): WikiLinkReference;
|
|
44
|
+
export declare function fileReference(node: ProseNode): FileReference;
|
|
45
|
+
export declare function namedFileMarkdown(node: ProseNode, inTable?: boolean): string | undefined;
|
|
46
|
+
export declare const wikiLinkSchema: import("@milkdown/kit/utils").$Node;
|
|
47
|
+
export declare function linkedSyntax(wikiLinks: boolean, files: boolean): import("@milkdown/kit/utils").$Remark<"inkkitLinkedSyntax", unknown>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { $node, $remark } from '@milkdown/kit/utils';
|
|
2
|
+
import { defaultHandlers } from 'mdast-util-to-markdown';
|
|
3
|
+
const fromTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${'\\'.repeat(Math.floor(escapes.length / 2))}|`);
|
|
4
|
+
const toTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${'\\'.repeat(escapes.length * 2 + 1)}|`);
|
|
5
|
+
export function parseLinkedSyntax(raw, inTable = false) {
|
|
6
|
+
const source = inTable ? fromTable(raw) : raw;
|
|
7
|
+
const embed = source.startsWith('!');
|
|
8
|
+
const prefix = embed ? '![[' : '[[';
|
|
9
|
+
if (!source.startsWith(prefix) || !source.endsWith(']]'))
|
|
10
|
+
return undefined;
|
|
11
|
+
const content = source.slice(prefix.length, -2);
|
|
12
|
+
const parts = { target: '', fragment: '', alias: '' };
|
|
13
|
+
let section = 'target', hasFragment = false, hasAlias = false;
|
|
14
|
+
for (let index = 0; index < content.length; index++) {
|
|
15
|
+
const character = content[index];
|
|
16
|
+
if (character === '\\') {
|
|
17
|
+
const escaped = content[++index];
|
|
18
|
+
if (!escaped || !'[]|#\\'.includes(escaped))
|
|
19
|
+
return undefined;
|
|
20
|
+
parts[section] += escaped;
|
|
21
|
+
}
|
|
22
|
+
else if (character === '|') {
|
|
23
|
+
if (hasAlias)
|
|
24
|
+
return undefined;
|
|
25
|
+
hasAlias = true;
|
|
26
|
+
section = 'alias';
|
|
27
|
+
}
|
|
28
|
+
else if (character === '#' && section !== 'alias') {
|
|
29
|
+
if (hasFragment)
|
|
30
|
+
return undefined;
|
|
31
|
+
hasFragment = true;
|
|
32
|
+
section = 'fragment';
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if ('[]'.includes(character) ||
|
|
36
|
+
character.charCodeAt(0) < 32 ||
|
|
37
|
+
character === '\u007f')
|
|
38
|
+
return undefined;
|
|
39
|
+
parts[section] += character;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if ((!parts.target.trim() && !(hasFragment && parts.fragment.trim())) ||
|
|
43
|
+
(hasFragment && !parts.fragment.trim()) ||
|
|
44
|
+
(hasAlias && !parts.alias.trim()) ||
|
|
45
|
+
parts.target.trimStart().startsWith('^') ||
|
|
46
|
+
parts.fragment.trimStart().startsWith('^'))
|
|
47
|
+
return undefined;
|
|
48
|
+
if (embed &&
|
|
49
|
+
(!parts.target.trim() ||
|
|
50
|
+
(hasAlias && (!/^\d{1,5}$/.test(parts.alias) || Number(parts.alias) < 1))))
|
|
51
|
+
return undefined;
|
|
52
|
+
return {
|
|
53
|
+
raw,
|
|
54
|
+
target: parts.target,
|
|
55
|
+
fragment: hasFragment ? parts.fragment : null,
|
|
56
|
+
label: !embed && hasAlias
|
|
57
|
+
? parts.alias
|
|
58
|
+
: parts.target + (hasFragment ? `#${parts.fragment}` : ''),
|
|
59
|
+
width: embed && hasAlias ? Number(parts.alias) : null,
|
|
60
|
+
embed,
|
|
61
|
+
inTable,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function linkedTokenizer(embed) {
|
|
65
|
+
return function (effects, ok, nok) {
|
|
66
|
+
const prefix = embed ? '![[' : '[[';
|
|
67
|
+
let index = 0, raw = '';
|
|
68
|
+
return opening;
|
|
69
|
+
function consume(code) {
|
|
70
|
+
raw += String.fromCodePoint(code);
|
|
71
|
+
effects.consume(code);
|
|
72
|
+
}
|
|
73
|
+
function opening(code) {
|
|
74
|
+
if (code !== prefix.charCodeAt(index))
|
|
75
|
+
return nok(code);
|
|
76
|
+
if (!index)
|
|
77
|
+
effects.enter('inkkitLinkedSyntax');
|
|
78
|
+
consume(code);
|
|
79
|
+
index++;
|
|
80
|
+
return index === prefix.length ? body : opening;
|
|
81
|
+
}
|
|
82
|
+
function body(code) {
|
|
83
|
+
if (code === null || code < 32 || code === 91)
|
|
84
|
+
return nok(code);
|
|
85
|
+
consume(code);
|
|
86
|
+
if (code === 92)
|
|
87
|
+
return escaped;
|
|
88
|
+
return code === 93 ? closing : body;
|
|
89
|
+
}
|
|
90
|
+
function escaped(code) {
|
|
91
|
+
if (code === null || code < 32)
|
|
92
|
+
return nok(code);
|
|
93
|
+
consume(code);
|
|
94
|
+
return body;
|
|
95
|
+
}
|
|
96
|
+
function closing(code) {
|
|
97
|
+
if (code !== 93)
|
|
98
|
+
return nok(code);
|
|
99
|
+
consume(code);
|
|
100
|
+
if (!parseLinkedSyntax(raw) && !parseLinkedSyntax(raw, true))
|
|
101
|
+
return nok(code);
|
|
102
|
+
effects.exit('inkkitLinkedSyntax');
|
|
103
|
+
return ok;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function tableForm(raw, authoredInTable, outputInTable) {
|
|
108
|
+
if (authoredInTable === outputInTable)
|
|
109
|
+
return raw;
|
|
110
|
+
return outputInTable ? toTable(raw) : fromTable(raw);
|
|
111
|
+
}
|
|
112
|
+
export function wikiMarkdown(node, inTable = false) {
|
|
113
|
+
return tableForm(String(node.attrs.raw), Boolean(node.attrs.inTable), inTable);
|
|
114
|
+
}
|
|
115
|
+
export function wikiReference(node) {
|
|
116
|
+
return {
|
|
117
|
+
target: String(node.attrs.target),
|
|
118
|
+
...(node.attrs.fragment != null
|
|
119
|
+
? { fragment: String(node.attrs.fragment) }
|
|
120
|
+
: {}),
|
|
121
|
+
label: String(node.attrs.label),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function fileReference(node) {
|
|
125
|
+
const reference = String(node.attrs.src ?? '');
|
|
126
|
+
const named = node.attrs.inkkitFileRaw != null;
|
|
127
|
+
const alt = String(node.attrs.alt ?? '');
|
|
128
|
+
const match = /\|(\d{1,5})$/.exec(alt);
|
|
129
|
+
const fragment = named
|
|
130
|
+
? node.attrs.inkkitFileFragment
|
|
131
|
+
: reference.includes('#')
|
|
132
|
+
? reference.slice(reference.indexOf('#') + 1)
|
|
133
|
+
: null;
|
|
134
|
+
const label = named
|
|
135
|
+
? String(node.attrs.inkkitFileTarget ?? '') +
|
|
136
|
+
(fragment != null ? `#${fragment}` : '')
|
|
137
|
+
: match
|
|
138
|
+
? alt.slice(0, match.index)
|
|
139
|
+
: alt;
|
|
140
|
+
const width = named && alt === label && node.attrs.inkkitFileWidth == null
|
|
141
|
+
? null
|
|
142
|
+
: match
|
|
143
|
+
? Number(match[1])
|
|
144
|
+
: null;
|
|
145
|
+
return {
|
|
146
|
+
reference,
|
|
147
|
+
kind: named ? 'wiki' : 'path',
|
|
148
|
+
...(fragment != null ? { fragment: String(fragment) } : {}),
|
|
149
|
+
...(label ? { label } : {}),
|
|
150
|
+
...(width != null ? { width } : {}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export function namedFileMarkdown(node, inTable = false) {
|
|
154
|
+
if (node.attrs.inkkitFileRaw == null)
|
|
155
|
+
return undefined;
|
|
156
|
+
let raw = String(node.attrs.inkkitFileRaw);
|
|
157
|
+
const authoredInTable = Boolean(node.attrs.inkkitFileInTable);
|
|
158
|
+
const width = fileReference(node).width ?? null;
|
|
159
|
+
const original = node.attrs.inkkitFileWidth ?? null;
|
|
160
|
+
if (width !== original) {
|
|
161
|
+
if (authoredInTable)
|
|
162
|
+
raw = fromTable(raw);
|
|
163
|
+
if (original != null)
|
|
164
|
+
raw = raw.replace(/\|\d{1,5}\]\]$/, ']]');
|
|
165
|
+
if (width != null)
|
|
166
|
+
raw = `${raw.slice(0, -2)}|${width}]]`;
|
|
167
|
+
return inTable ? toTable(raw) : raw;
|
|
168
|
+
}
|
|
169
|
+
return tableForm(raw, authoredInTable, inTable);
|
|
170
|
+
}
|
|
171
|
+
export const wikiLinkSchema = $node('inkkit_wiki_link', () => ({
|
|
172
|
+
inline: true,
|
|
173
|
+
group: 'inline',
|
|
174
|
+
atom: true,
|
|
175
|
+
marks: '',
|
|
176
|
+
attrs: {
|
|
177
|
+
raw: { default: '' },
|
|
178
|
+
target: { default: '' },
|
|
179
|
+
fragment: { default: null },
|
|
180
|
+
label: { default: '' },
|
|
181
|
+
inTable: { default: false },
|
|
182
|
+
},
|
|
183
|
+
parseDOM: [
|
|
184
|
+
{
|
|
185
|
+
tag: 'span[data-inkkit-wiki]',
|
|
186
|
+
getAttrs(dom) {
|
|
187
|
+
const parsed = parseLinkedSyntax(dom.getAttribute('data-inkkit-wiki-raw') ?? '', dom.getAttribute('data-inkkit-wiki-table') === 'true');
|
|
188
|
+
return parsed && !parsed.embed
|
|
189
|
+
? {
|
|
190
|
+
raw: parsed.raw,
|
|
191
|
+
target: parsed.target,
|
|
192
|
+
fragment: parsed.fragment,
|
|
193
|
+
label: parsed.label,
|
|
194
|
+
inTable: parsed.inTable,
|
|
195
|
+
}
|
|
196
|
+
: false;
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
toDOM: (node) => [
|
|
201
|
+
'span',
|
|
202
|
+
{
|
|
203
|
+
'data-inkkit-wiki': node.attrs.target,
|
|
204
|
+
'data-inkkit-fragment': node.attrs.fragment ?? '',
|
|
205
|
+
'data-inkkit-wiki-raw': node.attrs.raw,
|
|
206
|
+
'data-inkkit-wiki-table': String(node.attrs.inTable),
|
|
207
|
+
role: 'link',
|
|
208
|
+
tabindex: '0',
|
|
209
|
+
},
|
|
210
|
+
node.attrs.label,
|
|
211
|
+
],
|
|
212
|
+
parseMarkdown: {
|
|
213
|
+
match: (node) => node.type === 'inkkitWikiLink',
|
|
214
|
+
runner(state, node, type) {
|
|
215
|
+
state.addNode(type, {
|
|
216
|
+
raw: node.raw,
|
|
217
|
+
target: node.target,
|
|
218
|
+
fragment: node.fragment,
|
|
219
|
+
label: node.label,
|
|
220
|
+
inTable: node.inTable,
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
toMarkdown: {
|
|
225
|
+
match: (node) => node.type.name === 'inkkit_wiki_link',
|
|
226
|
+
runner(state, node) {
|
|
227
|
+
state.addNode('inkkitWikiLink', undefined, node.attrs.label, {
|
|
228
|
+
raw: node.attrs.raw,
|
|
229
|
+
target: node.attrs.target,
|
|
230
|
+
fragment: node.attrs.fragment,
|
|
231
|
+
label: node.attrs.label,
|
|
232
|
+
inTable: node.attrs.inTable,
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
}));
|
|
237
|
+
export function linkedSyntax(wikiLinks, files) {
|
|
238
|
+
const syntax = { text: {} };
|
|
239
|
+
if (wikiLinks)
|
|
240
|
+
syntax.text[91] = {
|
|
241
|
+
name: 'inkkitWikiLink',
|
|
242
|
+
tokenize: linkedTokenizer(false),
|
|
243
|
+
previous: (code) => code !== 33 && code !== 91,
|
|
244
|
+
};
|
|
245
|
+
if (files)
|
|
246
|
+
syntax.text[33] = {
|
|
247
|
+
name: 'inkkitNamedFile',
|
|
248
|
+
tokenize: linkedTokenizer(true),
|
|
249
|
+
};
|
|
250
|
+
const from = {
|
|
251
|
+
enter: {
|
|
252
|
+
inkkitLinkedSyntax(token) {
|
|
253
|
+
const inTable = this.stack.some((node) => node.type === 'tableCell');
|
|
254
|
+
const parsed = parseLinkedSyntax(this.sliceSerialize(token), inTable);
|
|
255
|
+
if (!parsed) {
|
|
256
|
+
this.enter({ type: 'text', value: this.sliceSerialize(token) }, token);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (parsed.embed)
|
|
260
|
+
this.enter({
|
|
261
|
+
type: 'image',
|
|
262
|
+
url: parsed.target +
|
|
263
|
+
(parsed.fragment != null ? `#${parsed.fragment}` : ''),
|
|
264
|
+
alt: parsed.label + (parsed.width != null ? `|${parsed.width}` : ''),
|
|
265
|
+
title: null,
|
|
266
|
+
inkkitFileRaw: parsed.raw,
|
|
267
|
+
inkkitFileTarget: parsed.target,
|
|
268
|
+
inkkitFileFragment: parsed.fragment,
|
|
269
|
+
inkkitFileWidth: parsed.width,
|
|
270
|
+
inkkitFileInTable: inTable,
|
|
271
|
+
}, token);
|
|
272
|
+
else
|
|
273
|
+
this.enter({
|
|
274
|
+
type: 'inkkitWikiLink',
|
|
275
|
+
value: parsed.label,
|
|
276
|
+
raw: parsed.raw,
|
|
277
|
+
target: parsed.target,
|
|
278
|
+
fragment: parsed.fragment,
|
|
279
|
+
label: parsed.label,
|
|
280
|
+
inTable,
|
|
281
|
+
}, token);
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
exit: {
|
|
285
|
+
inkkitLinkedSyntax(token) {
|
|
286
|
+
this.exit(token);
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
const wiki = (node, _parent, state) => tableForm(String(node.raw), Boolean(node.inTable), state.stack.includes('tableCell'));
|
|
291
|
+
const named = (node, parent, state, info) => {
|
|
292
|
+
if (node.inkkitFileRaw != null)
|
|
293
|
+
return tableForm(String(node.inkkitFileRaw), Boolean(node.inkkitFileInTable), state.stack.includes('tableCell'));
|
|
294
|
+
return defaultHandlers.image(node, parent, state, info);
|
|
295
|
+
};
|
|
296
|
+
// Standard image handling is installed by CommonMark; wrap it only for authored named tokens.
|
|
297
|
+
return $remark('inkkitLinkedSyntax', () => function () {
|
|
298
|
+
const data = this.data();
|
|
299
|
+
(data.micromarkExtensions ??= []).push(syntax);
|
|
300
|
+
(data.fromMarkdownExtensions ??= []).push(from);
|
|
301
|
+
(data.toMarkdownExtensions ??= []).push({
|
|
302
|
+
handlers: {
|
|
303
|
+
inkkitWikiLink: wiki,
|
|
304
|
+
...(files ? { image: named } : {}),
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
package/dist/literals.js
CHANGED
|
@@ -47,6 +47,44 @@ function containsUnresolved(node, source) {
|
|
|
47
47
|
return ('children' in node &&
|
|
48
48
|
node.children.some((child) => containsUnresolved(child, source)));
|
|
49
49
|
}
|
|
50
|
+
function containsUnsupportedLinked(node, source) {
|
|
51
|
+
const start = node.position?.start.offset, end = node.position?.end.offset;
|
|
52
|
+
if (start == null || end == null || node.type === 'definition')
|
|
53
|
+
return false;
|
|
54
|
+
const supported = [];
|
|
55
|
+
const visit = (child) => {
|
|
56
|
+
if ([
|
|
57
|
+
'inkkitWikiLink',
|
|
58
|
+
'image',
|
|
59
|
+
'code',
|
|
60
|
+
'inlineCode',
|
|
61
|
+
'link',
|
|
62
|
+
'inkkitCommentInline',
|
|
63
|
+
'inkkitCommentBlock',
|
|
64
|
+
].includes(child.type)) {
|
|
65
|
+
const from = child.position?.start.offset, to = child.position?.end.offset;
|
|
66
|
+
if (from != null && to != null)
|
|
67
|
+
supported.push({ from, to });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if ('children' in child)
|
|
71
|
+
child.children.forEach(visit);
|
|
72
|
+
};
|
|
73
|
+
visit(node);
|
|
74
|
+
for (const match of source
|
|
75
|
+
.slice(start, end)
|
|
76
|
+
.matchAll(/!?\[\[[^\r\n]*?\]\]/g)) {
|
|
77
|
+
const from = start + match.index, to = from + match[0].length;
|
|
78
|
+
let escapes = 0;
|
|
79
|
+
while (from - escapes > 0 && source[from - escapes - 1] === '\\')
|
|
80
|
+
escapes++;
|
|
81
|
+
if (escapes % 2)
|
|
82
|
+
continue;
|
|
83
|
+
if (!supported.some((span) => span.from <= from && span.to >= to))
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
50
88
|
// Keep syntax we cannot faithfully edit as literal text, without making the rest
|
|
51
89
|
// of the document a source editor or allowing HTML and image network requests.
|
|
52
90
|
export function createLiteralPreservation(images) {
|
|
@@ -66,7 +104,8 @@ export function createLiteralPreservation(images) {
|
|
|
66
104
|
if (start == null ||
|
|
67
105
|
end == null ||
|
|
68
106
|
(!containsUnsupported(node, images) &&
|
|
69
|
-
!containsUnresolved(node, source)
|
|
107
|
+
!containsUnresolved(node, source) &&
|
|
108
|
+
!containsUnsupportedLinked(node, source)))
|
|
70
109
|
return node;
|
|
71
110
|
let raw = source.slice(start, end);
|
|
72
111
|
// Definition indentation is restored by the footnote serializer.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Node } from '@milkdown/kit/prose/model';
|
|
2
|
+
import type { DocumentContext, FileAdapter, FilePresentation, FileReference, ImageAdapter, PortableImage } from './types.js';
|
|
3
|
+
export interface PortableFile {
|
|
4
|
+
reference: FileReference;
|
|
5
|
+
presentation: FilePresentation;
|
|
6
|
+
label: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function portableFile(node: Node, adapter: FileAdapter | undefined, context: DocumentContext, signal: AbortSignal): Promise<PortableFile | undefined>;
|
|
10
|
+
export declare function portableFileImage(file: PortableFile, files: FileAdapter, legacy: ImageAdapter | undefined, context: DocumentContext, signal: AbortSignal): Promise<PortableImage>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveFile, withFileSignal } from './files.js';
|
|
2
|
+
import { fileReference } from './linked-syntax.js';
|
|
3
|
+
export async function portableFile(node, adapter, context, signal) {
|
|
4
|
+
if (!adapter)
|
|
5
|
+
return undefined;
|
|
6
|
+
const reference = fileReference(node);
|
|
7
|
+
let presentation;
|
|
8
|
+
try {
|
|
9
|
+
presentation = await resolveFile(adapter, reference, { ...context, signal });
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
presentation = {
|
|
14
|
+
kind: 'error',
|
|
15
|
+
message: error instanceof Error ? error.message : 'The file is unavailable.',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const label = presentation.label || reference.label || reference.reference || 'File';
|
|
19
|
+
const kind = presentation.kind;
|
|
20
|
+
const description = kind === 'missing' || kind === 'error'
|
|
21
|
+
? `[File: ${label} (${kind === 'missing' ? 'unavailable' : 'error'})]`
|
|
22
|
+
: `[${kind === 'pdf' ? 'PDF' : kind[0].toUpperCase() + kind.slice(1)}: ${label}]`;
|
|
23
|
+
return { reference, presentation, label, description };
|
|
24
|
+
}
|
|
25
|
+
export async function portableFileImage(file, files, legacy, context, signal) {
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
const image = files.exportImage
|
|
28
|
+
? await withFileSignal(files.exportImage(file.reference, { ...context, signal }), signal)
|
|
29
|
+
: file.reference.kind === 'path' && legacy
|
|
30
|
+
? await withFileSignal(legacy.exportImage(file.reference.reference, { ...context }), signal)
|
|
31
|
+
: undefined;
|
|
32
|
+
signal.throwIfAborted();
|
|
33
|
+
if (!image)
|
|
34
|
+
throw new Error('The image has no portable export adapter.');
|
|
35
|
+
return image;
|
|
36
|
+
}
|
package/dist/preserve.js
CHANGED
|
@@ -266,6 +266,10 @@ function blockForm(ctx, node) {
|
|
|
266
266
|
}
|
|
267
267
|
function semanticSignature(doc) {
|
|
268
268
|
const clean = (value) => {
|
|
269
|
+
if (value.type === 'inkkit_wiki_link' && value.attrs) {
|
|
270
|
+
const { raw: _raw, inTable: _inTable, ...attrs } = value.attrs;
|
|
271
|
+
value = { ...value, attrs };
|
|
272
|
+
}
|
|
269
273
|
const content = value.content;
|
|
270
274
|
if (!content)
|
|
271
275
|
return value;
|
|
@@ -305,6 +309,9 @@ function semanticSignature(doc) {
|
|
|
305
309
|
'label',
|
|
306
310
|
'spread',
|
|
307
311
|
'authoredFence',
|
|
312
|
+
'inkkitFileRaw',
|
|
313
|
+
'inkkitFileWidth',
|
|
314
|
+
'inkkitFileInTable',
|
|
308
315
|
].includes(key)
|
|
309
316
|
? undefined
|
|
310
317
|
: value));
|
package/dist/print.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Fragment, Schema } from '@milkdown/kit/prose/model';
|
|
2
|
-
import { type DocumentContext, type ImageAdapter, type PrintableDocument } from './types.js';
|
|
2
|
+
import { type DocumentContext, type ImageAdapter, type FileAdapter, type PrintableDocument } from './types.js';
|
|
3
3
|
type PrintableContent = Pick<PrintableDocument, 'html' | 'styles' | 'assets' | 'warnings'>;
|
|
4
4
|
export declare function printableText(text: string): PrintableContent;
|
|
5
5
|
export declare function hasAuthoredImagesInLiterals(content: Fragment, parse: (source: string) => unknown): boolean;
|
|
6
|
-
export declare function printableMarkdown(content: Fragment, schema: Schema, adapter: ImageAdapter | undefined, context: DocumentContext): Promise<PrintableContent>;
|
|
6
|
+
export declare function printableMarkdown(content: Fragment, schema: Schema, adapter: ImageAdapter | undefined, context: DocumentContext, files?: FileAdapter, signal?: AbortSignal): Promise<PrintableContent>;
|
|
7
7
|
export {};
|
package/dist/print.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { clipboardContent } from './clipboard.js';
|
|
2
2
|
import { shareableFragment } from './comments.js';
|
|
3
|
-
import {
|
|
3
|
+
import { fileReference } from './linked-syntax.js';
|
|
4
4
|
import { sourceFrontmatter } from './literals.js';
|
|
5
5
|
import { diagramImage, renderDiagram } from './mermaid.js';
|
|
6
|
+
import { portableFile, portableFileImage } from './portable-files.js';
|
|
7
|
+
import { withFileSignal } from './files.js';
|
|
6
8
|
import { InkKitError, } from './types.js';
|
|
7
9
|
const styles = `
|
|
8
10
|
:root { color-scheme: light; font: 11pt/1.5 Arial, sans-serif; }
|
|
@@ -124,7 +126,8 @@ function matchesSignature(bytes, mimeType) {
|
|
|
124
126
|
return false;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
async function validatedImage(input) {
|
|
129
|
+
async function validatedImage(input, signal) {
|
|
130
|
+
signal.throwIfAborted();
|
|
128
131
|
const image = {
|
|
129
132
|
bytes: new Uint8Array(input.bytes),
|
|
130
133
|
mimeType: input.mimeType.toLowerCase(),
|
|
@@ -136,18 +139,23 @@ async function validatedImage(input) {
|
|
|
136
139
|
await new Promise((resolve, reject) => {
|
|
137
140
|
const decoder = new Image();
|
|
138
141
|
const timeout = setTimeout(() => {
|
|
139
|
-
decoder.
|
|
140
|
-
|
|
141
|
-
reject(new Error('The portable image could not be decoded.'));
|
|
142
|
+
decoder.src = '';
|
|
143
|
+
finish(new Error('The portable image could not be decoded.'));
|
|
142
144
|
}, 10000);
|
|
143
145
|
const finish = (error) => {
|
|
144
146
|
clearTimeout(timeout);
|
|
145
147
|
decoder.onload = decoder.onerror = null;
|
|
148
|
+
signal.removeEventListener('abort', cancelled);
|
|
146
149
|
if (error)
|
|
147
150
|
reject(error);
|
|
148
151
|
else
|
|
149
152
|
resolve();
|
|
150
153
|
};
|
|
154
|
+
const cancelled = () => {
|
|
155
|
+
decoder.src = '';
|
|
156
|
+
finish(new DOMException('Image export cancelled', 'AbortError'));
|
|
157
|
+
};
|
|
158
|
+
signal.addEventListener('abort', cancelled, { once: true });
|
|
151
159
|
decoder.onload = () => finish(decoder.naturalWidth > 0 && decoder.naturalHeight > 0
|
|
152
160
|
? undefined
|
|
153
161
|
: new Error('The portable image has no usable dimensions.'));
|
|
@@ -272,7 +280,8 @@ function cleanSemantics(container, localIDs) {
|
|
|
272
280
|
for (const comment of comments)
|
|
273
281
|
comment.parentNode?.removeChild(comment);
|
|
274
282
|
}
|
|
275
|
-
export async function printableMarkdown(content, schema, adapter, context) {
|
|
283
|
+
export async function printableMarkdown(content, schema, adapter, context, files, signal = new AbortController().signal) {
|
|
284
|
+
signal.throwIfAborted();
|
|
276
285
|
content = shareableFragment(content);
|
|
277
286
|
const container = document.createElement('div');
|
|
278
287
|
container.innerHTML = clipboardContent(content, schema).html;
|
|
@@ -280,10 +289,7 @@ export async function printableMarkdown(content, schema, adapter, context) {
|
|
|
280
289
|
const tasks = [];
|
|
281
290
|
content.descendants((node) => {
|
|
282
291
|
if (node.type.name === 'image')
|
|
283
|
-
references.push(
|
|
284
|
-
reference: String(node.attrs.src ?? ''),
|
|
285
|
-
...splitAlt(String(node.attrs.alt ?? '')),
|
|
286
|
-
});
|
|
292
|
+
references.push(node);
|
|
287
293
|
if (node.type.name === 'list_item')
|
|
288
294
|
tasks.push(node.attrs.checked ?? null);
|
|
289
295
|
});
|
|
@@ -320,33 +326,58 @@ export async function printableMarkdown(content, schema, adapter, context) {
|
|
|
320
326
|
reference.replaceChildren(link);
|
|
321
327
|
}
|
|
322
328
|
const images = new Map();
|
|
329
|
+
const warnings = [];
|
|
323
330
|
for (const [index, element] of [
|
|
324
331
|
...container.querySelectorAll('img'),
|
|
325
332
|
].entries()) {
|
|
326
|
-
const
|
|
333
|
+
const node = references[index];
|
|
327
334
|
try {
|
|
328
|
-
if (!
|
|
335
|
+
if (!node)
|
|
329
336
|
throw new Error('The authored image is unavailable.');
|
|
330
|
-
const
|
|
337
|
+
const reference = fileReference(node);
|
|
338
|
+
const file = files
|
|
339
|
+
? await portableFile(node, files, context, signal)
|
|
340
|
+
: undefined;
|
|
341
|
+
if (file && file.presentation.kind !== 'image') {
|
|
342
|
+
const fallback = document.createElement('span');
|
|
343
|
+
fallback.textContent = file.description;
|
|
344
|
+
element.replaceWith(fallback);
|
|
345
|
+
const unavailable = file.presentation.kind === 'missing' ||
|
|
346
|
+
file.presentation.kind === 'error';
|
|
347
|
+
warnings.push({
|
|
348
|
+
code: unavailable ? 'attachment-unavailable' : 'attachment-fallback',
|
|
349
|
+
message: unavailable
|
|
350
|
+
? `${file.label} is unavailable; its description is printed.`
|
|
351
|
+
: `${file.label} is printed as a ${file.presentation.kind === 'pdf' ? 'PDF' : file.presentation.kind} description.`,
|
|
352
|
+
});
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (!file && !adapter)
|
|
356
|
+
throw new Error('The authored image is unavailable.');
|
|
357
|
+
const image = await validatedImage(file
|
|
358
|
+
? await portableFileImage(file, files, adapter, context, signal)
|
|
359
|
+
: await withFileSignal(adapter.exportImage(reference.reference, { ...context }), signal), signal);
|
|
360
|
+
signal.throwIfAborted();
|
|
331
361
|
images.set(element, image);
|
|
332
362
|
element.setAttribute('src', dataURL(image));
|
|
333
|
-
element.setAttribute('alt', reference.
|
|
363
|
+
element.setAttribute('alt', file?.label || reference.label || '');
|
|
334
364
|
if (reference.width != null && reference.width > 0)
|
|
335
365
|
element.setAttribute('width', String(Math.min(4096, reference.width)));
|
|
336
366
|
}
|
|
337
367
|
catch (error) {
|
|
368
|
+
signal.throwIfAborted();
|
|
338
369
|
throw new InkKitError('image-unavailable', error instanceof Error
|
|
339
370
|
? error.message
|
|
340
371
|
: 'The authored image is unavailable.');
|
|
341
372
|
}
|
|
342
373
|
}
|
|
343
|
-
const warnings = [];
|
|
344
374
|
for (const code of container.querySelectorAll('pre[data-language="mermaid"] > code')) {
|
|
345
375
|
const source = code.textContent ?? '';
|
|
346
376
|
try {
|
|
347
|
-
await renderDiagram(source);
|
|
377
|
+
await withFileSignal(renderDiagram(source), signal);
|
|
348
378
|
}
|
|
349
379
|
catch (error) {
|
|
380
|
+
signal.throwIfAborted();
|
|
350
381
|
const message = `${error instanceof Error ? error.message : 'This diagram could not be rendered.'} The source is printed below.`;
|
|
351
382
|
warnings.push({ code: 'diagram-unavailable', message });
|
|
352
383
|
const warning = document.createElement('p');
|
|
@@ -355,7 +386,7 @@ export async function printableMarkdown(content, schema, adapter, context) {
|
|
|
355
386
|
continue;
|
|
356
387
|
}
|
|
357
388
|
try {
|
|
358
|
-
const image = await validatedImage(await diagramImage(source));
|
|
389
|
+
const image = await validatedImage(await withFileSignal(diagramImage(source), signal), signal);
|
|
359
390
|
const figure = document.createElement('figure');
|
|
360
391
|
figure.className = 'inkkit-print-diagram';
|
|
361
392
|
const element = document.createElement('img');
|
|
@@ -373,5 +404,6 @@ export async function printableMarkdown(content, schema, adapter, context) {
|
|
|
373
404
|
}
|
|
374
405
|
cleanSemantics(container, localIDs);
|
|
375
406
|
const assets = [...container.querySelectorAll('img')].map((element) => images.get(element));
|
|
407
|
+
signal.throwIfAborted();
|
|
376
408
|
return documentOutput(container, assets, warnings);
|
|
377
409
|
}
|
package/dist/search.d.ts
CHANGED
|
@@ -10,5 +10,6 @@ export interface LiteralReplacement extends TextMatch {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function findLiteral(text: string, search: string, from?: number): TextMatch | undefined;
|
|
12
12
|
export declare function replaceLiteral(text: string, search: string, replacement: string, selection: TextMatch, all?: boolean): LiteralReplacement;
|
|
13
|
-
export declare function findFormatted(view: EditorView, text: string):
|
|
13
|
+
export declare function findFormatted(view: EditorView, text: string): boolean;
|
|
14
14
|
export declare function replaceFormatted(view: EditorView, search: string, replacement: string, preflight: (doc: ProseNode) => void, all?: boolean): number;
|
|
15
|
+
export declare function replaceFormattedRange(view: EditorView, from: number, to: number, replacement: string, preflight: (doc: ProseNode) => void): boolean;
|