@segnavia/markdown-editor 0.44.0 → 0.46.0
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/dist/editor-css.js +4 -2
- package/dist/element-properties.d.ts +2 -2
- package/dist/element-properties.js +4 -2
- package/dist/markdown-editor-labels.d.ts +6 -0
- package/dist/markdown-editor-labels.js +7 -0
- package/dist/markdown-editor-model.js +12 -7
- package/dist/markdown-editor-state.js +30 -0
- package/dist/markdown-editor-title-input.js +50 -0
- package/dist/markdown-editor-types.d.ts +2 -0
- package/dist/markdown-editor.d.ts +3 -1
- package/dist/markdown-editor.js +38 -55
- package/dist/markdown-raw-inline-indication.d.ts +13 -0
- package/dist/markdown-raw-inline-indication.js +91 -0
- package/dist/markdown-rich-editor.js +36 -3
- package/dist/markdown-rich-extensions.js +214 -11
- package/dist/markdown-rich-locked.js +28 -2
- package/dist/markdown-rich-raw-inline.js +123 -0
- package/dist/markdown-rich-save.js +132 -14
- package/dist/markdown-rich-source-attrs.js +32 -0
- package/dist/markdown-rich-source-forms.js +368 -0
- package/dist/vue.d.ts +2 -0
- package/package.json +8 -6
package/dist/editor-css.js
CHANGED
|
@@ -9,10 +9,12 @@ const EDITOR_CSS = `
|
|
|
9
9
|
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] .ProseMirror){min-block-size:100%;outline:0}
|
|
10
10
|
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] table){border-collapse:collapse;inline-size:max-content;min-inline-size:100%}
|
|
11
11
|
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] th),:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] td){min-inline-size:1em}
|
|
12
|
-
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] [data-
|
|
13
|
-
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] [data-
|
|
12
|
+
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] ul:has(> li[data-checked])){list-style:none;padding-inline-start:0}
|
|
13
|
+
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] li[data-checked]){display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--segnavia-markdown-task-gap,.5em);align-items:baseline}
|
|
14
|
+
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] li[data-checked] > div > p){margin:0}
|
|
14
15
|
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="locked-block"]){display:block;position:relative;min-block-size:1em}
|
|
15
16
|
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="locked-block"] segnavia-page){display:block}
|
|
17
|
+
:where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] [part~="raw-inline"]){unicode-bidi:isolate;color:var(--segnavia-markdown-raw-inline-color,inherit);background:var(--segnavia-markdown-raw-inline-background,transparent);border:var(--segnavia-markdown-raw-inline-border,none);border-radius:var(--segnavia-markdown-raw-inline-radius,0);padding-inline:var(--segnavia-markdown-raw-inline-padding,.15em)}
|
|
16
18
|
`;
|
|
17
19
|
//#endregion
|
|
18
20
|
export { EDITOR_CSS };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export declare const MARKDOWN_EDITOR_TAG = "segnavia-markdown-editor";
|
|
3
3
|
export declare const MARKDOWN_EDITOR_PROPERTIES: {
|
|
4
4
|
readonly tag: "segnavia-markdown-editor";
|
|
5
|
-
readonly properties: readonly ["markdown", "mode", "title", "titleSource", "lockedFences", "css", "fonts", "fontReferences", "linkOrigins", "alertLabels"];
|
|
6
|
-
readonly events: readonly ["segnavia-markdown-change", "segnavia-title-change", "segnavia-mode-change", "segnavia-save", "segnavia-editor-state", "segnavia-link-request"];
|
|
5
|
+
readonly properties: readonly ["markdown", "mode", "title", "titleSource", "lockedFences", "css", "fonts", "fontReferences", "linkOrigins", "alertLabels", "labels"];
|
|
6
|
+
readonly events: readonly ["segnavia-markdown-change", "segnavia-title-change", "segnavia-mode-change", "segnavia-save", "segnavia-editor-state", "segnavia-link-request", "segnavia-raw-inline-indicate"];
|
|
7
7
|
};
|
|
8
8
|
//#endregion
|
|
@@ -12,7 +12,8 @@ const MARKDOWN_EDITOR_PROPERTIES = {
|
|
|
12
12
|
"fonts",
|
|
13
13
|
"fontReferences",
|
|
14
14
|
"linkOrigins",
|
|
15
|
-
"alertLabels"
|
|
15
|
+
"alertLabels",
|
|
16
|
+
"labels"
|
|
16
17
|
],
|
|
17
18
|
events: [
|
|
18
19
|
"segnavia-markdown-change",
|
|
@@ -20,7 +21,8 @@ const MARKDOWN_EDITOR_PROPERTIES = {
|
|
|
20
21
|
"segnavia-mode-change",
|
|
21
22
|
"segnavia-save",
|
|
22
23
|
"segnavia-editor-state",
|
|
23
|
-
"segnavia-link-request"
|
|
24
|
+
"segnavia-link-request",
|
|
25
|
+
"segnavia-raw-inline-indicate"
|
|
24
26
|
]
|
|
25
27
|
};
|
|
26
28
|
//#endregion
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/markdown-editor-labels.ts
|
|
2
|
+
const ENGLISH_MARKDOWN_EDITOR_LABELS = { rawInline: (display, source) => `${display}, Markdown source: ${source}` };
|
|
3
|
+
function markdownEditorLabels(labels) {
|
|
4
|
+
return { rawInline: labels.rawInline ?? ENGLISH_MARKDOWN_EDITOR_LABELS.rawInline };
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { ENGLISH_MARKDOWN_EDITOR_LABELS, markdownEditorLabels };
|
|
@@ -9,17 +9,21 @@ function markdownEditorModel() {
|
|
|
9
9
|
retained: ""
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
+
function normalizeMarkdownLineEndings(markdown) {
|
|
13
|
+
return markdown.replace(/\r\n?/g, "\n");
|
|
14
|
+
}
|
|
12
15
|
function setModelMarkdown(model, markdown) {
|
|
13
|
-
|
|
16
|
+
const normalized = normalizeMarkdownLineEndings(markdown);
|
|
17
|
+
if (normalized === model.markdown) return model;
|
|
14
18
|
if (model.titleSource === "separate") return {
|
|
15
19
|
...model,
|
|
16
|
-
markdown,
|
|
17
|
-
editable:
|
|
20
|
+
markdown: normalized,
|
|
21
|
+
editable: normalized
|
|
18
22
|
};
|
|
19
|
-
const split = splitLeadingHeading(
|
|
23
|
+
const split = splitLeadingHeading(normalized);
|
|
20
24
|
return {
|
|
21
25
|
...model,
|
|
22
|
-
markdown,
|
|
26
|
+
markdown: normalized,
|
|
23
27
|
title: split.title ?? "",
|
|
24
28
|
editable: split.editable,
|
|
25
29
|
retained: split.retained
|
|
@@ -63,7 +67,8 @@ function setModelTitle(model, value) {
|
|
|
63
67
|
retained
|
|
64
68
|
};
|
|
65
69
|
}
|
|
66
|
-
function setModelEditable(model,
|
|
70
|
+
function setModelEditable(model, edited) {
|
|
71
|
+
const editable = normalizeMarkdownLineEndings(edited);
|
|
67
72
|
const markdown = model.titleSource === "leading-heading" ? joinLeadingHeading(editable, model.retained) : editable;
|
|
68
73
|
if (markdown === model.markdown && editable === model.editable) return model;
|
|
69
74
|
return {
|
|
@@ -73,4 +78,4 @@ function setModelEditable(model, editable) {
|
|
|
73
78
|
};
|
|
74
79
|
}
|
|
75
80
|
//#endregion
|
|
76
|
-
export { markdownEditorModel, setModelEditable, setModelMarkdown, setModelTitle, setModelTitleSource };
|
|
81
|
+
export { markdownEditorModel, normalizeMarkdownLineEndings, setModelEditable, setModelMarkdown, setModelTitle, setModelTitleSource };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { richEditorState } from "./markdown-rich-commands.js";
|
|
2
|
+
import { redoDepth, undoDepth } from "@codemirror/commands";
|
|
3
|
+
//#region src/markdown-editor-state.ts
|
|
4
|
+
var MarkdownEditorStateEmitter = class {
|
|
5
|
+
dispatch;
|
|
6
|
+
key = "";
|
|
7
|
+
constructor(dispatch) {
|
|
8
|
+
this.dispatch = dispatch;
|
|
9
|
+
}
|
|
10
|
+
source(view, canUndo = view ? undoDepth(view.state) > 0 : false, canRedo = view ? redoDepth(view.state) > 0 : false) {
|
|
11
|
+
this.emit({
|
|
12
|
+
marks: [],
|
|
13
|
+
nodes: [],
|
|
14
|
+
canUndo,
|
|
15
|
+
canRedo
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
visible(mode, richView, sourceView) {
|
|
19
|
+
if (mode === "rich" && richView) this.emit(richEditorState(richView));
|
|
20
|
+
else this.source(sourceView);
|
|
21
|
+
}
|
|
22
|
+
emit(state) {
|
|
23
|
+
const key = JSON.stringify(state);
|
|
24
|
+
if (key === this.key) return;
|
|
25
|
+
this.key = key;
|
|
26
|
+
this.dispatch(state);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { MarkdownEditorStateEmitter };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { normalizeMarkdownTitle } from "./markdown-title.js";
|
|
2
|
+
import { setModelTitle } from "./markdown-editor-model.js";
|
|
3
|
+
import { markdownEditorKeyAction } from "./markdown-editor-policy.js";
|
|
4
|
+
//#region src/markdown-editor-title-input.ts
|
|
5
|
+
var MarkdownEditorTitleInput = class {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
connect() {
|
|
11
|
+
const { field } = this.options;
|
|
12
|
+
field.addEventListener("input", this.input);
|
|
13
|
+
field.addEventListener("keydown", this.keyDown);
|
|
14
|
+
field.addEventListener("paste", this.paste);
|
|
15
|
+
}
|
|
16
|
+
disconnect() {
|
|
17
|
+
const { field } = this.options;
|
|
18
|
+
field.removeEventListener("input", this.input);
|
|
19
|
+
field.removeEventListener("keydown", this.keyDown);
|
|
20
|
+
field.removeEventListener("paste", this.paste);
|
|
21
|
+
}
|
|
22
|
+
input = () => {
|
|
23
|
+
const { field } = this.options;
|
|
24
|
+
const normalized = normalizeMarkdownTitle(field.value);
|
|
25
|
+
field.value = normalized;
|
|
26
|
+
const model = setModelTitle(this.options.model(), normalized);
|
|
27
|
+
if (model === this.options.model()) return;
|
|
28
|
+
this.options.update(model);
|
|
29
|
+
this.options.emit("segnavia-title-change", { title: normalized });
|
|
30
|
+
if (model.titleSource === "leading-heading") this.options.emit("segnavia-markdown-change", { markdown: model.markdown });
|
|
31
|
+
};
|
|
32
|
+
keyDown = (event) => {
|
|
33
|
+
if (event.key === "Enter") event.preventDefault();
|
|
34
|
+
const action = markdownEditorKeyAction(event);
|
|
35
|
+
if (!action) return;
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
if (action === "save") this.options.emit("segnavia-save");
|
|
38
|
+
else this.options.toggleMode();
|
|
39
|
+
};
|
|
40
|
+
paste = (event) => {
|
|
41
|
+
const pasted = event.clipboardData?.getData("text");
|
|
42
|
+
if (pasted === void 0) return;
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
const { field } = this.options;
|
|
45
|
+
field.setRangeText(normalizeMarkdownTitle(pasted), field.selectionStart ?? 0, field.selectionEnd ?? 0, "end");
|
|
46
|
+
this.input();
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { MarkdownEditorTitleInput };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MarkdownEditorLabels } from "./markdown-editor-labels.js";
|
|
1
2
|
import { MarkdownTitleSource } from "./markdown-editor-model.js";
|
|
2
3
|
import { MarkdownEditorCommand, MarkdownEditorCommandArgs, MarkdownEditorMode } from "./markdown-editor-policy.js";
|
|
3
4
|
import { AlertLabels } from "@segnavia/html";
|
|
@@ -22,6 +23,7 @@ export interface SegnaviaMarkdownEditor extends Omit<HTMLElement, 'title'> {
|
|
|
22
23
|
fontReferences: FontReference[];
|
|
23
24
|
linkOrigins: Iterable<string> | undefined;
|
|
24
25
|
alertLabels: Partial<AlertLabels> | undefined;
|
|
26
|
+
labels: Partial<MarkdownEditorLabels>;
|
|
25
27
|
unstyled: boolean;
|
|
26
28
|
run<C extends MarkdownEditorCommand>(command: C, args?: C extends keyof MarkdownEditorCommandArgs ? MarkdownEditorCommandArgs[C] : never): boolean;
|
|
27
29
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { ENGLISH_MARKDOWN_EDITOR_LABELS, MarkdownEditorLabels } from "./markdown-editor-labels.js";
|
|
1
2
|
import { MarkdownTitleSource } from "./markdown-editor-model.js";
|
|
2
3
|
import { MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, MarkdownEditorCommand, MarkdownEditorMode } from "./markdown-editor-policy.js";
|
|
3
4
|
import { MarkdownEditorStateDetail, SegnaviaMarkdownEditor as SegnaviaMarkdownEditor$1 } from "./markdown-editor-types.js";
|
|
4
5
|
import { MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG } from "./element-properties.js";
|
|
6
|
+
import { RawInlineIndicationDetail } from "./markdown-raw-inline-indication.js";
|
|
5
7
|
import { joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading } from "./markdown-title.js";
|
|
6
8
|
import { FontsOptions } from "@segnavia/frame/fonts";
|
|
7
9
|
//#region src/markdown-editor.d.ts
|
|
@@ -10,4 +12,4 @@ export declare const SegnaviaMarkdownEditor: {
|
|
|
10
12
|
new (): SegnaviaMarkdownEditor;
|
|
11
13
|
};
|
|
12
14
|
//#endregion
|
|
13
|
-
export { type FontsOptions, MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG, type MarkdownEditorCommand, type MarkdownEditorMode, type MarkdownEditorStateDetail, type MarkdownTitleSource, joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading };
|
|
15
|
+
export { ENGLISH_MARKDOWN_EDITOR_LABELS, type FontsOptions, MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG, type MarkdownEditorCommand, type MarkdownEditorLabels, type MarkdownEditorMode, type MarkdownEditorStateDetail, type MarkdownTitleSource, type RawInlineIndicationDetail, joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading };
|
package/dist/markdown-editor.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { ensureEditorStyle } from "./editor-style.js";
|
|
2
2
|
import { MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG } from "./element-properties.js";
|
|
3
3
|
import { MarkdownEditorFontRegistration } from "./markdown-editor-font-registration.js";
|
|
4
|
+
import { ENGLISH_MARKDOWN_EDITOR_LABELS, markdownEditorLabels } from "./markdown-editor-labels.js";
|
|
4
5
|
import { validateMarkdownLink } from "./markdown-editor-link.js";
|
|
5
6
|
import { joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading } from "./markdown-title.js";
|
|
6
|
-
import { markdownEditorModel, setModelEditable, setModelMarkdown,
|
|
7
|
+
import { markdownEditorModel, setModelEditable, setModelMarkdown, setModelTitleSource } from "./markdown-editor-model.js";
|
|
7
8
|
import { equalAlertLabels, equalFontReferences, equalFonts, equalLockedFences, normalizeAlertLabels, normalizeFontReferences, normalizeLockedFences } from "./markdown-editor-options.js";
|
|
8
|
-
import { MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, availableMarkdownMode,
|
|
9
|
+
import { MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, availableMarkdownMode, richModeCommandRefusal } from "./markdown-editor-policy.js";
|
|
10
|
+
import { RICH_COMMANDS, lockedBlockSourceOffset, selectedLockedBlock } from "./markdown-rich-commands.js";
|
|
11
|
+
import { MarkdownEditorStateEmitter } from "./markdown-editor-state.js";
|
|
9
12
|
import { renderMarkdownEditorTitle, updateMarkdownEditorTitle } from "./markdown-editor-title.js";
|
|
10
|
-
import {
|
|
13
|
+
import { MarkdownEditorTitleInput } from "./markdown-editor-title-input.js";
|
|
14
|
+
import { RAW_INLINE_ATTRIBUTE } from "./markdown-rich-raw-inline.js";
|
|
15
|
+
import { RawInlineIndicator, labelRawInline } from "./markdown-raw-inline-indication.js";
|
|
11
16
|
import { mountRichEditor } from "./markdown-rich-editor.js";
|
|
12
17
|
import { parseRichDocument, saveRichDocument } from "./markdown-rich-save.js";
|
|
13
18
|
import { markdownSourceState } from "./markdown-source.js";
|
|
14
|
-
import { redo,
|
|
19
|
+
import { redo, undo } from "@codemirror/commands";
|
|
15
20
|
import { EditorView } from "@codemirror/view";
|
|
16
21
|
import { upgradeProperties } from "@segnavia/frame/element-properties";
|
|
17
22
|
import { sanitizeBookCss } from "@segnavia/html";
|
|
@@ -36,14 +41,23 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
36
41
|
modeValue = "rich";
|
|
37
42
|
reflecting = false;
|
|
38
43
|
richOriginal = "";
|
|
39
|
-
stateKey = "";
|
|
40
44
|
lockedFenceOptions = {};
|
|
41
45
|
cssValue = "";
|
|
42
46
|
fontOptions;
|
|
43
47
|
fontReferenceOptions = [];
|
|
44
48
|
linkOriginOptions;
|
|
45
49
|
alertLabelOptions;
|
|
50
|
+
words = ENGLISH_MARKDOWN_EDITOR_LABELS;
|
|
51
|
+
rawInlineIndicator = new RawInlineIndicator((detail) => this.emit("segnavia-raw-inline-indicate", detail));
|
|
46
52
|
fontRegistration = new MarkdownEditorFontRegistration(this);
|
|
53
|
+
state = new MarkdownEditorStateEmitter((detail) => this.emit("segnavia-editor-state", detail));
|
|
54
|
+
titleInput = new MarkdownEditorTitleInput({
|
|
55
|
+
field: this.titleField,
|
|
56
|
+
model: () => this.model,
|
|
57
|
+
update: (model) => this.model = model,
|
|
58
|
+
emit: (name, detail) => this.emit(name, detail),
|
|
59
|
+
toggleMode: () => this.run("toggle-mode")
|
|
60
|
+
});
|
|
47
61
|
constructor() {
|
|
48
62
|
super();
|
|
49
63
|
upgradeProperties(this);
|
|
@@ -59,16 +73,13 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
59
73
|
}
|
|
60
74
|
connectedCallback() {
|
|
61
75
|
ensureEditorStyle(this.container);
|
|
62
|
-
this.
|
|
63
|
-
this.titleField.addEventListener("keydown", this.onTitleKeyDown);
|
|
64
|
-
this.titleField.addEventListener("paste", this.onTitlePaste);
|
|
76
|
+
this.titleInput.connect();
|
|
65
77
|
this.mountMode();
|
|
66
78
|
renderMarkdownEditorTitle(this.titleField, this.model);
|
|
67
79
|
}
|
|
68
80
|
disconnectedCallback() {
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
this.titleField.removeEventListener("paste", this.onTitlePaste);
|
|
81
|
+
this.rawInlineIndicator.end();
|
|
82
|
+
this.titleInput.disconnect();
|
|
72
83
|
this.sourceView?.destroy();
|
|
73
84
|
this.sourceView = null;
|
|
74
85
|
this.richView?.destroy();
|
|
@@ -185,6 +196,13 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
185
196
|
this.alertLabelOptions = next;
|
|
186
197
|
if (this.modeValue === "rich") this.resetRich();
|
|
187
198
|
}
|
|
199
|
+
get labels() {
|
|
200
|
+
return this.words;
|
|
201
|
+
}
|
|
202
|
+
set labels(value) {
|
|
203
|
+
this.words = markdownEditorLabels(value ?? {});
|
|
204
|
+
for (const atom of this.richMount.querySelectorAll(`[${RAW_INLINE_ATTRIBUTE}]`)) labelRawInline(atom, this.words);
|
|
205
|
+
}
|
|
188
206
|
get unstyled() {
|
|
189
207
|
return this.hasAttribute("unstyled");
|
|
190
208
|
}
|
|
@@ -209,6 +227,7 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
209
227
|
setMode(mode, emit) {
|
|
210
228
|
if (mode === this.modeValue) return false;
|
|
211
229
|
this.modeValue = mode;
|
|
230
|
+
this.rawInlineIndicator.end();
|
|
212
231
|
this.reflect("mode", mode);
|
|
213
232
|
this.mountMode();
|
|
214
233
|
if (emit) this.emit("segnavia-mode-change", { mode });
|
|
@@ -243,10 +262,11 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
243
262
|
resetVisibleEditor() {
|
|
244
263
|
if (this.modeValue === "source" && this.sourceView) this.sourceView.setState(this.sourceState());
|
|
245
264
|
if (this.modeValue === "rich") this.resetRich();
|
|
246
|
-
this.
|
|
265
|
+
this.state.visible(this.modeValue, this.richView, this.sourceView);
|
|
247
266
|
}
|
|
248
267
|
resetRich() {
|
|
249
268
|
if (!this.isConnected) return;
|
|
269
|
+
this.rawInlineIndicator.end();
|
|
250
270
|
this.richView?.destroy();
|
|
251
271
|
this.richMount.replaceChildren();
|
|
252
272
|
this.richOriginal = this.model.editable;
|
|
@@ -258,8 +278,10 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
258
278
|
fontReferences: this.fontReferenceOptions,
|
|
259
279
|
alertLabels: this.alertLabelOptions,
|
|
260
280
|
linkOrigins: () => this.linkOriginOptions,
|
|
281
|
+
labels: () => this.words,
|
|
282
|
+
indicator: this.rawInlineIndicator,
|
|
261
283
|
change: (content) => this.onRichChange(content),
|
|
262
|
-
state: (state) => this.
|
|
284
|
+
state: (state) => this.state.emit(state),
|
|
263
285
|
linkRequest: (href) => this.emit("segnavia-link-request", { href }),
|
|
264
286
|
save: () => this.emit("segnavia-save"),
|
|
265
287
|
toggleMode: () => this.setMode("source", true),
|
|
@@ -292,47 +314,8 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
292
314
|
this.model = model;
|
|
293
315
|
this.emit("segnavia-markdown-change", { markdown: model.markdown });
|
|
294
316
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.titleField.value = normalized;
|
|
298
|
-
const model = setModelTitle(this.model, normalized);
|
|
299
|
-
if (model === this.model) return;
|
|
300
|
-
this.model = model;
|
|
301
|
-
this.emit("segnavia-title-change", { title: normalized });
|
|
302
|
-
if (model.titleSource === "leading-heading") this.emit("segnavia-markdown-change", { markdown: model.markdown });
|
|
303
|
-
};
|
|
304
|
-
onTitleKeyDown = (event) => {
|
|
305
|
-
if (event.key === "Enter") event.preventDefault();
|
|
306
|
-
const action = markdownEditorKeyAction(event);
|
|
307
|
-
if (!action) return;
|
|
308
|
-
event.preventDefault();
|
|
309
|
-
if (action === "save") this.emit("segnavia-save");
|
|
310
|
-
else this.run("toggle-mode");
|
|
311
|
-
};
|
|
312
|
-
onTitlePaste = (event) => {
|
|
313
|
-
const pasted = event.clipboardData?.getData("text");
|
|
314
|
-
if (pasted === void 0) return;
|
|
315
|
-
event.preventDefault();
|
|
316
|
-
this.titleField.setRangeText(normalizeMarkdownTitle(pasted), this.titleField.selectionStart ?? 0, this.titleField.selectionEnd ?? 0, "end");
|
|
317
|
-
this.onTitleInput();
|
|
318
|
-
};
|
|
319
|
-
emitSourceState(canUndo = this.sourceView ? undoDepth(this.sourceView.state) > 0 : false, canRedo = this.sourceView ? redoDepth(this.sourceView.state) > 0 : false) {
|
|
320
|
-
this.emitState({
|
|
321
|
-
marks: [],
|
|
322
|
-
nodes: [],
|
|
323
|
-
canUndo,
|
|
324
|
-
canRedo
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
emitVisibleState() {
|
|
328
|
-
if (this.modeValue === "rich" && this.richView) this.emitState(richEditorState(this.richView));
|
|
329
|
-
else this.emitSourceState();
|
|
330
|
-
}
|
|
331
|
-
emitState(state) {
|
|
332
|
-
const key = JSON.stringify(state);
|
|
333
|
-
if (key === this.stateKey) return;
|
|
334
|
-
this.stateKey = key;
|
|
335
|
-
this.emit("segnavia-editor-state", state);
|
|
317
|
+
emitSourceState(canUndo, canRedo) {
|
|
318
|
+
this.state.source(this.sourceView, canUndo, canRedo);
|
|
336
319
|
}
|
|
337
320
|
emit(name, detail) {
|
|
338
321
|
this.dispatchEvent(new CustomEvent(name, {
|
|
@@ -352,4 +335,4 @@ var MarkdownEditorElement = class extends HTMLElement {
|
|
|
352
335
|
const SegnaviaMarkdownEditor = MarkdownEditorElement;
|
|
353
336
|
if (!customElements.get("segnavia-markdown-editor")) customElements.define(MARKDOWN_EDITOR_TAG, SegnaviaMarkdownEditor);
|
|
354
337
|
//#endregion
|
|
355
|
-
export { MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG, SegnaviaMarkdownEditor, joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading };
|
|
338
|
+
export { ENGLISH_MARKDOWN_EDITOR_LABELS, MARKDOWN_EDITOR_COMMANDS, MARKDOWN_EDITOR_MODES, MARKDOWN_EDITOR_PROPERTIES, MARKDOWN_EDITOR_TAG, SegnaviaMarkdownEditor, joinLeadingHeading, normalizeMarkdownTitle, splitLeadingHeading };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/markdown-raw-inline-indication.d.ts
|
|
2
|
+
export type RawInlineIndicationDetail = Readonly<{
|
|
3
|
+
source: string;
|
|
4
|
+
display: string;
|
|
5
|
+
rect: Readonly<{
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
}>;
|
|
11
|
+
via: 'pointer' | 'selection';
|
|
12
|
+
}>;
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { RAW_INLINE_ATTRIBUTE } from "./markdown-rich-raw-inline.js";
|
|
2
|
+
import { NodeSelection } from "@tiptap/pm/state";
|
|
3
|
+
import { resolveDirection } from "@segnavia/format";
|
|
4
|
+
//#region src/markdown-raw-inline-indication.ts
|
|
5
|
+
const rawInlineSelector = `[${RAW_INLINE_ATTRIBUTE}]`;
|
|
6
|
+
function rawInlineElement(target) {
|
|
7
|
+
return target && "closest" in target ? target.closest(rawInlineSelector) : null;
|
|
8
|
+
}
|
|
9
|
+
function selectedRawInlineAtom(editor) {
|
|
10
|
+
const selection = editor.state.selection;
|
|
11
|
+
if (!(selection instanceof NodeSelection) || selection.node.type.name !== "rawInline") return null;
|
|
12
|
+
return rawInlineElement(editor.view.nodeDOM(selection.from));
|
|
13
|
+
}
|
|
14
|
+
function readRawInlineAtom(atom) {
|
|
15
|
+
return {
|
|
16
|
+
source: atom.getAttribute("data-raw-source") ?? "",
|
|
17
|
+
display: atom.getAttribute("data-raw-display") ?? ""
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function directionContext(atom) {
|
|
21
|
+
let dir;
|
|
22
|
+
let lang;
|
|
23
|
+
for (let element = atom; element && (!dir || !lang);) {
|
|
24
|
+
const declaredDir = element.getAttribute("dir");
|
|
25
|
+
if (!dir && (declaredDir === "ltr" || declaredDir === "rtl")) dir = declaredDir;
|
|
26
|
+
if (!lang) lang = element.getAttribute("lang") || void 0;
|
|
27
|
+
const root = element.getRootNode();
|
|
28
|
+
element = element.parentElement ?? (root && "host" in root ? root.host : null);
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
dir,
|
|
32
|
+
lang
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function labelRawInline(atom, labels) {
|
|
36
|
+
const { source, display } = readRawInlineAtom(atom);
|
|
37
|
+
const { dir, lang } = directionContext(atom);
|
|
38
|
+
const isolate = resolveDirection(dir, lang, source) === "rtl" ? "" : "";
|
|
39
|
+
atom.setAttribute("role", "img");
|
|
40
|
+
atom.setAttribute("aria-label", labels.rawInline(display, `${isolate}${source}\u2069`));
|
|
41
|
+
}
|
|
42
|
+
function detail(atom, via) {
|
|
43
|
+
const { x, y, width, height } = atom.getBoundingClientRect();
|
|
44
|
+
const { source, display } = readRawInlineAtom(atom);
|
|
45
|
+
return {
|
|
46
|
+
source,
|
|
47
|
+
display,
|
|
48
|
+
rect: {
|
|
49
|
+
x,
|
|
50
|
+
y,
|
|
51
|
+
width,
|
|
52
|
+
height
|
|
53
|
+
},
|
|
54
|
+
via
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var RawInlineIndicator = class {
|
|
58
|
+
dispatch;
|
|
59
|
+
pointer = null;
|
|
60
|
+
selection = null;
|
|
61
|
+
key = "null";
|
|
62
|
+
constructor(dispatch) {
|
|
63
|
+
this.dispatch = dispatch;
|
|
64
|
+
}
|
|
65
|
+
pointerEnter(atom) {
|
|
66
|
+
this.pointer = atom;
|
|
67
|
+
this.emit();
|
|
68
|
+
}
|
|
69
|
+
pointerLeave(atom) {
|
|
70
|
+
if (this.pointer === atom) this.pointer = null;
|
|
71
|
+
this.emit();
|
|
72
|
+
}
|
|
73
|
+
selectionSelect(atom) {
|
|
74
|
+
this.selection = atom;
|
|
75
|
+
this.emit();
|
|
76
|
+
}
|
|
77
|
+
end() {
|
|
78
|
+
this.pointer = null;
|
|
79
|
+
this.selection = null;
|
|
80
|
+
this.emit();
|
|
81
|
+
}
|
|
82
|
+
emit() {
|
|
83
|
+
const next = this.selection ? detail(this.selection, "selection") : this.pointer ? detail(this.pointer, "pointer") : null;
|
|
84
|
+
const key = JSON.stringify(next);
|
|
85
|
+
if (key === this.key) return;
|
|
86
|
+
this.key = key;
|
|
87
|
+
this.dispatch(next);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
//#endregion
|
|
91
|
+
export { RawInlineIndicator, labelRawInline, rawInlineElement, readRawInlineAtom, selectedRawInlineAtom };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { LockedBlock } from "./markdown-rich-locked.js";
|
|
2
2
|
import { richEditorState, richKeyboard } from "./markdown-rich-commands.js";
|
|
3
|
+
import { RAW_INLINE_ATTRIBUTE, RAW_INLINE_DISPLAY_ATTRIBUTE, RAW_INLINE_SOURCE_ATTRIBUTE, RawInline } from "./markdown-rich-raw-inline.js";
|
|
4
|
+
import { labelRawInline, selectedRawInlineAtom } from "./markdown-raw-inline-indication.js";
|
|
3
5
|
import { richExtensions } from "./markdown-rich-extensions.js";
|
|
4
6
|
import { safeMarkdownUrl } from "@segnavia/html";
|
|
5
7
|
import { HEADLESS_ELEMENTS, SegnaviaPage, createPageElement } from "@segnavia/frame";
|
|
@@ -37,8 +39,32 @@ function lockedView(options) {
|
|
|
37
39
|
};
|
|
38
40
|
} });
|
|
39
41
|
}
|
|
42
|
+
function rawInlineView(options) {
|
|
43
|
+
return RawInline.extend({ addNodeView() {
|
|
44
|
+
return ({ node }) => {
|
|
45
|
+
const dom = document.createElement("span");
|
|
46
|
+
dom.part.add("raw-inline");
|
|
47
|
+
dom.setAttribute(RAW_INLINE_ATTRIBUTE, "");
|
|
48
|
+
dom.setAttribute(RAW_INLINE_SOURCE_ATTRIBUTE, String(node.attrs.source ?? ""));
|
|
49
|
+
dom.setAttribute(RAW_INLINE_DISPLAY_ATTRIBUTE, String(node.attrs.display ?? ""));
|
|
50
|
+
dom.dir = "auto";
|
|
51
|
+
dom.contentEditable = "false";
|
|
52
|
+
dom.textContent = String(node.attrs.display ?? "");
|
|
53
|
+
labelRawInline(dom, options().labels());
|
|
54
|
+
dom.addEventListener("pointerenter", () => options().indicator.pointerEnter(dom));
|
|
55
|
+
dom.addEventListener("pointerleave", () => options().indicator.pointerLeave(dom));
|
|
56
|
+
return {
|
|
57
|
+
dom,
|
|
58
|
+
destroy: () => options().indicator.pointerLeave(dom)
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
} });
|
|
62
|
+
}
|
|
40
63
|
function extensions(options, keyboard) {
|
|
41
|
-
return [...richExtensions().map((extension) =>
|
|
64
|
+
return [...richExtensions().map((extension) => {
|
|
65
|
+
if (extension.name === "lockedBlock") return lockedView(options);
|
|
66
|
+
return extension.name === "rawInline" ? rawInlineView(options) : extension;
|
|
67
|
+
}), richKeyboard(keyboard)];
|
|
42
68
|
}
|
|
43
69
|
function pastedHtml(html, linkOrigins) {
|
|
44
70
|
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
@@ -69,8 +95,15 @@ function mountRichEditor(options) {
|
|
|
69
95
|
transformPastedHTML: (html) => pastedHtml(html, options.linkOrigins())
|
|
70
96
|
},
|
|
71
97
|
onUpdate: ({ editor: instance }) => options.change(Array.from(instance.state.doc.content.content)),
|
|
72
|
-
onTransaction: ({ editor: instance }) =>
|
|
73
|
-
|
|
98
|
+
onTransaction: ({ editor: instance }) => {
|
|
99
|
+
options.state(richEditorState(instance));
|
|
100
|
+
options.indicator.selectionSelect(selectedRawInlineAtom(instance));
|
|
101
|
+
},
|
|
102
|
+
onSelectionUpdate: ({ editor: instance }) => {
|
|
103
|
+
options.state(richEditorState(instance));
|
|
104
|
+
options.indicator.selectionSelect(selectedRawInlineAtom(instance));
|
|
105
|
+
},
|
|
106
|
+
onBlur: () => options.indicator.end()
|
|
74
107
|
});
|
|
75
108
|
options.state(richEditorState(editor));
|
|
76
109
|
return editor;
|