@threadlabs/looma 0.26.11 → 0.26.13
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadlabs/looma",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Looma components as HTML, Vue, and plain DOM, with design tokens.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,12 @@
|
|
|
57
57
|
"./tokens.css": "./tokens.css",
|
|
58
58
|
"./theme-light.css": "./theme-light.css",
|
|
59
59
|
"./theme-dark.css": "./theme-dark.css",
|
|
60
|
-
"./theme-high-contrast.css": "./theme-high-contrast.css"
|
|
60
|
+
"./theme-high-contrast.css": "./theme-high-contrast.css",
|
|
61
|
+
"./vue/editor/compact": {
|
|
62
|
+
"types": "./vue/editor/compact/index.d.ts",
|
|
63
|
+
"import": "./vue/editor/compact/index.js",
|
|
64
|
+
"default": "./vue/editor/compact/index.js"
|
|
65
|
+
}
|
|
61
66
|
},
|
|
62
67
|
"files": [
|
|
63
68
|
"dist/",
|
package/vue/components.css
CHANGED
|
@@ -9957,6 +9957,45 @@ html[data-ui-input-modality="touch"]
|
|
|
9957
9957
|
[data-component~="ui-tree-item"][hidden][data-v-8f2cc947] {
|
|
9958
9958
|
display: none;
|
|
9959
9959
|
}
|
|
9960
|
+
/* Short-form field primitive: the same field tokens as Textarea. */
|
|
9961
|
+
.looma-compact-editor {
|
|
9962
|
+
min-inline-size: 0;
|
|
9963
|
+
box-sizing: border-box;
|
|
9964
|
+
min-block-size: calc(4.5em + 2 * var(--ui-border-width));
|
|
9965
|
+
border: var(--ui-border-width) solid var(--ui-control-border, var(--ui-border-strong));
|
|
9966
|
+
border-radius: var(--ui-field-radius, var(--ui-radius-md));
|
|
9967
|
+
background: var(--ui-control-surface, var(--ui-surface-elevated));
|
|
9968
|
+
color: var(--ui-text-primary);
|
|
9969
|
+
box-shadow: var(--ui-control-inset);
|
|
9970
|
+
}
|
|
9971
|
+
.looma-compact-editor:focus-within {
|
|
9972
|
+
outline: var(--ui-focus-ring-width, 2px) solid var(--ui-focus-ring, var(--ui-accent-solid));
|
|
9973
|
+
outline-offset: var(--ui-focus-ring-offset, 2px);
|
|
9974
|
+
}
|
|
9975
|
+
.looma-compact-editor .ProseMirror {
|
|
9976
|
+
box-sizing: border-box;
|
|
9977
|
+
min-block-size: 4.5em;
|
|
9978
|
+
max-block-size: 12em;
|
|
9979
|
+
overflow-y: auto;
|
|
9980
|
+
padding: var(--ui-space-2) var(--ui-space-3);
|
|
9981
|
+
outline: none;
|
|
9982
|
+
}
|
|
9983
|
+
.looma-compact-editor, .looma-compact-content {
|
|
9984
|
+
font-size: var(--ui-font-size-sm);
|
|
9985
|
+
font-weight: var(--ui-font-normal, 400);
|
|
9986
|
+
line-height: var(--ui-line-height-md);
|
|
9987
|
+
overflow-wrap: anywhere;
|
|
9988
|
+
}
|
|
9989
|
+
.looma-compact-editor p, .looma-compact-content p { margin: 0; }
|
|
9990
|
+
.looma-compact-editor p + p, .looma-compact-content p + p { margin-block-start: var(--ui-space-2); }
|
|
9991
|
+
.looma-compact-editor strong, .looma-compact-content strong { font-weight: var(--ui-font-bold); }
|
|
9992
|
+
.looma-compact-editor code, .looma-compact-content code {
|
|
9993
|
+
font-family: var(--ui-font-mono);
|
|
9994
|
+
padding-inline: var(--ui-space-1);
|
|
9995
|
+
border-radius: var(--ui-radius-sm);
|
|
9996
|
+
background: var(--ui-surface-muted);
|
|
9997
|
+
}
|
|
9998
|
+
@media (pointer: coarse) { .looma-compact-editor { font-size: var(--ui-font-size-md); } }
|
|
9960
9999
|
/* The editor surface owns its renderer canvas. Existing hooks retain their defaults. */
|
|
9961
10000
|
[data-component~="ui-editor-surface"] .ProseMirror {
|
|
9962
10001
|
min-height: var(--ui-editor-content-min-height, var(--_ui-editor-content-min-height, 300px));
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type PropType } from "vue";
|
|
2
|
+
import type { AnyExtension, JSONContent } from "@tiptap/core";
|
|
3
|
+
import { Editor } from "@tiptap/vue-3";
|
|
4
|
+
import "./compact-editor.css";
|
|
5
|
+
export { getCompactEditorExtensions } from "./extensions.js";
|
|
6
|
+
/**
|
|
7
|
+
* Toolbar-free editing for short rich text. Owns editor lifecycle and standard
|
|
8
|
+
* formatting/history shortcuts; the host owns persistence and optional extensions.
|
|
9
|
+
* Importing this entry is SSR-safe and does not load the full document editor.
|
|
10
|
+
* @ownership The component owns the editor; callers own content and extensions.
|
|
11
|
+
* @lifecycle Mount creates one editor; unmount destroys it and prop watchers.
|
|
12
|
+
* @failure Persistence failures are host-owned; the controlled document can restore input.
|
|
13
|
+
*/
|
|
14
|
+
export declare const LoomaCompactEditor: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
15
|
+
modelValue: {
|
|
16
|
+
type: PropType<JSONContent>;
|
|
17
|
+
default: () => JSONContent;
|
|
18
|
+
};
|
|
19
|
+
label: {
|
|
20
|
+
type: StringConstructor;
|
|
21
|
+
default: string;
|
|
22
|
+
};
|
|
23
|
+
id: {
|
|
24
|
+
type: StringConstructor;
|
|
25
|
+
default: string;
|
|
26
|
+
};
|
|
27
|
+
editable: {
|
|
28
|
+
type: BooleanConstructor;
|
|
29
|
+
default: boolean;
|
|
30
|
+
};
|
|
31
|
+
extensions: {
|
|
32
|
+
type: PropType<AnyExtension[]>;
|
|
33
|
+
default: () => never[];
|
|
34
|
+
};
|
|
35
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
36
|
+
[key: string]: any;
|
|
37
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
38
|
+
"update:modelValue": (_value: JSONContent) => true;
|
|
39
|
+
ready: (_editor: Editor) => true;
|
|
40
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
41
|
+
modelValue: {
|
|
42
|
+
type: PropType<JSONContent>;
|
|
43
|
+
default: () => JSONContent;
|
|
44
|
+
};
|
|
45
|
+
label: {
|
|
46
|
+
type: StringConstructor;
|
|
47
|
+
default: string;
|
|
48
|
+
};
|
|
49
|
+
id: {
|
|
50
|
+
type: StringConstructor;
|
|
51
|
+
default: string;
|
|
52
|
+
};
|
|
53
|
+
editable: {
|
|
54
|
+
type: BooleanConstructor;
|
|
55
|
+
default: boolean;
|
|
56
|
+
};
|
|
57
|
+
extensions: {
|
|
58
|
+
type: PropType<AnyExtension[]>;
|
|
59
|
+
default: () => never[];
|
|
60
|
+
};
|
|
61
|
+
}>> & Readonly<{
|
|
62
|
+
"onUpdate:modelValue"?: ((_value: JSONContent) => any) | undefined;
|
|
63
|
+
onReady?: ((_editor: Editor) => any) | undefined;
|
|
64
|
+
}>, {
|
|
65
|
+
label: string;
|
|
66
|
+
id: string;
|
|
67
|
+
modelValue: JSONContent;
|
|
68
|
+
editable: boolean;
|
|
69
|
+
extensions: AnyExtension[];
|
|
70
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
71
|
+
/** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
|
|
72
|
+
export declare const LoomaCompactContent: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
73
|
+
content: {
|
|
74
|
+
type: PropType<JSONContent>;
|
|
75
|
+
required: true;
|
|
76
|
+
};
|
|
77
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
78
|
+
[key: string]: any;
|
|
79
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
80
|
+
content: {
|
|
81
|
+
type: PropType<JSONContent>;
|
|
82
|
+
required: true;
|
|
83
|
+
};
|
|
84
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef, watch } from "vue";
|
|
2
|
+
import { Extension, Mark, Node, markInputRule, markPasteRule, mergeAttributes } from "@tiptap/core";
|
|
3
|
+
import { Editor, EditorContent } from "@tiptap/vue-3";
|
|
4
|
+
import { history, redo, undo } from "@tiptap/pm/history";
|
|
5
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-document@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-document/dist/index.js
|
|
6
|
+
/**
|
|
7
|
+
* The default document node which represents the top level node of the editor.
|
|
8
|
+
* @see https://tiptap.dev/api/nodes/document
|
|
9
|
+
*/
|
|
10
|
+
var Document = Node.create({
|
|
11
|
+
name: "doc",
|
|
12
|
+
topNode: true,
|
|
13
|
+
content: "block+"
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-paragraph@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-paragraph/dist/index.js
|
|
17
|
+
/**
|
|
18
|
+
* This extension allows you to create paragraphs.
|
|
19
|
+
* @see https://www.tiptap.dev/api/nodes/paragraph
|
|
20
|
+
*/
|
|
21
|
+
var Paragraph = Node.create({
|
|
22
|
+
name: "paragraph",
|
|
23
|
+
priority: 1e3,
|
|
24
|
+
addOptions() {
|
|
25
|
+
return { HTMLAttributes: {} };
|
|
26
|
+
},
|
|
27
|
+
group: "block",
|
|
28
|
+
content: "inline*",
|
|
29
|
+
parseHTML() {
|
|
30
|
+
return [{ tag: "p" }];
|
|
31
|
+
},
|
|
32
|
+
renderHTML({ HTMLAttributes }) {
|
|
33
|
+
return [
|
|
34
|
+
"p",
|
|
35
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
36
|
+
0
|
|
37
|
+
];
|
|
38
|
+
},
|
|
39
|
+
addCommands() {
|
|
40
|
+
return { setParagraph: () => ({ commands }) => {
|
|
41
|
+
return commands.setNode(this.name);
|
|
42
|
+
} };
|
|
43
|
+
},
|
|
44
|
+
addKeyboardShortcuts() {
|
|
45
|
+
return { "Mod-Alt-0": () => this.editor.commands.setParagraph() };
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-text@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-text/dist/index.js
|
|
50
|
+
/**
|
|
51
|
+
* This extension allows you to create text nodes.
|
|
52
|
+
* @see https://www.tiptap.dev/api/nodes/text
|
|
53
|
+
*/
|
|
54
|
+
var Text = Node.create({
|
|
55
|
+
name: "text",
|
|
56
|
+
group: "inline"
|
|
57
|
+
});
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-bold@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-bold/dist/index.js
|
|
60
|
+
/**
|
|
61
|
+
* Matches bold text via `**` as input.
|
|
62
|
+
*/
|
|
63
|
+
var starInputRegex$1 = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/;
|
|
64
|
+
/**
|
|
65
|
+
* Matches bold text via `**` while pasting.
|
|
66
|
+
*/
|
|
67
|
+
var starPasteRegex$1 = /(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g;
|
|
68
|
+
/**
|
|
69
|
+
* Matches bold text via `__` as input.
|
|
70
|
+
*/
|
|
71
|
+
var underscoreInputRegex$1 = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/;
|
|
72
|
+
/**
|
|
73
|
+
* Matches bold text via `__` while pasting.
|
|
74
|
+
*/
|
|
75
|
+
var underscorePasteRegex$1 = /(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g;
|
|
76
|
+
/**
|
|
77
|
+
* This extension allows you to mark text as bold.
|
|
78
|
+
* @see https://tiptap.dev/api/marks/bold
|
|
79
|
+
*/
|
|
80
|
+
var Bold = Mark.create({
|
|
81
|
+
name: "bold",
|
|
82
|
+
addOptions() {
|
|
83
|
+
return { HTMLAttributes: {} };
|
|
84
|
+
},
|
|
85
|
+
parseHTML() {
|
|
86
|
+
return [
|
|
87
|
+
{ tag: "strong" },
|
|
88
|
+
{
|
|
89
|
+
tag: "b",
|
|
90
|
+
getAttrs: (node) => node.style.fontWeight !== "normal" && null
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
style: "font-weight=400",
|
|
94
|
+
clearMark: (mark) => mark.type.name === this.name
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
style: "font-weight",
|
|
98
|
+
getAttrs: (value) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
|
|
99
|
+
}
|
|
100
|
+
];
|
|
101
|
+
},
|
|
102
|
+
renderHTML({ HTMLAttributes }) {
|
|
103
|
+
return [
|
|
104
|
+
"strong",
|
|
105
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
106
|
+
0
|
|
107
|
+
];
|
|
108
|
+
},
|
|
109
|
+
addCommands() {
|
|
110
|
+
return {
|
|
111
|
+
setBold: () => ({ commands }) => {
|
|
112
|
+
return commands.setMark(this.name);
|
|
113
|
+
},
|
|
114
|
+
toggleBold: () => ({ commands }) => {
|
|
115
|
+
return commands.toggleMark(this.name);
|
|
116
|
+
},
|
|
117
|
+
unsetBold: () => ({ commands }) => {
|
|
118
|
+
return commands.unsetMark(this.name);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
addKeyboardShortcuts() {
|
|
123
|
+
return {
|
|
124
|
+
"Mod-b": () => this.editor.commands.toggleBold(),
|
|
125
|
+
"Mod-B": () => this.editor.commands.toggleBold()
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
addInputRules() {
|
|
129
|
+
return [markInputRule({
|
|
130
|
+
find: starInputRegex$1,
|
|
131
|
+
type: this.type
|
|
132
|
+
}), markInputRule({
|
|
133
|
+
find: underscoreInputRegex$1,
|
|
134
|
+
type: this.type
|
|
135
|
+
})];
|
|
136
|
+
},
|
|
137
|
+
addPasteRules() {
|
|
138
|
+
return [markPasteRule({
|
|
139
|
+
find: starPasteRegex$1,
|
|
140
|
+
type: this.type
|
|
141
|
+
}), markPasteRule({
|
|
142
|
+
find: underscorePasteRegex$1,
|
|
143
|
+
type: this.type
|
|
144
|
+
})];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-italic@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-italic/dist/index.js
|
|
149
|
+
/**
|
|
150
|
+
* Matches an italic to a *italic* on input.
|
|
151
|
+
*/
|
|
152
|
+
var starInputRegex = /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/;
|
|
153
|
+
/**
|
|
154
|
+
* Matches an italic to a *italic* on paste.
|
|
155
|
+
*/
|
|
156
|
+
var starPasteRegex = /(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))/g;
|
|
157
|
+
/**
|
|
158
|
+
* Matches an italic to a _italic_ on input.
|
|
159
|
+
*/
|
|
160
|
+
var underscoreInputRegex = /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))$/;
|
|
161
|
+
/**
|
|
162
|
+
* Matches an italic to a _italic_ on paste.
|
|
163
|
+
*/
|
|
164
|
+
var underscorePasteRegex = /(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))/g;
|
|
165
|
+
/**
|
|
166
|
+
* This extension allows you to create italic text.
|
|
167
|
+
* @see https://www.tiptap.dev/api/marks/italic
|
|
168
|
+
*/
|
|
169
|
+
var Italic = Mark.create({
|
|
170
|
+
name: "italic",
|
|
171
|
+
addOptions() {
|
|
172
|
+
return { HTMLAttributes: {} };
|
|
173
|
+
},
|
|
174
|
+
parseHTML() {
|
|
175
|
+
return [
|
|
176
|
+
{ tag: "em" },
|
|
177
|
+
{
|
|
178
|
+
tag: "i",
|
|
179
|
+
getAttrs: (node) => node.style.fontStyle !== "normal" && null
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
style: "font-style=normal",
|
|
183
|
+
clearMark: (mark) => mark.type.name === this.name
|
|
184
|
+
},
|
|
185
|
+
{ style: "font-style=italic" }
|
|
186
|
+
];
|
|
187
|
+
},
|
|
188
|
+
renderHTML({ HTMLAttributes }) {
|
|
189
|
+
return [
|
|
190
|
+
"em",
|
|
191
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
192
|
+
0
|
|
193
|
+
];
|
|
194
|
+
},
|
|
195
|
+
addCommands() {
|
|
196
|
+
return {
|
|
197
|
+
setItalic: () => ({ commands }) => {
|
|
198
|
+
return commands.setMark(this.name);
|
|
199
|
+
},
|
|
200
|
+
toggleItalic: () => ({ commands }) => {
|
|
201
|
+
return commands.toggleMark(this.name);
|
|
202
|
+
},
|
|
203
|
+
unsetItalic: () => ({ commands }) => {
|
|
204
|
+
return commands.unsetMark(this.name);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
addKeyboardShortcuts() {
|
|
209
|
+
return {
|
|
210
|
+
"Mod-i": () => this.editor.commands.toggleItalic(),
|
|
211
|
+
"Mod-I": () => this.editor.commands.toggleItalic()
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
addInputRules() {
|
|
215
|
+
return [markInputRule({
|
|
216
|
+
find: starInputRegex,
|
|
217
|
+
type: this.type
|
|
218
|
+
}), markInputRule({
|
|
219
|
+
find: underscoreInputRegex,
|
|
220
|
+
type: this.type
|
|
221
|
+
})];
|
|
222
|
+
},
|
|
223
|
+
addPasteRules() {
|
|
224
|
+
return [markPasteRule({
|
|
225
|
+
find: starPasteRegex,
|
|
226
|
+
type: this.type
|
|
227
|
+
}), markPasteRule({
|
|
228
|
+
find: underscorePasteRegex,
|
|
229
|
+
type: this.type
|
|
230
|
+
})];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-code@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-code/dist/index.js
|
|
235
|
+
/**
|
|
236
|
+
* Regular expressions to match inline code blocks enclosed in backticks.
|
|
237
|
+
* It matches:
|
|
238
|
+
* - An opening backtick, followed by
|
|
239
|
+
* - Any text that doesn't include a backtick (captured for marking), followed by
|
|
240
|
+
* - A closing backtick.
|
|
241
|
+
* This ensures that any text between backticks is formatted as code,
|
|
242
|
+
* regardless of the surrounding characters (exception being another backtick).
|
|
243
|
+
*/
|
|
244
|
+
var inputRegex = /(^|[^`])`([^`]+)`(?!`)/;
|
|
245
|
+
/**
|
|
246
|
+
* Matches inline code while pasting.
|
|
247
|
+
*/
|
|
248
|
+
var pasteRegex = /(^|[^`])`([^`]+)`(?!`)/g;
|
|
249
|
+
/**
|
|
250
|
+
* This extension allows you to mark text as inline code.
|
|
251
|
+
* @see https://tiptap.dev/api/marks/code
|
|
252
|
+
*/
|
|
253
|
+
var Code = Mark.create({
|
|
254
|
+
name: "code",
|
|
255
|
+
addOptions() {
|
|
256
|
+
return { HTMLAttributes: {} };
|
|
257
|
+
},
|
|
258
|
+
excludes: "_",
|
|
259
|
+
code: true,
|
|
260
|
+
exitable: true,
|
|
261
|
+
parseHTML() {
|
|
262
|
+
return [{ tag: "code" }];
|
|
263
|
+
},
|
|
264
|
+
renderHTML({ HTMLAttributes }) {
|
|
265
|
+
return [
|
|
266
|
+
"code",
|
|
267
|
+
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
|
268
|
+
0
|
|
269
|
+
];
|
|
270
|
+
},
|
|
271
|
+
addCommands() {
|
|
272
|
+
return {
|
|
273
|
+
setCode: () => ({ commands }) => {
|
|
274
|
+
return commands.setMark(this.name);
|
|
275
|
+
},
|
|
276
|
+
toggleCode: () => ({ commands }) => {
|
|
277
|
+
return commands.toggleMark(this.name);
|
|
278
|
+
},
|
|
279
|
+
unsetCode: () => ({ commands }) => {
|
|
280
|
+
return commands.unsetMark(this.name);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
},
|
|
284
|
+
addKeyboardShortcuts() {
|
|
285
|
+
return { "Mod-e": () => this.editor.commands.toggleCode() };
|
|
286
|
+
},
|
|
287
|
+
addInputRules() {
|
|
288
|
+
return [markInputRule({
|
|
289
|
+
find: inputRegex,
|
|
290
|
+
type: this.type
|
|
291
|
+
})];
|
|
292
|
+
},
|
|
293
|
+
addPasteRules() {
|
|
294
|
+
return [markPasteRule({
|
|
295
|
+
find: pasteRegex,
|
|
296
|
+
type: this.type
|
|
297
|
+
})];
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-hard-break@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2_/node_modules/@tiptap/extension-hard-break/dist/index.js
|
|
302
|
+
/**
|
|
303
|
+
* This extension allows you to insert hard breaks.
|
|
304
|
+
* @see https://www.tiptap.dev/api/nodes/hard-break
|
|
305
|
+
*/
|
|
306
|
+
var HardBreak = Node.create({
|
|
307
|
+
name: "hardBreak",
|
|
308
|
+
addOptions() {
|
|
309
|
+
return {
|
|
310
|
+
keepMarks: true,
|
|
311
|
+
HTMLAttributes: {}
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
inline: true,
|
|
315
|
+
group: "inline",
|
|
316
|
+
selectable: false,
|
|
317
|
+
linebreakReplacement: true,
|
|
318
|
+
parseHTML() {
|
|
319
|
+
return [{ tag: "br" }];
|
|
320
|
+
},
|
|
321
|
+
renderHTML({ HTMLAttributes }) {
|
|
322
|
+
return ["br", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
|
|
323
|
+
},
|
|
324
|
+
renderText() {
|
|
325
|
+
return "\n";
|
|
326
|
+
},
|
|
327
|
+
addCommands() {
|
|
328
|
+
return { setHardBreak: () => ({ commands, chain, state, editor }) => {
|
|
329
|
+
return commands.first([() => commands.exitCode(), () => commands.command(() => {
|
|
330
|
+
const { selection, storedMarks } = state;
|
|
331
|
+
if (selection.$from.parent.type.spec.isolating) return false;
|
|
332
|
+
const { keepMarks } = this.options;
|
|
333
|
+
const { splittableMarks } = editor.extensionManager;
|
|
334
|
+
const marks = storedMarks || selection.$to.parentOffset && selection.$from.marks();
|
|
335
|
+
return chain().insertContent({ type: this.name }).command(({ tr, dispatch }) => {
|
|
336
|
+
if (dispatch && marks && keepMarks) {
|
|
337
|
+
const filteredMarks = marks.filter((mark) => splittableMarks.includes(mark.type.name));
|
|
338
|
+
tr.ensureMarks(filteredMarks);
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}).run();
|
|
342
|
+
})]);
|
|
343
|
+
} };
|
|
344
|
+
},
|
|
345
|
+
addKeyboardShortcuts() {
|
|
346
|
+
return {
|
|
347
|
+
"Mod-Enter": () => this.editor.commands.setHardBreak(),
|
|
348
|
+
"Shift-Enter": () => this.editor.commands.setHardBreak()
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region ../../node_modules/.pnpm/@tiptap+extension-history@2.27.2_@tiptap+core@2.27.2_@tiptap+pm@2.27.2__@tiptap+pm@2.27.2/node_modules/@tiptap/extension-history/dist/index.js
|
|
354
|
+
/**
|
|
355
|
+
* This extension allows you to undo and redo recent changes.
|
|
356
|
+
* @see https://www.tiptap.dev/api/extensions/history
|
|
357
|
+
*
|
|
358
|
+
* **Important**: If the `@tiptap/extension-collaboration` package is used, make sure to remove
|
|
359
|
+
* the `history` extension, as it is not compatible with the `collaboration` extension.
|
|
360
|
+
*
|
|
361
|
+
* `@tiptap/extension-collaboration` uses its own history implementation.
|
|
362
|
+
*/
|
|
363
|
+
var History = Extension.create({
|
|
364
|
+
name: "history",
|
|
365
|
+
addOptions() {
|
|
366
|
+
return {
|
|
367
|
+
depth: 100,
|
|
368
|
+
newGroupDelay: 500
|
|
369
|
+
};
|
|
370
|
+
},
|
|
371
|
+
addCommands() {
|
|
372
|
+
return {
|
|
373
|
+
undo: () => ({ state, dispatch }) => {
|
|
374
|
+
return undo(state, dispatch);
|
|
375
|
+
},
|
|
376
|
+
redo: () => ({ state, dispatch }) => {
|
|
377
|
+
return redo(state, dispatch);
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
addProseMirrorPlugins() {
|
|
382
|
+
return [history(this.options)];
|
|
383
|
+
},
|
|
384
|
+
addKeyboardShortcuts() {
|
|
385
|
+
return {
|
|
386
|
+
"Mod-z": () => this.editor.commands.undo(),
|
|
387
|
+
"Shift-Mod-z": () => this.editor.commands.redo(),
|
|
388
|
+
"Mod-y": () => this.editor.commands.redo(),
|
|
389
|
+
"Mod-я": () => this.editor.commands.undo(),
|
|
390
|
+
"Shift-Mod-я": () => this.editor.commands.redo()
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/vue/editor/compact/extensions.ts
|
|
396
|
+
/** Minimal short-form schema; additional consumer extensions are opt-in. */
|
|
397
|
+
function getCompactEditorExtensions() {
|
|
398
|
+
return [
|
|
399
|
+
Document,
|
|
400
|
+
Paragraph,
|
|
401
|
+
Text,
|
|
402
|
+
Bold,
|
|
403
|
+
Italic,
|
|
404
|
+
Code,
|
|
405
|
+
HardBreak,
|
|
406
|
+
History
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/vue/editor/compact/index.ts
|
|
411
|
+
var emptyDocument = () => ({
|
|
412
|
+
type: "doc",
|
|
413
|
+
content: [{ type: "paragraph" }]
|
|
414
|
+
});
|
|
415
|
+
/**
|
|
416
|
+
* Toolbar-free editing for short rich text. Owns editor lifecycle and standard
|
|
417
|
+
* formatting/history shortcuts; the host owns persistence and optional extensions.
|
|
418
|
+
* Importing this entry is SSR-safe and does not load the full document editor.
|
|
419
|
+
* @ownership The component owns the editor; callers own content and extensions.
|
|
420
|
+
* @lifecycle Mount creates one editor; unmount destroys it and prop watchers.
|
|
421
|
+
* @failure Persistence failures are host-owned; the controlled document can restore input.
|
|
422
|
+
*/
|
|
423
|
+
var LoomaCompactEditor = defineComponent({
|
|
424
|
+
name: "LoomaCompactEditor",
|
|
425
|
+
inheritAttrs: false,
|
|
426
|
+
props: {
|
|
427
|
+
modelValue: {
|
|
428
|
+
type: Object,
|
|
429
|
+
default: emptyDocument
|
|
430
|
+
},
|
|
431
|
+
label: {
|
|
432
|
+
type: String,
|
|
433
|
+
default: "Message"
|
|
434
|
+
},
|
|
435
|
+
id: {
|
|
436
|
+
type: String,
|
|
437
|
+
default: ""
|
|
438
|
+
},
|
|
439
|
+
editable: {
|
|
440
|
+
type: Boolean,
|
|
441
|
+
default: true
|
|
442
|
+
},
|
|
443
|
+
extensions: {
|
|
444
|
+
type: Array,
|
|
445
|
+
default: () => []
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
emits: {
|
|
449
|
+
"update:modelValue": (_value) => true,
|
|
450
|
+
ready: (_editor) => true
|
|
451
|
+
},
|
|
452
|
+
setup(props, { attrs, emit, expose }) {
|
|
453
|
+
const editor = shallowRef();
|
|
454
|
+
onMounted(() => {
|
|
455
|
+
editor.value = new Editor({
|
|
456
|
+
extensions: [...getCompactEditorExtensions(), ...props.extensions],
|
|
457
|
+
content: props.modelValue,
|
|
458
|
+
editable: props.editable,
|
|
459
|
+
editorProps: { attributes: () => ({
|
|
460
|
+
role: "textbox",
|
|
461
|
+
"aria-multiline": "true",
|
|
462
|
+
"aria-label": props.label,
|
|
463
|
+
"aria-readonly": String(!props.editable),
|
|
464
|
+
id: props.id
|
|
465
|
+
}) },
|
|
466
|
+
onUpdate: ({ editor }) => emit("update:modelValue", editor.getJSON())
|
|
467
|
+
});
|
|
468
|
+
emit("ready", editor.value);
|
|
469
|
+
});
|
|
470
|
+
watch(() => props.modelValue, (value) => {
|
|
471
|
+
if (editor.value && JSON.stringify(value) !== JSON.stringify(editor.value.getJSON())) editor.value.commands.setContent(value, false);
|
|
472
|
+
}, { deep: true });
|
|
473
|
+
watch(() => [
|
|
474
|
+
props.id,
|
|
475
|
+
props.label,
|
|
476
|
+
props.editable
|
|
477
|
+
], () => editor.value?.setEditable(props.editable, false));
|
|
478
|
+
onBeforeUnmount(() => editor.value?.destroy());
|
|
479
|
+
expose({
|
|
480
|
+
focus: () => editor.value?.commands.focus(),
|
|
481
|
+
getEditor: () => editor.value
|
|
482
|
+
});
|
|
483
|
+
return () => h("div", {
|
|
484
|
+
...attrs,
|
|
485
|
+
id: void 0,
|
|
486
|
+
class: ["looma-compact-editor", attrs.class]
|
|
487
|
+
}, [editor.value ? h(EditorContent, { editor: editor.value }) : null]);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
function renderNode(node) {
|
|
491
|
+
if (node.type === "hardBreak") return h("br");
|
|
492
|
+
if (node.type === "text") {
|
|
493
|
+
let text = node.text ?? "";
|
|
494
|
+
for (const mark of node.marks ?? []) {
|
|
495
|
+
const tag = {
|
|
496
|
+
bold: "strong",
|
|
497
|
+
italic: "em",
|
|
498
|
+
code: "code"
|
|
499
|
+
}[mark.type];
|
|
500
|
+
if (tag) text = h(tag, {}, [text]);
|
|
501
|
+
}
|
|
502
|
+
return text;
|
|
503
|
+
}
|
|
504
|
+
const children = (node.content ?? []).map(renderNode);
|
|
505
|
+
return node.type === "paragraph" ? h("p", {}, children) : children;
|
|
506
|
+
}
|
|
507
|
+
/** Renders basic compact JSON as escaped Vue nodes, including during SSR. */
|
|
508
|
+
var LoomaCompactContent = defineComponent({
|
|
509
|
+
name: "LoomaCompactContent",
|
|
510
|
+
props: { content: {
|
|
511
|
+
type: Object,
|
|
512
|
+
required: true
|
|
513
|
+
} },
|
|
514
|
+
setup: (props) => () => h("div", { class: "looma-compact-content" }, [renderNode(props.content)])
|
|
515
|
+
});
|
|
516
|
+
//#endregion
|
|
517
|
+
export { LoomaCompactContent, LoomaCompactEditor, getCompactEditorExtensions };
|