@abraca/nuxt 2.11.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module.d.mts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -0
- package/dist/runtime/components/ACodeEditor.vue +123 -22
- package/dist/runtime/components/ADocPickerModal.d.vue.ts +31 -0
- package/dist/runtime/components/ADocPickerModal.vue +191 -0
- package/dist/runtime/components/ADocPickerModal.vue.d.ts +31 -0
- package/dist/runtime/components/ADocViewToggle.d.vue.ts +40 -0
- package/dist/runtime/components/ADocViewToggle.vue +234 -0
- package/dist/runtime/components/ADocViewToggle.vue.d.ts +40 -0
- package/dist/runtime/components/ADocumentTree.vue +66 -1
- package/dist/runtime/components/AEditor.d.vue.ts +17 -10
- package/dist/runtime/components/AEditor.vue +403 -167
- package/dist/runtime/components/AEditor.vue.d.ts +17 -10
- package/dist/runtime/components/ANodePanel.d.vue.ts +9 -1
- package/dist/runtime/components/ANodePanel.vue +553 -481
- package/dist/runtime/components/ANodePanel.vue.d.ts +9 -1
- package/dist/runtime/components/ATagsEditor.d.vue.ts +19 -0
- package/dist/runtime/components/ATagsEditor.vue +60 -0
- package/dist/runtime/components/ATagsEditor.vue.d.ts +19 -0
- package/dist/runtime/components/aware/AMedia.d.vue.ts +1 -1
- package/dist/runtime/components/aware/AMedia.vue.d.ts +1 -1
- package/dist/runtime/components/chat/AChatInput.d.vue.ts +11 -6
- package/dist/runtime/components/chat/AChatInput.vue +33 -2
- package/dist/runtime/components/chat/AChatInput.vue.d.ts +11 -6
- package/dist/runtime/components/chat/AChatList.d.vue.ts +12 -0
- package/dist/runtime/components/chat/AChatList.vue +76 -32
- package/dist/runtime/components/chat/AChatList.vue.d.ts +12 -0
- package/dist/runtime/components/chat/AChatMessages.d.vue.ts +4 -0
- package/dist/runtime/components/chat/AChatMessages.vue +57 -4
- package/dist/runtime/components/chat/AChatMessages.vue.d.ts +4 -0
- package/dist/runtime/components/chat/AChatPanel.d.vue.ts +6 -2
- package/dist/runtime/components/chat/AChatPanel.vue +17 -1
- package/dist/runtime/components/chat/AChatPanel.vue.d.ts +6 -2
- package/dist/runtime/components/chat/ANodeChatPanel.vue +1 -1
- package/dist/runtime/components/docs/ADocsSearch.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearch.vue.d.ts +1 -1
- package/dist/runtime/components/editor/ADocSuggestMenu.d.vue.ts +7 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue +68 -0
- package/dist/runtime/components/editor/ADocSuggestMenu.vue.d.ts +7 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.d.vue.ts +17 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue +622 -0
- package/dist/runtime/components/renderers/AChartRenderer.client.vue.d.ts +17 -0
- package/dist/runtime/components/renderers/AGraphRenderer.vue +64 -15
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/calendar/ACalendarToolbar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/media/MediaTransportBar.vue.d.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.d.vue.ts +2 -2
- package/dist/runtime/components/renderers/sheets/ASheetsGrid.vue.d.ts +2 -2
- package/dist/runtime/components/settings/ASettingsAppearancePanel.d.vue.ts +3 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue +67 -0
- package/dist/runtime/components/settings/ASettingsAppearancePanel.vue.d.ts +3 -0
- package/dist/runtime/components/settings/ASettingsGroup.d.vue.ts +24 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue +31 -0
- package/dist/runtime/components/settings/ASettingsGroup.vue.d.ts +24 -0
- package/dist/runtime/components/settings/ASettingsModal.vue +84 -53
- package/dist/runtime/components/settings/ASettingsPlaceholder.d.vue.ts +20 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue +32 -0
- package/dist/runtime/components/settings/ASettingsPlaceholder.vue.d.ts +20 -0
- package/dist/runtime/components/settings/ASettingsRow.d.vue.ts +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue +34 -0
- package/dist/runtime/components/settings/ASettingsRow.vue.d.ts +34 -0
- package/dist/runtime/components/settings/sections.d.ts +37 -0
- package/dist/runtime/components/settings/sections.js +45 -0
- package/dist/runtime/components/shell/AUserMenu.d.vue.ts +2 -2
- package/dist/runtime/components/shell/AUserMenu.vue.d.ts +2 -2
- package/dist/runtime/components/shell/AUserProfilePopover.d.vue.ts +1 -1
- package/dist/runtime/components/shell/AUserProfilePopover.vue.d.ts +1 -1
- package/dist/runtime/composables/useChat.d.ts +22 -1
- package/dist/runtime/composables/useChat.js +79 -8
- package/dist/runtime/composables/useDocLinkPick.d.ts +9 -8
- package/dist/runtime/composables/useDocLinkPick.js +7 -18
- package/dist/runtime/composables/useDocSuggest.d.ts +34 -0
- package/dist/runtime/composables/useDocSuggest.js +56 -0
- package/dist/runtime/composables/useNodeContextMenu.d.ts +4 -0
- package/dist/runtime/composables/useNodeContextMenu.js +18 -0
- package/dist/runtime/composables/useSettingsModal.d.ts +1 -1
- package/dist/runtime/extensions/doc-link-drop.js +2 -2
- package/dist/runtime/extensions/doc-suggest.d.ts +28 -0
- package/dist/runtime/extensions/doc-suggest.js +85 -0
- package/dist/runtime/locale.d.ts +8 -0
- package/dist/runtime/locale.js +9 -1
- package/dist/runtime/utils/chatContent.d.ts +20 -2
- package/dist/runtime/utils/chatContent.js +20 -1
- package/dist/runtime/utils/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/docTypes.js +43 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/dist/runtime/utils/titleSync.d.ts +130 -0
- package/dist/runtime/utils/titleSync.js +53 -0
- package/package.json +12 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Usage:
|
|
10
10
|
* const { isOpen, activeTab, openSettings, closeSettings } = useSettingsModal()
|
|
11
11
|
*/
|
|
12
|
-
export type SettingsTab = 'profile' | 'connection' | 'spaces' | 'security' | 'invites' | 'members' | 'trash' | 'offline' | 'plugins' | 'admin';
|
|
12
|
+
export type SettingsTab = 'profile' | 'appearance' | 'connection' | 'spaces' | 'security' | 'invites' | 'members' | 'trash' | 'offline' | 'plugins' | 'admin';
|
|
13
13
|
export declare function useSettingsModal(): {
|
|
14
14
|
isOpen: import("vue").WritableComputedRef<boolean, boolean>;
|
|
15
15
|
activeTab: import("vue").WritableComputedRef<SettingsTab, SettingsTab>;
|
|
@@ -14,7 +14,7 @@ export const DocLinkDrop = Extension.create({
|
|
|
14
14
|
const de = event;
|
|
15
15
|
if (!de.dataTransfer?.types.includes(DOC_DRAG_MIME)) return false;
|
|
16
16
|
de.preventDefault();
|
|
17
|
-
de.dataTransfer.dropEffect = de.
|
|
17
|
+
de.dataTransfer.dropEffect = de.metaKey || de.ctrlKey ? "link" : "copy";
|
|
18
18
|
return false;
|
|
19
19
|
},
|
|
20
20
|
drop(view, event) {
|
|
@@ -38,7 +38,7 @@ export const DocLinkDrop = Extension.create({
|
|
|
38
38
|
const dropPos = view.posAtCoords({ left: de.clientX, top: de.clientY })?.pos;
|
|
39
39
|
if (dropPos == null) return true;
|
|
40
40
|
const { schema, tr } = view.state;
|
|
41
|
-
if (de.
|
|
41
|
+
if (de.metaKey || de.ctrlKey) {
|
|
42
42
|
const linkType = schema.nodes.docLink;
|
|
43
43
|
if (!linkType) return true;
|
|
44
44
|
const node = linkType.create({ docId: data.id });
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core';
|
|
2
|
+
import type { DocSuggestItem, DocSuggestPopupState } from '../composables/useDocSuggest.js';
|
|
3
|
+
export interface DocSuggestOptions {
|
|
4
|
+
/** Whole-space doc search (label match + create-on-miss). */
|
|
5
|
+
search: (query: string) => DocSuggestItem[];
|
|
6
|
+
/** Commit the chosen item as a doc link or embed over `range`. */
|
|
7
|
+
onCommit: (item: DocSuggestItem, mode: 'link' | 'embed', range: {
|
|
8
|
+
from: number;
|
|
9
|
+
to: number;
|
|
10
|
+
}, query: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface DocSuggestStorage {
|
|
13
|
+
popup: DocSuggestPopupState;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Inline `[[` (doc link) and `![[` (doc embed) suggestion popups — the
|
|
17
|
+
* mention-style counterpart for cross-document references.
|
|
18
|
+
*
|
|
19
|
+
* One `@tiptap/suggestion` plugin triggers on `[[`; the leading `!` (when
|
|
20
|
+
* present) is what distinguishes embed from link. The popup is driven through
|
|
21
|
+
* `editor.storage.docSuggest.popup` (a per-editor reactive object) which the
|
|
22
|
+
* `ADocSuggestMenu` component renders — so nested/embedded editors never share
|
|
23
|
+
* or duplicate one popup.
|
|
24
|
+
*
|
|
25
|
+
* Ported from cou-sh/app/extensions/doc-suggest.ts.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DocSuggest: Extension<DocSuggestOptions, DocSuggestStorage>;
|
|
28
|
+
export default DocSuggest;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
import { Suggestion } from "@tiptap/suggestion";
|
|
4
|
+
import { emptyDocSuggestState } from "../composables/useDocSuggest.js";
|
|
5
|
+
export const DocSuggest = Extension.create({
|
|
6
|
+
name: "docSuggest",
|
|
7
|
+
addOptions() {
|
|
8
|
+
return {
|
|
9
|
+
search: () => [],
|
|
10
|
+
onCommit: () => {
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
addStorage() {
|
|
15
|
+
return { popup: reactive(emptyDocSuggestState()) };
|
|
16
|
+
},
|
|
17
|
+
addProseMirrorPlugins() {
|
|
18
|
+
const reset = () => Object.assign(this.storage.popup, emptyDocSuggestState());
|
|
19
|
+
return [
|
|
20
|
+
Suggestion({
|
|
21
|
+
editor: this.editor,
|
|
22
|
+
char: "[[",
|
|
23
|
+
startOfLine: false,
|
|
24
|
+
allowSpaces: true,
|
|
25
|
+
allowedPrefixes: null,
|
|
26
|
+
// allow triggering after `!` and mid-line
|
|
27
|
+
items: ({ query }) => this.options.search(query),
|
|
28
|
+
command: () => {
|
|
29
|
+
},
|
|
30
|
+
// selection handled via the popup / onKeyDown
|
|
31
|
+
render: () => {
|
|
32
|
+
const sync = (props) => {
|
|
33
|
+
const popup = this.storage.popup;
|
|
34
|
+
const from = props.range.from;
|
|
35
|
+
const before = props.editor.state.doc.textBetween(Math.max(0, from - 1), from);
|
|
36
|
+
const mode = before === "!" ? "embed" : "link";
|
|
37
|
+
const rect = props.clientRect?.();
|
|
38
|
+
popup.active = true;
|
|
39
|
+
popup.mode = mode;
|
|
40
|
+
popup.query = props.query;
|
|
41
|
+
popup.items = props.items;
|
|
42
|
+
popup.index = 0;
|
|
43
|
+
popup.rect = rect ? { left: rect.left, top: rect.top, bottom: rect.bottom } : null;
|
|
44
|
+
popup.onSelect = (item) => {
|
|
45
|
+
const realBefore = props.editor.state.doc.textBetween(Math.max(0, from - 1), from);
|
|
46
|
+
const realMode = realBefore === "!" ? "embed" : "link";
|
|
47
|
+
const realFrom = realMode === "embed" ? from - 1 : from;
|
|
48
|
+
this.options.onCommit(item, realMode, { from: realFrom, to: props.range.to }, props.query);
|
|
49
|
+
reset();
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
onStart: sync,
|
|
54
|
+
onUpdate: sync,
|
|
55
|
+
onKeyDown: ({ event }) => {
|
|
56
|
+
const popup = this.storage.popup;
|
|
57
|
+
const items = popup.items;
|
|
58
|
+
if (event.key === "Escape") {
|
|
59
|
+
reset();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (!items.length) return false;
|
|
63
|
+
if (event.key === "ArrowDown") {
|
|
64
|
+
popup.index = (popup.index + 1) % items.length;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (event.key === "ArrowUp") {
|
|
68
|
+
popup.index = (popup.index - 1 + items.length) % items.length;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (event.key === "Enter") {
|
|
72
|
+
const item = items[popup.index];
|
|
73
|
+
if (item) popup.onSelect?.(item);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
},
|
|
78
|
+
onExit: () => reset()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
export default DocSuggest;
|
package/dist/runtime/locale.d.ts
CHANGED
|
@@ -61,6 +61,10 @@ export interface AbracadabraLocale {
|
|
|
61
61
|
nodePanel: {
|
|
62
62
|
editorTab: string;
|
|
63
63
|
propertiesTab: string;
|
|
64
|
+
chatTab: string;
|
|
65
|
+
settingsTab: string;
|
|
66
|
+
serverTab: string;
|
|
67
|
+
openAsFullPage: string;
|
|
64
68
|
close: string;
|
|
65
69
|
icon: string;
|
|
66
70
|
color: string;
|
|
@@ -251,6 +255,10 @@ export interface AbracadabraLocale {
|
|
|
251
255
|
edgeNormal: string;
|
|
252
256
|
edgeThick: string;
|
|
253
257
|
filter: string;
|
|
258
|
+
scrollBehavior: string;
|
|
259
|
+
personalSetting: string;
|
|
260
|
+
scrollPan: string;
|
|
261
|
+
scrollZoom: string;
|
|
254
262
|
};
|
|
255
263
|
dashboard: {
|
|
256
264
|
addItem: string;
|
package/dist/runtime/locale.js
CHANGED
|
@@ -47,6 +47,10 @@ export const DEFAULT_LOCALE = {
|
|
|
47
47
|
nodePanel: {
|
|
48
48
|
editorTab: "Editor",
|
|
49
49
|
propertiesTab: "Properties",
|
|
50
|
+
chatTab: "Chat",
|
|
51
|
+
settingsTab: "Settings",
|
|
52
|
+
serverTab: "Server",
|
|
53
|
+
openAsFullPage: "Open as full page",
|
|
50
54
|
close: "Close",
|
|
51
55
|
icon: "Icon",
|
|
52
56
|
color: "Color",
|
|
@@ -236,7 +240,11 @@ export const DEFAULT_LOCALE = {
|
|
|
236
240
|
edgeThin: "Thin",
|
|
237
241
|
edgeNormal: "Normal",
|
|
238
242
|
edgeThick: "Thick",
|
|
239
|
-
filter: "Filter"
|
|
243
|
+
filter: "Filter",
|
|
244
|
+
scrollBehavior: "Scroll behavior",
|
|
245
|
+
personalSetting: "Personal \u2014 this device only",
|
|
246
|
+
scrollPan: "Pan",
|
|
247
|
+
scrollZoom: "Zoom"
|
|
240
248
|
},
|
|
241
249
|
dashboard: {
|
|
242
250
|
addItem: "Add item",
|
|
@@ -24,7 +24,11 @@ export type ParsedMessage = {
|
|
|
24
24
|
icon?: string;
|
|
25
25
|
};
|
|
26
26
|
export declare function parseMessageContent(content: string): ParsedMessage;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Segment types for rich text rendering. Markdown spans (bold/italic/strike/
|
|
29
|
+
* code) carry their inner `value` as plain text — rendered as styled spans, so
|
|
30
|
+
* there is NO HTML injection (no `v-html`); the renderer just picks a tag/class.
|
|
31
|
+
*/
|
|
28
32
|
export type TextSegment = {
|
|
29
33
|
type: 'text';
|
|
30
34
|
value: string;
|
|
@@ -34,8 +38,22 @@ export type TextSegment = {
|
|
|
34
38
|
} | {
|
|
35
39
|
type: 'mention';
|
|
36
40
|
name: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'bold';
|
|
43
|
+
value: string;
|
|
44
|
+
} | {
|
|
45
|
+
type: 'italic';
|
|
46
|
+
value: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'strike';
|
|
49
|
+
value: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'code';
|
|
52
|
+
value: string;
|
|
37
53
|
};
|
|
38
|
-
/** Split plain text into segments for rich rendering (links, @mentions) */
|
|
54
|
+
/** Split plain text into segments for rich rendering (markdown, links, @mentions). */
|
|
39
55
|
export declare function renderMessageSegments(text: string): TextSegment[];
|
|
56
|
+
/** Whether a plain-text message contains any rich span (markdown/link/mention). */
|
|
57
|
+
export declare function hasRichText(text: string): boolean;
|
|
40
58
|
/** Return a short plain-text preview for any message content string. */
|
|
41
59
|
export declare function previewMessageContent(content: string): string;
|
|
@@ -28,16 +28,32 @@ export function parseMessageContent(content) {
|
|
|
28
28
|
}
|
|
29
29
|
const URL_RE = /https?:\/\/[^\s<>)"']+/g;
|
|
30
30
|
const MENTION_RE = /@(\w+)/g;
|
|
31
|
+
const CODE_RE = /`([^`\n]+)`/g;
|
|
32
|
+
const BOLD_RE = /\*\*([^*\n]+)\*\*/g;
|
|
33
|
+
const STRIKE_RE = /~~([^~\n]+)~~/g;
|
|
34
|
+
const ITALIC_RE = /\*([^*\n]+)\*|_([^_\n]+)_/g;
|
|
31
35
|
export function renderMessageSegments(text) {
|
|
32
36
|
const segments = [];
|
|
33
37
|
const matches = [];
|
|
38
|
+
for (const m of text.matchAll(CODE_RE)) {
|
|
39
|
+
matches.push({ index: m.index, length: m[0].length, segment: { type: "code", value: m[1] } });
|
|
40
|
+
}
|
|
41
|
+
for (const m of text.matchAll(BOLD_RE)) {
|
|
42
|
+
matches.push({ index: m.index, length: m[0].length, segment: { type: "bold", value: m[1] } });
|
|
43
|
+
}
|
|
44
|
+
for (const m of text.matchAll(STRIKE_RE)) {
|
|
45
|
+
matches.push({ index: m.index, length: m[0].length, segment: { type: "strike", value: m[1] } });
|
|
46
|
+
}
|
|
47
|
+
for (const m of text.matchAll(ITALIC_RE)) {
|
|
48
|
+
matches.push({ index: m.index, length: m[0].length, segment: { type: "italic", value: m[1] ?? m[2] } });
|
|
49
|
+
}
|
|
34
50
|
for (const m of text.matchAll(URL_RE)) {
|
|
35
51
|
matches.push({ index: m.index, length: m[0].length, segment: { type: "link", url: m[0] } });
|
|
36
52
|
}
|
|
37
53
|
for (const m of text.matchAll(MENTION_RE)) {
|
|
38
54
|
matches.push({ index: m.index, length: m[0].length, segment: { type: "mention", name: m[1] } });
|
|
39
55
|
}
|
|
40
|
-
matches.sort((a, b) => a.index - b.index);
|
|
56
|
+
matches.sort((a, b) => a.index - b.index || b.length - a.length);
|
|
41
57
|
let cursor = 0;
|
|
42
58
|
for (const m of matches) {
|
|
43
59
|
if (m.index < cursor) continue;
|
|
@@ -52,6 +68,9 @@ export function renderMessageSegments(text) {
|
|
|
52
68
|
}
|
|
53
69
|
return segments.length > 0 ? segments : [{ type: "text", value: text }];
|
|
54
70
|
}
|
|
71
|
+
export function hasRichText(text) {
|
|
72
|
+
return /@\w+|https?:\/\/|\*\*[^*\n]+\*\*|~~[^~\n]+~~|`[^`\n]+`|\*[^*\n]+\*|_[^_\n]+_/.test(text);
|
|
73
|
+
}
|
|
55
74
|
export function previewMessageContent(content) {
|
|
56
75
|
const parsed = parseMessageContent(content);
|
|
57
76
|
if (parsed.kind === "doc-quote") {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Atom One — Nuxt UI syntax highlighting.
|
|
3
|
+
*
|
|
4
|
+
* Keeps Atom One Dark's familiar token roles but pulls each colour from the
|
|
5
|
+
* host's Nuxt UI theme tokens (`--ui-*` / `--color-*`), so the code editor
|
|
6
|
+
* matches the surrounding UI. Every value falls back to the canonical Atom
|
|
7
|
+
* One hex when the token isn't defined.
|
|
8
|
+
*
|
|
9
|
+
* A factory (not a const) because CodeMirror is lazy-loaded — `HighlightStyle`
|
|
10
|
+
* and the lezer `tags` come from the bundle resolved in `loadCodeMirror.ts`.
|
|
11
|
+
*/
|
|
12
|
+
type HighlightStyleStatic = (typeof import('@codemirror/language'))['HighlightStyle'];
|
|
13
|
+
type TagsObject = (typeof import('@lezer/highlight'))['tags'];
|
|
14
|
+
export declare function buildAtomOneNuxtHighlight(HighlightStyleCtor: HighlightStyleStatic, t: TagsObject): import("@codemirror/language").HighlightStyle;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function buildAtomOneNuxtHighlight(HighlightStyleCtor, t) {
|
|
2
|
+
return HighlightStyleCtor.define([
|
|
3
|
+
// Comments — dimmed + italic.
|
|
4
|
+
{ tag: [t.comment, t.lineComment, t.blockComment, t.docComment], color: "var(--ui-text-dimmed, #5c6370)", fontStyle: "italic" },
|
|
5
|
+
// Keywords / storage — brand colour (Atom One purple fallback).
|
|
6
|
+
{ tag: [t.keyword, t.moduleKeyword, t.controlKeyword, t.operatorKeyword, t.definitionKeyword, t.self], color: "var(--ui-primary, #c678dd)" },
|
|
7
|
+
// Strings + escapes — green.
|
|
8
|
+
{ tag: [t.string, t.special(t.string), t.docString, t.regexp, t.attributeValue], color: "var(--color-green-400, #98c379)" },
|
|
9
|
+
{ tag: [t.escape, t.character], color: "var(--color-cyan-400, #56b6c2)" },
|
|
10
|
+
// Numbers / constants / booleans — orange.
|
|
11
|
+
{ tag: [t.number, t.integer, t.float, t.bool, t.null, t.atom, t.unit, t.constant(t.name), t.standard(t.name)], color: "var(--color-orange-400, #d19a66)" },
|
|
12
|
+
// Functions / methods — blue.
|
|
13
|
+
{ tag: [t.function(t.variableName), t.function(t.propertyName), t.labelName, t.macroName], color: "var(--color-blue-400, #61afef)" },
|
|
14
|
+
// Types / classes / namespaces — amber.
|
|
15
|
+
{ tag: [t.typeName, t.className, t.namespace, t.definition(t.typeName)], color: "var(--color-amber-400, #e5c07b)" },
|
|
16
|
+
// Properties / attributes / tags — red.
|
|
17
|
+
{ tag: [t.propertyName, t.attributeName, t.tagName], color: "var(--color-red-400, #e06c75)" },
|
|
18
|
+
// Plain identifiers — body text colour.
|
|
19
|
+
{ tag: [t.variableName, t.definition(t.variableName)], color: "var(--ui-text-highlighted, #abb2bf)" },
|
|
20
|
+
// Operators — cyan.
|
|
21
|
+
{ tag: [t.operator, t.derefOperator, t.arithmeticOperator, t.logicOperator, t.bitwiseOperator, t.compareOperator, t.updateOperator], color: "var(--color-cyan-400, #56b6c2)" },
|
|
22
|
+
// Punctuation / brackets — muted.
|
|
23
|
+
{ tag: [t.punctuation, t.separator, t.bracket, t.angleBracket, t.squareBracket, t.paren, t.brace], color: "var(--ui-text-muted, #abb2bf)" },
|
|
24
|
+
// Meta / annotations — cyan.
|
|
25
|
+
{ tag: [t.meta, t.documentMeta, t.annotation, t.processingInstruction], color: "var(--color-cyan-400, #56b6c2)" },
|
|
26
|
+
// Markdown / prose niceties.
|
|
27
|
+
{ tag: [t.heading], fontWeight: "600", color: "var(--color-red-400, #e06c75)" },
|
|
28
|
+
{ tag: [t.strong], fontWeight: "600" },
|
|
29
|
+
{ tag: [t.emphasis], fontStyle: "italic" },
|
|
30
|
+
{ tag: [t.strikethrough], textDecoration: "line-through" },
|
|
31
|
+
{ tag: [t.link, t.url], color: "var(--ui-primary, #61afef)", textDecoration: "underline" },
|
|
32
|
+
{ tag: [t.invalid], color: "var(--ui-error, #e06c75)" }
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
@@ -138,6 +138,19 @@ export const DOC_TYPES = {
|
|
|
138
138
|
() => import("../components/renderers/ATableRenderer.vue")
|
|
139
139
|
)
|
|
140
140
|
},
|
|
141
|
+
sheets: {
|
|
142
|
+
key: "sheets",
|
|
143
|
+
label: "Sheets",
|
|
144
|
+
icon: "i-lucide-grid-3x3",
|
|
145
|
+
description: "Spreadsheet \u2014 cells, formulas, and formatting in a collaborative grid",
|
|
146
|
+
available: true,
|
|
147
|
+
supportsChildren: true,
|
|
148
|
+
childLabel: "Column",
|
|
149
|
+
grandchildLabel: "Cell",
|
|
150
|
+
component: defineDocComponent(
|
|
151
|
+
() => import("../components/renderers/ASheetsRenderer.client.vue")
|
|
152
|
+
)
|
|
153
|
+
},
|
|
141
154
|
outline: {
|
|
142
155
|
key: "outline",
|
|
143
156
|
label: "Outline",
|
|
@@ -226,6 +239,24 @@ export const DOC_TYPES = {
|
|
|
226
239
|
() => import("../components/renderers/AGraphRenderer.vue")
|
|
227
240
|
)
|
|
228
241
|
},
|
|
242
|
+
chart: {
|
|
243
|
+
key: "chart",
|
|
244
|
+
label: "Chart",
|
|
245
|
+
icon: "i-lucide-bar-chart-3",
|
|
246
|
+
description: "Charts \u2014 manual data points or aggregation over document trees",
|
|
247
|
+
available: true,
|
|
248
|
+
supportsChildren: true,
|
|
249
|
+
childLabel: "Data Point",
|
|
250
|
+
grandchildLabel: "Data Point",
|
|
251
|
+
component: defineDocComponent(
|
|
252
|
+
() => import("../components/renderers/AChartRenderer.client.vue")
|
|
253
|
+
),
|
|
254
|
+
metaSchema: [
|
|
255
|
+
{ type: "number", key: "number", step: 0.01, label: "Value" },
|
|
256
|
+
{ type: "colorPreset", key: "color", presets: ["#6366f1", "#ec4899", "#f97316", "#22c55e", "#3b82f6", "#a855f7", "#14b8a6", "#eab308"], label: "Color" },
|
|
257
|
+
{ type: "tags", key: "tags", label: "Tags" }
|
|
258
|
+
]
|
|
259
|
+
},
|
|
229
260
|
dashboard: {
|
|
230
261
|
key: "dashboard",
|
|
231
262
|
label: "Dashboard",
|
|
@@ -271,6 +302,18 @@ export const DOC_TYPES = {
|
|
|
271
302
|
{ type: "select", key: "slidesTransition", options: ["none", "fade", "slide"], label: "Transition" },
|
|
272
303
|
{ type: "colorPreset", key: "color", presets: ["#6366f1", "#ec4899", "#f97316", "#22c55e", "#3b82f6", "#a855f7"], label: "Accent" }
|
|
273
304
|
]
|
|
305
|
+
},
|
|
306
|
+
overview: {
|
|
307
|
+
key: "overview",
|
|
308
|
+
label: "Overview",
|
|
309
|
+
icon: "i-lucide-radar",
|
|
310
|
+
description: "Space home \u2014 activity, people, stats, and health at a glance",
|
|
311
|
+
available: true,
|
|
312
|
+
supportsChildren: true,
|
|
313
|
+
childLabel: "Page",
|
|
314
|
+
component: defineDocComponent(
|
|
315
|
+
() => import("../components/renderers/AOverviewRenderer.vue")
|
|
316
|
+
)
|
|
274
317
|
}
|
|
275
318
|
};
|
|
276
319
|
export const DEFAULT_DOC_TYPE = DOC_TYPES.doc;
|
|
@@ -28,5 +28,6 @@ export interface CodeMirrorBundle {
|
|
|
28
28
|
langVue: typeof import('@codemirror/lang-vue');
|
|
29
29
|
langJson: typeof import('@codemirror/lang-json');
|
|
30
30
|
yCollab: typeof import('y-codemirror.next');
|
|
31
|
+
lezerHighlight: typeof import('@lezer/highlight');
|
|
31
32
|
}
|
|
32
33
|
export declare function loadCodeMirror(): Promise<CodeMirrorBundle | null>;
|
|
@@ -17,7 +17,8 @@ export async function loadCodeMirror() {
|
|
|
17
17
|
"@codemirror/lang-css",
|
|
18
18
|
"@codemirror/lang-vue",
|
|
19
19
|
"@codemirror/lang-json",
|
|
20
|
-
"y-codemirror.next"
|
|
20
|
+
"y-codemirror.next",
|
|
21
|
+
"@lezer/highlight"
|
|
21
22
|
];
|
|
22
23
|
const [
|
|
23
24
|
view,
|
|
@@ -30,7 +31,8 @@ export async function loadCodeMirror() {
|
|
|
30
31
|
langCss,
|
|
31
32
|
langVue,
|
|
32
33
|
langJson,
|
|
33
|
-
yCollab
|
|
34
|
+
yCollab,
|
|
35
|
+
lezerHighlight
|
|
34
36
|
] = await Promise.all(names.map((n) => import(
|
|
35
37
|
/* @vite-ignore */
|
|
36
38
|
n
|
|
@@ -46,7 +48,8 @@ export async function loadCodeMirror() {
|
|
|
46
48
|
langCss,
|
|
47
49
|
langVue,
|
|
48
50
|
langJson,
|
|
49
|
-
yCollab
|
|
51
|
+
yCollab,
|
|
52
|
+
lezerHighlight
|
|
50
53
|
};
|
|
51
54
|
return cache;
|
|
52
55
|
} catch {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* titleSync — pure decision logic for keeping the H1 documentHeader and the
|
|
3
|
+
* tree-entry `label` field in agreement, plus a defensive `setHeaderText`.
|
|
4
|
+
*
|
|
5
|
+
* The two sources of truth are:
|
|
6
|
+
* - the `documentHeader` Y.XmlElement inside the child doc's Y.XmlFragment
|
|
7
|
+
* (rendered by TipTap as the H1 at the top of the page);
|
|
8
|
+
* - the `label` field on the doc's tree entry in the root provider's treeMap
|
|
9
|
+
* (rendered by the sidebar, breadcrumb, browser title, etc.).
|
|
10
|
+
*
|
|
11
|
+
* Past regressions in this area lost user titles because:
|
|
12
|
+
* - the editor mounted on an empty Y.XmlFragment, y-prosemirror seeded an
|
|
13
|
+
* empty documentHeader, then the server snapshot arrived with the real
|
|
14
|
+
* title and the local empty got merged in — sometimes winning the
|
|
15
|
+
* "canonical pick" inside enforceDocumentShape;
|
|
16
|
+
* - the post-sync flow only wrote header → tree on LOCAL updates, so a
|
|
17
|
+
* server-delivered title never reflected back into the tree label and the
|
|
18
|
+
* sidebar stayed stuck on "Untitled";
|
|
19
|
+
* - the tree-label → header watcher unconditionally called
|
|
20
|
+
* `setHeaderText(editor, newLabel || '')`, which on a transient
|
|
21
|
+
* `newLabel = undefined` would erase the user's title.
|
|
22
|
+
*
|
|
23
|
+
* `decideTitleSync` is the canonical answer for "what should I do given the
|
|
24
|
+
* current state of both sides?", returning one of three actions. It is pure
|
|
25
|
+
* so it can be exhaustively table-tested.
|
|
26
|
+
*
|
|
27
|
+
* `setHeaderText` is the only sanctioned way for the renderer to write into
|
|
28
|
+
* the documentHeader. It refuses to overwrite a non-empty header with an
|
|
29
|
+
* empty string unless the caller passes `force: true`.
|
|
30
|
+
*
|
|
31
|
+
* Ported 1:1 from cou-sh/app/utils/titleSync.ts.
|
|
32
|
+
*/
|
|
33
|
+
/** Sentinel placeholder label used by the tree when no real label has been set. */
|
|
34
|
+
export declare const UNTITLED = "Untitled";
|
|
35
|
+
/**
|
|
36
|
+
* The tree label is "empty" — meaning it carries no user-supplied title —
|
|
37
|
+
* when it's the empty string, null/undefined, or the literal placeholder
|
|
38
|
+
* `"Untitled"`. The two former cases come from a freshly-created tree row
|
|
39
|
+
* (no label yet); the latter comes from `useChildTree.renameEntry`
|
|
40
|
+
* canonicalising a cleared title to `"Untitled"` so the UI never has to
|
|
41
|
+
* render `null`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isEmptyTreeLabel(label: string | null | undefined): boolean;
|
|
44
|
+
export type TitleSyncAction = {
|
|
45
|
+
kind: 'noop';
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'header-from-tree';
|
|
48
|
+
text: string;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'tree-from-header';
|
|
51
|
+
label: string;
|
|
52
|
+
};
|
|
53
|
+
export interface TitleSyncInput {
|
|
54
|
+
/** Text content of the `documentHeader` node (what TipTap renders). */
|
|
55
|
+
headerText: string;
|
|
56
|
+
/**
|
|
57
|
+
* Current tree-entry label, or null/undefined if the entry hasn't loaded.
|
|
58
|
+
* Caller is expected to fall back to `props.docLabel` before passing in.
|
|
59
|
+
*/
|
|
60
|
+
treeLabel: string | null | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* True when the originating ProseMirror transaction was driven by a Yjs
|
|
63
|
+
* remote update (i.e. `transaction.getMeta('y-sync$')?.isChangeOrigin`).
|
|
64
|
+
* Used to disambiguate "the user just typed this" from "the server told
|
|
65
|
+
* us this".
|
|
66
|
+
*/
|
|
67
|
+
isRemoteUpdate: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether `syncHeader` has already performed its first-mount reconciliation
|
|
70
|
+
* for this editor instance. Set by the caller; the first call must pass
|
|
71
|
+
* `false` and every subsequent call must pass `true`.
|
|
72
|
+
*/
|
|
73
|
+
initialSyncDone: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Decide what (if anything) needs to change to keep the header and the tree
|
|
77
|
+
* label in agreement.
|
|
78
|
+
*
|
|
79
|
+
* The truth table — every cell is pinned by `test/title-sync-decision.test.ts`:
|
|
80
|
+
*
|
|
81
|
+
* ┌──────────────┬───────────────────┬───────────────────────────────────────┐
|
|
82
|
+
* │ headerText │ treeLabel │ result │
|
|
83
|
+
* ├──────────────┼───────────────────┼───────────────────────────────────────┤
|
|
84
|
+
* │ empty │ empty/Untitled │ noop (no information either way) │
|
|
85
|
+
* │ same │ same │ noop │
|
|
86
|
+
* │ has text │ empty/Untitled │ tree-from-header (RESCUE — incl. │
|
|
87
|
+
* │ │ │ remote: server delivered the title │
|
|
88
|
+
* │ │ │ via the doc fragment, tree needs │
|
|
89
|
+
* │ │ │ to catch up) │
|
|
90
|
+
* │ empty │ has text │ initial OR remote → header-from-tree │
|
|
91
|
+
* │ │ │ local + post-initial → tree-from- │
|
|
92
|
+
* │ │ │ header('Untitled') (user emptied │
|
|
93
|
+
* │ │ │ the title, so canonicalise) │
|
|
94
|
+
* │ different │ different │ initial OR local → tree-from-header │
|
|
95
|
+
* │ │ │ remote + post-initial → noop (the │
|
|
96
|
+
* │ │ │ label watcher reconciles header │
|
|
97
|
+
* │ │ │ from tree if needed) │
|
|
98
|
+
* └──────────────┴───────────────────┴───────────────────────────────────────┘
|
|
99
|
+
*/
|
|
100
|
+
export declare function decideTitleSync(input: TitleSyncInput): TitleSyncAction;
|
|
101
|
+
export interface SetHeaderTextOptions {
|
|
102
|
+
/**
|
|
103
|
+
* Allow overwriting a non-empty `documentHeader` with an empty string.
|
|
104
|
+
* Default `false`. The watcher on the tree label MUST NOT pass `true` — a
|
|
105
|
+
* transient `newLabel = undefined` during reload would otherwise erase the
|
|
106
|
+
* user's title. Setting `force: true` is reserved for the explicit "user
|
|
107
|
+
* emptied the title locally" path.
|
|
108
|
+
*/
|
|
109
|
+
force?: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Replace the inline text content of the first `documentHeader` node in
|
|
113
|
+
* `ed.state.doc`. Refuses the write when:
|
|
114
|
+
* - there is no documentHeader at index 0;
|
|
115
|
+
* - the header already has the requested text (cheap no-op);
|
|
116
|
+
* - the user's selection is inside the header (an external sync echo must
|
|
117
|
+
* never clobber in-flight typing);
|
|
118
|
+
* - `text` is empty AND the existing header is non-empty AND `force`
|
|
119
|
+
* wasn't passed (the no-destroy invariant).
|
|
120
|
+
*
|
|
121
|
+
* Returns `true` if a transaction was dispatched, `false` otherwise. Used
|
|
122
|
+
* by tests to assert the no-destroy invariant.
|
|
123
|
+
*/
|
|
124
|
+
export declare function setHeaderText(ed: {
|
|
125
|
+
state: any;
|
|
126
|
+
view: {
|
|
127
|
+
dispatch: (tr: any) => void;
|
|
128
|
+
hasFocus?: () => boolean;
|
|
129
|
+
};
|
|
130
|
+
}, text: string, opts?: SetHeaderTextOptions): boolean;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Fragment } from "@tiptap/pm/model";
|
|
2
|
+
export const UNTITLED = "Untitled";
|
|
3
|
+
export function isEmptyTreeLabel(label) {
|
|
4
|
+
return !label || label === UNTITLED;
|
|
5
|
+
}
|
|
6
|
+
export function decideTitleSync(input) {
|
|
7
|
+
const { headerText, treeLabel, isRemoteUpdate, initialSyncDone } = input;
|
|
8
|
+
const treeEmpty = isEmptyTreeLabel(treeLabel);
|
|
9
|
+
const headerEmpty = isEmptyTreeLabel(headerText);
|
|
10
|
+
if (treeEmpty && headerEmpty) return { kind: "noop" };
|
|
11
|
+
if (!headerEmpty && !treeEmpty && headerText === treeLabel) {
|
|
12
|
+
return { kind: "noop" };
|
|
13
|
+
}
|
|
14
|
+
if (!headerEmpty && treeEmpty) {
|
|
15
|
+
return { kind: "tree-from-header", label: headerText };
|
|
16
|
+
}
|
|
17
|
+
if (headerEmpty && !treeEmpty) {
|
|
18
|
+
if (!initialSyncDone) {
|
|
19
|
+
return { kind: "header-from-tree", text: treeLabel };
|
|
20
|
+
}
|
|
21
|
+
if (isRemoteUpdate) {
|
|
22
|
+
return { kind: "header-from-tree", text: treeLabel };
|
|
23
|
+
}
|
|
24
|
+
return { kind: "noop" };
|
|
25
|
+
}
|
|
26
|
+
if (!initialSyncDone) {
|
|
27
|
+
return { kind: "tree-from-header", label: headerText };
|
|
28
|
+
}
|
|
29
|
+
if (!isRemoteUpdate) {
|
|
30
|
+
return { kind: "tree-from-header", label: headerText };
|
|
31
|
+
}
|
|
32
|
+
return { kind: "noop" };
|
|
33
|
+
}
|
|
34
|
+
export function setHeaderText(ed, text, opts = {}) {
|
|
35
|
+
const first = ed.state.doc.firstChild;
|
|
36
|
+
if (!first || first.type.name !== "documentHeader") return false;
|
|
37
|
+
if (first.textContent === text) return false;
|
|
38
|
+
const hasFocus = typeof ed.view.hasFocus === "function" ? ed.view.hasFocus() : false;
|
|
39
|
+
if (hasFocus) {
|
|
40
|
+
const { $head } = ed.state.selection;
|
|
41
|
+
if ($head?.parent?.type?.name === "documentHeader") return false;
|
|
42
|
+
}
|
|
43
|
+
if (!text && first.textContent && !opts.force) return false;
|
|
44
|
+
const { tr, schema } = ed.state;
|
|
45
|
+
const to = 1 + first.content.size;
|
|
46
|
+
if (text) {
|
|
47
|
+
tr.replaceWith(1, to, schema.text(text));
|
|
48
|
+
} else {
|
|
49
|
+
tr.replaceWith(1, to, Fragment.empty);
|
|
50
|
+
}
|
|
51
|
+
ed.view.dispatch(tr);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abraca/nuxt",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "First-class Nuxt module for the Abracadabra CRDT collaboration platform",
|
|
5
5
|
"repository": "abracadabra/abracadabra-nuxt",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,7 +69,10 @@
|
|
|
69
69
|
"@tiptap/extension-text-align": "^3.0.0",
|
|
70
70
|
"@tiptap/extension-text-style": "^3.0.0",
|
|
71
71
|
"@tiptap/starter-kit": "^3.0.0",
|
|
72
|
+
"@tiptap/suggestion": "^3.0.0",
|
|
72
73
|
"@tiptap/vue-3": "^3.0.0",
|
|
74
|
+
"@unovis/ts": "^1.6.5",
|
|
75
|
+
"@unovis/vue": "^1.6.5",
|
|
73
76
|
"d3-force": "^3.0.0",
|
|
74
77
|
"jszip": "^3.0.0",
|
|
75
78
|
"lowlight": "^3.0.0",
|
|
@@ -131,6 +134,12 @@
|
|
|
131
134
|
},
|
|
132
135
|
"@tiptap/extension-emoji": {
|
|
133
136
|
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@unovis/vue": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
141
|
+
"@unovis/ts": {
|
|
142
|
+
"optional": true
|
|
134
143
|
}
|
|
135
144
|
},
|
|
136
145
|
"devDependencies": {
|
|
@@ -163,6 +172,8 @@
|
|
|
163
172
|
"@types/d3-force": "^3.0.10",
|
|
164
173
|
"@types/node": "latest",
|
|
165
174
|
"@types/ws": "^8.18.1",
|
|
175
|
+
"@unovis/ts": "^1.6.5",
|
|
176
|
+
"@unovis/vue": "^1.6.5",
|
|
166
177
|
"@vue/test-utils": "^2.4.10",
|
|
167
178
|
"changelogen": "^0.6.2",
|
|
168
179
|
"d3-force": "^3.0.0",
|