@aicayzer/inkkit 0.0.6 → 0.0.8
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/NOTICE +25 -2
- package/README.md +16 -14
- package/dist/callouts.d.ts +2 -0
- package/dist/callouts.js +17 -3
- package/dist/decorations.js +7 -6
- package/dist/dialect.js +10 -2
- package/dist/editor.d.ts +90 -7
- package/dist/editor.js +806 -116
- package/dist/images.d.ts +1 -0
- package/dist/images.js +11 -0
- package/dist/index.d.ts +3 -1
- package/dist/labels.d.ts +28 -0
- package/dist/labels.js +26 -0
- package/dist/mermaid.js +6 -3
- package/dist/outline.d.ts +22 -0
- package/dist/outline.js +79 -0
- package/dist/paste.d.ts +1 -0
- package/dist/paste.js +5 -1
- package/dist/references.js +5 -4
- package/dist/search.d.ts +14 -0
- package/dist/search.js +171 -0
- package/dist/selection.js +22 -10
- package/dist/source.d.ts +17 -0
- package/dist/source.js +117 -0
- package/dist/style.css +377 -299
- package/dist/tables.d.ts +10 -1
- package/dist/tables.js +87 -11
- package/dist/tasks.js +2 -0
- package/dist/types.d.ts +1 -1
- package/package.json +4 -2
package/dist/images.d.ts
CHANGED
package/dist/images.js
CHANGED
|
@@ -30,6 +30,7 @@ class ImageView {
|
|
|
30
30
|
this.adapter = adapter;
|
|
31
31
|
this.dom = document.createElement('span');
|
|
32
32
|
this.dom.className = 'image';
|
|
33
|
+
this.view.dom.addEventListener('inkkit-cancel-resize', this.cancelResize);
|
|
33
34
|
this.render();
|
|
34
35
|
}
|
|
35
36
|
update(node) {
|
|
@@ -47,7 +48,13 @@ class ImageView {
|
|
|
47
48
|
ignoreMutation() {
|
|
48
49
|
return true;
|
|
49
50
|
}
|
|
51
|
+
cancelResize = () => {
|
|
52
|
+
this.stopResize?.();
|
|
53
|
+
this.stopResize = undefined;
|
|
54
|
+
this.render();
|
|
55
|
+
};
|
|
50
56
|
destroy() {
|
|
57
|
+
this.view.dom.removeEventListener('inkkit-cancel-resize', this.cancelResize);
|
|
51
58
|
this.stopResize?.();
|
|
52
59
|
this.stopResize = undefined;
|
|
53
60
|
}
|
|
@@ -91,6 +98,10 @@ class ImageView {
|
|
|
91
98
|
const limit = this.view.dom.clientWidth;
|
|
92
99
|
handle.setPointerCapture?.(event.pointerId);
|
|
93
100
|
const move = (moved) => {
|
|
101
|
+
if (!this.view.editable) {
|
|
102
|
+
this.cancelResize();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
94
105
|
const next = Math.round(Math.min(limit, Math.max(minimumWidth, startWidth + (moved.clientX - startX))));
|
|
95
106
|
this.width = next;
|
|
96
107
|
image.style.width = `${next}px`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { InkKitEditor } from './editor.js';
|
|
2
|
-
export type { EditorEvents, CaretState, Mark, Block, FormatCommand, Keymap, } from './editor.js';
|
|
2
|
+
export type { EditorEvents, EditorOptions, TextInputPreferences, CommandState, EditingMode, CaretState, Mark, Block, FormatCommand, Keymap, } from './editor.js';
|
|
3
3
|
export type { TableCommand, TableOptions } from './tables.js';
|
|
4
|
+
export type { Heading } from './outline.js';
|
|
4
5
|
export { InkKitError } from './types.js';
|
|
5
6
|
export type { DocumentFormat, DocumentContext, DocumentInput, DocumentSnapshot, EditorErrorCode, ImageAdapter, CapturedImage, PortableImage, ClipboardInput, ClipboardOutput, ClipboardImage, ClipboardDiagram, PrintableDocument, PrintableWarning, } from './types.js';
|
|
7
|
+
export type { EditorLabels } from './labels.js';
|
package/dist/labels.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Ctx } from '@milkdown/kit/ctx';
|
|
2
|
+
/** Built-in editor text. Hosts can override individual labels at mount time. */
|
|
3
|
+
export interface EditorLabels {
|
|
4
|
+
formattedEditor: string;
|
|
5
|
+
plainTextEditor: string;
|
|
6
|
+
sourceEditor: string;
|
|
7
|
+
placeholder: string;
|
|
8
|
+
copyCode: string;
|
|
9
|
+
expandCallout: string;
|
|
10
|
+
collapseCallout: string;
|
|
11
|
+
diagramRendering: string;
|
|
12
|
+
/** Prefix shown before the renderer's diagnostic message. */
|
|
13
|
+
diagramUnavailable: string;
|
|
14
|
+
/** Prefix followed by the authored footnote label. */
|
|
15
|
+
footnote: string;
|
|
16
|
+
tableAddRow: string;
|
|
17
|
+
tableAddColumn: string;
|
|
18
|
+
tableDeleteRow: string;
|
|
19
|
+
tableDeleteColumn: string;
|
|
20
|
+
tableAlignLeft: string;
|
|
21
|
+
tableAlignCenter: string;
|
|
22
|
+
tableAlignRight: string;
|
|
23
|
+
tableMoveColumn: string;
|
|
24
|
+
tableMoveRow: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const defaultLabels: Readonly<EditorLabels>;
|
|
27
|
+
export declare const labelsCtx: import("@milkdown/kit/utils").$Ctx<Readonly<EditorLabels>, "inkkitLabels">;
|
|
28
|
+
export declare function editorLabels(ctx: Ctx): Readonly<EditorLabels>;
|
package/dist/labels.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { $ctx } from '@milkdown/kit/utils';
|
|
2
|
+
export const defaultLabels = {
|
|
3
|
+
formattedEditor: 'Formatted Markdown editor',
|
|
4
|
+
plainTextEditor: 'Plain text editor',
|
|
5
|
+
sourceEditor: 'Markdown source editor',
|
|
6
|
+
placeholder: 'Start typing…',
|
|
7
|
+
copyCode: 'Copy',
|
|
8
|
+
expandCallout: 'Expand',
|
|
9
|
+
collapseCallout: 'Collapse',
|
|
10
|
+
diagramRendering: 'Rendering diagram…',
|
|
11
|
+
diagramUnavailable: 'Diagram unavailable:',
|
|
12
|
+
footnote: 'Footnote',
|
|
13
|
+
tableAddRow: 'Add row',
|
|
14
|
+
tableAddColumn: 'Add column',
|
|
15
|
+
tableDeleteRow: 'Delete row',
|
|
16
|
+
tableDeleteColumn: 'Delete column',
|
|
17
|
+
tableAlignLeft: 'Align left',
|
|
18
|
+
tableAlignCenter: 'Align center',
|
|
19
|
+
tableAlignRight: 'Align right',
|
|
20
|
+
tableMoveColumn: 'Move column',
|
|
21
|
+
tableMoveRow: 'Move row',
|
|
22
|
+
};
|
|
23
|
+
export const labelsCtx = $ctx(defaultLabels, 'inkkitLabels');
|
|
24
|
+
export function editorLabels(ctx) {
|
|
25
|
+
return ctx.isInjected(labelsCtx.key) ? ctx.get(labelsCtx.key) : defaultLabels;
|
|
26
|
+
}
|
package/dist/mermaid.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { editorLabels } from './labels.js';
|
|
1
2
|
import mermaid from 'mermaid';
|
|
2
3
|
import DOMPurify from 'dompurify';
|
|
3
4
|
import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
|
|
@@ -166,7 +167,7 @@ export async function diagramImage(source) {
|
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
export function mermaidPreview(onError) {
|
|
169
|
-
return $prose(() => new Plugin({
|
|
170
|
+
return $prose((ctx) => new Plugin({
|
|
170
171
|
key: new PluginKey('mermaidPreview'),
|
|
171
172
|
props: {
|
|
172
173
|
decorations(state) {
|
|
@@ -179,7 +180,9 @@ export function mermaidPreview(onError) {
|
|
|
179
180
|
const preview = document.createElement('div');
|
|
180
181
|
preview.className = 'inkkit-mermaid-preview';
|
|
181
182
|
preview.contentEditable = 'false';
|
|
182
|
-
preview.textContent =
|
|
183
|
+
preview.textContent = editorLabels(ctx).diagramRendering;
|
|
184
|
+
preview.setAttribute('role', 'status');
|
|
185
|
+
preview.setAttribute('aria-live', 'polite');
|
|
183
186
|
let active = true;
|
|
184
187
|
preview.cancel =
|
|
185
188
|
() => {
|
|
@@ -196,7 +199,7 @@ export function mermaidPreview(onError) {
|
|
|
196
199
|
const message = error instanceof Error
|
|
197
200
|
? error.message
|
|
198
201
|
: 'The diagram could not be rendered.';
|
|
199
|
-
preview.textContent =
|
|
202
|
+
preview.textContent = `${editorLabels(ctx).diagramUnavailable} ${message}`;
|
|
200
203
|
preview.dataset.state = 'error';
|
|
201
204
|
onError(new InkKitError('diagram-unavailable', message));
|
|
202
205
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Ctx } from '@milkdown/kit/ctx';
|
|
2
|
+
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
3
|
+
import type { EditorView } from '@milkdown/kit/prose/view';
|
|
4
|
+
export interface Heading {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly level: number;
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly documentId: string;
|
|
9
|
+
readonly generation: number;
|
|
10
|
+
readonly revision: number;
|
|
11
|
+
}
|
|
12
|
+
export interface OutlineContext {
|
|
13
|
+
documentId: string;
|
|
14
|
+
generation: number;
|
|
15
|
+
revision: number;
|
|
16
|
+
epoch: number;
|
|
17
|
+
mode: 'source' | 'formatted';
|
|
18
|
+
}
|
|
19
|
+
export declare function collectFormattedHeadings(doc: ProseNode, context: OutlineContext): readonly Heading[];
|
|
20
|
+
export declare function collectSourceHeadings(ctx: Ctx, source: string, context: OutlineContext): readonly Heading[];
|
|
21
|
+
export declare function sourceHeadingPosition(ctx: Ctx, source: string, requested: Heading, context: OutlineContext): number;
|
|
22
|
+
export declare function navigateFormattedHeading(view: EditorView, requested: Heading, context: OutlineContext): boolean;
|
package/dist/outline.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parserCtx, remarkCtx } from '@milkdown/kit/core';
|
|
2
|
+
import { TextSelection } from '@milkdown/kit/prose/state';
|
|
3
|
+
import { clipboardText } from './clipboard.js';
|
|
4
|
+
import { revealCalloutAncestors } from './callouts.js';
|
|
5
|
+
import { InkKitError } from './types.js';
|
|
6
|
+
function formattedLocations(doc) {
|
|
7
|
+
const headings = [];
|
|
8
|
+
doc.descendants((node, position) => {
|
|
9
|
+
if (node.type.name === 'heading')
|
|
10
|
+
headings.push({ node, position });
|
|
11
|
+
});
|
|
12
|
+
return headings;
|
|
13
|
+
}
|
|
14
|
+
function sourceLocations(ctx, source) {
|
|
15
|
+
const processor = ctx.get(remarkCtx);
|
|
16
|
+
const tree = processor.runSync(processor.parse(source), {
|
|
17
|
+
value: source,
|
|
18
|
+
});
|
|
19
|
+
const positions = [];
|
|
20
|
+
const bom = source.startsWith('\uFEFF') ? 1 : 0;
|
|
21
|
+
const visit = (node) => {
|
|
22
|
+
if (node.type === 'heading' && node.position?.start.offset != null)
|
|
23
|
+
positions.push({
|
|
24
|
+
position: node.position.start.offset + bom,
|
|
25
|
+
level: node.depth,
|
|
26
|
+
});
|
|
27
|
+
node.children?.forEach(visit);
|
|
28
|
+
};
|
|
29
|
+
visit(tree);
|
|
30
|
+
const parsed = formattedLocations(ctx.get(parserCtx)(source));
|
|
31
|
+
if (positions.length !== parsed.length ||
|
|
32
|
+
positions.some((item, index) => item.level !== parsed[index].node.attrs.level))
|
|
33
|
+
throw new InkKitError('preservation', 'The current Markdown headings cannot be located safely');
|
|
34
|
+
return parsed.map((heading, index) => ({
|
|
35
|
+
...heading,
|
|
36
|
+
position: positions[index].position,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
function entry(heading, context) {
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
id: `heading:${context.epoch}:${context.mode}:${context.generation}:${context.revision}:${heading.position}`,
|
|
42
|
+
level: Number(heading.node.attrs.level),
|
|
43
|
+
text: clipboardText(heading.node.content),
|
|
44
|
+
documentId: context.documentId,
|
|
45
|
+
generation: context.generation,
|
|
46
|
+
revision: context.revision,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function collectFormattedHeadings(doc, context) {
|
|
50
|
+
return Object.freeze(formattedLocations(doc).map((item) => entry(item, context)));
|
|
51
|
+
}
|
|
52
|
+
export function collectSourceHeadings(ctx, source, context) {
|
|
53
|
+
return Object.freeze(sourceLocations(ctx, source).map((item) => entry(item, context)));
|
|
54
|
+
}
|
|
55
|
+
function currentLocation(locations, requested, context) {
|
|
56
|
+
for (const location of locations) {
|
|
57
|
+
const current = entry(location, context);
|
|
58
|
+
if (current.id === requested.id &&
|
|
59
|
+
current.documentId === requested.documentId &&
|
|
60
|
+
current.generation === requested.generation &&
|
|
61
|
+
current.revision === requested.revision &&
|
|
62
|
+
current.level === requested.level &&
|
|
63
|
+
current.text === requested.text)
|
|
64
|
+
return location.position;
|
|
65
|
+
}
|
|
66
|
+
throw new InkKitError('stale-document', 'The heading is no longer current');
|
|
67
|
+
}
|
|
68
|
+
export function sourceHeadingPosition(ctx, source, requested, context) {
|
|
69
|
+
return currentLocation(sourceLocations(ctx, source), requested, context);
|
|
70
|
+
}
|
|
71
|
+
export function navigateFormattedHeading(view, requested, context) {
|
|
72
|
+
const position = currentLocation(formattedLocations(view.state.doc), requested, context);
|
|
73
|
+
revealCalloutAncestors(view, position + 1);
|
|
74
|
+
view.dispatch(view.state.tr
|
|
75
|
+
.setSelection(TextSelection.create(view.state.doc, position + 1))
|
|
76
|
+
.scrollIntoView());
|
|
77
|
+
view.focus();
|
|
78
|
+
return true;
|
|
79
|
+
}
|
package/dist/paste.d.ts
CHANGED
package/dist/paste.js
CHANGED
|
@@ -70,6 +70,7 @@ export class PasteController {
|
|
|
70
70
|
}
|
|
71
71
|
cancelPending() {
|
|
72
72
|
this.active = undefined;
|
|
73
|
+
this.options.onPendingChanged?.();
|
|
73
74
|
}
|
|
74
75
|
destroy() {
|
|
75
76
|
this.dead = true;
|
|
@@ -101,6 +102,7 @@ export class PasteController {
|
|
|
101
102
|
to: view.state.selection.to,
|
|
102
103
|
};
|
|
103
104
|
this.active = operation;
|
|
105
|
+
this.options.onPendingChanged?.();
|
|
104
106
|
try {
|
|
105
107
|
const images = [...(input.images ?? [])];
|
|
106
108
|
for (const file of files)
|
|
@@ -428,8 +430,10 @@ export class PasteController {
|
|
|
428
430
|
.scrollIntoView());
|
|
429
431
|
}
|
|
430
432
|
finally {
|
|
431
|
-
if (this.active === operation)
|
|
433
|
+
if (this.active === operation) {
|
|
432
434
|
this.active = undefined;
|
|
435
|
+
this.options.onPendingChanged?.();
|
|
436
|
+
}
|
|
433
437
|
}
|
|
434
438
|
}
|
|
435
439
|
}
|
package/dist/references.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { editorLabels } from './labels.js';
|
|
1
2
|
import { $node, $remark, $prose } from '@milkdown/kit/utils';
|
|
2
3
|
import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
|
|
3
4
|
import { linkSchema } from '@milkdown/kit/preset/commonmark';
|
|
@@ -247,7 +248,7 @@ export const referenceDefinition = $node('reference_definition', () => ({
|
|
|
247
248
|
},
|
|
248
249
|
},
|
|
249
250
|
}));
|
|
250
|
-
export const footnoteReference = $node('footnote_reference', () => ({
|
|
251
|
+
export const footnoteReference = $node('footnote_reference', (ctx) => ({
|
|
251
252
|
inline: true,
|
|
252
253
|
group: 'inline',
|
|
253
254
|
atom: true,
|
|
@@ -268,7 +269,7 @@ export const footnoteReference = $node('footnote_reference', () => ({
|
|
|
268
269
|
class: 'inkkit-footnote-reference',
|
|
269
270
|
tabindex: '0',
|
|
270
271
|
role: 'button',
|
|
271
|
-
'aria-label':
|
|
272
|
+
'aria-label': `${editorLabels(ctx).footnote} ${node.attrs.label}`,
|
|
272
273
|
'data-inkkit-footnote-reference': node.attrs.identifier,
|
|
273
274
|
'data-inkkit-label': node.attrs.label,
|
|
274
275
|
},
|
|
@@ -292,7 +293,7 @@ export const footnoteReference = $node('footnote_reference', () => ({
|
|
|
292
293
|
},
|
|
293
294
|
},
|
|
294
295
|
}));
|
|
295
|
-
export const footnoteDefinition = $node('footnote_definition', () => ({
|
|
296
|
+
export const footnoteDefinition = $node('footnote_definition', (ctx) => ({
|
|
296
297
|
group: 'block',
|
|
297
298
|
content: 'block+',
|
|
298
299
|
defining: true,
|
|
@@ -310,7 +311,7 @@ export const footnoteDefinition = $node('footnote_definition', () => ({
|
|
|
310
311
|
'div',
|
|
311
312
|
{
|
|
312
313
|
class: 'inkkit-footnote-definition',
|
|
313
|
-
'aria-label':
|
|
314
|
+
'aria-label': `${editorLabels(ctx).footnote} ${node.attrs.label}`,
|
|
314
315
|
'data-inkkit-footnote-definition': node.attrs.identifier,
|
|
315
316
|
'data-inkkit-label': node.attrs.label,
|
|
316
317
|
},
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
2
|
+
import type { EditorView } from '@milkdown/kit/prose/view';
|
|
3
|
+
export interface TextMatch {
|
|
4
|
+
from: number;
|
|
5
|
+
to: number;
|
|
6
|
+
}
|
|
7
|
+
export interface LiteralReplacement extends TextMatch {
|
|
8
|
+
text: string;
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function findLiteral(text: string, search: string, from?: number): TextMatch | undefined;
|
|
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): void;
|
|
14
|
+
export declare function replaceFormatted(view: EditorView, search: string, replacement: string, preflight: (doc: ProseNode) => void, all?: boolean): number;
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Fragment, Slice } from '@milkdown/kit/prose/model';
|
|
2
|
+
import { closeHistory } from '@milkdown/kit/prose/history';
|
|
3
|
+
import { Selection, TextSelection } from '@milkdown/kit/prose/state';
|
|
4
|
+
import { SearchQuery, setSearchState, getSearchState } from 'prosemirror-search';
|
|
5
|
+
import { InkKitError } from './types.js';
|
|
6
|
+
function escapedPattern(text) {
|
|
7
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8
|
+
}
|
|
9
|
+
function literalMatches(text, search) {
|
|
10
|
+
if (!search)
|
|
11
|
+
return [];
|
|
12
|
+
const pattern = new RegExp(escapedPattern(search), 'giu');
|
|
13
|
+
return [...text.matchAll(pattern)].map((match) => ({
|
|
14
|
+
from: match.index,
|
|
15
|
+
to: match.index + match[0].length,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
export function findLiteral(text, search, from = 0) {
|
|
19
|
+
const matches = literalMatches(text, search);
|
|
20
|
+
return matches.find((match) => match.from >= from) ?? matches[0];
|
|
21
|
+
}
|
|
22
|
+
export function replaceLiteral(text, search, replacement, selection, all = false) {
|
|
23
|
+
const matches = literalMatches(text, search);
|
|
24
|
+
const selected = matches.find((match) => match.from === selection.from && match.to === selection.to) ??
|
|
25
|
+
matches.find((match) => match.from >= selection.to) ??
|
|
26
|
+
matches[0];
|
|
27
|
+
const changed = (all ? matches : selected ? [selected] : []).filter((match) => text.slice(match.from, match.to) !== replacement);
|
|
28
|
+
if (!changed.length)
|
|
29
|
+
return { ...selection, text, count: 0 };
|
|
30
|
+
let output = text;
|
|
31
|
+
for (const match of [...changed].reverse())
|
|
32
|
+
output = output.slice(0, match.from) + replacement + output.slice(match.to);
|
|
33
|
+
if (!all)
|
|
34
|
+
return {
|
|
35
|
+
text: output,
|
|
36
|
+
count: 1,
|
|
37
|
+
from: changed[0].from,
|
|
38
|
+
to: changed[0].from + replacement.length,
|
|
39
|
+
};
|
|
40
|
+
const map = (position) => {
|
|
41
|
+
let difference = 0;
|
|
42
|
+
for (const match of changed) {
|
|
43
|
+
if (position < match.from)
|
|
44
|
+
break;
|
|
45
|
+
if (position <= match.to)
|
|
46
|
+
return (match.from +
|
|
47
|
+
difference +
|
|
48
|
+
(position === match.from ? 0 : replacement.length));
|
|
49
|
+
difference += replacement.length - (match.to - match.from);
|
|
50
|
+
}
|
|
51
|
+
return position + difference;
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
text: output,
|
|
55
|
+
count: changed.length,
|
|
56
|
+
from: map(selection.from),
|
|
57
|
+
to: map(selection.to),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function formattedQuery(text) {
|
|
61
|
+
// Unicode regexp matching retains offsets when lowercasing would expand a character.
|
|
62
|
+
return new SearchQuery({
|
|
63
|
+
search: escapedPattern(text),
|
|
64
|
+
regexp: true,
|
|
65
|
+
literal: true,
|
|
66
|
+
filter: (state, match) => {
|
|
67
|
+
const from = state.doc.resolve(match.from);
|
|
68
|
+
const to = state.doc.resolve(match.to);
|
|
69
|
+
if (from.parent !== to.parent || !from.parent.inlineContent)
|
|
70
|
+
return false;
|
|
71
|
+
const content = state.doc.slice(match.from, match.to).content;
|
|
72
|
+
let textOnly = true;
|
|
73
|
+
content.forEach((node) => {
|
|
74
|
+
if (!node.isText)
|
|
75
|
+
textOnly = false;
|
|
76
|
+
});
|
|
77
|
+
return textOnly;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export function findFormatted(view, text) {
|
|
82
|
+
const query = formattedQuery(text);
|
|
83
|
+
const previous = getSearchState(view.state)?.query.search;
|
|
84
|
+
let tr = setSearchState(view.state.tr, query);
|
|
85
|
+
if (text) {
|
|
86
|
+
const start = previous === query.search
|
|
87
|
+
? view.state.selection.to
|
|
88
|
+
: view.state.selection.from;
|
|
89
|
+
const match = query.findNext(view.state, start) ?? query.findNext(view.state, 0);
|
|
90
|
+
if (match)
|
|
91
|
+
tr = tr
|
|
92
|
+
.setSelection(TextSelection.create(tr.doc, match.from, match.to))
|
|
93
|
+
.scrollIntoView();
|
|
94
|
+
else
|
|
95
|
+
tr = tr.setSelection(Selection.near(view.state.selection.$to));
|
|
96
|
+
}
|
|
97
|
+
else if (previous)
|
|
98
|
+
tr = tr.setSelection(Selection.near(view.state.selection.$to));
|
|
99
|
+
view.dispatch(tr);
|
|
100
|
+
}
|
|
101
|
+
export function replaceFormatted(view, search, replacement, preflight, all = false) {
|
|
102
|
+
if (!search)
|
|
103
|
+
return 0;
|
|
104
|
+
const state = view.state;
|
|
105
|
+
const query = formattedQuery(search);
|
|
106
|
+
if (!query.valid)
|
|
107
|
+
return 0;
|
|
108
|
+
const matches = [];
|
|
109
|
+
if (all) {
|
|
110
|
+
for (let position = 0;;) {
|
|
111
|
+
const match = query.findNext(state, position);
|
|
112
|
+
if (!match)
|
|
113
|
+
break;
|
|
114
|
+
matches.push(match);
|
|
115
|
+
position = match.to;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const selection = state.selection;
|
|
120
|
+
const current = query.findNext(state, selection.from, selection.to);
|
|
121
|
+
const match = (current?.from === selection.from && current.to === selection.to
|
|
122
|
+
? current
|
|
123
|
+
: undefined) ??
|
|
124
|
+
query.findNext(state, selection.to) ??
|
|
125
|
+
query.findNext(state, 0);
|
|
126
|
+
if (match)
|
|
127
|
+
matches.push(match);
|
|
128
|
+
}
|
|
129
|
+
const value = replacement.replace(/\r\n?/g, '\n');
|
|
130
|
+
const changed = matches.filter((match) => state.doc.textBetween(match.from, match.to) !== value);
|
|
131
|
+
if (!changed.length)
|
|
132
|
+
return 0;
|
|
133
|
+
let tr = setSearchState(state.tr, query);
|
|
134
|
+
for (const match of [...changed].reverse()) {
|
|
135
|
+
const from = state.doc.resolve(match.from);
|
|
136
|
+
const to = state.doc.resolve(match.to);
|
|
137
|
+
const marks = from.marksAcross(to) ?? from.marks();
|
|
138
|
+
let content = Fragment.empty;
|
|
139
|
+
if (from.parent.type.spec.code) {
|
|
140
|
+
if (value)
|
|
141
|
+
content = Fragment.from(state.schema.text(value, marks));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const nodes = [];
|
|
145
|
+
const lines = value.split('\n');
|
|
146
|
+
lines.forEach((line, index) => {
|
|
147
|
+
if (index)
|
|
148
|
+
nodes.push(state.schema.nodes.hardbreak.create({ isHTML: index === lines.length - 1 && line === '' }, null, marks));
|
|
149
|
+
if (line)
|
|
150
|
+
nodes.push(state.schema.text(line, marks));
|
|
151
|
+
});
|
|
152
|
+
content = Fragment.fromArray(nodes);
|
|
153
|
+
}
|
|
154
|
+
tr.replace(match.from, match.to, new Slice(content, 0, 0));
|
|
155
|
+
}
|
|
156
|
+
if (!all) {
|
|
157
|
+
const match = changed[0];
|
|
158
|
+
tr = tr.setSelection(TextSelection.create(tr.doc, match.from, tr.mapping.map(match.to, 1)));
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
preflight(tr.doc);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof InkKitError)
|
|
165
|
+
throw error;
|
|
166
|
+
throw new InkKitError('preservation', error instanceof Error ? error.message : 'Cannot preserve replacement');
|
|
167
|
+
}
|
|
168
|
+
view.dispatch(closeHistory(tr).scrollIntoView());
|
|
169
|
+
view.dispatch(closeHistory(view.state.tr).setMeta('addToHistory', false));
|
|
170
|
+
return changed.length;
|
|
171
|
+
}
|
package/dist/selection.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
|
|
2
2
|
import { $prose } from '@milkdown/kit/utils';
|
|
3
3
|
/** The stylesheet paints the selection under this name. */
|
|
4
|
-
const name = 'selection';
|
|
4
|
+
const name = 'inkkit-selection';
|
|
5
|
+
const ranges = new Map();
|
|
5
6
|
/** The selected text within each block. */
|
|
6
7
|
function covered(state) {
|
|
7
8
|
const { from, to, empty, visible } = state.selection;
|
|
@@ -27,21 +28,32 @@ function domRange(view, span) {
|
|
|
27
28
|
range.setEnd(end.node, end.offset);
|
|
28
29
|
return range;
|
|
29
30
|
}
|
|
30
|
-
function paint(view
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
function paint(view) {
|
|
32
|
+
ranges.set(view, (covered(view.state) ?? []).map((span) => domRange(view, span)));
|
|
33
|
+
refresh();
|
|
34
|
+
}
|
|
35
|
+
function refresh() {
|
|
36
|
+
if (ranges.size === 0) {
|
|
37
|
+
CSS.highlights.delete(name);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const highlight = new Highlight();
|
|
41
|
+
for (const selection of ranges.values())
|
|
42
|
+
for (const range of selection)
|
|
43
|
+
highlight.add(range);
|
|
44
|
+
CSS.highlights.set(name, highlight);
|
|
34
45
|
}
|
|
35
46
|
// Paint only selected text, without WebKit filling the surrounding margins.
|
|
36
47
|
export const selectionPlugin = $prose(() => new Plugin({
|
|
37
48
|
key: new PluginKey('selection'),
|
|
38
49
|
view(view) {
|
|
39
|
-
|
|
40
|
-
CSS.highlights.set(name, highlight);
|
|
41
|
-
paint(view, highlight);
|
|
50
|
+
paint(view);
|
|
42
51
|
return {
|
|
43
|
-
update: (view) => paint(view
|
|
44
|
-
destroy: () =>
|
|
52
|
+
update: (view) => paint(view),
|
|
53
|
+
destroy: () => {
|
|
54
|
+
ranges.delete(view);
|
|
55
|
+
refresh();
|
|
56
|
+
},
|
|
45
57
|
};
|
|
46
58
|
},
|
|
47
59
|
}));
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
2
|
+
export declare const sourceAttribute = "inkkitSource";
|
|
3
|
+
export interface SourceProvenance {
|
|
4
|
+
readonly text: string;
|
|
5
|
+
readonly format: 'md' | 'txt';
|
|
6
|
+
readonly from: number;
|
|
7
|
+
readonly to: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function cleanSourceDoc(doc: ProseNode): ProseNode;
|
|
10
|
+
export declare function rawOffset(source: string, normalized: number): number;
|
|
11
|
+
export declare function normalizedOffset(source: string, raw: number): number;
|
|
12
|
+
export declare function editedPlainSource(previous: string, value: string): string;
|
|
13
|
+
export declare function sourceSelection(source: string, start: number, end: number): {
|
|
14
|
+
markdown: string;
|
|
15
|
+
shareable: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function revealSourceCaret(plain: HTMLTextAreaElement): void;
|