@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.
@@ -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-type="taskList"]){list-style:none;padding-inline-start:0}
13
- :where(:host(segnavia-markdown-editor:not([unstyled])) [part~="rich"] [data-type="taskItem"]){display:grid;grid-template-columns:auto minmax(0,1fr);gap:var(--segnavia-markdown-task-gap,.5em)}
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,6 @@
1
+ //#region src/markdown-editor-labels.d.ts
2
+ export interface MarkdownEditorLabels {
3
+ rawInline: (display: string, source: string) => string;
4
+ }
5
+ export declare const ENGLISH_MARKDOWN_EDITOR_LABELS: MarkdownEditorLabels;
6
+ //#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
- if (markdown === model.markdown) return model;
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: markdown
20
+ markdown: normalized,
21
+ editable: normalized
18
22
  };
19
- const split = splitLeadingHeading(markdown);
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, editable) {
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 };
@@ -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, setModelTitle, setModelTitleSource } from "./markdown-editor-model.js";
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, markdownEditorKeyAction, richModeCommandRefusal } from "./markdown-editor-policy.js";
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 { RICH_COMMANDS, lockedBlockSourceOffset, richEditorState, selectedLockedBlock } from "./markdown-rich-commands.js";
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, redoDepth, undo, undoDepth } from "@codemirror/commands";
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.titleField.addEventListener("input", this.onTitleInput);
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.titleField.removeEventListener("input", this.onTitleInput);
70
- this.titleField.removeEventListener("keydown", this.onTitleKeyDown);
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.emitVisibleState();
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.emitState(state),
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
- onTitleInput = () => {
296
- const normalized = normalizeMarkdownTitle(this.titleField.value);
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) => extension.name === "lockedBlock" ? lockedView(options) : extension), richKeyboard(keyboard)];
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 }) => options.state(richEditorState(instance)),
73
- onSelectionUpdate: ({ editor: instance }) => options.state(richEditorState(instance))
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;