@threadlabs/looma 0.18.1 → 0.18.3
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/package.json +1 -1
- package/vue/components.css +11 -10
- package/vue/editor/LoomaEditor.d.ts +34 -3
- package/vue/editor/index.js +127 -73
- package/vue/editor/code-block-view.d.ts +0 -29
package/package.json
CHANGED
package/vue/components.css
CHANGED
|
@@ -8662,17 +8662,18 @@ html[data-ui-input-modality="touch"]
|
|
|
8662
8662
|
font-size: var(--ui-font-size-sm);
|
|
8663
8663
|
}
|
|
8664
8664
|
|
|
8665
|
-
|
|
8666
|
-
position: relative;
|
|
8667
|
-
padding-block-start: calc(var(--ui-space-4) + 2rem);
|
|
8668
|
-
}
|
|
8669
|
-
|
|
8665
|
+
/* Same surface as the floating formatting and table toolbars. */
|
|
8670
8666
|
.looma-editor__code-language {
|
|
8671
|
-
position:
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
inline-size:
|
|
8675
|
-
|
|
8667
|
+
position: fixed;
|
|
8668
|
+
z-index: var(--ui-editor-popover-z-index, 300);
|
|
8669
|
+
box-sizing: content-box;
|
|
8670
|
+
inline-size: 8.5rem;
|
|
8671
|
+
padding: var(--ui-editor-toolbar-floating-padding, 4px);
|
|
8672
|
+
background: color-mix(in srgb, var(--ui-editor-toolbar-surface, var(--ui-surface)) 96%, transparent);
|
|
8673
|
+
border: 1px solid var(--ui-editor-toolbar-border, var(--ui-border-strong));
|
|
8674
|
+
border-radius: var(--ui-editor-toolbar-floating-radius, var(--ui-radius-lg));
|
|
8675
|
+
box-shadow: var(--ui-editor-toolbar-floating-shadow, var(--ui-shadow-lg, 0 10px 24px rgb(0 0 0 / 0.12)));
|
|
8676
|
+
backdrop-filter: blur(10px);
|
|
8676
8677
|
font-family: var(--ui-font-family-sans, sans-serif);
|
|
8677
8678
|
}
|
|
8678
8679
|
|
|
@@ -21,10 +21,11 @@ export interface LoomaImageUploadResult extends Omit<LoomaImageDescriptor, "src"
|
|
|
21
21
|
*/
|
|
22
22
|
export type LoomaImageUploader = (file: File) => Promise<string | LoomaImageUploadResult>;
|
|
23
23
|
/**
|
|
24
|
-
* Chooses whether formatting controls follow a selection
|
|
25
|
-
* editor chrome
|
|
24
|
+
* Chooses whether formatting controls follow a selection, occupy persistent
|
|
25
|
+
* editor chrome, or open from an app-owned button (`popover`, with a text-only
|
|
26
|
+
* bubble for selections); it does not alter document commands or stored content.
|
|
26
27
|
*/
|
|
27
|
-
export type LoomaEditorToolbarMode = "bubble" | "sticky";
|
|
28
|
+
export type LoomaEditorToolbarMode = "bubble" | "sticky" | "popover";
|
|
28
29
|
/**
|
|
29
30
|
* Turnkey Vue editor that composes Looma's framework-neutral Tiptap extensions
|
|
30
31
|
* with declarative menus, image delivery, and table interaction chrome.
|
|
@@ -99,6 +100,19 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
99
100
|
type: PropType<LoomaEditorToolbarMode>;
|
|
100
101
|
default: string;
|
|
101
102
|
};
|
|
103
|
+
/**
|
|
104
|
+
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
105
|
+
* The button needs no click handler of its own.
|
|
106
|
+
*/
|
|
107
|
+
toolbarTriggerId: {
|
|
108
|
+
type: StringConstructor;
|
|
109
|
+
default: string;
|
|
110
|
+
};
|
|
111
|
+
/** In `popover` mode, whether the full toolbar is open. Use with `v-model:toolbar-open`. */
|
|
112
|
+
toolbarOpen: {
|
|
113
|
+
type: BooleanConstructor;
|
|
114
|
+
default: boolean;
|
|
115
|
+
};
|
|
102
116
|
/**
|
|
103
117
|
* Authors can't highlight: no toolbar button, shortcut, `==text==` rule, or `<mark>` paste.
|
|
104
118
|
* Highlights already in the document still show. Read once, when the editor is created.
|
|
@@ -111,6 +125,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
111
125
|
[key: string]: any;
|
|
112
126
|
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
113
127
|
"update:modelValue": (_value: JSONContent) => true;
|
|
128
|
+
"update:toolbarOpen": (_value: boolean) => true;
|
|
114
129
|
update: (_value: JSONContent) => true;
|
|
115
130
|
ready: (_editor: Editor) => true;
|
|
116
131
|
uploadError: (_error: unknown, _file: File) => true;
|
|
@@ -178,6 +193,19 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
178
193
|
type: PropType<LoomaEditorToolbarMode>;
|
|
179
194
|
default: string;
|
|
180
195
|
};
|
|
196
|
+
/**
|
|
197
|
+
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
198
|
+
* The button needs no click handler of its own.
|
|
199
|
+
*/
|
|
200
|
+
toolbarTriggerId: {
|
|
201
|
+
type: StringConstructor;
|
|
202
|
+
default: string;
|
|
203
|
+
};
|
|
204
|
+
/** In `popover` mode, whether the full toolbar is open. Use with `v-model:toolbar-open`. */
|
|
205
|
+
toolbarOpen: {
|
|
206
|
+
type: BooleanConstructor;
|
|
207
|
+
default: boolean;
|
|
208
|
+
};
|
|
181
209
|
/**
|
|
182
210
|
* Authors can't highlight: no toolbar button, shortcut, `==text==` rule, or `<mark>` paste.
|
|
183
211
|
* Highlights already in the document still show. Read once, when the editor is created.
|
|
@@ -188,6 +216,7 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
188
216
|
};
|
|
189
217
|
}>> & Readonly<{
|
|
190
218
|
"onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
|
|
219
|
+
"onUpdate:toolbarOpen"?: ((_value: boolean) => any) | undefined;
|
|
191
220
|
onUpdate?: ((_value: JSONContent) => any) | undefined;
|
|
192
221
|
onReady?: ((_editor: Editor) => any) | undefined;
|
|
193
222
|
onUploadError?: ((_error: unknown, _file: File) => any) | undefined;
|
|
@@ -207,4 +236,6 @@ export declare const LoomaEditor: import("vue").DefineComponent<import("vue").Ex
|
|
|
207
236
|
uploadImage: LoomaImageUploader | undefined;
|
|
208
237
|
resolveImageAttributes: LoomaImageAttributeResolver | undefined;
|
|
209
238
|
toolbarMode: LoomaEditorToolbarMode;
|
|
239
|
+
toolbarTriggerId: string;
|
|
240
|
+
toolbarOpen: boolean;
|
|
210
241
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/vue/editor/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { computed, defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, useId, watch } from "vue";
|
|
2
2
|
import { DEFAULT_MENTION_RESULT_LIMIT, LOOMA_CHIP_COLORS, LOOMA_ICONS, createLoomaMentionExtension, createLoomaSlashCommandExtension, getActiveTableUiState, getDefaultEditorExtensions, getVisualViewportRect, handleTableAction, handleTableOverlayAction, measureTableOverlayGeometry, normalizeActiveTableColumnWidths, normalizeLoomaChipColor, resolveTableCellAt, shouldShowTextFormattingToolbar } from "@threadlabs/looma/editor";
|
|
3
3
|
import { Combobox, EditorInsertTableGrid, EditorMentionMenu, EditorSlashMenu, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Menu, MenuItem, Popover, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
-
import { BubbleMenu, EditorContent,
|
|
4
|
+
import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
|
|
5
|
+
import { closeHistory } from "@tiptap/pm/history";
|
|
6
|
+
import { createLowlight } from "lowlight";
|
|
5
7
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
6
8
|
import { Extension } from "@tiptap/core";
|
|
7
9
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
8
|
-
import { closeHistory } from "@tiptap/pm/history";
|
|
9
|
-
import { createLowlight } from "lowlight";
|
|
10
10
|
export * from "@threadlabs/looma/editor";
|
|
11
11
|
//#region src/vue/editor/primitives.ts
|
|
12
12
|
/** Each Vue editor component's element tag, for tooling that enumerates them. */
|
|
@@ -180,69 +180,6 @@ function imageUploadContent(image) {
|
|
|
180
180
|
};
|
|
181
181
|
}
|
|
182
182
|
//#endregion
|
|
183
|
-
//#region src/vue/editor/code-block-view.ts
|
|
184
|
-
/** Per-block language control; the document stores only the code and explicit language. */
|
|
185
|
-
function createCodeBlockView(languages, isEditable) {
|
|
186
|
-
const lowlight = createLowlight(languages);
|
|
187
|
-
const names = Object.keys(languages).sort();
|
|
188
|
-
return defineComponent({
|
|
189
|
-
name: "LoomaCodeBlockView",
|
|
190
|
-
props: nodeViewProps,
|
|
191
|
-
setup(props) {
|
|
192
|
-
const selected = computed(() => {
|
|
193
|
-
const language = props.node.attrs.language;
|
|
194
|
-
return typeof language === "string" && language ? language : "auto";
|
|
195
|
-
});
|
|
196
|
-
const detected = computed(() => {
|
|
197
|
-
if (!props.node.textContent.trim()) return null;
|
|
198
|
-
const language = lowlight.highlightAuto(props.node.textContent).data?.language;
|
|
199
|
-
return typeof language === "string" ? language : null;
|
|
200
|
-
});
|
|
201
|
-
const options = computed(() => [
|
|
202
|
-
{
|
|
203
|
-
id: "auto",
|
|
204
|
-
value: "auto",
|
|
205
|
-
label: detected.value ? `Auto (${detected.value.toUpperCase()})` : "Auto"
|
|
206
|
-
},
|
|
207
|
-
...selected.value !== "auto" && !Object.hasOwn(languages, selected.value) ? [{
|
|
208
|
-
id: selected.value,
|
|
209
|
-
value: selected.value,
|
|
210
|
-
label: `${selected.value.toUpperCase()} (unavailable)`
|
|
211
|
-
}] : [],
|
|
212
|
-
...names.map((name) => ({
|
|
213
|
-
id: name,
|
|
214
|
-
value: name,
|
|
215
|
-
label: name.toUpperCase()
|
|
216
|
-
}))
|
|
217
|
-
]);
|
|
218
|
-
const choose = (detail) => {
|
|
219
|
-
if (detail.kind !== "selection" || !detail.value) return;
|
|
220
|
-
const language = detail.value === "auto" ? null : detail.value;
|
|
221
|
-
if (language !== null && !Object.hasOwn(languages, language)) return;
|
|
222
|
-
props.editor.view.dispatch(closeHistory(props.editor.state.tr));
|
|
223
|
-
props.updateAttributes({ language });
|
|
224
|
-
props.editor.view.dispatch(closeHistory(props.editor.state.tr));
|
|
225
|
-
};
|
|
226
|
-
const showControl = () => names.length > 0 && isEditable() && props.editor.isEditable;
|
|
227
|
-
return () => h(NodeViewWrapper, {
|
|
228
|
-
as: "pre",
|
|
229
|
-
class: showControl() ? "looma-editor__code-block--has-language" : void 0
|
|
230
|
-
}, () => [showControl() ? h("div", {
|
|
231
|
-
class: "looma-editor__code-language",
|
|
232
|
-
contenteditable: "false"
|
|
233
|
-
}, [h(Combobox, {
|
|
234
|
-
label: "Code language",
|
|
235
|
-
labelVisibility: "sr-only",
|
|
236
|
-
size: "sm",
|
|
237
|
-
disclosure: true,
|
|
238
|
-
filter: "label",
|
|
239
|
-
value: selected.value,
|
|
240
|
-
onValueChange: choose
|
|
241
|
-
}, () => options.value.map((option) => h("option", { value: option.value }, option.label)))]) : null, h(NodeViewContent, { as: "code" })]);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
//#endregion
|
|
246
183
|
//#region src/vue/editor/LoomaEditor.ts
|
|
247
184
|
var EMPTY_DOCUMENT = {
|
|
248
185
|
type: "doc",
|
|
@@ -394,6 +331,19 @@ var LoomaEditor = defineComponent({
|
|
|
394
331
|
default: "bubble"
|
|
395
332
|
},
|
|
396
333
|
/**
|
|
334
|
+
* In `popover` mode, the ID of the app's button that toggles the full toolbar and anchors it.
|
|
335
|
+
* The button needs no click handler of its own.
|
|
336
|
+
*/
|
|
337
|
+
toolbarTriggerId: {
|
|
338
|
+
type: String,
|
|
339
|
+
default: ""
|
|
340
|
+
},
|
|
341
|
+
/** In `popover` mode, whether the full toolbar is open. Use with `v-model:toolbar-open`. */
|
|
342
|
+
toolbarOpen: {
|
|
343
|
+
type: Boolean,
|
|
344
|
+
default: false
|
|
345
|
+
},
|
|
346
|
+
/**
|
|
397
347
|
* Authors can't highlight: no toolbar button, shortcut, `==text==` rule, or `<mark>` paste.
|
|
398
348
|
* Highlights already in the document still show. Read once, when the editor is created.
|
|
399
349
|
*/
|
|
@@ -404,6 +354,7 @@ var LoomaEditor = defineComponent({
|
|
|
404
354
|
},
|
|
405
355
|
emits: {
|
|
406
356
|
"update:modelValue": (_value) => true,
|
|
357
|
+
"update:toolbarOpen": (_value) => true,
|
|
407
358
|
update: (_value) => true,
|
|
408
359
|
ready: (_editor) => true,
|
|
409
360
|
uploadError: (_error, _file) => true,
|
|
@@ -459,6 +410,7 @@ var LoomaEditor = defineComponent({
|
|
|
459
410
|
const tableOverlayShell = ref(null);
|
|
460
411
|
const tableMenuShell = ref(null);
|
|
461
412
|
const mobileToolbarShell = ref(null);
|
|
413
|
+
const codeLanguageShell = ref(null);
|
|
462
414
|
const dragOver = ref(false);
|
|
463
415
|
const uploading = ref(false);
|
|
464
416
|
const failedUpload = ref(null);
|
|
@@ -583,8 +535,7 @@ var LoomaEditor = defineComponent({
|
|
|
583
535
|
placeholder: props.placeholder,
|
|
584
536
|
mention: mentionExtension ?? false,
|
|
585
537
|
disableHighlight: props.disableHighlight,
|
|
586
|
-
codeLanguages: props.codeLanguages
|
|
587
|
-
codeBlockNodeView: VueNodeViewRenderer(createCodeBlockView(props.codeLanguages, () => props.editable))
|
|
538
|
+
codeLanguages: props.codeLanguages
|
|
588
539
|
}),
|
|
589
540
|
imageDelivery.extension,
|
|
590
541
|
slashExtension,
|
|
@@ -794,6 +745,77 @@ var LoomaEditor = defineComponent({
|
|
|
794
745
|
};
|
|
795
746
|
});
|
|
796
747
|
};
|
|
748
|
+
const codeLanguageNames = Object.keys(props.codeLanguages).sort();
|
|
749
|
+
const codeLowlight = createLowlight(props.codeLanguages);
|
|
750
|
+
const codeUi = reactive({
|
|
751
|
+
open: false,
|
|
752
|
+
pos: 0,
|
|
753
|
+
language: "auto",
|
|
754
|
+
text: "",
|
|
755
|
+
style: {}
|
|
756
|
+
});
|
|
757
|
+
let codeBlurTimer;
|
|
758
|
+
const updateCodeUi = (focusSettled = false) => {
|
|
759
|
+
const instance = editor.value?.isDestroyed ? void 0 : editor.value;
|
|
760
|
+
const selection = instance?.state.selection;
|
|
761
|
+
const block = selection?.$from.parent;
|
|
762
|
+
const dom = instance && block?.type.name === "codeBlock" && selection.$from.sameParent(selection.$to) ? instance.view.nodeDOM(selection.$from.before()) : null;
|
|
763
|
+
if (!instance || !props.editable || !codeLanguageNames.length || !(dom instanceof HTMLElement)) {
|
|
764
|
+
codeUi.open = false;
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (!instance.isFocused && !codeLanguageShell.value?.contains(document.activeElement)) {
|
|
768
|
+
clearTimeout(codeBlurTimer);
|
|
769
|
+
if (codeUi.open && !focusSettled) codeBlurTimer = setTimeout(() => updateCodeUi(true), 0);
|
|
770
|
+
else codeUi.open = false;
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const rect = dom.getBoundingClientRect();
|
|
774
|
+
const above = rect.top > 76;
|
|
775
|
+
codeUi.open = true;
|
|
776
|
+
codeUi.pos = selection.$from.before();
|
|
777
|
+
codeUi.language = typeof block.attrs.language === "string" && block.attrs.language ? block.attrs.language : "auto";
|
|
778
|
+
codeUi.text = block.textContent;
|
|
779
|
+
codeUi.style = {
|
|
780
|
+
top: `${above ? rect.top - 8 : rect.bottom + 8}px`,
|
|
781
|
+
left: `${rect.left + rect.width / 2}px`,
|
|
782
|
+
transform: `translate(-50%, ${above ? "-100%" : "0"})`
|
|
783
|
+
};
|
|
784
|
+
};
|
|
785
|
+
const codeLanguageOptions = computed(() => {
|
|
786
|
+
const detected = codeUi.open && codeUi.text.trim() ? codeLowlight.highlightAuto(codeUi.text).data?.language : null;
|
|
787
|
+
return [
|
|
788
|
+
{
|
|
789
|
+
value: "auto",
|
|
790
|
+
label: typeof detected === "string" ? `Auto (${detected.toUpperCase()})` : "Auto"
|
|
791
|
+
},
|
|
792
|
+
...codeUi.language !== "auto" && !Object.hasOwn(props.codeLanguages, codeUi.language) ? [{
|
|
793
|
+
value: codeUi.language,
|
|
794
|
+
label: `${codeUi.language.toUpperCase()} (unavailable)`
|
|
795
|
+
}] : [],
|
|
796
|
+
...codeLanguageNames.map((name) => ({
|
|
797
|
+
value: name,
|
|
798
|
+
label: name.toUpperCase()
|
|
799
|
+
}))
|
|
800
|
+
];
|
|
801
|
+
});
|
|
802
|
+
const chooseCodeLanguage = (detail) => {
|
|
803
|
+
const instance = editor.value;
|
|
804
|
+
if (!instance || detail.kind !== "selection" || !detail.value) return;
|
|
805
|
+
const language = detail.value === "auto" ? null : detail.value;
|
|
806
|
+
if (language !== null && !Object.hasOwn(props.codeLanguages, language)) return;
|
|
807
|
+
const node = instance.state.doc.nodeAt(codeUi.pos);
|
|
808
|
+
if (node?.type.name !== "codeBlock") return;
|
|
809
|
+
instance.view.dispatch(closeHistory(instance.state.tr));
|
|
810
|
+
instance.view.dispatch(instance.state.tr.setNodeMarkup(codeUi.pos, void 0, {
|
|
811
|
+
...node.attrs,
|
|
812
|
+
language
|
|
813
|
+
}));
|
|
814
|
+
instance.view.dispatch(closeHistory(instance.state.tr));
|
|
815
|
+
};
|
|
816
|
+
const onCodeTransaction = () => updateCodeUi();
|
|
817
|
+
watch(() => props.editable, () => nextTick(onCodeTransaction));
|
|
818
|
+
onBeforeUnmount(() => clearTimeout(codeBlurTimer));
|
|
797
819
|
const updateTableUi = () => {
|
|
798
820
|
const instance = editor.value;
|
|
799
821
|
if (!instance || !props.editable || !instance.isFocused && !tableInteractionActive && !hoveredTableCell) {
|
|
@@ -840,10 +862,12 @@ var LoomaEditor = defineComponent({
|
|
|
840
862
|
const onEditorFocus = () => {
|
|
841
863
|
editorFocused.value = true;
|
|
842
864
|
updateTableUi();
|
|
865
|
+
updateCodeUi();
|
|
843
866
|
updateMobileViewport();
|
|
844
867
|
};
|
|
845
868
|
const onEditorBlur = () => {
|
|
846
869
|
updateTableUi();
|
|
870
|
+
updateCodeUi();
|
|
847
871
|
setTimeout(() => {
|
|
848
872
|
if (!tableInteractionActive && !editor.value?.isFocused) editorFocused.value = false;
|
|
849
873
|
}, 0);
|
|
@@ -852,6 +876,7 @@ var LoomaEditor = defineComponent({
|
|
|
852
876
|
if (!instance) return;
|
|
853
877
|
instance.on("selectionUpdate", updateTableUi);
|
|
854
878
|
instance.on("transaction", updateTableUi);
|
|
879
|
+
instance.on("transaction", onCodeTransaction);
|
|
855
880
|
instance.on("focus", onEditorFocus);
|
|
856
881
|
instance.on("blur", onEditorBlur);
|
|
857
882
|
nextTick(updateTableUi);
|
|
@@ -860,6 +885,7 @@ var LoomaEditor = defineComponent({
|
|
|
860
885
|
if (!instance) return;
|
|
861
886
|
instance.off("selectionUpdate", updateTableUi);
|
|
862
887
|
instance.off("transaction", updateTableUi);
|
|
888
|
+
instance.off("transaction", onCodeTransaction);
|
|
863
889
|
instance.off("focus", onEditorFocus);
|
|
864
890
|
instance.off("blur", onEditorBlur);
|
|
865
891
|
};
|
|
@@ -870,6 +896,7 @@ var LoomaEditor = defineComponent({
|
|
|
870
896
|
const onViewportChange = () => {
|
|
871
897
|
updateMobileViewport();
|
|
872
898
|
updateTableUi();
|
|
899
|
+
updateCodeUi();
|
|
873
900
|
tableUi.menuOpen = false;
|
|
874
901
|
};
|
|
875
902
|
const onDocumentPointerDown = (event) => {
|
|
@@ -1140,14 +1167,17 @@ var LoomaEditor = defineComponent({
|
|
|
1140
1167
|
onClick: run
|
|
1141
1168
|
}, () => loomaIcon(icon));
|
|
1142
1169
|
};
|
|
1143
|
-
const renderToolbar = (instance, floating = true) => {
|
|
1144
|
-
const
|
|
1170
|
+
const renderToolbar = (instance, floating = true, textOnly = false) => {
|
|
1171
|
+
const textButtons = [
|
|
1145
1172
|
commandButton("Bold", "bold", instance.isActive("bold"), !instance.can().toggleBold(), () => instance.chain().focus().toggleBold().run()),
|
|
1146
1173
|
commandButton("Italic", "italic", instance.isActive("italic"), !instance.can().toggleItalic(), () => instance.chain().focus().toggleItalic().run()),
|
|
1147
1174
|
commandButton("Underline", "underline", instance.isActive("underline"), !instance.can().toggleUnderline(), () => instance.chain().focus().toggleUnderline().run()),
|
|
1148
1175
|
commandButton("Strike", "strikethrough", instance.isActive("strike"), !instance.can().toggleStrike(), () => instance.chain().focus().toggleStrike().run()),
|
|
1149
1176
|
...authorHighlight ? [commandButton("Highlight", "highlighter", instance.isActive("highlight"), !instance.can().toggleHighlight(), () => instance.chain().focus().toggleHighlight().run())] : [],
|
|
1150
|
-
commandButton("Inline code", "code-xml", instance.isActive("code"), !instance.can().toggleCode(), () => instance.chain().focus().toggleCode().run())
|
|
1177
|
+
commandButton("Inline code", "code-xml", instance.isActive("code"), !instance.can().toggleCode(), () => instance.chain().focus().toggleCode().run())
|
|
1178
|
+
];
|
|
1179
|
+
const buttons = textOnly ? textButtons : [
|
|
1180
|
+
...textButtons,
|
|
1151
1181
|
h(IconButton, {
|
|
1152
1182
|
id: linkAnchorId,
|
|
1153
1183
|
class: "looma-editor__toolbar-button",
|
|
@@ -1300,7 +1330,7 @@ var LoomaEditor = defineComponent({
|
|
|
1300
1330
|
onDblclick: onImageDoubleClick,
|
|
1301
1331
|
onKeydown: onImageKeyDown
|
|
1302
1332
|
}, [
|
|
1303
|
-
instance && props.editable && !mobile.value && props.toolbarMode === "bubble" ? h(BubbleMenu, {
|
|
1333
|
+
instance && props.editable && !mobile.value && (props.toolbarMode === "bubble" || props.toolbarMode === "popover" && !props.toolbarOpen) ? h(BubbleMenu, {
|
|
1304
1334
|
editor: instance,
|
|
1305
1335
|
pluginKey: "looma-text-formatting-menu",
|
|
1306
1336
|
shouldShow: ({ editor: menuEditor, from, to }) => shouldShowTextFormattingToolbar(menuEditor, from, to),
|
|
@@ -1310,13 +1340,37 @@ var LoomaEditor = defineComponent({
|
|
|
1310
1340
|
maxWidth: "none",
|
|
1311
1341
|
placement: "top"
|
|
1312
1342
|
}
|
|
1313
|
-
}, { default: () => renderToolbar(instance) }) : null,
|
|
1343
|
+
}, { default: () => renderToolbar(instance, true, props.toolbarMode === "popover") }) : null,
|
|
1344
|
+
instance && props.editable && !mobile.value && props.toolbarMode === "popover" && props.toolbarTriggerId ? h(Popover, {
|
|
1345
|
+
class: "looma-editor__formatting-popover",
|
|
1346
|
+
role: "region",
|
|
1347
|
+
"aria-label": "Formatting tools",
|
|
1348
|
+
open: props.toolbarOpen,
|
|
1349
|
+
for: props.toolbarTriggerId,
|
|
1350
|
+
placement: "bottom-end",
|
|
1351
|
+
onOpen: () => emit("update:toolbarOpen", true),
|
|
1352
|
+
onClose: () => emit("update:toolbarOpen", false)
|
|
1353
|
+
}, () => [renderToolbar(instance, false)]) : null,
|
|
1314
1354
|
instance && props.editable && !mobile.value && props.toolbarMode === "sticky" ? h("div", {
|
|
1315
1355
|
class: "looma-editor__sticky-toolbar-shell",
|
|
1316
1356
|
role: "toolbar",
|
|
1317
1357
|
"aria-label": "Editor tools"
|
|
1318
1358
|
}, [renderToolbar(instance, false)]) : null,
|
|
1319
1359
|
instance ? h(EditorContent, { editor: instance }) : null,
|
|
1360
|
+
codeUi.open ? h("div", {
|
|
1361
|
+
ref: codeLanguageShell,
|
|
1362
|
+
class: "looma-editor__code-language",
|
|
1363
|
+
style: codeUi.style,
|
|
1364
|
+
onFocusout: () => updateCodeUi()
|
|
1365
|
+
}, [h(Combobox, {
|
|
1366
|
+
label: "Code language",
|
|
1367
|
+
labelVisibility: "sr-only",
|
|
1368
|
+
size: "sm",
|
|
1369
|
+
disclosure: true,
|
|
1370
|
+
filter: "label",
|
|
1371
|
+
value: codeUi.language,
|
|
1372
|
+
onValueChange: chooseCodeLanguage
|
|
1373
|
+
}, () => codeLanguageOptions.value.map((option) => h("option", { value: option.value }, option.label)))]) : null,
|
|
1320
1374
|
instance && props.editable && mobile.value && editorFocused.value ? h("div", {
|
|
1321
1375
|
ref: mobileToolbarShell,
|
|
1322
1376
|
class: "looma-editor__mobile-toolbar-shell",
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { LoomaCodeLanguages } from "@threadlabs/looma/editor";
|
|
2
|
-
/** Per-block language control; the document stores only the code and explicit language. */
|
|
3
|
-
export declare function createCodeBlockView(languages: LoomaCodeLanguages, isEditable: () => boolean): import("vue").DefineComponent<{
|
|
4
|
-
decorations: readonly import("@tiptap/core").DecorationWithType[];
|
|
5
|
-
selected: boolean;
|
|
6
|
-
updateAttributes: (attributes: Record<string, any>) => void;
|
|
7
|
-
deleteNode: () => void;
|
|
8
|
-
node: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[0];
|
|
9
|
-
view: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[1];
|
|
10
|
-
getPos: () => number;
|
|
11
|
-
innerDecorations: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[4];
|
|
12
|
-
editor: import("@tiptap/core").Editor;
|
|
13
|
-
extension: import("@tiptap/core").Node;
|
|
14
|
-
HTMLAttributes: Record<string, any>;
|
|
15
|
-
}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
16
|
-
[key: string]: any;
|
|
17
|
-
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
|
|
18
|
-
decorations: readonly import("@tiptap/core").DecorationWithType[];
|
|
19
|
-
selected: boolean;
|
|
20
|
-
updateAttributes: (attributes: Record<string, any>) => void;
|
|
21
|
-
deleteNode: () => void;
|
|
22
|
-
node: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[0];
|
|
23
|
-
view: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[1];
|
|
24
|
-
getPos: () => number;
|
|
25
|
-
innerDecorations: Parameters<import("@tiptap/pm/view").NodeViewConstructor>[4];
|
|
26
|
-
editor: import("@tiptap/core").Editor;
|
|
27
|
-
extension: import("@tiptap/core").Node;
|
|
28
|
-
HTMLAttributes: Record<string, any>;
|
|
29
|
-
}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|