@sveltia/ui 0.78.4 → 0.79.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/alert/alert-content.svelte +51 -0
- package/dist/components/alert/alert-content.svelte.d.ts +39 -0
- package/dist/components/alert/alert.d.ts +3 -0
- package/dist/components/alert/alert.js +21 -0
- package/dist/components/alert/alert.svelte +6 -30
- package/dist/components/alert/infobar.svelte +7 -21
- package/dist/components/alert/infobar.svelte.d.ts +2 -2
- package/dist/components/button/button.svelte +11 -11
- package/dist/components/button/button.svelte.d.ts +2 -2
- package/dist/components/button/select-button.svelte +1 -1
- package/dist/components/button/select-button.svelte.d.ts +2 -2
- package/dist/components/button/split-button.svelte +1 -1
- package/dist/components/button/split-button.svelte.d.ts +3 -3
- package/dist/components/calendar/calendar.d.ts +2 -0
- package/dist/components/calendar/calendar.js +34 -0
- package/dist/components/calendar/calendar.svelte +49 -5
- package/dist/components/checkbox/checkbox.svelte +11 -4
- package/dist/components/checkbox/checkbox.svelte.d.ts +2 -2
- package/dist/components/dialog/alert-dialog.svelte +1 -1
- package/dist/components/dialog/alert-dialog.svelte.d.ts +2 -2
- package/dist/components/dialog/confirmation-dialog.svelte +1 -1
- package/dist/components/dialog/confirmation-dialog.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog.svelte +8 -18
- package/dist/components/dialog/dialog.svelte.d.ts +2 -2
- package/dist/components/dialog/modal-close-button.svelte +50 -0
- package/dist/components/dialog/modal-close-button.svelte.d.ts +43 -0
- package/dist/components/dialog/prompt-dialog.svelte +1 -1
- package/dist/components/dialog/prompt-dialog.svelte.d.ts +4 -4
- package/dist/components/disclosure/disclosure.svelte +1 -1
- package/dist/components/drawer/drawer.svelte +12 -34
- package/dist/components/drawer/drawer.svelte.d.ts +1 -1
- package/dist/components/emoji/caret.d.ts +1 -1
- package/dist/components/emoji/caret.js +1 -1
- package/dist/components/emoji/emoji-suggestions.svelte +1 -1
- package/dist/components/emoji/emoji-suggestions.svelte.d.ts +3 -3
- package/dist/components/emoji/emoji.d.ts +2 -2
- package/dist/components/emoji/emoji.js +1 -1
- package/dist/components/emoji/position.d.ts +2 -2
- package/dist/components/emoji/position.js +1 -1
- package/dist/components/grid/grid-body.svelte +6 -2
- package/dist/components/grid/grid-cell.svelte +3 -9
- package/dist/components/grid/grid-col-header.svelte +3 -9
- package/dist/components/grid/grid-foot.svelte +4 -11
- package/dist/components/grid/grid-head.svelte +4 -11
- package/dist/components/grid/grid-row-header.svelte +3 -10
- package/dist/components/grid/grid-row.svelte +3 -16
- package/dist/components/listbox/listbox.svelte +1 -1
- package/dist/components/listbox/listbox.svelte.d.ts +1 -1
- package/dist/components/listbox/option.svelte +1 -1
- package/dist/components/listbox/option.svelte.d.ts +2 -2
- package/dist/components/menu/menu-button.svelte +2 -2
- package/dist/components/menu/menu-button.svelte.d.ts +4 -4
- package/dist/components/menu/menu-item-checkable.svelte +71 -0
- package/dist/components/menu/menu-item-checkable.svelte.d.ts +25 -0
- package/dist/components/menu/menu-item-checkbox.svelte +3 -36
- package/dist/components/menu/menu-item-checkbox.svelte.d.ts +3 -3
- package/dist/components/menu/menu-item-group.svelte +8 -5
- package/dist/components/menu/menu-item-group.svelte.d.ts +9 -1
- package/dist/components/menu/menu-item-radio.svelte +3 -36
- package/dist/components/menu/menu-item-radio.svelte.d.ts +3 -3
- package/dist/components/menu/menu-item.svelte +22 -6
- package/dist/components/menu/menu-item.svelte.d.ts +4 -4
- package/dist/components/radio/radio.svelte +12 -1
- package/dist/components/resizable-pane/resizable-handle.svelte +14 -18
- package/dist/components/resizable-pane/resizable-pane-group.svelte +9 -6
- package/dist/components/resizable-pane/resizable-pane.svelte +4 -4
- package/dist/components/resizable-pane/resizable-pane.svelte.d.ts +7 -6
- package/dist/components/resizable-pane/sizing.d.ts +5 -4
- package/dist/components/resizable-pane/sizing.js +84 -25
- package/dist/components/scroll/infinite-scroll.svelte +16 -21
- package/dist/components/select/combobox.svelte +43 -52
- package/dist/components/select/combobox.svelte.d.ts +2 -2
- package/dist/components/select/select-tags.svelte +7 -1
- package/dist/components/select/select.svelte +2 -22
- package/dist/components/select/select.svelte.d.ts +1 -1
- package/dist/components/slider/slider.d.ts +6 -0
- package/dist/components/slider/slider.js +55 -2
- package/dist/components/slider/slider.svelte +63 -32
- package/dist/components/table/table-cell.svelte +3 -9
- package/dist/components/table/table-col-header.svelte +3 -9
- package/dist/components/table/table-foot.svelte +4 -11
- package/dist/components/table/table-head.svelte +4 -11
- package/dist/components/table/table-part.svelte +69 -0
- package/dist/components/table/table-part.svelte.d.ts +48 -0
- package/dist/components/table/table-row-header.svelte +3 -10
- package/dist/components/table/table-row.svelte +3 -10
- package/dist/components/tabs/tab-list.svelte +12 -0
- package/dist/components/tabs/tab.svelte +1 -1
- package/dist/components/tabs/tab.svelte.d.ts +2 -2
- package/dist/components/text-editor/constants.d.ts +5 -5
- package/dist/components/text-editor/constants.js +1 -1
- package/dist/components/text-editor/core.d.ts +4 -5
- package/dist/components/text-editor/core.js +372 -285
- package/dist/components/text-editor/emoji-autocomplete.svelte +1 -1
- package/dist/components/text-editor/lexical-root.svelte +4 -2
- package/dist/components/text-editor/raw-markdown.d.ts +6 -33
- package/dist/components/text-editor/raw-markdown.js +63 -8
- package/dist/components/text-editor/shiki/facade.d.ts +1 -1
- package/dist/components/text-editor/shiki/facade.js +63 -48
- package/dist/components/text-editor/shiki/generated.d.ts +1 -1
- package/dist/components/text-editor/shiki/generated.js +1 -1
- package/dist/components/text-editor/shiki/highlighter.d.ts +6 -1
- package/dist/components/text-editor/shiki/highlighter.js +3 -4
- package/dist/components/text-editor/shiki/loader.d.ts +1 -1
- package/dist/components/text-editor/shiki/loader.js +1 -1
- package/dist/components/text-editor/shiki/version.d.ts +1 -1
- package/dist/components/text-editor/shiki/version.js +1 -1
- package/dist/components/text-editor/store.svelte.d.ts +1 -1
- package/dist/components/text-editor/store.svelte.js +29 -2
- package/dist/components/text-editor/text-editor.svelte +2 -2
- package/dist/components/text-editor/text-editor.svelte.d.ts +3 -3
- package/dist/components/text-editor/toolbar/code-language-switcher.svelte +24 -16
- package/dist/components/text-editor/toolbar/format-text-button.svelte +7 -31
- package/dist/components/text-editor/toolbar/format-text-button.svelte.d.ts +1 -1
- package/dist/components/text-editor/toolbar/insert-component.d.ts +3 -0
- package/dist/components/text-editor/toolbar/insert-component.js +34 -0
- package/dist/components/text-editor/toolbar/insert-item-button.svelte +4 -24
- package/dist/components/text-editor/toolbar/insert-item-button.svelte.d.ts +1 -1
- package/dist/components/text-editor/toolbar/insert-link-button.svelte +9 -25
- package/dist/components/text-editor/toolbar/insert-menu-button.svelte +4 -24
- package/dist/components/text-editor/toolbar/insert-menu-button.svelte.d.ts +1 -1
- package/dist/components/text-editor/toolbar/text-editor-toolbar.svelte +1 -1
- package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte +31 -35
- package/dist/components/text-editor/toolbar/toggle-block-menu-item.svelte.d.ts +1 -1
- package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte +0 -10
- package/dist/components/text-editor/toolbar/toolbar-wrapper.svelte.d.ts +1 -11
- package/dist/components/text-editor/transformers/table.d.ts +0 -1
- package/dist/components/text-editor/transformers/table.js +1 -2
- package/dist/components/text-field/emoji-autocomplete.svelte +1 -1
- package/dist/components/text-field/masked-input.svelte +179 -0
- package/dist/components/text-field/masked-input.svelte.d.ts +42 -0
- package/dist/components/text-field/number-input.js +7 -1
- package/dist/components/text-field/number-input.svelte +13 -5
- package/dist/components/text-field/number-input.svelte.d.ts +3 -3
- package/dist/components/text-field/password-input.svelte +3 -116
- package/dist/components/text-field/password-input.svelte.d.ts +3 -3
- package/dist/components/text-field/search-bar.svelte +1 -1
- package/dist/components/text-field/search-bar.svelte.d.ts +3 -3
- package/dist/components/text-field/secret-input.svelte +3 -119
- package/dist/components/text-field/secret-input.svelte.d.ts +3 -3
- package/dist/components/text-field/text-area.svelte +1 -1
- package/dist/components/text-field/text-area.svelte.d.ts +3 -3
- package/dist/components/text-field/text-input.svelte +1 -1
- package/dist/components/text-field/text-input.svelte.d.ts +3 -3
- package/dist/components/toast/toast.svelte +11 -20
- package/dist/components/toast/toast.svelte.d.ts +1 -1
- package/dist/components/tree/tree-item.svelte +1 -1
- package/dist/components/tree/tree-item.svelte.d.ts +1 -1
- package/dist/components/tree/tree.svelte +1 -1
- package/dist/components/tree/tree.svelte.d.ts +1 -1
- package/dist/components/util/font-links.svelte +1 -0
- package/dist/components/util/modal.svelte +37 -1
- package/dist/components/util/modal.svelte.d.ts +1 -1
- package/dist/components/util/popup.svelte +2 -5
- package/dist/components/util/popup.svelte.d.ts +2 -2
- package/dist/components/util/visibility-observer.svelte +1 -0
- package/dist/locales/ar.yaml +1 -1
- package/dist/locales/sk.yaml +136 -0
- package/dist/services/group.svelte.d.ts +10 -1
- package/dist/services/group.svelte.js +361 -251
- package/dist/services/navigation.d.ts +19 -0
- package/dist/services/navigation.js +84 -0
- package/dist/services/popup-position.d.ts +90 -0
- package/dist/services/popup-position.js +275 -0
- package/dist/services/popup.svelte.d.ts +16 -17
- package/dist/services/popup.svelte.js +25 -169
- package/dist/services/scroll.d.ts +1 -0
- package/dist/services/scroll.js +17 -0
- package/dist/services/select.svelte.d.ts +2 -2
- package/dist/services/select.svelte.js +1 -1
- package/dist/services/text.d.ts +2 -0
- package/dist/services/text.js +28 -0
- package/dist/services/tree.svelte.d.ts +3 -11
- package/dist/services/tree.svelte.js +35 -77
- package/dist/services/type-ahead.d.ts +0 -4
- package/dist/services/type-ahead.js +3 -1
- package/dist/services/widget.d.ts +8 -0
- package/dist/services/widget.js +39 -0
- package/dist/shiki-engine.js +15 -15
- package/dist/styles/mixins.scss +126 -0
- package/dist/typedefs.d.ts +5 -0
- package/dist/typedefs.js +2 -0
- package/package.json +24 -20
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import EmojiSuggestions from '../emoji/emoji-suggestions.svelte';
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* @import { EmojiAnchorRect, EmojiEntry, EmojiTrigger, TextEditorStore } from '../../typedefs';
|
|
27
|
+
* @import { EmojiAnchorRect, EmojiEntry, EmojiTrigger, TextEditorStore } from '../../typedefs.js';
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @import { Snippet } from 'svelte';
|
|
9
|
-
* @import { TextEditorStore } from '../../typedefs';
|
|
9
|
+
* @import { TextEditorStore } from '../../typedefs.js';
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -88,7 +88,9 @@
|
|
|
88
88
|
// The content has been converted, if it has changed at all
|
|
89
89
|
editorStore.pending = false;
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// Ignore the content while a value is being imported, like the empty code block the editor
|
|
92
|
+
// starts with: it would overwrite the value, which then replaces the content once imported
|
|
93
|
+
if (hasConverterError || !useRichText || editorStore.importing) {
|
|
92
94
|
return;
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -1,33 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @import {
|
|
3
|
-
* TextEditorBlockType,
|
|
4
|
-
* TextEditorComponent,
|
|
5
|
-
* TextEditorFormatType,
|
|
6
|
-
* } from '../../typedefs';
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Current value and selection of the `<textarea>`.
|
|
10
|
-
* @typedef {object} RawTextState
|
|
11
|
-
* @property {string} value Value.
|
|
12
|
-
* @property {number} start Selection start.
|
|
13
|
-
* @property {number} end Selection end.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* Edit to apply to the `<textarea>`: replace the text between `start` and `end` with `text`, then
|
|
17
|
-
* select the text between `selectionStart` and `selectionEnd`, which are offsets in the new value.
|
|
18
|
-
* @typedef {object} RawTextEdit
|
|
19
|
-
* @property {number} start Start of the range to replace.
|
|
20
|
-
* @property {number} end End of the range to replace.
|
|
21
|
-
* @property {string} text Replacement text.
|
|
22
|
-
* @property {number} selectionStart New selection start.
|
|
23
|
-
* @property {number} selectionEnd New selection end.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Markdown markers for the inline formats.
|
|
27
|
-
* @type {Record<TextEditorFormatType, string>}
|
|
28
|
-
*/
|
|
29
|
-
export const INLINE_MARKERS: Record<TextEditorFormatType, string>;
|
|
30
|
-
export function getBlockType({ value, start }: RawTextState): TextEditorBlockType;
|
|
1
|
+
export function getBlockType(state: RawTextState): TextEditorBlockType;
|
|
31
2
|
export function toggleInlineFormat({ value, start, end }: RawTextState, type: TextEditorFormatType): RawTextEdit;
|
|
32
3
|
export function setBlockType(state: RawTextState, type: TextEditorBlockType): RawTextEdit;
|
|
33
4
|
export function insertLink({ value, start, end }: RawTextState, { url, text }: {
|
|
@@ -41,6 +12,8 @@ export function getRawTextState({ value, selectionStart, selectionEnd }: HTMLTex
|
|
|
41
12
|
export function focusTextArea(textArea: HTMLTextAreaElement): Promise<boolean>;
|
|
42
13
|
export function isRawTextEditable({ readOnly, disabled }: HTMLTextAreaElement): boolean;
|
|
43
14
|
export function applyRawTextEdit(textArea: HTMLTextAreaElement, { start, end, text, selectionStart, selectionEnd }: RawTextEdit): Promise<void>;
|
|
15
|
+
export function editRawText(textArea: HTMLTextAreaElement | undefined, getEdit: (state: RawTextState) => RawTextEdit): Promise<void>;
|
|
16
|
+
export function registerRawTextShortcut(textArea: HTMLTextAreaElement, key: string, handler: () => void): () => void;
|
|
44
17
|
export function insertComponent(textArea: HTMLTextAreaElement, component: TextEditorComponent): Promise<void>;
|
|
45
18
|
/**
|
|
46
19
|
* Current value and selection of the `<textarea>`.
|
|
@@ -85,6 +58,6 @@ export type RawTextEdit = {
|
|
|
85
58
|
*/
|
|
86
59
|
selectionEnd: number;
|
|
87
60
|
};
|
|
88
|
-
import type {
|
|
89
|
-
import type {
|
|
90
|
-
import type { TextEditorComponent } from '../../typedefs';
|
|
61
|
+
import type { TextEditorBlockType } from '../../typedefs.js';
|
|
62
|
+
import type { TextEditorFormatType } from '../../typedefs.js';
|
|
63
|
+
import type { TextEditorComponent } from '../../typedefs.js';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Markdown editing helpers for the plain text mode of the text editor, where the toolbar buttons
|
|
3
3
|
* edit the Markdown source in the `<textarea>` instead of Lexical nodes. Apart from
|
|
4
|
-
* {@link getRawTextState}, {@link applyRawTextEdit}
|
|
5
|
-
*
|
|
4
|
+
* {@link getRawTextState}, {@link applyRawTextEdit}, {@link editRawText},
|
|
5
|
+
* {@link registerRawTextShortcut} and {@link insertComponent}, everything here is pure: a function
|
|
6
|
+
* takes the current value and selection, and returns the edit to apply.
|
|
6
7
|
*/
|
|
7
8
|
|
|
9
|
+
import { isMac, matchesShortcuts } from '@sveltia/utils/events';
|
|
8
10
|
import { getComponentMarkdown } from './core.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -12,7 +14,7 @@ import { getComponentMarkdown } from './core.js';
|
|
|
12
14
|
* TextEditorBlockType,
|
|
13
15
|
* TextEditorComponent,
|
|
14
16
|
* TextEditorFormatType,
|
|
15
|
-
* } from '../../typedefs';
|
|
17
|
+
* } from '../../typedefs.js';
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -38,7 +40,7 @@ import { getComponentMarkdown } from './core.js';
|
|
|
38
40
|
* Markdown markers for the inline formats.
|
|
39
41
|
* @type {Record<TextEditorFormatType, string>}
|
|
40
42
|
*/
|
|
41
|
-
|
|
43
|
+
const INLINE_MARKERS = {
|
|
42
44
|
bold: '**',
|
|
43
45
|
italic: '_',
|
|
44
46
|
strikethrough: '~~',
|
|
@@ -66,6 +68,13 @@ const getLineRange = ({ value, start, end }) => {
|
|
|
66
68
|
return { lineStart, lineEnd: lineEndIndex === -1 ? value.length : lineEndIndex };
|
|
67
69
|
};
|
|
68
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Get the index of the line where the selection starts.
|
|
73
|
+
* @param {RawTextState} state Current state.
|
|
74
|
+
* @returns {number} Line index.
|
|
75
|
+
*/
|
|
76
|
+
const getStartLineIndex = ({ value, start }) => value.slice(0, start).split('\n').length - 1;
|
|
77
|
+
|
|
69
78
|
/**
|
|
70
79
|
* Find the fenced code block the given line is in.
|
|
71
80
|
* @param {string[]} lines All the lines.
|
|
@@ -104,9 +113,9 @@ const findCodeBlock = (lines, lineIndex) => {
|
|
|
104
113
|
* @param {RawTextState} state Current state.
|
|
105
114
|
* @returns {TextEditorBlockType} Block type.
|
|
106
115
|
*/
|
|
107
|
-
export const getBlockType = (
|
|
108
|
-
const lines = value.split('\n');
|
|
109
|
-
const lineIndex =
|
|
116
|
+
export const getBlockType = (state) => {
|
|
117
|
+
const lines = state.value.split('\n');
|
|
118
|
+
const lineIndex = getStartLineIndex(state);
|
|
110
119
|
const line = lines[lineIndex];
|
|
111
120
|
|
|
112
121
|
if (findCodeBlock(lines, lineIndex)) {
|
|
@@ -205,7 +214,7 @@ export const toggleInlineFormat = ({ value, start, end }, type) => {
|
|
|
205
214
|
export const setBlockType = (state, type) => {
|
|
206
215
|
const { value, start, end } = state;
|
|
207
216
|
const lines = value.split('\n');
|
|
208
|
-
const firstIndex =
|
|
217
|
+
const firstIndex = getStartLineIndex(state);
|
|
209
218
|
const codeBlock = findCodeBlock(lines, firstIndex);
|
|
210
219
|
|
|
211
220
|
// Leave a code block by removing its fences, then change the type of the selected lines
|
|
@@ -476,6 +485,52 @@ export const applyRawTextEdit = async (
|
|
|
476
485
|
textArea.setSelectionRange(selectionStart, selectionEnd);
|
|
477
486
|
};
|
|
478
487
|
|
|
488
|
+
/**
|
|
489
|
+
* Apply the edit made by the given function from the current state of the `<textarea>`, and focus
|
|
490
|
+
* it. Nothing happens if there’s no `<textarea>`.
|
|
491
|
+
* @param {HTMLTextAreaElement | undefined} textArea `<textarea>` element.
|
|
492
|
+
* @param {(state: RawTextState) => RawTextEdit} getEdit Function to make the edit from the current
|
|
493
|
+
* state.
|
|
494
|
+
* @returns {Promise<void>} Nothing.
|
|
495
|
+
*/
|
|
496
|
+
export const editRawText = async (textArea, getEdit) => {
|
|
497
|
+
if (!textArea) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
await applyRawTextEdit(textArea, getEdit(getRawTextState(textArea)));
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Handle the given keyboard shortcut, Accel+{key}, on the `<textarea>` while it’s editable. The
|
|
506
|
+
* rich text editor handles its shortcuts on its own.
|
|
507
|
+
* @param {HTMLTextAreaElement} textArea `<textarea>` element.
|
|
508
|
+
* @param {string} key Key to combine with the Accel key, like `B`.
|
|
509
|
+
* @param {() => void} handler Function to call when the shortcut is pressed.
|
|
510
|
+
* @returns {() => void} Function to stop handling the shortcut.
|
|
511
|
+
*/
|
|
512
|
+
export const registerRawTextShortcut = (textArea, key, handler) => {
|
|
513
|
+
/**
|
|
514
|
+
* Handle the `keydown` event.
|
|
515
|
+
* @param {KeyboardEvent} event `keydown` event.
|
|
516
|
+
*/
|
|
517
|
+
const onKeyDown = (event) => {
|
|
518
|
+
if (
|
|
519
|
+
isRawTextEditable(textArea) &&
|
|
520
|
+
matchesShortcuts(event, isMac() ? `Meta+${key}` : `Ctrl+${key}`)
|
|
521
|
+
) {
|
|
522
|
+
event.preventDefault();
|
|
523
|
+
handler();
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
textArea.addEventListener('keydown', onKeyDown);
|
|
528
|
+
|
|
529
|
+
return () => {
|
|
530
|
+
textArea.removeEventListener('keydown', onKeyDown);
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
|
|
479
534
|
/**
|
|
480
535
|
* Insert the Markdown of a new instance of the given editor component into the `<textarea>`. A
|
|
481
536
|
* component matched by a single-line pattern is inserted inline at the caret; anything else is
|
|
@@ -18,4 +18,4 @@ import type { LexicalEditor } from 'lexical';
|
|
|
18
18
|
import type { NodeKey } from 'lexical';
|
|
19
19
|
import type { CodeNode } from '@lexical/code-core';
|
|
20
20
|
import type { LexicalNode } from 'lexical';
|
|
21
|
-
import type { HighlightedToken } from '../../../typedefs';
|
|
21
|
+
import type { HighlightedToken } from '../../../typedefs.js';
|
|
@@ -29,7 +29,7 @@ import { getCodeTheme } from './theme.js';
|
|
|
29
29
|
/**
|
|
30
30
|
* @import { CodeNode } from '@lexical/code-core';
|
|
31
31
|
* @import { LexicalEditor, LexicalNode, NodeKey } from 'lexical';
|
|
32
|
-
* @import { HighlightedToken } from '../../../typedefs';
|
|
32
|
+
* @import { HighlightedToken } from '../../../typedefs.js';
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -242,15 +242,48 @@ const resolvePayload = async (kind, id, load) => {
|
|
|
242
242
|
return payload;
|
|
243
243
|
};
|
|
244
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Load a grammar or theme into the highlighter once, then refresh the given code node. The
|
|
247
|
+
* configured loader and the highlighter share the method name for each kind.
|
|
248
|
+
* @param {'lang' | 'theme'} kind Payload kind.
|
|
249
|
+
* @param {string} id Language or theme ID.
|
|
250
|
+
* @param {LexicalEditor} [editor] Editor instance to refresh once the payload is loaded.
|
|
251
|
+
* @param {NodeKey} [codeNodeKey] Key of the code node to refresh.
|
|
252
|
+
* @returns {Promise<void>} Promise resolving when the payload is loaded.
|
|
253
|
+
*/
|
|
254
|
+
const loadAsset = (kind, id, editor, codeNodeKey) => {
|
|
255
|
+
const method = kind === 'lang' ? 'loadLanguage' : 'loadTheme';
|
|
256
|
+
|
|
257
|
+
return loadOnce(
|
|
258
|
+
`${kind}/${id}`,
|
|
259
|
+
async () => {
|
|
260
|
+
const payload = await resolvePayload(kind, id, () => getCodeHighlighterLoaders()[method](id));
|
|
261
|
+
|
|
262
|
+
if (payload) {
|
|
263
|
+
await highlighter[method](payload);
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
editor,
|
|
267
|
+
codeNodeKey,
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Find the metadata of the given language.
|
|
273
|
+
* @param {string} language Language identifier or alias, like `ts`.
|
|
274
|
+
* @returns {(typeof LANGUAGES)[number] | undefined} Language metadata, or `undefined` when the
|
|
275
|
+
* language is unknown.
|
|
276
|
+
*/
|
|
277
|
+
const findLanguage = (language) =>
|
|
278
|
+
LANGUAGES.find(({ id, aliases }) => id === language || aliases?.includes(language));
|
|
279
|
+
|
|
245
280
|
/**
|
|
246
281
|
* Resolve a language alias to its canonical identifier.
|
|
247
282
|
* @param {string} language Language identifier or alias, like `ts`.
|
|
248
283
|
* @returns {string} Canonical identifier, like `typescript`. Returns the input unchanged when the
|
|
249
284
|
* language is unknown.
|
|
250
285
|
*/
|
|
251
|
-
export const normalizeCodeLanguage = (language) =>
|
|
252
|
-
LANGUAGES.find(({ id, aliases }) => id === language || aliases?.includes(language))?.id ??
|
|
253
|
-
language;
|
|
286
|
+
export const normalizeCodeLanguage = (language) => findLanguage(language)?.id ?? language;
|
|
254
287
|
|
|
255
288
|
/**
|
|
256
289
|
* Load the grammar for the given language, then refresh the given code node.
|
|
@@ -267,26 +300,13 @@ export const loadCodeLanguage = (language, editor, codeNodeKey) => {
|
|
|
267
300
|
return undefined;
|
|
268
301
|
}
|
|
269
302
|
|
|
270
|
-
const info =
|
|
303
|
+
const info = findLanguage(id);
|
|
271
304
|
|
|
272
305
|
if (!info) {
|
|
273
306
|
return undefined;
|
|
274
307
|
}
|
|
275
308
|
|
|
276
|
-
return
|
|
277
|
-
`lang/${info.id}`,
|
|
278
|
-
async () => {
|
|
279
|
-
const payload = await resolvePayload('lang', info.id, () =>
|
|
280
|
-
getCodeHighlighterLoaders().loadLanguage(info.id),
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
if (payload) {
|
|
284
|
-
await highlighter.loadLanguage(payload);
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
editor,
|
|
288
|
-
codeNodeKey,
|
|
289
|
-
);
|
|
309
|
+
return loadAsset('lang', info.id, editor, codeNodeKey);
|
|
290
310
|
};
|
|
291
311
|
|
|
292
312
|
/**
|
|
@@ -319,20 +339,7 @@ export const loadCodeTheme = (theme, editor, codeNodeKey) => {
|
|
|
319
339
|
return undefined;
|
|
320
340
|
}
|
|
321
341
|
|
|
322
|
-
return
|
|
323
|
-
`theme/${theme}`,
|
|
324
|
-
async () => {
|
|
325
|
-
const payload = await resolvePayload('theme', theme, () =>
|
|
326
|
-
getCodeHighlighterLoaders().loadTheme(theme),
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
if (payload) {
|
|
330
|
-
await highlighter.loadTheme(payload);
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
editor,
|
|
334
|
-
codeNodeKey,
|
|
335
|
-
);
|
|
342
|
+
return loadAsset('theme', theme, editor, codeNodeKey);
|
|
336
343
|
};
|
|
337
344
|
|
|
338
345
|
/**
|
|
@@ -419,6 +426,24 @@ export const getHighlightNodes = (codeNode, language) => {
|
|
|
419
426
|
return mapTokensToLexicalStructure(tokens, !!diffedLanguage);
|
|
420
427
|
};
|
|
421
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Get the theme to highlight a snippet of code with, if it can be highlighted synchronously: the
|
|
431
|
+
* engine, the grammar of the given language and the theme are all loaded.
|
|
432
|
+
* @param {string} id Canonical language identifier.
|
|
433
|
+
* @param {string} [theme] Shiki theme ID. Defaults to the one matching the app’s appearance.
|
|
434
|
+
* @returns {string | undefined} Theme ID, or `undefined` when the code cannot be highlighted yet.
|
|
435
|
+
*/
|
|
436
|
+
const getHighlightTheme = (id, theme) => {
|
|
437
|
+
if (!highlighter || isPlainLanguage(id) || !isCodeLanguageLoaded(id)) {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Resolved only once the cheap checks pass, so bailing out early costs nothing
|
|
442
|
+
const resolvedTheme = theme ?? getCodeTheme();
|
|
443
|
+
|
|
444
|
+
return isCodeThemeLoaded(resolvedTheme) ? resolvedTheme : undefined;
|
|
445
|
+
};
|
|
446
|
+
|
|
422
447
|
/**
|
|
423
448
|
* Highlight a snippet of code as an HTML string.
|
|
424
449
|
*
|
|
@@ -436,16 +461,11 @@ export const getHighlightNodes = (codeNode, language) => {
|
|
|
436
461
|
* yet.
|
|
437
462
|
*/
|
|
438
463
|
export const highlightCodeToHTML = (code, language, { theme } = {}) => {
|
|
464
|
+
// Unlike tokens, the HTML is highlighted with the language a `diff-*` language wraps
|
|
439
465
|
const id = normalizeCodeLanguage(getDiffedLanguage(language) ?? language);
|
|
466
|
+
const resolvedTheme = getHighlightTheme(id, theme);
|
|
440
467
|
|
|
441
|
-
if (
|
|
442
|
-
return undefined;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Resolved only once the cheap checks pass, so bailing out early costs nothing
|
|
446
|
-
const resolvedTheme = theme ?? getCodeTheme();
|
|
447
|
-
|
|
448
|
-
if (!isCodeThemeLoaded(resolvedTheme)) {
|
|
468
|
+
if (resolvedTheme === undefined) {
|
|
449
469
|
return undefined;
|
|
450
470
|
}
|
|
451
471
|
|
|
@@ -471,14 +491,9 @@ export const highlightCodeToHTML = (code, language, { theme } = {}) => {
|
|
|
471
491
|
*/
|
|
472
492
|
export const highlightCodeToTokens = (code, language, { theme } = {}) => {
|
|
473
493
|
const id = normalizeCodeLanguage(language);
|
|
494
|
+
const resolvedTheme = getHighlightTheme(id, theme);
|
|
474
495
|
|
|
475
|
-
if (
|
|
476
|
-
return undefined;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const resolvedTheme = theme ?? getCodeTheme();
|
|
480
|
-
|
|
481
|
-
if (!isCodeThemeLoaded(resolvedTheme)) {
|
|
496
|
+
if (resolvedTheme === undefined) {
|
|
482
497
|
return undefined;
|
|
483
498
|
}
|
|
484
499
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* version bundled into `dist/shiki-engine.js`, because grammar and theme payloads are only
|
|
4
4
|
* guaranteed to be compatible with the matching engine.
|
|
5
5
|
*/
|
|
6
|
-
export const SHIKI_VERSION: "4.
|
|
6
|
+
export const SHIKI_VERSION: "4.5.0";
|
|
7
7
|
/**
|
|
8
8
|
* Available syntax highlighting languages, sorted by display name.
|
|
9
9
|
* @type {{ id: string, name: string, aliases?: string[] }[]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* version bundled into `dist/shiki-engine.js`, because grammar and theme payloads are only
|
|
6
6
|
* guaranteed to be compatible with the matching engine.
|
|
7
7
|
*/
|
|
8
|
-
export const SHIKI_VERSION = "4.
|
|
8
|
+
export const SHIKI_VERSION = "4.5.0";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Available syntax highlighting languages, sorted by display name.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { CodeNode as CodeNodeType } from '@lexical/code-core';
|
|
3
|
+
* @import { LexicalEditor, LexicalNode, NodeKey } from 'lexical';
|
|
4
|
+
* @import { CodeTokenizer, CodeTransformState } from '../../../typedefs.js';
|
|
5
|
+
*/
|
|
1
6
|
/**
|
|
2
7
|
* Default tokenizer backed by Shiki.
|
|
3
8
|
*
|
|
@@ -7,5 +12,5 @@
|
|
|
7
12
|
*/
|
|
8
13
|
export const shikiTokenizer: CodeTokenizer;
|
|
9
14
|
export function registerCodeHighlighting(editor: LexicalEditor, tokenizer?: CodeTokenizer): () => void;
|
|
10
|
-
import type { CodeTokenizer } from '../../../typedefs';
|
|
15
|
+
import type { CodeTokenizer } from '../../../typedefs.js';
|
|
11
16
|
import type { LexicalEditor } from 'lexical';
|
|
@@ -44,15 +44,14 @@ import {
|
|
|
44
44
|
loadCodeTheme,
|
|
45
45
|
loadEngine,
|
|
46
46
|
} from './facade.js';
|
|
47
|
+
import { CODE_THEME_LIGHT } from './theme.js';
|
|
47
48
|
|
|
48
49
|
/**
|
|
49
50
|
* @import { CodeNode as CodeNodeType } from '@lexical/code-core';
|
|
50
51
|
* @import { LexicalEditor, LexicalNode, NodeKey } from 'lexical';
|
|
51
|
-
* @import { CodeTokenizer, CodeTransformState } from '../../../typedefs';
|
|
52
|
+
* @import { CodeTokenizer, CodeTransformState } from '../../../typedefs.js';
|
|
52
53
|
*/
|
|
53
54
|
|
|
54
|
-
const DEFAULT_CODE_THEME = 'github-light';
|
|
55
|
-
|
|
56
55
|
/**
|
|
57
56
|
* Default tokenizer backed by Shiki.
|
|
58
57
|
*
|
|
@@ -62,7 +61,7 @@ const DEFAULT_CODE_THEME = 'github-light';
|
|
|
62
61
|
*/
|
|
63
62
|
export const shikiTokenizer = {
|
|
64
63
|
defaultLanguage: DEFAULT_CODE_LANGUAGE,
|
|
65
|
-
defaultTheme:
|
|
64
|
+
defaultTheme: CODE_THEME_LIGHT,
|
|
66
65
|
/**
|
|
67
66
|
* Convert a code node’s content into Lexical nodes.
|
|
68
67
|
* @param {CodeNodeType} codeNode Code node to tokenize.
|
|
@@ -3,4 +3,4 @@ export function getLanguageURL(id: string): string;
|
|
|
3
3
|
export function getThemeURL(id: string): string;
|
|
4
4
|
export function setCodeHighlighterLoaders(overrides: Partial<CodeHighlighterLoaders>): void;
|
|
5
5
|
export function getCodeHighlighterLoaders(): CodeHighlighterLoaders;
|
|
6
|
-
import type { CodeHighlighterLoaders } from '../../../typedefs';
|
|
6
|
+
import type { CodeHighlighterLoaders } from '../../../typedefs.js';
|
|
@@ -2,7 +2,7 @@ import { SHIKI_VERSION } from './generated.js';
|
|
|
2
2
|
import { UI_VERSION } from './version.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @import { CodeHighlighterLoaders } from '../../../typedefs';
|
|
5
|
+
* @import { CodeHighlighterLoaders } from '../../../typedefs.js';
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const SHIKI_CDN_BASE_URL = 'https://unpkg.com/@shikijs';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export function createEditorStore(): TextEditorStore;
|
|
2
|
-
import type { TextEditorStore } from '../../typedefs';
|
|
2
|
+
import type { TextEditorStore } from '../../typedefs.js';
|
|
@@ -4,7 +4,7 @@ import { convertMarkdownToLexical } from './core.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* @import { LexicalEditor } from 'lexical';
|
|
6
6
|
* @import { Transformer } from '@lexical/markdown';
|
|
7
|
-
* @import { TextEditorConfig, TextEditorSelectionState, TextEditorStore } from '../../typedefs';
|
|
7
|
+
* @import { TextEditorConfig, TextEditorSelectionState, TextEditorStore } from '../../typedefs.js';
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -50,6 +50,16 @@ export const createEditorStore = () => {
|
|
|
50
50
|
* @type {{ source: string, exported: string } | undefined}
|
|
51
51
|
*/
|
|
52
52
|
let lastImport = undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Number of imports started, to tell the latest one.
|
|
55
|
+
* @type {number}
|
|
56
|
+
*/
|
|
57
|
+
let importCount = 0;
|
|
58
|
+
/**
|
|
59
|
+
* Whether the latest import is still in progress.
|
|
60
|
+
* @type {boolean}
|
|
61
|
+
*/
|
|
62
|
+
let importing = false;
|
|
53
63
|
|
|
54
64
|
/**
|
|
55
65
|
* Flag a conversion error, which takes the editor out of the rich text mode, and shows the error
|
|
@@ -76,6 +86,12 @@ export const createEditorStore = () => {
|
|
|
76
86
|
|
|
77
87
|
const originalValue = inputValue;
|
|
78
88
|
|
|
89
|
+
importCount += 1;
|
|
90
|
+
|
|
91
|
+
const importId = importCount;
|
|
92
|
+
|
|
93
|
+
importing = true;
|
|
94
|
+
|
|
79
95
|
try {
|
|
80
96
|
// We should avoid an empty editor; there should be at least one `<p>`, so give it an empty
|
|
81
97
|
// string if the `value` is `undefined`
|
|
@@ -86,13 +102,21 @@ export const createEditorStore = () => {
|
|
|
86
102
|
enabledTransformers,
|
|
87
103
|
);
|
|
88
104
|
|
|
89
|
-
|
|
105
|
+
// Skip an import superseded by a newer one, which sets its own value
|
|
106
|
+
if (exported !== undefined) {
|
|
107
|
+
lastImport = { source: originalValue, exported };
|
|
108
|
+
}
|
|
90
109
|
} catch (ex) {
|
|
91
110
|
// Go through the setter, so the editor actually falls back to the plain text mode
|
|
92
111
|
setConverterError(true);
|
|
93
112
|
inputValue = originalValue;
|
|
94
113
|
// eslint-disable-next-line no-console
|
|
95
114
|
console.error(ex);
|
|
115
|
+
} finally {
|
|
116
|
+
// An earlier import finishing last doesn’t end the latest one
|
|
117
|
+
if (importId === importCount) {
|
|
118
|
+
importing = false;
|
|
119
|
+
}
|
|
96
120
|
}
|
|
97
121
|
};
|
|
98
122
|
|
|
@@ -176,6 +200,9 @@ export const createEditorStore = () => {
|
|
|
176
200
|
set pending(newValue) {
|
|
177
201
|
pending = newValue;
|
|
178
202
|
},
|
|
203
|
+
get importing() {
|
|
204
|
+
return importing;
|
|
205
|
+
},
|
|
179
206
|
editorId,
|
|
180
207
|
convertMarkdown,
|
|
181
208
|
/**
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @import { Snippet } from 'svelte';
|
|
23
|
-
* @import { TextEditorComponent, TextEditorMode, TextEditorNodeType } from '../../typedefs';
|
|
24
|
-
* @import { HighlightedToken } from '../../typedefs';
|
|
23
|
+
* @import { TextEditorComponent, TextEditorMode, TextEditorNodeType } from '../../typedefs.js';
|
|
24
|
+
* @import { HighlightedToken } from '../../typedefs.js';
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -186,7 +186,7 @@ type Props = {
|
|
|
186
186
|
*/
|
|
187
187
|
children?: Snippet<[]> | undefined;
|
|
188
188
|
};
|
|
189
|
-
import type { TextEditorMode } from '../../typedefs';
|
|
190
|
-
import type { TextEditorNodeType } from '../../typedefs';
|
|
191
|
-
import type { TextEditorComponent } from '../../typedefs';
|
|
189
|
+
import type { TextEditorMode } from '../../typedefs.js';
|
|
190
|
+
import type { TextEditorNodeType } from '../../typedefs.js';
|
|
191
|
+
import type { TextEditorComponent } from '../../typedefs.js';
|
|
192
192
|
import type { Snippet } from 'svelte';
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { LANGUAGES } from '../shiki/generated.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* @import {
|
|
12
|
+
* @import { CodeNode } from '@lexical/code-core';
|
|
13
|
+
* @import { TextEditorStore } from '../../../typedefs.js';
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -46,15 +47,31 @@
|
|
|
46
47
|
)?.key ?? 'plain',
|
|
47
48
|
);
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Get the code block the switcher targets. A code editor has exactly one code block, and its
|
|
52
|
+
* `blockNodeKey` is still unset the first time the switcher is used, before the editor has ever
|
|
53
|
+
* been focused. Call this within `editor.read()` or `editor.update()`.
|
|
54
|
+
* @returns {CodeNode | null} Code node, or `null` if the selection is not in a code block.
|
|
55
|
+
*/
|
|
56
|
+
const getCodeNode = () => {
|
|
57
|
+
const { blockNodeKey } = editorStore.selection;
|
|
58
|
+
|
|
59
|
+
const node = editorStore.config.isCodeEditor
|
|
60
|
+
? getRoot().getChildren()[0]
|
|
61
|
+
: blockNodeKey
|
|
62
|
+
? getNodeByKey(blockNodeKey)
|
|
63
|
+
: null;
|
|
64
|
+
|
|
65
|
+
return isCodeNode(node) ? node : null;
|
|
66
|
+
};
|
|
67
|
+
|
|
49
68
|
$effect(() => {
|
|
50
69
|
void editorStore.selection.blockNodeKey;
|
|
51
70
|
|
|
52
71
|
editorStore.editor?.read(() => {
|
|
53
|
-
const node =
|
|
54
|
-
? getRoot().getChildren()[0]
|
|
55
|
-
: getNodeByKey(/** @type {string} */ (editorStore.selection.blockNodeKey));
|
|
72
|
+
const node = getCodeNode();
|
|
56
73
|
|
|
57
|
-
if (
|
|
74
|
+
if (node) {
|
|
58
75
|
// The highlighter gives a block without a language the default one as it’s transformed,
|
|
59
76
|
// so the fallbacks here are only for the unexpected
|
|
60
77
|
/* v8 ignore next */
|
|
@@ -77,19 +94,10 @@
|
|
|
77
94
|
await loadCodeHighlighter(lang);
|
|
78
95
|
|
|
79
96
|
editorStore.editor.update(() => {
|
|
80
|
-
// Resolve the target the same way the effect above does. A code editor has exactly one code
|
|
81
|
-
// block, and its `blockNodeKey` is still unset the first time the switcher is used, before
|
|
82
|
-
// the editor has ever been focused.
|
|
83
97
|
// https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/ToolbarPlugin/index.tsx#L713
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const node = editorStore.config.isCodeEditor
|
|
87
|
-
? getRoot().getChildren()[0]
|
|
88
|
-
: blockNodeKey
|
|
89
|
-
? getNodeByKey(blockNodeKey)
|
|
90
|
-
: null;
|
|
98
|
+
const node = getCodeNode();
|
|
91
99
|
|
|
92
|
-
if (
|
|
100
|
+
if (node) {
|
|
93
101
|
node.setLanguage(lang);
|
|
94
102
|
selectedLanguage = lang;
|
|
95
103
|
}
|