@threadlabs/looma 0.17.1 → 0.17.2
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.
|
@@ -9200,6 +9200,12 @@ function setActiveTableCellBackground(editor, backgroundColor) {
|
|
|
9200
9200
|
* Default Tiptap extension preset for Looma editor.
|
|
9201
9201
|
* Uses the Vanilla JS Tiptap API; apps provide @tiptap/core and Looma ships the preset extensions.
|
|
9202
9202
|
*/
|
|
9203
|
+
var LoomaCodeBlock = CodeBlockLowlight.extend({ addInputRules() {
|
|
9204
|
+
return [...this.parent?.() ?? [], textblockTypeInputRule({
|
|
9205
|
+
find: /^```$/,
|
|
9206
|
+
type: this.type
|
|
9207
|
+
})];
|
|
9208
|
+
} });
|
|
9203
9209
|
var StoredOnlyHighlight = Highlight.extend({
|
|
9204
9210
|
parseHTML: () => [],
|
|
9205
9211
|
addKeyboardShortcuts: () => ({}),
|
|
@@ -9243,9 +9249,13 @@ function getLoomaTableExtensions() {
|
|
|
9243
9249
|
* can be replaced without coupling UI chrome to an application directory.
|
|
9244
9250
|
* Use with `new Editor({ extensions: getDefaultEditorExtensions(), ... })` or a
|
|
9245
9251
|
* framework's Tiptap editor hook.
|
|
9252
|
+
* @contract Code blocks accept three backticks in an empty paragraph as an
|
|
9253
|
+
* immediate typing shortcut. An optional node view changes presentation only;
|
|
9254
|
+
* the code and explicit language stay in the document schema.
|
|
9246
9255
|
*/
|
|
9247
9256
|
function getDefaultEditorExtensions(options = {}) {
|
|
9248
|
-
const { placeholder = "Type “/” for commands, or start writing…", linkOpenOnClick = false, imageInline = false, mention = createLoomaMentionExtension(), disableHighlight = false, codeLanguages } = options;
|
|
9257
|
+
const { placeholder = "Type “/” for commands, or start writing…", linkOpenOnClick = false, imageInline = false, mention = createLoomaMentionExtension(), disableHighlight = false, codeLanguages, codeBlockNodeView } = options;
|
|
9258
|
+
const codeBlock = LoomaCodeBlock.configure({ lowlight: createLowlight(codeLanguages) });
|
|
9249
9259
|
return [
|
|
9250
9260
|
Document,
|
|
9251
9261
|
Paragraph,
|
|
@@ -9283,7 +9293,7 @@ function getDefaultEditorExtensions(options = {}) {
|
|
|
9283
9293
|
(disableHighlight ? StoredOnlyHighlight : Highlight).configure({ multicolor: false }),
|
|
9284
9294
|
Code,
|
|
9285
9295
|
LoomaSmartPaste,
|
|
9286
|
-
|
|
9296
|
+
codeBlockNodeView ? codeBlock.extend({ addNodeView: () => codeBlockNodeView }) : codeBlock,
|
|
9287
9297
|
Typography,
|
|
9288
9298
|
Placeholder.configure({
|
|
9289
9299
|
placeholder: ({ node }) => node.type.name === "paragraph" ? placeholder : "",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Default Tiptap extension preset for Looma editor.
|
|
3
3
|
* Uses the Vanilla JS Tiptap API; apps provide @tiptap/core and Looma ships the preset extensions.
|
|
4
4
|
*/
|
|
5
|
-
import { type AnyExtension } from "@tiptap/core";
|
|
5
|
+
import { type AnyExtension, type NodeViewRenderer } from "@tiptap/core";
|
|
6
6
|
import { createLowlight } from "lowlight";
|
|
7
7
|
/** Grammars supplied by the host; importing a language remains the host's choice. */
|
|
8
8
|
export type LoomaCodeLanguages = NonNullable<Parameters<typeof createLowlight>[0]>;
|
|
@@ -29,6 +29,8 @@ export interface DefaultEditorExtensionsOptions {
|
|
|
29
29
|
disableHighlight?: boolean;
|
|
30
30
|
/** Code block grammars to register. None are loaded by default. */
|
|
31
31
|
codeLanguages?: LoomaCodeLanguages;
|
|
32
|
+
/** Optional presentation for a code block; the Vue editor supplies its language control. */
|
|
33
|
+
codeBlockNodeView?: NodeViewRenderer;
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Complete Looma table schema as one Tiptap extension.
|
|
@@ -50,5 +52,8 @@ export declare function getLoomaTableExtensions(): AnyExtension[];
|
|
|
50
52
|
* can be replaced without coupling UI chrome to an application directory.
|
|
51
53
|
* Use with `new Editor({ extensions: getDefaultEditorExtensions(), ... })` or a
|
|
52
54
|
* framework's Tiptap editor hook.
|
|
55
|
+
* @contract Code blocks accept three backticks in an empty paragraph as an
|
|
56
|
+
* immediate typing shortcut. An optional node view changes presentation only;
|
|
57
|
+
* the code and explicit language stay in the document schema.
|
|
53
58
|
*/
|
|
54
59
|
export declare function getDefaultEditorExtensions(options?: DefaultEditorExtensionsOptions): AnyExtension[];
|
package/package.json
CHANGED
package/vue/components.css
CHANGED
|
@@ -8661,6 +8661,20 @@ html[data-ui-input-modality="touch"]
|
|
|
8661
8661
|
font-size: var(--ui-font-size-sm);
|
|
8662
8662
|
}
|
|
8663
8663
|
|
|
8664
|
+
.looma-editor .ProseMirror pre.looma-editor__code-block--has-language {
|
|
8665
|
+
position: relative;
|
|
8666
|
+
padding-block-start: calc(var(--ui-space-4) + 2rem);
|
|
8667
|
+
}
|
|
8668
|
+
|
|
8669
|
+
.looma-editor__code-language {
|
|
8670
|
+
position: absolute;
|
|
8671
|
+
inset-block-start: var(--ui-space-2);
|
|
8672
|
+
inset-inline-end: var(--ui-space-2);
|
|
8673
|
+
inline-size: min(8.5rem, calc(100% - 2 * var(--ui-space-2)));
|
|
8674
|
+
z-index: 1;
|
|
8675
|
+
font-family: var(--ui-font-family-sans, sans-serif);
|
|
8676
|
+
}
|
|
8677
|
+
|
|
8664
8678
|
.looma-editor .ProseMirror code {
|
|
8665
8679
|
padding: 0.1em 0.3em;
|
|
8666
8680
|
border-radius: var(--ui-radius-sm);
|
|
@@ -0,0 +1,29 @@
|
|
|
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>;
|
package/vue/editor/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { defineComponent, h, nextTick, onBeforeUnmount, onMounted, reactive, ref, useId, watch } from "vue";
|
|
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
|
-
import { EditorInsertTableGrid, EditorMentionMenu, EditorSlashMenu, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Menu, MenuItem, Popover, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
-
import { BubbleMenu, EditorContent, useEditor } from "@tiptap/vue-3";
|
|
3
|
+
import { Combobox, EditorInsertTableGrid, EditorMentionMenu, EditorSlashMenu, EditorTableContextMenu, EditorTableOverlay, EditorTableToolbar, EditorToolbar, IconButton, Menu, MenuItem, Popover, Tooltip } from "@threadlabs/looma/vue";
|
|
4
|
+
import { BubbleMenu, EditorContent, NodeViewContent, NodeViewWrapper, VueNodeViewRenderer, nodeViewProps, useEditor } from "@tiptap/vue-3";
|
|
5
5
|
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
6
6
|
import { Extension } from "@tiptap/core";
|
|
7
7
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
8
|
+
import { closeHistory } from "@tiptap/pm/history";
|
|
9
|
+
import { createLowlight } from "lowlight";
|
|
8
10
|
export * from "@threadlabs/looma/editor";
|
|
9
11
|
//#region src/vue/editor/primitives.ts
|
|
10
12
|
/** Each Vue editor component's element tag, for tooling that enumerates them. */
|
|
@@ -178,6 +180,69 @@ function imageUploadContent(image) {
|
|
|
178
180
|
};
|
|
179
181
|
}
|
|
180
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
|
|
181
246
|
//#region src/vue/editor/LoomaEditor.ts
|
|
182
247
|
var EMPTY_DOCUMENT = {
|
|
183
248
|
type: "doc",
|
|
@@ -518,7 +583,8 @@ var LoomaEditor = defineComponent({
|
|
|
518
583
|
placeholder: props.placeholder,
|
|
519
584
|
mention: mentionExtension ?? false,
|
|
520
585
|
disableHighlight: props.disableHighlight,
|
|
521
|
-
codeLanguages: props.codeLanguages
|
|
586
|
+
codeLanguages: props.codeLanguages,
|
|
587
|
+
codeBlockNodeView: VueNodeViewRenderer(createCodeBlockView(props.codeLanguages, () => props.editable))
|
|
522
588
|
}),
|
|
523
589
|
imageDelivery.extension,
|
|
524
590
|
slashExtension,
|