@abraca/nuxt 2.13.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 +2 -0
- package/dist/runtime/components/ACodeEditor.vue +123 -22
- 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 +1 -1
- package/dist/runtime/components/AEditor.vue +183 -15
- package/dist/runtime/components/ANodePanel.vue +84 -86
- 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/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/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/utils/codeHighlightStyle.d.ts +15 -0
- package/dist/runtime/utils/codeHighlightStyle.js +34 -0
- package/dist/runtime/utils/loadCodeMirror.d.ts +1 -0
- package/dist/runtime/utils/loadCodeMirror.js +6 -3
- package/package.json +2 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { useChildTree } from './useChildTree.js';
|
|
2
|
+
type Tree = ReturnType<typeof useChildTree>;
|
|
3
|
+
export interface DocSuggestItem {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
icon: string;
|
|
7
|
+
/** Dimmed ancestor path (e.g. "Kanban Board / Todo") for nesting context. */
|
|
8
|
+
prefix?: string;
|
|
9
|
+
/** When true, selecting this item creates a new child doc named `label`. */
|
|
10
|
+
isCreate?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Reactive state shared between the DocSuggest extension and its popup. */
|
|
13
|
+
export interface DocSuggestPopupState {
|
|
14
|
+
active: boolean;
|
|
15
|
+
mode: 'link' | 'embed';
|
|
16
|
+
query: string;
|
|
17
|
+
items: DocSuggestItem[];
|
|
18
|
+
index: number;
|
|
19
|
+
rect: {
|
|
20
|
+
left: number;
|
|
21
|
+
top: number;
|
|
22
|
+
bottom: number;
|
|
23
|
+
} | null;
|
|
24
|
+
onSelect: ((item: DocSuggestItem) => void) | null;
|
|
25
|
+
}
|
|
26
|
+
export declare function emptyDocSuggestState(): DocSuggestPopupState;
|
|
27
|
+
/**
|
|
28
|
+
* Build a whole-space doc search for the `[[` / `![[` popups. Ranks entries by
|
|
29
|
+
* label match and appends a "Create …" item when the query has no exact match.
|
|
30
|
+
* Each result carries a dimmed ancestor-path `prefix` for nesting context — the
|
|
31
|
+
* popup stays a flat, searchable list (no tree drill-down).
|
|
32
|
+
*/
|
|
33
|
+
export declare function makeDocSearch(tree: Tree, currentDocId: string): (rawQuery: string) => DocSuggestItem[];
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { resolveDocType } from "../utils/docTypes.js";
|
|
2
|
+
export function emptyDocSuggestState() {
|
|
3
|
+
return { active: false, mode: "link", query: "", items: [], index: 0, rect: null, onSelect: null };
|
|
4
|
+
}
|
|
5
|
+
const MAX_RESULTS = 20;
|
|
6
|
+
export function makeDocSearch(tree, currentDocId) {
|
|
7
|
+
return (rawQuery) => {
|
|
8
|
+
const query = rawQuery.trim();
|
|
9
|
+
const q = query.toLowerCase();
|
|
10
|
+
const entries = tree.entries.value;
|
|
11
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
12
|
+
const pathOf = (parentId) => {
|
|
13
|
+
const out = [];
|
|
14
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15
|
+
let pid = parentId;
|
|
16
|
+
while (pid && byId.has(pid) && !seen.has(pid)) {
|
|
17
|
+
seen.add(pid);
|
|
18
|
+
const e = byId.get(pid);
|
|
19
|
+
out.unshift(e.label || "Untitled");
|
|
20
|
+
pid = e.parentId;
|
|
21
|
+
}
|
|
22
|
+
return out.join(" / ");
|
|
23
|
+
};
|
|
24
|
+
const scored = [];
|
|
25
|
+
for (const e of entries) {
|
|
26
|
+
if (e.id === currentDocId) continue;
|
|
27
|
+
const label = (e.label || "").trim();
|
|
28
|
+
if (!label) continue;
|
|
29
|
+
const lower = label.toLowerCase();
|
|
30
|
+
let score = -1;
|
|
31
|
+
if (!q) score = 0;
|
|
32
|
+
else if (lower === q) score = 3;
|
|
33
|
+
else if (lower.startsWith(q)) score = 2;
|
|
34
|
+
else if (lower.includes(q)) score = 1;
|
|
35
|
+
if (score < 0) continue;
|
|
36
|
+
scored.push({ entry: e, score });
|
|
37
|
+
}
|
|
38
|
+
scored.sort((a, b) => b.score - a.score || a.entry.label.localeCompare(b.entry.label));
|
|
39
|
+
const items = scored.slice(0, MAX_RESULTS).map(({ entry }) => ({
|
|
40
|
+
id: entry.id,
|
|
41
|
+
label: entry.label,
|
|
42
|
+
icon: resolveDocType(entry.type).icon,
|
|
43
|
+
prefix: pathOf(entry.parentId) || void 0
|
|
44
|
+
}));
|
|
45
|
+
const hasExact = items.some((i) => i.label.toLowerCase() === q);
|
|
46
|
+
if (query && !hasExact) {
|
|
47
|
+
items.push({
|
|
48
|
+
id: "__create__",
|
|
49
|
+
label: `Create "${query}"`,
|
|
50
|
+
icon: "i-lucide-file-plus",
|
|
51
|
+
isCreate: true
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return items;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -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;
|
|
@@ -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
|
+
}
|
|
@@ -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 {
|
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,6 +69,7 @@
|
|
|
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",
|
|
73
74
|
"@unovis/ts": "^1.6.5",
|
|
74
75
|
"@unovis/vue": "^1.6.5",
|