@abraca/nuxt 2.13.0 → 2.15.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/assets/editor.css +3 -1
- package/dist/runtime/components/ACodeEditor.vue +138 -23
- 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 +91 -91
- 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/aware/ASlider.d.vue.ts +1 -1
- package/dist/runtime/components/aware/ASlider.vue.d.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.d.vue.ts +1 -1
- package/dist/runtime/components/docs/ADocsSearchButton.vue.d.ts +1 -1
- package/dist/runtime/components/editor/AColorPalettePopover.vue +97 -5
- 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/editor/AIconPickerPopover.vue +81 -3
- package/dist/runtime/components/editor/AMetaNumberStepper.d.vue.ts +40 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue +214 -0
- package/dist/runtime/components/editor/AMetaNumberStepper.vue.d.ts +40 -0
- package/dist/runtime/components/renderers/ACalendarRenderer.vue +7 -1
- 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/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/useTouchDrag.d.ts +21 -4
- package/dist/runtime/composables/useTouchDrag.js +30 -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/extensions/views/MetaFieldView.vue +17 -28
- 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
package/dist/module.d.mts
CHANGED
|
@@ -59,6 +59,7 @@ declare module '@nuxt/schema' {
|
|
|
59
59
|
};
|
|
60
60
|
debug: boolean;
|
|
61
61
|
docBasePath: string;
|
|
62
|
+
docPicker: 'inline' | 'command-palette';
|
|
62
63
|
guestName: {
|
|
63
64
|
adjectives: string[];
|
|
64
65
|
nouns: string[];
|
|
@@ -258,6 +259,20 @@ interface ModuleOptions {
|
|
|
258
259
|
* docBasePath: '' → /{docId}
|
|
259
260
|
*/
|
|
260
261
|
docBasePath?: string;
|
|
262
|
+
/**
|
|
263
|
+
* How documents are picked for cross-document references (doc links/embeds).
|
|
264
|
+
*
|
|
265
|
+
* - `'inline'` (default) — typing `[[` opens an inline mention-style doc-link
|
|
266
|
+
* popup, and `![[` an inline doc-embed popup (flat search + ancestor-path
|
|
267
|
+
* prefix). The slash commands + doc-link toolbar button still open the
|
|
268
|
+
* command palette.
|
|
269
|
+
* - `'command-palette'` — disables the inline `[[` / `![[` triggers; the only
|
|
270
|
+
* doc-reference entry points are the slash commands + toolbar button, which
|
|
271
|
+
* open a `UCommandPalette` modal.
|
|
272
|
+
*
|
|
273
|
+
* Default: 'inline'.
|
|
274
|
+
*/
|
|
275
|
+
docPicker?: 'inline' | 'command-palette';
|
|
261
276
|
/**
|
|
262
277
|
* Mapbox GL access token for the Map renderer.
|
|
263
278
|
* Can be overridden at runtime via NUXT_PUBLIC_ABRACADABRA_MAPBOX_TOKEN.
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -50,6 +50,7 @@ const module$1 = defineNuxtModule({
|
|
|
50
50
|
},
|
|
51
51
|
debug: false,
|
|
52
52
|
docBasePath: "/doc",
|
|
53
|
+
docPicker: "inline",
|
|
53
54
|
guestName: {},
|
|
54
55
|
webrtc: {
|
|
55
56
|
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
|
@@ -96,6 +97,7 @@ const module$1 = defineNuxtModule({
|
|
|
96
97
|
server: options.server ?? {},
|
|
97
98
|
debug: options.debug ?? false,
|
|
98
99
|
docBasePath: options.docBasePath ?? "/doc",
|
|
100
|
+
docPicker: options.docPicker ?? "inline",
|
|
99
101
|
mapboxToken: options.mapboxToken ?? "",
|
|
100
102
|
guestName: {
|
|
101
103
|
adjectives: options.guestName?.adjectives ?? [],
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
html
|
|
1
|
+
html .cm-editor,html .cm-editor .cm-content,html .cm-editor .cm-gutters,html .cm-editor .cm-scroller,html .cm-editor .cm-tooltip{font-family:var(
|
|
2
|
+
--font-code,ui-monospace,"SF Mono",Menlo,Monaco,Consolas,monospace
|
|
3
|
+
)!important}html.dark .tiptap .shiki,html.dark .tiptap .shiki span{background-color:var(--ui-bg-muted)!important;color:var(--shiki-dark)!important}.collaboration-carets__caret{border-left:1px solid #0d0d0d;border-right:1px solid #0d0d0d;margin-left:-1px;margin-right:-1px;opacity:0;pointer-events:none;position:relative;transition:opacity .3s ease;word-break:normal}.collaboration-carets__caret.is-hidden{display:none}.collaboration-carets__caret.is-active{opacity:1}.collaboration-carets__label{border-radius:3px 3px 3px 0;color:#0d0d0d;font-size:12px;font-style:normal;font-weight:600;left:-1px;line-height:normal;padding:.1rem .3rem;position:absolute;top:-1.4em;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.ProseMirror-yjs-selection,.collaboration-carets__selection{background-color:var(--collaboration-selection-color)!important;pointer-events:none}.search-highlight{background-color:color-mix(in srgb,var(--color-primary-400) 35%,transparent);border-radius:2px;padding:0 1px}.doc-passage-highlight{background-color:color-mix(in srgb,var(--color-success-400) 35%,transparent);border-radius:2px;padding:0 1px}.tiptap{min-height:100%;padding-bottom:8rem}.tiptap.file-drop-active{border-radius:4px;outline:2px dashed var(--ui-primary);outline-offset:4px}.tiptap .document-header{font-size:2.5rem;font-weight:800;letter-spacing:-.025em;line-height:1.2;margin-bottom:1rem}.tiptap [data-type=document-meta]{align-items:center;display:flex;flex-wrap:wrap;font-size:.875rem;gap:.375rem;line-height:1.75rem;margin-bottom:1.5rem;min-height:1.75rem}.tiptap [data-type=document-meta][data-cursor-in-meta=true][data-empty=true]:after{color:var(--ui-text-dimmed);content:"Type '/' to add a property…";pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.prose-variant .tiptap [data-type=document-meta]{display:none}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, shallowRef, watch, computed, onMounted, onBeforeUnmount } from "vue";
|
|
3
3
|
import { loadCodeMirror } from "../utils/loadCodeMirror";
|
|
4
|
+
import { buildAtomOneNuxtHighlight } from "../utils/codeHighlightStyle";
|
|
4
5
|
import { useNuxtApp } from "#imports";
|
|
5
6
|
const props = defineProps({
|
|
6
7
|
provider: { type: null, required: true },
|
|
@@ -47,60 +48,148 @@ function buildTheme(bundle) {
|
|
|
47
48
|
backgroundColor: "var(--ui-bg)",
|
|
48
49
|
color: "var(--ui-text-highlighted)",
|
|
49
50
|
height: "100%",
|
|
50
|
-
fontSize: "13px"
|
|
51
|
-
fontFamily: 'ui-monospace, "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", Menlo, Monaco, Consolas, monospace'
|
|
51
|
+
fontSize: "13px"
|
|
52
52
|
},
|
|
53
|
+
// Honour a host-defined code font (`--font-code`) when present; falls back
|
|
54
|
+
// to the system monospace stack. `!important` + the `.cm-content`/
|
|
55
|
+
// `.cm-gutters` specificity guards against an aggressive host body-font
|
|
56
|
+
// rule (e.g. `html[data-font] *`) leaking the UI sans-serif into the editor.
|
|
53
57
|
".cm-content": {
|
|
54
|
-
fontFamily: "
|
|
58
|
+
fontFamily: 'var(--font-code, ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace) !important',
|
|
55
59
|
caretColor: "var(--ui-text-highlighted)",
|
|
56
|
-
padding
|
|
60
|
+
// Top padding leaves room for a remote caret's name badge on the FIRST
|
|
61
|
+
// line — y-codemirror positions it at `top: -1.4em`, so without headroom
|
|
62
|
+
// it renders above the content box and is clipped by the toolbar above.
|
|
63
|
+
padding: "1.6em 0 8px"
|
|
57
64
|
},
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
// Local caret — `drawSelection()` hides the native caret and draws this
|
|
66
|
+
// bar, so style it to read like the native caret used elsewhere in the
|
|
67
|
+
// app: a crisp 2px stroke in the body text colour.
|
|
68
|
+
".cm-cursor, .cm-dropCursor": {
|
|
69
|
+
borderLeftColor: "var(--ui-text-highlighted)",
|
|
70
|
+
borderLeftWidth: "2px"
|
|
60
71
|
},
|
|
61
72
|
".cm-activeLine": {
|
|
62
73
|
backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)"
|
|
63
74
|
},
|
|
64
75
|
".cm-activeLineGutter": {
|
|
65
|
-
backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)"
|
|
76
|
+
backgroundColor: "color-mix(in srgb, var(--ui-bg-elevated) 50%, transparent)",
|
|
77
|
+
color: "var(--ui-text)"
|
|
66
78
|
},
|
|
67
79
|
".cm-gutters": {
|
|
68
|
-
fontFamily: "
|
|
80
|
+
fontFamily: 'var(--font-code, ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace) !important',
|
|
69
81
|
backgroundColor: "var(--ui-bg)",
|
|
70
82
|
color: "var(--ui-text-dimmed)",
|
|
71
83
|
border: "none",
|
|
72
84
|
borderRight: "1px solid var(--ui-border)"
|
|
73
85
|
},
|
|
86
|
+
".cm-foldPlaceholder": {
|
|
87
|
+
backgroundColor: "var(--ui-bg-elevated)",
|
|
88
|
+
color: "var(--ui-text-muted)",
|
|
89
|
+
border: "1px solid var(--ui-border)"
|
|
90
|
+
},
|
|
91
|
+
// Local selection — translucent primary tint, brighter while focused.
|
|
74
92
|
".cm-selectionBackground": {
|
|
75
93
|
backgroundColor: "color-mix(in srgb, var(--ui-primary) 25%, transparent) !important"
|
|
76
94
|
},
|
|
77
95
|
"&.cm-focused .cm-selectionBackground": {
|
|
78
|
-
backgroundColor: "color-mix(in srgb, var(--ui-primary)
|
|
96
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 40%, transparent) !important"
|
|
79
97
|
},
|
|
80
98
|
".cm-matchingBracket": {
|
|
81
99
|
backgroundColor: "color-mix(in srgb, var(--ui-primary) 20%, transparent)",
|
|
82
100
|
outline: "1px solid color-mix(in srgb, var(--ui-primary) 40%, transparent)"
|
|
83
101
|
},
|
|
102
|
+
// Occurrences of the current selection (highlightSelectionMatches).
|
|
103
|
+
".cm-selectionMatch": {
|
|
104
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 15%, transparent)"
|
|
105
|
+
},
|
|
106
|
+
// Find/replace panel + search hits — themed to the UI tokens.
|
|
107
|
+
".cm-panels": {
|
|
108
|
+
backgroundColor: "var(--ui-bg-elevated)",
|
|
109
|
+
color: "var(--ui-text)"
|
|
110
|
+
},
|
|
111
|
+
".cm-panels.cm-panels-top": { borderBottom: "1px solid var(--ui-border)" },
|
|
112
|
+
".cm-panels.cm-panels-bottom": { borderTop: "1px solid var(--ui-border)" },
|
|
113
|
+
".cm-searchMatch": {
|
|
114
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 25%, transparent)",
|
|
115
|
+
outline: "1px solid color-mix(in srgb, var(--ui-primary) 45%, transparent)",
|
|
116
|
+
borderRadius: "2px"
|
|
117
|
+
},
|
|
118
|
+
".cm-searchMatch-selected": {
|
|
119
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 45%, transparent) !important"
|
|
120
|
+
},
|
|
121
|
+
".cm-textfield": {
|
|
122
|
+
backgroundColor: "var(--ui-bg)",
|
|
123
|
+
color: "var(--ui-text)",
|
|
124
|
+
border: "1px solid var(--ui-border)",
|
|
125
|
+
borderRadius: "4px"
|
|
126
|
+
},
|
|
127
|
+
".cm-button": {
|
|
128
|
+
backgroundColor: "var(--ui-bg)",
|
|
129
|
+
backgroundImage: "none",
|
|
130
|
+
color: "var(--ui-text)",
|
|
131
|
+
border: "1px solid var(--ui-border)",
|
|
132
|
+
borderRadius: "4px"
|
|
133
|
+
},
|
|
134
|
+
// Autocomplete tooltip.
|
|
135
|
+
".cm-tooltip": {
|
|
136
|
+
backgroundColor: "var(--ui-bg-elevated)",
|
|
137
|
+
color: "var(--ui-text)",
|
|
138
|
+
border: "1px solid var(--ui-border)",
|
|
139
|
+
borderRadius: "6px"
|
|
140
|
+
},
|
|
141
|
+
".cm-tooltip-autocomplete > ul > li[aria-selected]": {
|
|
142
|
+
backgroundColor: "var(--ui-primary)",
|
|
143
|
+
color: "var(--ui-bg)"
|
|
144
|
+
},
|
|
145
|
+
// ── Remote presence (y-codemirror.next) — matches the TipTap editor's
|
|
146
|
+
// `.collaboration-carets__*` look so multiplayer feels identical across
|
|
147
|
+
// both editors. Per-user colour stays inline (set from awareness
|
|
148
|
+
// `user.color`); we restyle shape only. The caret already matches TipTap
|
|
149
|
+
// (1px border bars, -1px margins); we only fix the label badge + hide the
|
|
150
|
+
// caret dot.
|
|
151
|
+
".cm-ySelection": {
|
|
152
|
+
borderRadius: "1px",
|
|
153
|
+
// Fallback selection fill. y-codemirror paints the band via an inline
|
|
154
|
+
// `background-color: <user.colorLight>`; a valid inline value wins by
|
|
155
|
+
// specificity and keeps the per-user colour. But peers that broadcast
|
|
156
|
+
// only `user.color` (older builds, native gpui/apertura/aperio clients)
|
|
157
|
+
// leave colorLight as y-codemirror's invalid `color + '33'` default,
|
|
158
|
+
// which the browser drops — making the band invisible while the caret
|
|
159
|
+
// (which uses `color` directly) still shows. This neutral tint then
|
|
160
|
+
// applies, so a remote selection is always visible.
|
|
161
|
+
backgroundColor: "color-mix(in srgb, var(--ui-primary) 30%, transparent)"
|
|
162
|
+
},
|
|
163
|
+
".cm-ySelectionCaretDot": { display: "none" },
|
|
84
164
|
".cm-ySelectionInfo": {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
padding: "1px 4px",
|
|
88
|
-
borderRadius: "3px",
|
|
89
|
-
opacity: "0.9",
|
|
90
|
-
position: "absolute",
|
|
91
|
-
top: "-1.3em",
|
|
165
|
+
opacity: "1",
|
|
166
|
+
top: "-1.4em",
|
|
92
167
|
left: "-1px",
|
|
168
|
+
padding: "0.1rem 0.3rem",
|
|
169
|
+
borderRadius: "3px 3px 3px 0",
|
|
170
|
+
fontFamily: "system-ui, sans-serif",
|
|
171
|
+
fontSize: "12px",
|
|
172
|
+
fontStyle: "normal",
|
|
173
|
+
fontWeight: "600",
|
|
174
|
+
lineHeight: "normal",
|
|
175
|
+
color: "#0d0d0d",
|
|
93
176
|
whiteSpace: "nowrap",
|
|
94
|
-
|
|
95
|
-
}
|
|
177
|
+
transitionDelay: "0s"
|
|
178
|
+
},
|
|
179
|
+
".cm-ySelectionCaret:hover > .cm-ySelectionInfo": { opacity: "1" }
|
|
96
180
|
}, { dark: true });
|
|
97
181
|
}
|
|
98
182
|
let xmlObserverCleanup = null;
|
|
183
|
+
let syncedOff = null;
|
|
99
184
|
function destroyEditor() {
|
|
100
185
|
if (xmlObserverCleanup) {
|
|
101
186
|
xmlObserverCleanup();
|
|
102
187
|
xmlObserverCleanup = null;
|
|
103
188
|
}
|
|
189
|
+
if (syncedOff) {
|
|
190
|
+
syncedOff();
|
|
191
|
+
syncedOff = null;
|
|
192
|
+
}
|
|
104
193
|
if (editorView.value) {
|
|
105
194
|
editorView.value.destroy();
|
|
106
195
|
editorView.value = null;
|
|
@@ -110,7 +199,7 @@ function createEditor(bundle, container) {
|
|
|
110
199
|
const { EditorView, lineNumbers, highlightActiveLine, highlightActiveLineGutter, drawSelection, rectangularSelection, crosshairCursor, highlightSpecialChars, dropCursor, keymap } = bundle.view;
|
|
111
200
|
const { EditorState } = bundle.state;
|
|
112
201
|
const { defaultKeymap, history, historyKeymap, indentWithTab } = bundle.commands;
|
|
113
|
-
const { syntaxHighlighting,
|
|
202
|
+
const { syntaxHighlighting, HighlightStyle, indentOnInput, bracketMatching, foldGutter, foldKeymap } = bundle.language;
|
|
114
203
|
const { closeBrackets, closeBracketsKeymap, autocompletion, completionKeymap } = bundle.autocomplete;
|
|
115
204
|
const { searchKeymap, highlightSelectionMatches } = bundle.search;
|
|
116
205
|
const baseExtensions = [
|
|
@@ -122,7 +211,7 @@ function createEditor(bundle, container) {
|
|
|
122
211
|
dropCursor(),
|
|
123
212
|
EditorState.allowMultipleSelections.of(true),
|
|
124
213
|
indentOnInput(),
|
|
125
|
-
syntaxHighlighting(
|
|
214
|
+
syntaxHighlighting(buildAtomOneNuxtHighlight(HighlightStyle, bundle.lezerHighlight.tags), { fallback: true }),
|
|
126
215
|
bracketMatching(),
|
|
127
216
|
closeBrackets(),
|
|
128
217
|
autocompletion(),
|
|
@@ -168,9 +257,14 @@ function createEditor(bundle, container) {
|
|
|
168
257
|
const ytext = ydoc.getText(props.fieldName);
|
|
169
258
|
const awareness = prov.awareness;
|
|
170
259
|
if (awareness && userName?.value && publicKeyB64?.value) {
|
|
260
|
+
const color = userColor?.value ?? "#888";
|
|
171
261
|
awareness.setLocalStateField("user", {
|
|
172
262
|
name: userName.value,
|
|
173
|
-
color
|
|
263
|
+
color,
|
|
264
|
+
// Translucent fill for remote selection blocks (y-codemirror.next).
|
|
265
|
+
// Its default `color + '33'` is invalid for hsl()/named/var() colours,
|
|
266
|
+
// leaving the selection invisible — supply a format-agnostic value.
|
|
267
|
+
colorLight: `color-mix(in srgb, ${color} 30%, transparent)`,
|
|
174
268
|
publicKey: publicKeyB64.value
|
|
175
269
|
});
|
|
176
270
|
}
|
|
@@ -180,10 +274,25 @@ function createEditor(bundle, container) {
|
|
|
180
274
|
} else {
|
|
181
275
|
extensions.push(history());
|
|
182
276
|
}
|
|
183
|
-
|
|
277
|
+
const view = new EditorView({
|
|
184
278
|
state: EditorState.create({ doc: ytext.toString(), extensions }),
|
|
185
279
|
parent: container
|
|
186
280
|
});
|
|
281
|
+
let rebuiltOnSync = false;
|
|
282
|
+
const onSynced = () => {
|
|
283
|
+
if (rebuiltOnSync) return;
|
|
284
|
+
const v = editorView.value;
|
|
285
|
+
const liveYtext = prov.document?.getText?.(props.fieldName);
|
|
286
|
+
if (!v || !liveYtext) return;
|
|
287
|
+
if (liveYtext.toString() !== v.state.doc.toString()) {
|
|
288
|
+
rebuiltOnSync = true;
|
|
289
|
+
void mount();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
prov.on?.("synced", onSynced);
|
|
293
|
+
syncedOff = () => prov.off?.("synced", onSynced);
|
|
294
|
+
if (prov.isSynced) Promise.resolve().then(onSynced);
|
|
295
|
+
return view;
|
|
187
296
|
}
|
|
188
297
|
return new EditorView({
|
|
189
298
|
state: EditorState.create({ doc: "", extensions: [...baseExtensions, history()] }),
|
|
@@ -224,7 +333,13 @@ watch(
|
|
|
224
333
|
([n, c, k]) => {
|
|
225
334
|
const awareness = props.provider?.awareness;
|
|
226
335
|
if (!awareness || !n || !k) return;
|
|
227
|
-
|
|
336
|
+
const color = c ?? "#888";
|
|
337
|
+
awareness.setLocalStateField("user", {
|
|
338
|
+
name: n,
|
|
339
|
+
color,
|
|
340
|
+
colorLight: `color-mix(in srgb, ${color} 30%, transparent)`,
|
|
341
|
+
publicKey: k
|
|
342
|
+
});
|
|
228
343
|
}
|
|
229
344
|
);
|
|
230
345
|
onBeforeUnmount(destroyEditor);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type DocViewTab = 'pageType' | 'editor' | 'chat' | 'settings';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Active tab. `null` (or absent) = nothing highlighted (e.g. another tab is active). */
|
|
4
|
+
modelValue?: DocViewTab | null;
|
|
5
|
+
docId: string;
|
|
6
|
+
docType?: string;
|
|
7
|
+
chatUnread?: number;
|
|
8
|
+
/** Disable the editor segment (e.g. main view is already the editor). */
|
|
9
|
+
editorDisabled?: boolean;
|
|
10
|
+
/** Disable the page-type segment (e.g. provider not ready). */
|
|
11
|
+
pageTypeDisabled?: boolean;
|
|
12
|
+
/** Whether the user may assign/change the page type (gates the + dropdown). */
|
|
13
|
+
canChangeType?: boolean;
|
|
14
|
+
/** Render the chat slot. Off → omitted (e.g. features.chat disabled). */
|
|
15
|
+
showChat?: boolean;
|
|
16
|
+
/** Render the settings slot. Off → omitted. */
|
|
17
|
+
showSettings?: boolean;
|
|
18
|
+
/** Suppress tooltips (touch devices / during open animations). */
|
|
19
|
+
disableTooltips?: boolean;
|
|
20
|
+
size?: 'sm' | 'md';
|
|
21
|
+
};
|
|
22
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
|
+
select: (tab: DocViewTab) => any;
|
|
24
|
+
"update:modelValue": (tab: DocViewTab) => any;
|
|
25
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
26
|
+
onSelect?: ((tab: DocViewTab) => any) | undefined;
|
|
27
|
+
"onUpdate:modelValue"?: ((tab: DocViewTab) => any) | undefined;
|
|
28
|
+
}>, {
|
|
29
|
+
size: "sm" | "md";
|
|
30
|
+
modelValue: DocViewTab | null;
|
|
31
|
+
chatUnread: number;
|
|
32
|
+
editorDisabled: boolean;
|
|
33
|
+
pageTypeDisabled: boolean;
|
|
34
|
+
canChangeType: boolean;
|
|
35
|
+
showChat: boolean;
|
|
36
|
+
showSettings: boolean;
|
|
37
|
+
disableTooltips: boolean;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
39
|
+
declare const _default: typeof __VLS_export;
|
|
40
|
+
export default _default;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, onBeforeUnmount } from "vue";
|
|
3
|
+
import { useAbracadabra } from "../composables/useAbracadabra";
|
|
4
|
+
import { useSyncedMap } from "../composables/useYDoc";
|
|
5
|
+
import { useAbraLocale } from "../composables/useAbraLocale";
|
|
6
|
+
import { resolveDocType, getAvailableDocTypes } from "../utils/docTypes";
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
modelValue: { type: [String, null], required: false, default: null },
|
|
9
|
+
docId: { type: String, required: true },
|
|
10
|
+
docType: { type: String, required: false },
|
|
11
|
+
chatUnread: { type: Number, required: false, default: 0 },
|
|
12
|
+
editorDisabled: { type: Boolean, required: false, default: false },
|
|
13
|
+
pageTypeDisabled: { type: Boolean, required: false, default: false },
|
|
14
|
+
canChangeType: { type: Boolean, required: false, default: true },
|
|
15
|
+
showChat: { type: Boolean, required: false, default: true },
|
|
16
|
+
showSettings: { type: Boolean, required: false, default: true },
|
|
17
|
+
disableTooltips: { type: Boolean, required: false, default: false },
|
|
18
|
+
size: { type: String, required: false, default: "md" }
|
|
19
|
+
});
|
|
20
|
+
const emit = defineEmits(["update:modelValue", "select"]);
|
|
21
|
+
const locale = computed(() => useAbraLocale("nodePanel"));
|
|
22
|
+
const { doc, registry } = useAbracadabra();
|
|
23
|
+
const treeMap = useSyncedMap(doc, "doc-tree");
|
|
24
|
+
const docTypeDef = computed(() => resolveDocType(props.docType, registry));
|
|
25
|
+
const hasPageType = computed(() => !!(props.docType && props.docType !== "doc"));
|
|
26
|
+
const docTypeItems = computed(
|
|
27
|
+
() => getAvailableDocTypes(registry).filter((dt) => dt.key !== props.docType).map((dt) => ({
|
|
28
|
+
label: dt.label,
|
|
29
|
+
icon: dt.icon,
|
|
30
|
+
onSelect() {
|
|
31
|
+
const entry = treeMap.get(props.docId);
|
|
32
|
+
if (!entry) return;
|
|
33
|
+
if (entry.type === dt.key) return;
|
|
34
|
+
treeMap.set(props.docId, { ...entry, type: dt.key, updatedAt: Date.now() });
|
|
35
|
+
}
|
|
36
|
+
}))
|
|
37
|
+
);
|
|
38
|
+
const visibleTabs = computed(() => {
|
|
39
|
+
const t = ["pageType", "editor"];
|
|
40
|
+
if (props.showChat) t.push("chat");
|
|
41
|
+
if (props.showSettings) t.push("settings");
|
|
42
|
+
return t;
|
|
43
|
+
});
|
|
44
|
+
function select(tab) {
|
|
45
|
+
emit("update:modelValue", tab);
|
|
46
|
+
emit("select", tab);
|
|
47
|
+
}
|
|
48
|
+
const typeMenuOpen = ref(false);
|
|
49
|
+
let pressTimer = null;
|
|
50
|
+
let longFired = false;
|
|
51
|
+
function onTypePointerDown() {
|
|
52
|
+
longFired = false;
|
|
53
|
+
cancelTypePress();
|
|
54
|
+
pressTimer = setTimeout(() => {
|
|
55
|
+
longFired = true;
|
|
56
|
+
typeMenuOpen.value = true;
|
|
57
|
+
}, 400);
|
|
58
|
+
}
|
|
59
|
+
function cancelTypePress() {
|
|
60
|
+
if (pressTimer) {
|
|
61
|
+
clearTimeout(pressTimer);
|
|
62
|
+
pressTimer = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function onTypeClick() {
|
|
66
|
+
cancelTypePress();
|
|
67
|
+
if (longFired) {
|
|
68
|
+
longFired = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
select("pageType");
|
|
72
|
+
}
|
|
73
|
+
onBeforeUnmount(cancelTypePress);
|
|
74
|
+
const activeIndex = computed(() => {
|
|
75
|
+
const v = props.modelValue;
|
|
76
|
+
if (!v) return -1;
|
|
77
|
+
if (v === "pageType" && !hasPageType.value) return -1;
|
|
78
|
+
return visibleTabs.value.indexOf(v);
|
|
79
|
+
});
|
|
80
|
+
const indicatorVisible = computed(() => activeIndex.value >= 0);
|
|
81
|
+
const segPx = computed(() => props.size === "sm" ? 24 : 28);
|
|
82
|
+
const iconClass = computed(() => props.size === "sm" ? "size-3" : "size-3.5");
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<div
|
|
87
|
+
class="dvt"
|
|
88
|
+
:class="`dvt-${size}`"
|
|
89
|
+
:style="{ '--dvt-seg': segPx + 'px' }"
|
|
90
|
+
>
|
|
91
|
+
<!-- Sliding active pill — like Nuxt UI tabs. Equal-width segments make the
|
|
92
|
+
offset a simple multiple of the segment size, no DOM measuring. -->
|
|
93
|
+
<span
|
|
94
|
+
class="dvt-indicator"
|
|
95
|
+
:class="{ 'dvt-indicator-hidden': !indicatorVisible }"
|
|
96
|
+
:style="{ transform: `translateX(${Math.max(activeIndex, 0) * 100}%)` }"
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<!-- Slot 1: page type (selectable tab) or + (assign-type dropdown) -->
|
|
101
|
+
<UDropdownMenu
|
|
102
|
+
v-if="!hasPageType"
|
|
103
|
+
:items="[docTypeItems]"
|
|
104
|
+
:disabled="!canChangeType"
|
|
105
|
+
>
|
|
106
|
+
<UTooltip
|
|
107
|
+
:text="locale.docType"
|
|
108
|
+
:content="{ side: 'bottom' }"
|
|
109
|
+
:disabled="disableTooltips"
|
|
110
|
+
>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
class="dvt-seg"
|
|
114
|
+
:disabled="!canChangeType"
|
|
115
|
+
>
|
|
116
|
+
<UIcon
|
|
117
|
+
name="i-lucide-plus"
|
|
118
|
+
:class="iconClass"
|
|
119
|
+
/>
|
|
120
|
+
</button>
|
|
121
|
+
</UTooltip>
|
|
122
|
+
</UDropdownMenu>
|
|
123
|
+
<div
|
|
124
|
+
v-else
|
|
125
|
+
class="dvt-seg-host"
|
|
126
|
+
>
|
|
127
|
+
<UTooltip
|
|
128
|
+
:text="docTypeDef.label"
|
|
129
|
+
:content="{ side: 'bottom' }"
|
|
130
|
+
:disabled="disableTooltips || typeMenuOpen"
|
|
131
|
+
>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="dvt-seg"
|
|
135
|
+
:class="{ active: modelValue === 'pageType' }"
|
|
136
|
+
:disabled="pageTypeDisabled"
|
|
137
|
+
@pointerdown="onTypePointerDown"
|
|
138
|
+
@pointerup="onTypeClick"
|
|
139
|
+
@pointerleave="cancelTypePress"
|
|
140
|
+
@pointercancel="cancelTypePress"
|
|
141
|
+
@contextmenu.prevent="canChangeType && (typeMenuOpen = true)"
|
|
142
|
+
>
|
|
143
|
+
<UIcon
|
|
144
|
+
:name="docTypeDef.icon"
|
|
145
|
+
:class="iconClass"
|
|
146
|
+
/>
|
|
147
|
+
</button>
|
|
148
|
+
</UTooltip>
|
|
149
|
+
<!-- Hidden anchor: the menu is opened programmatically by long-press so
|
|
150
|
+
the trigger never steals the click. -->
|
|
151
|
+
<UDropdownMenu
|
|
152
|
+
v-if="canChangeType"
|
|
153
|
+
v-model:open="typeMenuOpen"
|
|
154
|
+
:items="[docTypeItems]"
|
|
155
|
+
:content="{ side: 'bottom', align: 'center' }"
|
|
156
|
+
>
|
|
157
|
+
<span
|
|
158
|
+
class="dvt-anchor"
|
|
159
|
+
aria-hidden="true"
|
|
160
|
+
/>
|
|
161
|
+
</UDropdownMenu>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<!-- Slot 2: editor -->
|
|
165
|
+
<UTooltip
|
|
166
|
+
:text="locale.editorTab"
|
|
167
|
+
:content="{ side: 'bottom' }"
|
|
168
|
+
:disabled="disableTooltips"
|
|
169
|
+
>
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
class="dvt-seg"
|
|
173
|
+
:class="{ active: modelValue === 'editor' }"
|
|
174
|
+
:disabled="editorDisabled"
|
|
175
|
+
@click="select('editor')"
|
|
176
|
+
>
|
|
177
|
+
<UIcon
|
|
178
|
+
name="i-lucide-text-cursor"
|
|
179
|
+
:class="iconClass"
|
|
180
|
+
/>
|
|
181
|
+
</button>
|
|
182
|
+
</UTooltip>
|
|
183
|
+
|
|
184
|
+
<!-- Slot 3: chat (with unread badge) -->
|
|
185
|
+
<UTooltip
|
|
186
|
+
v-if="showChat"
|
|
187
|
+
:text="locale.chatTab"
|
|
188
|
+
:content="{ side: 'bottom' }"
|
|
189
|
+
:disabled="disableTooltips"
|
|
190
|
+
>
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
class="dvt-seg"
|
|
194
|
+
:class="{ active: modelValue === 'chat' }"
|
|
195
|
+
@click="select('chat')"
|
|
196
|
+
>
|
|
197
|
+
<span class="relative inline-flex">
|
|
198
|
+
<UIcon
|
|
199
|
+
name="i-lucide-message-circle"
|
|
200
|
+
:class="iconClass"
|
|
201
|
+
/>
|
|
202
|
+
<span
|
|
203
|
+
v-if="chatUnread > 0 && modelValue !== 'chat'"
|
|
204
|
+
class="dvt-badge"
|
|
205
|
+
>{{ chatUnread > 99 ? "99+" : chatUnread }}</span>
|
|
206
|
+
</span>
|
|
207
|
+
</button>
|
|
208
|
+
</UTooltip>
|
|
209
|
+
|
|
210
|
+
<!-- Slot 4: settings -->
|
|
211
|
+
<UTooltip
|
|
212
|
+
v-if="showSettings"
|
|
213
|
+
:text="locale.settingsTab"
|
|
214
|
+
:content="{ side: 'bottom' }"
|
|
215
|
+
:disabled="disableTooltips"
|
|
216
|
+
>
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
class="dvt-seg"
|
|
220
|
+
:class="{ active: modelValue === 'settings' }"
|
|
221
|
+
@click="select('settings')"
|
|
222
|
+
>
|
|
223
|
+
<UIcon
|
|
224
|
+
name="i-lucide-settings-2"
|
|
225
|
+
:class="iconClass"
|
|
226
|
+
/>
|
|
227
|
+
</button>
|
|
228
|
+
</UTooltip>
|
|
229
|
+
</div>
|
|
230
|
+
</template>
|
|
231
|
+
|
|
232
|
+
<style scoped>
|
|
233
|
+
.dvt{align-items:center;background:var(--ui-bg-elevated);border-radius:calc(var(--ui-radius)*1.5);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ui-border-accented),#fff 25%);display:inline-flex;flex-shrink:0;padding:2px;position:relative}.dvt-indicator{background:var(--ui-bg);border-radius:var(--ui-radius);box-shadow:0 1px 2px rgba(0,0,0,.08),inset 0 0 0 1px var(--ui-border-accented);height:calc(100% - 4px);left:2px;pointer-events:none;position:absolute;top:2px;transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .15s ease;width:var(--dvt-seg)}.dvt-indicator-hidden{opacity:0}.dvt-seg-host{display:flex;position:relative}.dvt-anchor{bottom:0;height:0;left:50%;position:absolute;width:0}.dvt-seg{align-items:center;border-radius:var(--ui-radius);color:var(--ui-text-muted);cursor:pointer;display:flex;height:var(--dvt-seg);justify-content:center;position:relative;transition:color .15s ease;width:var(--dvt-seg);z-index:1}.dvt-seg:hover:not(:disabled){color:var(--ui-text)}.dvt-seg.active{color:var(--ui-text-highlighted)}.dvt-seg:disabled{cursor:default;opacity:.4}.dvt-badge{background:var(--ui-color-error-500);border-radius:999px;color:#fff;font-size:9px;font-weight:700;height:15px;line-height:15px;min-width:15px;padding:0 3px;pointer-events:none;position:absolute;right:-7px;text-align:center;top:-6px}
|
|
234
|
+
</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type DocViewTab = 'pageType' | 'editor' | 'chat' | 'settings';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Active tab. `null` (or absent) = nothing highlighted (e.g. another tab is active). */
|
|
4
|
+
modelValue?: DocViewTab | null;
|
|
5
|
+
docId: string;
|
|
6
|
+
docType?: string;
|
|
7
|
+
chatUnread?: number;
|
|
8
|
+
/** Disable the editor segment (e.g. main view is already the editor). */
|
|
9
|
+
editorDisabled?: boolean;
|
|
10
|
+
/** Disable the page-type segment (e.g. provider not ready). */
|
|
11
|
+
pageTypeDisabled?: boolean;
|
|
12
|
+
/** Whether the user may assign/change the page type (gates the + dropdown). */
|
|
13
|
+
canChangeType?: boolean;
|
|
14
|
+
/** Render the chat slot. Off → omitted (e.g. features.chat disabled). */
|
|
15
|
+
showChat?: boolean;
|
|
16
|
+
/** Render the settings slot. Off → omitted. */
|
|
17
|
+
showSettings?: boolean;
|
|
18
|
+
/** Suppress tooltips (touch devices / during open animations). */
|
|
19
|
+
disableTooltips?: boolean;
|
|
20
|
+
size?: 'sm' | 'md';
|
|
21
|
+
};
|
|
22
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
23
|
+
select: (tab: DocViewTab) => any;
|
|
24
|
+
"update:modelValue": (tab: DocViewTab) => any;
|
|
25
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
26
|
+
onSelect?: ((tab: DocViewTab) => any) | undefined;
|
|
27
|
+
"onUpdate:modelValue"?: ((tab: DocViewTab) => any) | undefined;
|
|
28
|
+
}>, {
|
|
29
|
+
size: "sm" | "md";
|
|
30
|
+
modelValue: DocViewTab | null;
|
|
31
|
+
chatUnread: number;
|
|
32
|
+
editorDisabled: boolean;
|
|
33
|
+
pageTypeDisabled: boolean;
|
|
34
|
+
canChangeType: boolean;
|
|
35
|
+
showChat: boolean;
|
|
36
|
+
showSettings: boolean;
|
|
37
|
+
disableTooltips: boolean;
|
|
38
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
39
|
+
declare const _default: typeof __VLS_export;
|
|
40
|
+
export default _default;
|
|
@@ -424,7 +424,7 @@ function onDragStart(e, item) {
|
|
|
424
424
|
if (!multiSelected.value.has(item.id)) multiSelected.value = /* @__PURE__ */ new Set();
|
|
425
425
|
dragId.value = item.id;
|
|
426
426
|
if (e.dataTransfer) {
|
|
427
|
-
e.dataTransfer.effectAllowed = "
|
|
427
|
+
e.dataTransfer.effectAllowed = "all";
|
|
428
428
|
e.dataTransfer.setData("text/plain", item.id);
|
|
429
429
|
e.dataTransfer.setData(DOC_DRAG_MIME, JSON.stringify({ id: item.id, label: item.name }));
|
|
430
430
|
}
|