@ai-matrx/context-menu 0.0.0 → 0.1.1
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/CHANGELOG.md +23 -0
- package/README.md +14 -1
- package/dist/builder.cjs +1810 -0
- package/dist/builder.cjs.map +1 -0
- package/dist/builder.d.cts +22 -0
- package/dist/builder.d.ts +22 -0
- package/dist/builder.js +1795 -0
- package/dist/builder.js.map +1 -0
- package/dist/design-store.cjs +195 -0
- package/dist/design-store.cjs.map +1 -0
- package/dist/design-store.d.cts +42 -0
- package/dist/design-store.d.ts +42 -0
- package/dist/design-store.js +175 -0
- package/dist/design-store.js.map +1 -0
- package/dist/engine.cjs +3069 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +731 -0
- package/dist/engine.d.ts +731 -0
- package/dist/engine.js +3250 -0
- package/dist/engine.js.map +1 -0
- package/dist/grouping-CB3zlD1T.d.ts +623 -0
- package/dist/grouping-CGcWsltu.d.cts +623 -0
- package/dist/host-types-AOiEYwR8.d.cts +9 -0
- package/dist/host-types-AOiEYwR8.d.ts +9 -0
- package/dist/index.cjs +583 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/menu-content.cjs +3146 -0
- package/dist/menu-content.cjs.map +1 -0
- package/dist/menu-content.d.cts +30 -0
- package/dist/menu-content.d.ts +30 -0
- package/dist/menu-content.js +3197 -0
- package/dist/menu-content.js.map +1 -0
- package/dist/model-CkLCR6WY.d.cts +123 -0
- package/dist/model-CkLCR6WY.d.ts +123 -0
- package/dist/react.cjs +3322 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +133 -0
- package/dist/react.d.ts +133 -0
- package/dist/react.js +3345 -0
- package/dist/react.js.map +1 -0
- package/package.json +170 -1
package/dist/engine.cjs
ADDED
|
@@ -0,0 +1,3069 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/engine/index.ts
|
|
23
|
+
var engine_exports = {};
|
|
24
|
+
__export(engine_exports, {
|
|
25
|
+
CONTEXT_MENU_ENGINE_ID_PREFIXES: () => CONTEXT_MENU_ENGINE_ID_PREFIXES,
|
|
26
|
+
CONTEXT_MENU_ENGINE_ROWS: () => CONTEXT_MENU_ENGINE_ROWS,
|
|
27
|
+
CONTEXT_MENU_SELECTION_HOST_KEY: () => CONTEXT_MENU_SELECTION_HOST_KEY,
|
|
28
|
+
CONTEXT_REGION_TRIGGER_ATTRS: () => CONTEXT_REGION_TRIGGER_ATTRS,
|
|
29
|
+
DEFAULT_CONTEXT_MENU_HOST: () => DEFAULT_CONTEXT_MENU_HOST,
|
|
30
|
+
FIELD_PREVIEW_MAX: () => FIELD_PREVIEW_MAX,
|
|
31
|
+
MANAGED_CONTEXT_MENU_AGENT_CONFIG: () => MANAGED_CONTEXT_MENU_AGENT_CONFIG,
|
|
32
|
+
OWN_ROWS_MAX: () => OWN_ROWS_MAX,
|
|
33
|
+
PROPOSED_MENU_GROUPING: () => PROPOSED_MENU_GROUPING,
|
|
34
|
+
RECORD_MENU_ATTR: () => RECORD_MENU_ATTR,
|
|
35
|
+
REGROUP_ID_PREFIX: () => REGROUP_ID_PREFIX,
|
|
36
|
+
STRIP_MAX: () => STRIP_MAX,
|
|
37
|
+
actionLabel: () => actionLabel,
|
|
38
|
+
actionsAlreadyHere: () => actionsAlreadyHere,
|
|
39
|
+
auditRegroup: () => auditRegroup,
|
|
40
|
+
auditSurfaceScope: () => auditSurfaceScope,
|
|
41
|
+
buildDefaultTableRowMenuDescriptor: () => buildDefaultTableRowMenuDescriptor,
|
|
42
|
+
buildEditableWidgetHandle: () => buildEditableWidgetHandle,
|
|
43
|
+
buildJsonMenuSection: () => buildJsonMenuSection,
|
|
44
|
+
buildMenuModel: () => buildMenuModel,
|
|
45
|
+
buildSelectionWriteBack: () => buildSelectionWriteBack,
|
|
46
|
+
chatMessageSubject: () => chatMessageSubject,
|
|
47
|
+
clickedKind: () => clickedKind,
|
|
48
|
+
configureContextMenuHost: () => configureContextMenuHost,
|
|
49
|
+
contextMenuActionsFromModel: () => contextMenuActionsFromModel,
|
|
50
|
+
contextMenuHostOf: () => contextMenuHostOf,
|
|
51
|
+
contextMenuProvider: () => contextMenuProvider,
|
|
52
|
+
contextMenuSelectionProvider: () => contextMenuSelectionProvider,
|
|
53
|
+
createDefaultTableRowMenuDescriptor: () => createDefaultTableRowMenuDescriptor,
|
|
54
|
+
createTableRowMenuDescriptor: () => createTableRowMenuDescriptor,
|
|
55
|
+
currentModel: () => currentModel,
|
|
56
|
+
destinationFor: () => destinationFor,
|
|
57
|
+
detectInertMenu: () => detectInertMenu,
|
|
58
|
+
drawnPlaces: () => drawnPlaces,
|
|
59
|
+
editorTextAgentCallbacks: () => editorTextAgentCallbacks,
|
|
60
|
+
eventMatchesCombo: () => eventMatchesCombo,
|
|
61
|
+
fieldLabelOf: () => fieldLabelOf,
|
|
62
|
+
fieldPreview: () => fieldPreview,
|
|
63
|
+
findComboMatch: () => findComboMatch,
|
|
64
|
+
getContextMenuHost: () => getContextMenuHost,
|
|
65
|
+
hasActionable: () => hasActionable,
|
|
66
|
+
hasActionableContent: () => hasActionableContent,
|
|
67
|
+
hasEditorInsertTarget: () => hasEditorInsertTarget,
|
|
68
|
+
headerPreview: () => headerPreview,
|
|
69
|
+
insertIntoEditor: () => insertIntoEditor,
|
|
70
|
+
itemMenuConfigToExtraSections: () => itemMenuConfigToExtraSections,
|
|
71
|
+
itemToastPromise: () => toastPromise,
|
|
72
|
+
joinExtraSections: () => joinExtraSections,
|
|
73
|
+
joinRowAndSurfaceContext: () => joinRowAndSurfaceContext,
|
|
74
|
+
jsonSectionLabel: () => jsonSectionLabel,
|
|
75
|
+
liftPrimarySections: () => liftPrimarySections,
|
|
76
|
+
matchRule: () => matchRule,
|
|
77
|
+
menuEngineRevision: () => menuEngineRevision,
|
|
78
|
+
menuHeader: () => menuHeader,
|
|
79
|
+
menuHeaderContent: () => menuHeaderContent,
|
|
80
|
+
mergeFieldName: () => mergeFieldName,
|
|
81
|
+
mergeResolvedContextData: () => mergeResolvedContextData,
|
|
82
|
+
modelRevision: () => modelRevision,
|
|
83
|
+
namedHeader: () => namedHeader,
|
|
84
|
+
openContextMenuForElement: () => openContextMenuForElement,
|
|
85
|
+
openRecordMenu: () => openRecordMenu,
|
|
86
|
+
ownParagraph: () => ownParagraph,
|
|
87
|
+
parseKeyCombo: () => parseKeyCombo,
|
|
88
|
+
proposedArrangement: () => proposedArrangement,
|
|
89
|
+
recordMenuChanged: () => recordMenuChanged,
|
|
90
|
+
recordMenusRevision: () => recordMenusRevision,
|
|
91
|
+
registerRecordMenu: () => registerRecordMenu,
|
|
92
|
+
registerTableRowContextResolver: () => registerTableRowContextResolver,
|
|
93
|
+
registryTreeNodes: () => registryTreeNodes,
|
|
94
|
+
regroupResolved: () => regroupResolved,
|
|
95
|
+
reportMenuDiagnostics: () => reportMenuDiagnostics,
|
|
96
|
+
resetContextMenuHost: () => resetContextMenuHost,
|
|
97
|
+
resolveActionText: () => resolveActionText,
|
|
98
|
+
resolveApplicationScope: () => resolveApplicationScope,
|
|
99
|
+
resolveEffectiveEntity: () => resolveEffectiveEntity,
|
|
100
|
+
resolveEffectiveEntityWithDom: () => resolveEffectiveEntityWithDom,
|
|
101
|
+
resolveRecordMenu: () => resolveRecordMenu,
|
|
102
|
+
resolveTableRowItem: () => resolveTableRowItem,
|
|
103
|
+
resolveTableRowMenuDescriptor: () => resolveTableRowMenuDescriptor,
|
|
104
|
+
rowRecordName: () => rowRecordName,
|
|
105
|
+
sniffEntityFromDom: () => sniffEntityFromDom,
|
|
106
|
+
subscribeRecordMenus: () => subscribeRecordMenus,
|
|
107
|
+
tableRowEditCommands: () => tableRowEditCommands,
|
|
108
|
+
tableTextAtTarget: () => tableTextAtTarget,
|
|
109
|
+
topLevelRowCount: () => topLevelRowCount,
|
|
110
|
+
useContextMenuHostBindings: () => useContextMenuHostBindings
|
|
111
|
+
});
|
|
112
|
+
module.exports = __toCommonJS(engine_exports);
|
|
113
|
+
|
|
114
|
+
// src/engine/types.ts
|
|
115
|
+
var types_exports = {};
|
|
116
|
+
__reExport(types_exports, require("@ai-matrx/chat/context-menu/types"));
|
|
117
|
+
|
|
118
|
+
// src/engine/index.ts
|
|
119
|
+
__reExport(engine_exports, types_exports, module.exports);
|
|
120
|
+
|
|
121
|
+
// src/engine/host/port.ts
|
|
122
|
+
var import_confirm_opener = require("@ai-matrx/kit/confirm-opener");
|
|
123
|
+
function unwired(name) {
|
|
124
|
+
const announced = slot().announced;
|
|
125
|
+
if (announced.has(name)) return;
|
|
126
|
+
announced.add(name);
|
|
127
|
+
console.warn(`[context-menu] host port "${name}" is not wired \u2014 configureContextMenuHost({ ${name} }).`);
|
|
128
|
+
}
|
|
129
|
+
function unwiredFn(name) {
|
|
130
|
+
return () => unwired(name);
|
|
131
|
+
}
|
|
132
|
+
var DEFAULT_QUICK_ACTIONS = {
|
|
133
|
+
openChatWindow: unwiredFn("quickActions.openChatWindow"),
|
|
134
|
+
openQuickChat: unwiredFn("quickActions.openQuickChat"),
|
|
135
|
+
openQuickData: unwiredFn("quickActions.openQuickData"),
|
|
136
|
+
openQuickFiles: unwiredFn("quickActions.openQuickFiles"),
|
|
137
|
+
openQuickNotes: unwiredFn("quickActions.openQuickNotes"),
|
|
138
|
+
openQuickTasks: unwiredFn("quickActions.openQuickTasks"),
|
|
139
|
+
openVoicePad: unwiredFn("quickActions.openVoicePad")
|
|
140
|
+
};
|
|
141
|
+
var DEFAULT_BINDINGS = {
|
|
142
|
+
isAdminDebugger: false,
|
|
143
|
+
hasAdminPower: false,
|
|
144
|
+
isDebugMode: false,
|
|
145
|
+
isAdminIndicatorOpen: false,
|
|
146
|
+
hasCompareBase: false,
|
|
147
|
+
activeScopeIds: null,
|
|
148
|
+
toggleDebugMode: unwiredFn("useBindings.toggleDebugMode"),
|
|
149
|
+
toggleAdminIndicator: unwiredFn("useBindings.toggleAdminIndicator"),
|
|
150
|
+
setCompareBase: unwiredFn("useBindings.setCompareBase"),
|
|
151
|
+
openCompareWithBase: async () => {
|
|
152
|
+
unwired("useBindings.openCompareWithBase");
|
|
153
|
+
return false;
|
|
154
|
+
},
|
|
155
|
+
openListenSummaryWindow: unwiredFn("useBindings.openListenSummaryWindow"),
|
|
156
|
+
openDiffWindow: unwiredFn("useBindings.openDiffWindow"),
|
|
157
|
+
openFindReplace: unwiredFn("useBindings.openFindReplace"),
|
|
158
|
+
openReferencePicker: unwiredFn("useBindings.openReferencePicker"),
|
|
159
|
+
openContextAssignment: unwiredFn("useBindings.openContextAssignment"),
|
|
160
|
+
openLinkRecordSheet: unwiredFn("useBindings.openLinkRecordSheet"),
|
|
161
|
+
openShareModalWindow: unwiredFn("useBindings.openShareModalWindow"),
|
|
162
|
+
openStateViewer: unwiredFn("useBindings.openStateViewer"),
|
|
163
|
+
openSurfaceInspector: unwiredFn("useBindings.openSurfaceInspector"),
|
|
164
|
+
openSurfaceContextWindow: unwiredFn("useBindings.openSurfaceContextWindow"),
|
|
165
|
+
openBindAgent: unwiredFn("useBindings.openBindAgent"),
|
|
166
|
+
quickActions: DEFAULT_QUICK_ACTIONS
|
|
167
|
+
};
|
|
168
|
+
var EMPTY_RENDER_DEFINITIONS = [];
|
|
169
|
+
var DEFAULT_CONTEXT_MENU_HOST = {
|
|
170
|
+
useBindings: () => DEFAULT_BINDINGS,
|
|
171
|
+
getSurfaceManifest: () => void 0,
|
|
172
|
+
selectRenderDefinitions: () => EMPTY_RENDER_DEFINITIONS,
|
|
173
|
+
confirm: import_confirm_opener.confirm,
|
|
174
|
+
showManualCopy: () => unwired("showManualCopy"),
|
|
175
|
+
listeningHomeSurface: "matrx-user/assistant-message",
|
|
176
|
+
DocumentAgentReview: null
|
|
177
|
+
};
|
|
178
|
+
var SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.context-menu.host");
|
|
179
|
+
function slot() {
|
|
180
|
+
const g = globalThis;
|
|
181
|
+
let s = g[SLOT];
|
|
182
|
+
if (!s) {
|
|
183
|
+
s = { host: DEFAULT_CONTEXT_MENU_HOST, announced: /* @__PURE__ */ new Set() };
|
|
184
|
+
g[SLOT] = s;
|
|
185
|
+
}
|
|
186
|
+
return s;
|
|
187
|
+
}
|
|
188
|
+
function configureContextMenuHost(host) {
|
|
189
|
+
slot().host = { ...slot().host, ...host };
|
|
190
|
+
}
|
|
191
|
+
function getContextMenuHost() {
|
|
192
|
+
return slot().host;
|
|
193
|
+
}
|
|
194
|
+
function resetContextMenuHost() {
|
|
195
|
+
slot().host = DEFAULT_CONTEXT_MENU_HOST;
|
|
196
|
+
slot().announced.clear();
|
|
197
|
+
}
|
|
198
|
+
function useContextMenuHostBindings() {
|
|
199
|
+
return getContextMenuHost().useBindings();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/engine/utils/build-application-scope.ts
|
|
203
|
+
var build_application_scope_exports = {};
|
|
204
|
+
__reExport(build_application_scope_exports, require("@ai-matrx/chat/context-menu/utils/build-application-scope"));
|
|
205
|
+
|
|
206
|
+
// src/engine/index.ts
|
|
207
|
+
__reExport(engine_exports, build_application_scope_exports, module.exports);
|
|
208
|
+
|
|
209
|
+
// src/engine/utils/availability.ts
|
|
210
|
+
var availability_exports = {};
|
|
211
|
+
__reExport(availability_exports, require("@ai-matrx/chat/context-menu/utils/availability"));
|
|
212
|
+
|
|
213
|
+
// src/engine/index.ts
|
|
214
|
+
__reExport(engine_exports, availability_exports, module.exports);
|
|
215
|
+
|
|
216
|
+
// src/engine/utils/open-context-menu.ts
|
|
217
|
+
function openContextMenuForElement(element) {
|
|
218
|
+
if (!element) return;
|
|
219
|
+
const rect = element.getBoundingClientRect();
|
|
220
|
+
element.dispatchEvent(
|
|
221
|
+
new MouseEvent("contextmenu", {
|
|
222
|
+
bubbles: true,
|
|
223
|
+
cancelable: true,
|
|
224
|
+
view: element.ownerDocument.defaultView ?? window,
|
|
225
|
+
button: 2,
|
|
226
|
+
buttons: 2,
|
|
227
|
+
clientX: rect.left + rect.width / 2,
|
|
228
|
+
clientY: rect.top + rect.height / 2
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/engine/utils/selection-tracking.ts
|
|
234
|
+
var selection_tracking_exports = {};
|
|
235
|
+
__reExport(selection_tracking_exports, require("@ai-matrx/chat/context-menu/utils/selection-tracking"));
|
|
236
|
+
|
|
237
|
+
// src/engine/index.ts
|
|
238
|
+
__reExport(engine_exports, selection_tracking_exports, module.exports);
|
|
239
|
+
|
|
240
|
+
// src/engine/utils/menu-deadline.ts
|
|
241
|
+
var menu_deadline_exports = {};
|
|
242
|
+
__reExport(menu_deadline_exports, require("@ai-matrx/chat/context-menu/utils/menu-deadline"));
|
|
243
|
+
|
|
244
|
+
// src/engine/index.ts
|
|
245
|
+
__reExport(engine_exports, menu_deadline_exports, module.exports);
|
|
246
|
+
|
|
247
|
+
// src/engine/utils/resolveMarkdownContext.ts
|
|
248
|
+
var resolveMarkdownContext_exports = {};
|
|
249
|
+
__reExport(resolveMarkdownContext_exports, require("@ai-matrx/chat/context-menu/utils/resolveMarkdownContext"));
|
|
250
|
+
|
|
251
|
+
// src/engine/index.ts
|
|
252
|
+
__reExport(engine_exports, resolveMarkdownContext_exports, module.exports);
|
|
253
|
+
|
|
254
|
+
// src/engine/model/requirement-gate.ts
|
|
255
|
+
var requirement_gate_exports = {};
|
|
256
|
+
__reExport(requirement_gate_exports, require("@ai-matrx/chat/context-menu/model/requirement-gate"));
|
|
257
|
+
|
|
258
|
+
// src/engine/index.ts
|
|
259
|
+
__reExport(engine_exports, requirement_gate_exports, module.exports);
|
|
260
|
+
|
|
261
|
+
// src/engine/model/menu-model.ts
|
|
262
|
+
var import_EditInPlace = require("@ai-matrx/rich-editor/in-place/EditInPlace");
|
|
263
|
+
|
|
264
|
+
// src/engine/utils/key-combo.ts
|
|
265
|
+
var MOD_ALIASES = {
|
|
266
|
+
alt: "alt",
|
|
267
|
+
option: "alt",
|
|
268
|
+
opt: "alt",
|
|
269
|
+
"\u2325": "alt",
|
|
270
|
+
shift: "shift",
|
|
271
|
+
"\u21E7": "shift",
|
|
272
|
+
ctrl: "ctrl",
|
|
273
|
+
control: "ctrl",
|
|
274
|
+
"\u2303": "ctrl",
|
|
275
|
+
cmd: "meta",
|
|
276
|
+
command: "meta",
|
|
277
|
+
meta: "meta",
|
|
278
|
+
"\u2318": "meta",
|
|
279
|
+
win: "meta",
|
|
280
|
+
mod: "mod"
|
|
281
|
+
};
|
|
282
|
+
function isApplePlatform() {
|
|
283
|
+
if (typeof navigator === "undefined") return false;
|
|
284
|
+
return /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent || "");
|
|
285
|
+
}
|
|
286
|
+
function parseKeyCombo(raw) {
|
|
287
|
+
if (!raw) return null;
|
|
288
|
+
const parts = raw.split(/\s*\+\s*/).map((p) => p.trim()).filter(Boolean);
|
|
289
|
+
if (parts.length < 2) return null;
|
|
290
|
+
const combo = { alt: false, shift: false, ctrl: false, meta: false, code: null, key: null };
|
|
291
|
+
const last = parts[parts.length - 1];
|
|
292
|
+
for (const part of parts.slice(0, -1)) {
|
|
293
|
+
const mod = MOD_ALIASES[part.toLowerCase()];
|
|
294
|
+
if (!mod) return null;
|
|
295
|
+
if (mod === "mod") combo[isApplePlatform() ? "meta" : "ctrl"] = true;
|
|
296
|
+
else combo[mod] = true;
|
|
297
|
+
}
|
|
298
|
+
if (/^[a-z]$/i.test(last)) combo.code = `Key${last.toUpperCase()}`;
|
|
299
|
+
else if (/^[0-9]$/.test(last)) combo.code = `Digit${last}`;
|
|
300
|
+
else combo.key = last.toLowerCase();
|
|
301
|
+
if (!combo.alt && !combo.ctrl && !combo.meta) return null;
|
|
302
|
+
if ((combo.ctrl || combo.meta) && combo.shift && !combo.alt && combo.code === "KeyK") return null;
|
|
303
|
+
return combo;
|
|
304
|
+
}
|
|
305
|
+
function eventMatchesCombo(e, combo) {
|
|
306
|
+
if (!!e.altKey !== combo.alt || !!e.shiftKey !== combo.shift) return false;
|
|
307
|
+
if (!!e.ctrlKey !== combo.ctrl || !!e.metaKey !== combo.meta) return false;
|
|
308
|
+
if (combo.code) return e.code === combo.code;
|
|
309
|
+
return !!combo.key && (e.key ?? "").toLowerCase() === combo.key;
|
|
310
|
+
}
|
|
311
|
+
function findComboMatch(e, items, comboOf) {
|
|
312
|
+
if (!e.altKey && !e.ctrlKey && !e.metaKey) return null;
|
|
313
|
+
for (const item of items) {
|
|
314
|
+
const combo = parseKeyCombo(comboOf(item));
|
|
315
|
+
if (combo && eventMatchesCombo(e, combo)) return item;
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/engine/model/menu-model.ts
|
|
321
|
+
var import_lucide_react2 = require("lucide-react");
|
|
322
|
+
var import_constants2 = require("@ai-matrx/chat/agents/shortcuts/constants");
|
|
323
|
+
var import_menuStructure2 = require("@ai-matrx/rich-content/rich-document/variants/shared/menuStructure");
|
|
324
|
+
|
|
325
|
+
// src/engine/utils/json-menu-actions.ts
|
|
326
|
+
var import_json_format = require("@ai-matrx/kit/json-format");
|
|
327
|
+
var import_json_format2 = require("@ai-matrx/kit/json-format");
|
|
328
|
+
function lineCount(text) {
|
|
329
|
+
let n = text === "" ? 0 : 1;
|
|
330
|
+
for (let i = 0; i < text.length; i++) if (text[i] === "\n") n++;
|
|
331
|
+
return n;
|
|
332
|
+
}
|
|
333
|
+
function sizeHint(before, after) {
|
|
334
|
+
const b = lineCount(before);
|
|
335
|
+
const a = lineCount(after);
|
|
336
|
+
if (b !== a) {
|
|
337
|
+
return `${b} line${b !== 1 ? "s" : ""} -> ${a} line${a !== 1 ? "s" : ""}`;
|
|
338
|
+
}
|
|
339
|
+
return `${before.length} -> ${after.length} chars`;
|
|
340
|
+
}
|
|
341
|
+
function shapePreservingStyle(detection) {
|
|
342
|
+
return detection.payload.includes("\n") ? "pretty" : "minify";
|
|
343
|
+
}
|
|
344
|
+
function buildJsonMenuSection(params) {
|
|
345
|
+
const { text, canWrite, onReplace, onCopy } = params;
|
|
346
|
+
if (!text.trim()) return null;
|
|
347
|
+
const detection = (0, import_json_format.detectJson)(text);
|
|
348
|
+
if (!detection.looksLikeJson) return null;
|
|
349
|
+
if (!detection.ok) {
|
|
350
|
+
return {
|
|
351
|
+
detection,
|
|
352
|
+
writes: false,
|
|
353
|
+
actions: [
|
|
354
|
+
{
|
|
355
|
+
id: "json-invalid",
|
|
356
|
+
label: "Invalid JSON",
|
|
357
|
+
hint: detection.error,
|
|
358
|
+
disabled: true,
|
|
359
|
+
run: () => {
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const apply = (next) => {
|
|
366
|
+
if (canWrite) onReplace(next);
|
|
367
|
+
else void onCopy(next);
|
|
368
|
+
};
|
|
369
|
+
const verb = canWrite ? "" : "Copy ";
|
|
370
|
+
const actions = [];
|
|
371
|
+
const addFormat = (id, label, style, fence = "preserve", sortKeys = false) => {
|
|
372
|
+
const result = (0, import_json_format2.formatJsonText)(text, { style, fence, sortKeys });
|
|
373
|
+
if (!result.ok) return;
|
|
374
|
+
if (canWrite && !result.changed) return;
|
|
375
|
+
actions.push({
|
|
376
|
+
id,
|
|
377
|
+
label,
|
|
378
|
+
hint: result.changed ? sizeHint(text, result.text) : void 0,
|
|
379
|
+
run: () => apply(result.text)
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
addFormat("json-condense", `${verb}Condense`, "compact");
|
|
383
|
+
addFormat("json-minify", `${verb}Minify (one line)`, "minify");
|
|
384
|
+
addFormat("json-expand", `${verb}Expand`, "pretty");
|
|
385
|
+
addFormat(
|
|
386
|
+
"json-sort-keys",
|
|
387
|
+
`${verb}Sort keys A-Z`,
|
|
388
|
+
shapePreservingStyle(detection),
|
|
389
|
+
"preserve",
|
|
390
|
+
true
|
|
391
|
+
);
|
|
392
|
+
if (detection.fence) {
|
|
393
|
+
addFormat(
|
|
394
|
+
"json-strip-fence",
|
|
395
|
+
`${verb}Remove code fence`,
|
|
396
|
+
shapePreservingStyle(detection),
|
|
397
|
+
"strip"
|
|
398
|
+
);
|
|
399
|
+
} else {
|
|
400
|
+
addFormat(
|
|
401
|
+
"json-add-fence",
|
|
402
|
+
`${verb}Wrap in json code fence`,
|
|
403
|
+
shapePreservingStyle(detection),
|
|
404
|
+
"add"
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (canWrite) {
|
|
408
|
+
const minified = (0, import_json_format2.formatJsonText)(text, { style: "minify", fence: "strip" });
|
|
409
|
+
if (minified.ok) {
|
|
410
|
+
actions.push({
|
|
411
|
+
id: "json-copy-minified",
|
|
412
|
+
label: "Copy minified",
|
|
413
|
+
hint: sizeHint(text, minified.text),
|
|
414
|
+
run: () => void onCopy(minified.text)
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (actions.length === 0) return null;
|
|
419
|
+
return { detection, writes: canWrite, actions };
|
|
420
|
+
}
|
|
421
|
+
function jsonSectionLabel(section) {
|
|
422
|
+
const { detection } = section;
|
|
423
|
+
if (!detection.ok) return "JSON (invalid)";
|
|
424
|
+
const n = detection.lineCount;
|
|
425
|
+
const kind = detection.root === "array" ? "array" : "object";
|
|
426
|
+
return `JSON ${kind} \xB7 ${n} line${n !== 1 ? "s" : ""}`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/engine/utils/compact.ts
|
|
430
|
+
function compact(value) {
|
|
431
|
+
const out = {};
|
|
432
|
+
for (const [key, v] of Object.entries(value)) if (v !== void 0) out[key] = v;
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/engine/utils/field-menu-header.ts
|
|
437
|
+
function fieldLabelOf(element) {
|
|
438
|
+
if (!element) return null;
|
|
439
|
+
const clean = (s) => (s ?? "").replace(/\s+/g, " ").replace(/\s*\*$/, "").trim() || null;
|
|
440
|
+
const aria = clean(element.getAttribute("aria-label"));
|
|
441
|
+
if (aria) return aria;
|
|
442
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
443
|
+
if (labelledBy) {
|
|
444
|
+
const text = labelledBy.split(/\s+/).map((id2) => element.ownerDocument?.getElementById(id2)?.textContent ?? "").join(" ");
|
|
445
|
+
if (clean(text)) return clean(text);
|
|
446
|
+
}
|
|
447
|
+
const labels = element.labels;
|
|
448
|
+
if (labels && labels.length > 0) {
|
|
449
|
+
const text = clean(labels[0]?.textContent);
|
|
450
|
+
if (text) return text;
|
|
451
|
+
}
|
|
452
|
+
const id = element.getAttribute("id");
|
|
453
|
+
if (id && element.ownerDocument) {
|
|
454
|
+
const label = element.ownerDocument.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
455
|
+
if (clean(label?.textContent)) return clean(label?.textContent);
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
var MERGE_FIELD = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
460
|
+
function mergeFieldName(token) {
|
|
461
|
+
const words = token.replace(/[._-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim().toLowerCase();
|
|
462
|
+
return words ? words[0].toUpperCase() + words.slice(1) : token;
|
|
463
|
+
}
|
|
464
|
+
var FIELD_PREVIEW_MAX = 60;
|
|
465
|
+
function fieldPreview(text) {
|
|
466
|
+
const withoutFields = text.replace(MERGE_FIELD, "").replace(/[\s\p{P}]+/gu, "");
|
|
467
|
+
if (!withoutFields) return "";
|
|
468
|
+
const readable = text.replace(MERGE_FIELD, (_, name) => mergeFieldName(name)).replace(/\s+/g, " ").trim();
|
|
469
|
+
return readable.length > FIELD_PREVIEW_MAX ? `${readable.slice(0, FIELD_PREVIEW_MAX - 1).trimEnd()}\u2026` : readable;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/engine/utils/already-here.ts
|
|
473
|
+
var SAVE_INTO_WHERE_YOU_ARE = [
|
|
474
|
+
{ sourceType: "note", surface: "matrx-user/notes", actionId: "save-to-notes" },
|
|
475
|
+
{ sourceType: "task", surface: "matrx-user/tasks", actionId: "save-to-task" }
|
|
476
|
+
];
|
|
477
|
+
var OPEN_WHERE_YOU_ARE = [
|
|
478
|
+
// The Feedback window's menus offered "Submit feedback" — reopening itself.
|
|
479
|
+
{ surface: "matrx-user/feedback", actionId: "submit-feedback" }
|
|
480
|
+
];
|
|
481
|
+
var EDITOR_DOORS = ["edit", "open-fullscreen-editor"];
|
|
482
|
+
function actionsAlreadyHere(where) {
|
|
483
|
+
const out = [];
|
|
484
|
+
for (const rule of SAVE_INTO_WHERE_YOU_ARE) {
|
|
485
|
+
if (where.sourceType === rule.sourceType || where.surfaceName === rule.surface) out.push(rule.actionId);
|
|
486
|
+
}
|
|
487
|
+
for (const rule of OPEN_WHERE_YOU_ARE) {
|
|
488
|
+
if (where.surfaceName === rule.surface) out.push(rule.actionId);
|
|
489
|
+
}
|
|
490
|
+
const ownPage = SAVE_INTO_WHERE_YOU_ARE.some(
|
|
491
|
+
(rule) => where.sourceType === rule.sourceType || where.surfaceName === rule.surface
|
|
492
|
+
);
|
|
493
|
+
if (where.isEditable || ownPage) out.push(...EDITOR_DOORS);
|
|
494
|
+
return [...new Set(out)];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/engine/utils/editor-text-agent.ts
|
|
498
|
+
function editorTextAgentCallbacks(callbacks, source, adapter, request) {
|
|
499
|
+
if (callbacks?.onRequestTextAgentAction || !request) return callbacks;
|
|
500
|
+
if (source.type === "raw" || source.readOnly || typeof adapter.edit !== "function") return callbacks;
|
|
501
|
+
return { ...callbacks ?? {}, onRequestTextAgentAction: request };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/engine/hooks/useContextMenuActions.ts
|
|
505
|
+
var import_react = require("react");
|
|
506
|
+
var import_clipboard = require("@ai-matrx/kit/clipboard");
|
|
507
|
+
var import_lucide_react = require("lucide-react");
|
|
508
|
+
var import_route_to_surface = require("@ai-matrx/chat/surfaces/utils/route-to-surface");
|
|
509
|
+
var import_surface_display = require("@ai-matrx/chat/surfaces/utils/surface-display");
|
|
510
|
+
var import_fetchRelatedSurfaces = require("@ai-matrx/chat/surfaces/runtime/fetchRelatedSurfaces");
|
|
511
|
+
var import_icons = require("@ai-matrx/icons");
|
|
512
|
+
var import_hooks = require("@ai-matrx/chat/store/hooks");
|
|
513
|
+
var import_identity = require("@ai-matrx/chat/host/identity");
|
|
514
|
+
var import_org = require("@ai-matrx/chat/host/org");
|
|
515
|
+
var import_useAgentLauncher = require("@ai-matrx/chat/agents/hooks/useAgentLauncher");
|
|
516
|
+
var import_react2 = require("@ai-matrx/media/react");
|
|
517
|
+
var import_speech = require("@ai-matrx/media/speech");
|
|
518
|
+
var import_useSurfaceConfig = require("@ai-matrx/chat/surfaces/hooks/useSurfaceConfig");
|
|
519
|
+
|
|
520
|
+
// src/engine/utils/editor-text-insertion.ts
|
|
521
|
+
function insertTextAtCursor(editorId, text) {
|
|
522
|
+
try {
|
|
523
|
+
const editor = document.querySelector(
|
|
524
|
+
`[data-editor-id="${editorId}"]`
|
|
525
|
+
);
|
|
526
|
+
if (!editor) {
|
|
527
|
+
console.error("[editor-text-insertion] Editor not found:", editorId);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
editor.focus();
|
|
531
|
+
const selection = window.getSelection();
|
|
532
|
+
if (!selection) {
|
|
533
|
+
console.error("[editor-text-insertion] Selection not available");
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
let range;
|
|
537
|
+
if (selection.rangeCount > 0) {
|
|
538
|
+
range = selection.getRangeAt(0);
|
|
539
|
+
} else {
|
|
540
|
+
range = document.createRange();
|
|
541
|
+
const lastNode = editor.lastChild;
|
|
542
|
+
if (lastNode) {
|
|
543
|
+
if (lastNode.nodeType === Node.TEXT_NODE) {
|
|
544
|
+
range.setStart(lastNode, lastNode.textContent?.length ?? 0);
|
|
545
|
+
} else {
|
|
546
|
+
range.setStartAfter(lastNode);
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
range.setStart(editor, 0);
|
|
550
|
+
}
|
|
551
|
+
range.collapse(true);
|
|
552
|
+
selection.removeAllRanges();
|
|
553
|
+
selection.addRange(range);
|
|
554
|
+
}
|
|
555
|
+
if (!range.collapsed) {
|
|
556
|
+
range.deleteContents();
|
|
557
|
+
}
|
|
558
|
+
const textNode = document.createTextNode(text);
|
|
559
|
+
range.insertNode(textNode);
|
|
560
|
+
range.setStartAfter(textNode);
|
|
561
|
+
range.setEndAfter(textNode);
|
|
562
|
+
selection.removeAllRanges();
|
|
563
|
+
selection.addRange(range);
|
|
564
|
+
editor.focus();
|
|
565
|
+
return true;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
console.error("[editor-text-insertion] Error inserting text:", error);
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/engine/utils/insert-into-editor.ts
|
|
573
|
+
var import_text_insertion = require("@ai-matrx/rich-editor/core/text-insertion");
|
|
574
|
+
function hasEditorInsertTarget(targets) {
|
|
575
|
+
return Boolean(targets.editorId) || Boolean(targets.getTextarea) || Boolean(targets.insertAtCaret);
|
|
576
|
+
}
|
|
577
|
+
function insertIntoEditor(targets, text) {
|
|
578
|
+
const shaped = typeof text === "string" ? { editor: text, textarea: () => text, caret: text } : text;
|
|
579
|
+
const { editorId, getTextarea, insertAtCaret, onTextReplace } = targets;
|
|
580
|
+
const placement = shaped.placement ?? "inline";
|
|
581
|
+
if (editorId) {
|
|
582
|
+
const took = attempt("editor", () => {
|
|
583
|
+
if (placement === "block") moveEditorCaretToLineEnd(editorId);
|
|
584
|
+
return insertTextAtCursor(editorId, shaped.editor);
|
|
585
|
+
});
|
|
586
|
+
if (took) return "editor";
|
|
587
|
+
}
|
|
588
|
+
const field = editorId ? null : getTextarea?.() ?? null;
|
|
589
|
+
if (field) {
|
|
590
|
+
const took = attempt("textarea", () => {
|
|
591
|
+
if (placement === "block" && field.selectionStart === field.selectionEnd) {
|
|
592
|
+
const at = (0, import_text_insertion.blockBoundary)(field.value, field.selectionStart, field.selectionEnd, "nearest");
|
|
593
|
+
field.setSelectionRange(at, at);
|
|
594
|
+
}
|
|
595
|
+
return (0, import_text_insertion.insertTextAtTextareaCursor)(field, shaped.textarea(field), onTextReplace);
|
|
596
|
+
});
|
|
597
|
+
if (took) return "textarea";
|
|
598
|
+
}
|
|
599
|
+
if (insertAtCaret && attempt("caret", () => insertAtCaret(shaped.caret, placement))) return "caret";
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
function attempt(target, insert) {
|
|
603
|
+
try {
|
|
604
|
+
return insert();
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error(`[ContextMenuV3] ${target} insert failed`, error);
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function moveEditorCaretToLineEnd(editorId) {
|
|
611
|
+
const editor = document.querySelector(`[data-editor-id="${editorId}"]`);
|
|
612
|
+
const selection = window.getSelection();
|
|
613
|
+
if (!editor || !selection || selection.rangeCount === 0) return;
|
|
614
|
+
const range = selection.getRangeAt(0);
|
|
615
|
+
if (!range.collapsed || !editor.contains(range.startContainer)) return;
|
|
616
|
+
const node = range.startContainer;
|
|
617
|
+
if (node.nodeType !== Node.TEXT_NODE) return;
|
|
618
|
+
const text = node.textContent ?? "";
|
|
619
|
+
const at = (0, import_text_insertion.blockBoundary)(text, range.startOffset, range.startOffset, "nearest");
|
|
620
|
+
const next = document.createRange();
|
|
621
|
+
next.setStart(node, at);
|
|
622
|
+
next.collapse(true);
|
|
623
|
+
selection.removeAllRanges();
|
|
624
|
+
selection.addRange(next);
|
|
625
|
+
}
|
|
626
|
+
function ownParagraph(block, field) {
|
|
627
|
+
const before = field.value.slice(0, field.selectionStart);
|
|
628
|
+
const after = field.value.slice(field.selectionEnd);
|
|
629
|
+
const lead = before.length === 0 || before.endsWith("\n\n") ? "" : before.endsWith("\n") ? "\n" : "\n\n";
|
|
630
|
+
const tail = after.length === 0 || after.startsWith("\n\n") ? "" : after.startsWith("\n") ? "\n" : "\n\n";
|
|
631
|
+
return `${lead}${block}${tail}`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/engine/hooks/useContextMenuActions.ts
|
|
635
|
+
var import_provider = require("@ai-matrx/rich-content/rich-document/actions/provider");
|
|
636
|
+
var import_menuStructure = require("@ai-matrx/rich-content/rich-document/variants/shared/menuStructure");
|
|
637
|
+
var import_sources = require("@ai-matrx/rich-content/rich-document/actions/sources/index");
|
|
638
|
+
var import_raw = require("@ai-matrx/rich-content/rich-document/actions/sources/raw");
|
|
639
|
+
var import_constants = require("@ai-matrx/chat/agents/shortcuts/constants");
|
|
640
|
+
var import_baseline2 = require("@ai-matrx/chat/surfaces/manifests/_baseline.manifest");
|
|
641
|
+
var import_useSurfaceConfig2 = require("@ai-matrx/chat/surfaces/hooks/useSurfaceConfig");
|
|
642
|
+
var import_useSurfaceBoundAgents = require("@ai-matrx/chat/surfaces/hooks/useSurfaceBoundAgents");
|
|
643
|
+
|
|
644
|
+
// src/engine/value-resolution.ts
|
|
645
|
+
var import_baseline_scope = require("@ai-matrx/chat/surfaces/utils/baseline-scope");
|
|
646
|
+
var import_baseline = require("@ai-matrx/chat/surfaces/manifests/_baseline.manifest");
|
|
647
|
+
var import_SurfaceRuntimeContext = require("@ai-matrx/chat/surfaces/runtime/SurfaceRuntimeContext");
|
|
648
|
+
var SKIP_MERGE_KEYS = /* @__PURE__ */ new Set([types_exports.CONTEXT_MENU_ENTITY_KEY, types_exports.CONTEXT_MENU_HEADING_KEY]);
|
|
649
|
+
var isDev = process.env.NODE_ENV !== "production";
|
|
650
|
+
function strOf(value) {
|
|
651
|
+
return typeof value === "string" ? value : "";
|
|
652
|
+
}
|
|
653
|
+
function adoptMountedRuntimeScope(surfaceName) {
|
|
654
|
+
try {
|
|
655
|
+
const mounted = surfaceName ? (0, import_SurfaceRuntimeContext.getSurfaceRuntimeForName)(surfaceName) : (0, import_SurfaceRuntimeContext.getSurfaceRuntime)();
|
|
656
|
+
if (!mounted) return null;
|
|
657
|
+
const payload = mounted.getScope();
|
|
658
|
+
if (payload && typeof payload.then === "function")
|
|
659
|
+
return null;
|
|
660
|
+
return payload;
|
|
661
|
+
} catch (err) {
|
|
662
|
+
if (isDev)
|
|
663
|
+
console.error(
|
|
664
|
+
"[ContextMenuV3] mounted SurfaceRuntime threw in getScope() \u2014 menu scope falls back to the surface's own values",
|
|
665
|
+
err
|
|
666
|
+
);
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function isEmptyValue(value) {
|
|
671
|
+
if (value === void 0 || value === null) return true;
|
|
672
|
+
if (typeof value === "string") return value.trim() === "";
|
|
673
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
674
|
+
return Object.keys(value).length === 0;
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
function resolveApplicationScope(args) {
|
|
678
|
+
const base = buildFromMenuContext(args);
|
|
679
|
+
const surfaceAnswered = new Set(
|
|
680
|
+
Object.keys(args.contextData ?? {}).filter((k) => !SKIP_MERGE_KEYS.has(k))
|
|
681
|
+
);
|
|
682
|
+
if (args.getApplicationScope) {
|
|
683
|
+
const live = args.getApplicationScope();
|
|
684
|
+
for (const [k, v] of Object.entries(live)) {
|
|
685
|
+
if (SKIP_MERGE_KEYS.has(k) || v === void 0) continue;
|
|
686
|
+
base[k] = v;
|
|
687
|
+
surfaceAnswered.add(k);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const runtime = adoptMountedRuntimeScope(args.surfaceName);
|
|
691
|
+
if (runtime) {
|
|
692
|
+
for (const [k, v] of Object.entries(runtime)) {
|
|
693
|
+
if (SKIP_MERGE_KEYS.has(k) || v === void 0) continue;
|
|
694
|
+
if (surfaceAnswered.has(k)) continue;
|
|
695
|
+
if (isEmptyValue(base[k])) base[k] = v;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (args.ambient && !surfaceAnswered.has("ambient") && isEmptyValue(base.ambient))
|
|
699
|
+
base.ambient = args.ambient;
|
|
700
|
+
const hasContent = strOf(base.content).trim().length > 0;
|
|
701
|
+
if (!args.getApplicationScope && !hasContent && args.fallbackContent && args.fallbackContent.trim()) {
|
|
702
|
+
base.content = args.fallbackContent;
|
|
703
|
+
}
|
|
704
|
+
return (0, import_baseline_scope.withBaselineScope)(base);
|
|
705
|
+
}
|
|
706
|
+
function buildFromMenuContext(args) {
|
|
707
|
+
const { selectedText, selectionRange, contextData: cd } = args;
|
|
708
|
+
let textBefore = "";
|
|
709
|
+
let textAfter = "";
|
|
710
|
+
let fullContent = "";
|
|
711
|
+
if (selectionRange && selectionRange.type === "editable" && selectionRange.element) {
|
|
712
|
+
const value = selectionRange.element.value ?? "";
|
|
713
|
+
fullContent = value;
|
|
714
|
+
textBefore = value.substring(0, selectionRange.start ?? 0);
|
|
715
|
+
textAfter = value.substring(selectionRange.end ?? 0);
|
|
716
|
+
}
|
|
717
|
+
const scope = {};
|
|
718
|
+
for (const [k, v] of Object.entries(cd)) {
|
|
719
|
+
if (SKIP_MERGE_KEYS.has(k) || v === void 0) continue;
|
|
720
|
+
scope[k] = v;
|
|
721
|
+
}
|
|
722
|
+
if (typeof cd.context === "string") {
|
|
723
|
+
scope.context = { raw: cd.context };
|
|
724
|
+
} else if (cd.context !== void 0 && typeof cd.context !== "string") {
|
|
725
|
+
scope.context = cd.context;
|
|
726
|
+
} else if (scope.context === void 0) {
|
|
727
|
+
scope.context = {};
|
|
728
|
+
}
|
|
729
|
+
scope.selection = selectedText;
|
|
730
|
+
scope.text_before = textBefore;
|
|
731
|
+
scope.text_after = textAfter;
|
|
732
|
+
if (typeof cd.content === "string") {
|
|
733
|
+
scope.content = cd.content;
|
|
734
|
+
} else if (fullContent) {
|
|
735
|
+
scope.content = fullContent;
|
|
736
|
+
} else if (scope.content === void 0) {
|
|
737
|
+
scope.content = "";
|
|
738
|
+
}
|
|
739
|
+
const activeText = scope.active_text;
|
|
740
|
+
if (scope.selection === "" && typeof activeText === "string" && activeText.length > 0) {
|
|
741
|
+
scope.selection = activeText;
|
|
742
|
+
}
|
|
743
|
+
return scope;
|
|
744
|
+
}
|
|
745
|
+
function resolveActionText(scope) {
|
|
746
|
+
const selection = strOf(scope.selection).trim();
|
|
747
|
+
if (selection) return { text: strOf(scope.selection), source: "selection" };
|
|
748
|
+
const content = strOf(scope.content).trim();
|
|
749
|
+
if (content) return { text: strOf(scope.content), source: "content" };
|
|
750
|
+
return { text: "", source: "none" };
|
|
751
|
+
}
|
|
752
|
+
function hasActionableContent(scope) {
|
|
753
|
+
return resolveActionText(scope).source !== "none";
|
|
754
|
+
}
|
|
755
|
+
function detectInertMenu(args) {
|
|
756
|
+
const reasons = [];
|
|
757
|
+
const selection = strOf(args.scope.selection).trim();
|
|
758
|
+
const content = strOf(args.scope.content).trim();
|
|
759
|
+
const baseline = new Set(
|
|
760
|
+
import_baseline.BASELINE_VALUE_NAMES
|
|
761
|
+
);
|
|
762
|
+
const hasApplicationScope = Object.entries(args.scope).some(
|
|
763
|
+
([key, value]) => !baseline.has(key) && !isEmptyValue(value)
|
|
764
|
+
);
|
|
765
|
+
if (!selection) reasons.push("no selection");
|
|
766
|
+
if (!content) reasons.push("no resolvable content (and no DOM-text fallback)");
|
|
767
|
+
const inert = !args.isEditable && !selection && !content && !hasApplicationScope && !args.hasExtraSections;
|
|
768
|
+
return { inert, reasons };
|
|
769
|
+
}
|
|
770
|
+
function auditSurfaceScope(surfaceName, scope) {
|
|
771
|
+
if (!surfaceName) return [];
|
|
772
|
+
const manifest = getContextMenuHost().getSurfaceManifest(surfaceName);
|
|
773
|
+
if (!manifest) {
|
|
774
|
+
return [
|
|
775
|
+
`surface "${surfaceName}" has no registered manifest \u2014 its declared values cannot be verified`
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
const baseline = new Set(import_baseline.BASELINE_VALUE_NAMES);
|
|
779
|
+
const warnings = [];
|
|
780
|
+
for (const value of manifest.values) {
|
|
781
|
+
if (baseline.has(value.name)) continue;
|
|
782
|
+
if (!value.alwaysAvailable) continue;
|
|
783
|
+
const present = scope[value.name];
|
|
784
|
+
if (!(value.name in scope) || present === void 0 || present === null) {
|
|
785
|
+
warnings.push(
|
|
786
|
+
`declared value "${value.name}" is alwaysAvailable but was not emitted into the scope`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return warnings;
|
|
791
|
+
}
|
|
792
|
+
function reportMenuDiagnostics(args) {
|
|
793
|
+
const diag = detectInertMenu(args);
|
|
794
|
+
if (!isDev) return diag;
|
|
795
|
+
const label = args.surfaceName ?? "(no surfaceName)";
|
|
796
|
+
if (diag.inert) {
|
|
797
|
+
console.error(
|
|
798
|
+
`%c[ContextMenuV3] INERT MENU on "${label}"%c \u2014 opened with nothing to act on (${diag.reasons.join(
|
|
799
|
+
", "
|
|
800
|
+
)}). This is the "fake menu" defect: the surface must provide a selection, contextData.content, getApplicationScope(), or render selectable content the DOM fallback can read.`,
|
|
801
|
+
"color:#ef4444;font-weight:bold",
|
|
802
|
+
"color:inherit",
|
|
803
|
+
{ scopeKeys: Object.keys(args.scope) }
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
const valueWarnings = auditSurfaceScope(args.surfaceName, args.scope);
|
|
807
|
+
if (valueWarnings.length > 0) {
|
|
808
|
+
console.error(
|
|
809
|
+
`%c[ContextMenuV3] VALUE MAPPING GAP on "${label}"%c \u2014 ${valueWarnings.join(
|
|
810
|
+
"; "
|
|
811
|
+
)}. Shortcuts/agents bound to these values will receive nothing.`,
|
|
812
|
+
"color:#f59e0b;font-weight:bold",
|
|
813
|
+
"color:inherit"
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
return diag;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/engine/utils/selection-write-back.ts
|
|
820
|
+
function isField(el) {
|
|
821
|
+
return typeof HTMLTextAreaElement !== "undefined" && el instanceof HTMLTextAreaElement || typeof HTMLInputElement !== "undefined" && el instanceof HTMLInputElement;
|
|
822
|
+
}
|
|
823
|
+
function buildSelectionWriteBack(args) {
|
|
824
|
+
const {
|
|
825
|
+
originalText,
|
|
826
|
+
textSource,
|
|
827
|
+
selectionRange,
|
|
828
|
+
onTextReplace,
|
|
829
|
+
onTextInsertAfter,
|
|
830
|
+
insertAtCaret
|
|
831
|
+
} = args;
|
|
832
|
+
if (!originalText) return null;
|
|
833
|
+
const fieldEl = selectionRange?.type === "editable" && isField(selectionRange.element) ? selectionRange.element : null;
|
|
834
|
+
if (fieldEl) {
|
|
835
|
+
let anchorText = originalText;
|
|
836
|
+
let anchorStart = textSource === "selection" ? selectionRange.start : 0;
|
|
837
|
+
const locate = () => {
|
|
838
|
+
if (!fieldEl.isConnected) return null;
|
|
839
|
+
const value = fieldEl.value;
|
|
840
|
+
if (value.slice(anchorStart, anchorStart + anchorText.length) === anchorText) {
|
|
841
|
+
return { start: anchorStart, end: anchorStart + anchorText.length };
|
|
842
|
+
}
|
|
843
|
+
const first = value.indexOf(anchorText);
|
|
844
|
+
if (first === -1 || value.indexOf(anchorText, first + 1) !== -1) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
return { start: first, end: first + anchorText.length };
|
|
848
|
+
};
|
|
849
|
+
const write = (start, end, insert) => {
|
|
850
|
+
const value = fieldEl.value;
|
|
851
|
+
const next = value.slice(0, start) + insert + value.slice(end);
|
|
852
|
+
if (onTextReplace) onTextReplace(next);
|
|
853
|
+
else (0, selection_tracking_exports.spliceInputValue)(fieldEl, start, end, insert);
|
|
854
|
+
};
|
|
855
|
+
return {
|
|
856
|
+
originalText,
|
|
857
|
+
replace: (text) => {
|
|
858
|
+
const at = locate();
|
|
859
|
+
if (!at) return false;
|
|
860
|
+
write(at.start, at.end, text);
|
|
861
|
+
anchorStart = at.start;
|
|
862
|
+
anchorText = text;
|
|
863
|
+
return true;
|
|
864
|
+
},
|
|
865
|
+
insertBelow: (text) => {
|
|
866
|
+
const at = locate();
|
|
867
|
+
if (!at) return false;
|
|
868
|
+
const block = "\n\n" + text;
|
|
869
|
+
write(at.end, at.end, block);
|
|
870
|
+
anchorStart = at.end + 2;
|
|
871
|
+
anchorText = text;
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
if (textSource !== "selection") return null;
|
|
877
|
+
if (!insertAtCaret && !onTextInsertAfter) return null;
|
|
878
|
+
return {
|
|
879
|
+
originalText,
|
|
880
|
+
replace: (text) => insertAtCaret ? insertAtCaret(text) : false,
|
|
881
|
+
insertBelow: (text) => {
|
|
882
|
+
if (!onTextInsertAfter) return false;
|
|
883
|
+
onTextInsertAfter(text);
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/engine/hooks/useContextMenuActions.ts
|
|
890
|
+
var import_launch_widget_handles = require("@ai-matrx/chat/agents/utils/launch-widget-handles");
|
|
891
|
+
|
|
892
|
+
// src/engine/managed-agent-launch.ts
|
|
893
|
+
var MANAGED_CONTEXT_MENU_AGENT_CONFIG = {
|
|
894
|
+
displayMode: "flexible-panel",
|
|
895
|
+
allowChat: true,
|
|
896
|
+
showVariablePanel: true
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// src/engine/hooks/useContextMenuActions.ts
|
|
900
|
+
var import_domain = require("@ai-matrx/icons/domain");
|
|
901
|
+
var PLACEMENT_LABEL_OVERRIDE = {
|
|
902
|
+
[import_constants.PLACEMENT_TYPES.USER_TOOL]: "My Items",
|
|
903
|
+
[import_constants.PLACEMENT_TYPES.ORGANIZATION_TOOL]: "Org Items"
|
|
904
|
+
};
|
|
905
|
+
var PLACEMENT_COLOR = {
|
|
906
|
+
[import_constants.PLACEMENT_TYPES.AI_ACTION]: "#0ea5e9",
|
|
907
|
+
[import_constants.PLACEMENT_TYPES.CONTENT_BLOCK]: "#8b5cf6",
|
|
908
|
+
[import_constants.PLACEMENT_TYPES.ORGANIZATION_TOOL]: "#f59e0b",
|
|
909
|
+
[import_constants.PLACEMENT_TYPES.USER_TOOL]: "#10b981"
|
|
910
|
+
};
|
|
911
|
+
function getPlacementIcon(placementType) {
|
|
912
|
+
switch (placementType) {
|
|
913
|
+
case import_constants.PLACEMENT_TYPES.AI_ACTION:
|
|
914
|
+
return import_lucide_react.Rocket;
|
|
915
|
+
case import_constants.PLACEMENT_TYPES.CONTENT_BLOCK:
|
|
916
|
+
return import_lucide_react.FileText;
|
|
917
|
+
case import_constants.PLACEMENT_TYPES.ORGANIZATION_TOOL:
|
|
918
|
+
return import_lucide_react.Building;
|
|
919
|
+
case import_constants.PLACEMENT_TYPES.USER_TOOL:
|
|
920
|
+
return import_lucide_react.User;
|
|
921
|
+
default:
|
|
922
|
+
return import_lucide_react.FileText;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function getPlacementLabel(placementType) {
|
|
926
|
+
return PLACEMENT_LABEL_OVERRIDE[placementType] ?? import_constants.PLACEMENT_TYPE_META[placementType]?.label ?? placementType;
|
|
927
|
+
}
|
|
928
|
+
function resolveIcon(iconName, fallback = "FileText") {
|
|
929
|
+
return (0, import_icons.getIconComponent)(iconName ?? fallback, fallback);
|
|
930
|
+
}
|
|
931
|
+
function resolveRichActionView(action, ctx) {
|
|
932
|
+
const label = typeof action.label === "function" ? action.label(ctx) : action.label;
|
|
933
|
+
const disabledResult = action.disabled?.(ctx);
|
|
934
|
+
const disabled = typeof disabledResult === "object" ? true : Boolean(disabledResult);
|
|
935
|
+
return { label, disabled };
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// src/engine/model/menu-model.ts
|
|
939
|
+
var import_domain2 = require("@ai-matrx/icons/domain");
|
|
940
|
+
function withEditInPlace(sections) {
|
|
941
|
+
if (!(0, import_EditInPlace.hasEditInPlaceTarget)()) return sections;
|
|
942
|
+
const edit = {
|
|
943
|
+
kind: "item",
|
|
944
|
+
id: "edit-in-place",
|
|
945
|
+
label: "Edit",
|
|
946
|
+
icon: import_lucide_react2.Pencil,
|
|
947
|
+
iconClassName: "text-muted-foreground",
|
|
948
|
+
onSelect: () => void (0, import_EditInPlace.openEditInPlaceFromLastInteraction)()
|
|
949
|
+
};
|
|
950
|
+
return [{ id: "edit-in-place", group: "editable", nodes: [edit] }, ...sections];
|
|
951
|
+
}
|
|
952
|
+
function hasActionable(nodes) {
|
|
953
|
+
return nodes.some((n) => {
|
|
954
|
+
if (n.kind === "separator" || n.kind === "label") return false;
|
|
955
|
+
if (n.kind === "submenu") return hasActionable(n.children);
|
|
956
|
+
return true;
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function compactNodes(nodes) {
|
|
960
|
+
return nodes.filter((node) => node !== null);
|
|
961
|
+
}
|
|
962
|
+
function fromExtraItem(item) {
|
|
963
|
+
const id = `x:${item.id}`;
|
|
964
|
+
switch (item.kind) {
|
|
965
|
+
case "separator":
|
|
966
|
+
return { kind: "separator", id };
|
|
967
|
+
case "submenu":
|
|
968
|
+
return {
|
|
969
|
+
kind: "submenu",
|
|
970
|
+
id,
|
|
971
|
+
label: item.label,
|
|
972
|
+
icon: item.icon,
|
|
973
|
+
disabled: item.disabled,
|
|
974
|
+
children: item.children.map(fromExtraItem)
|
|
975
|
+
};
|
|
976
|
+
case "checkbox":
|
|
977
|
+
return {
|
|
978
|
+
kind: "checkbox",
|
|
979
|
+
id,
|
|
980
|
+
label: item.label,
|
|
981
|
+
icon: item.icon,
|
|
982
|
+
description: item.description,
|
|
983
|
+
hint: item.hint,
|
|
984
|
+
checked: item.checked,
|
|
985
|
+
disabled: item.disabled,
|
|
986
|
+
onCheckedChange: item.onCheckedChange
|
|
987
|
+
};
|
|
988
|
+
case "link":
|
|
989
|
+
return {
|
|
990
|
+
kind: "link",
|
|
991
|
+
id,
|
|
992
|
+
label: item.label,
|
|
993
|
+
icon: item.icon,
|
|
994
|
+
description: item.description,
|
|
995
|
+
hint: item.hint,
|
|
996
|
+
href: item.href,
|
|
997
|
+
target: item.target,
|
|
998
|
+
disabled: item.disabled
|
|
999
|
+
};
|
|
1000
|
+
case "item":
|
|
1001
|
+
default:
|
|
1002
|
+
return {
|
|
1003
|
+
kind: "item",
|
|
1004
|
+
id,
|
|
1005
|
+
label: item.label,
|
|
1006
|
+
icon: item.icon,
|
|
1007
|
+
description: item.description,
|
|
1008
|
+
hint: item.hint,
|
|
1009
|
+
disabled: item.disabled,
|
|
1010
|
+
destructive: item.destructive,
|
|
1011
|
+
onSelect: item.onSelect
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function extrasByAnchor(extraSections) {
|
|
1016
|
+
const out = {
|
|
1017
|
+
"after-clipboard": [],
|
|
1018
|
+
"after-compare": [],
|
|
1019
|
+
"after-placements": []
|
|
1020
|
+
};
|
|
1021
|
+
for (const s of extraSections ?? []) {
|
|
1022
|
+
const anchor = s.anchor ?? "after-compare";
|
|
1023
|
+
out[anchor].push({
|
|
1024
|
+
id: `extra:${s.id}`,
|
|
1025
|
+
group: "surface",
|
|
1026
|
+
label: s.label,
|
|
1027
|
+
icon: s.icon,
|
|
1028
|
+
primary: s.primary === true ? true : void 0,
|
|
1029
|
+
nodes: s.items.map(fromExtraItem)
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
return out;
|
|
1033
|
+
}
|
|
1034
|
+
function liftPrimarySections(sections) {
|
|
1035
|
+
const primary = sections.filter((s) => s.primary);
|
|
1036
|
+
if (primary.length === 0) return sections;
|
|
1037
|
+
return [...primary, ...sections.filter((s) => !s.primary)];
|
|
1038
|
+
}
|
|
1039
|
+
function richActionNode(action, ctx) {
|
|
1040
|
+
const { label, disabled } = resolveRichActionView(action, ctx);
|
|
1041
|
+
return {
|
|
1042
|
+
kind: "item",
|
|
1043
|
+
id: `rich:${action.id}`,
|
|
1044
|
+
label,
|
|
1045
|
+
icon: action.icon,
|
|
1046
|
+
iconClassName: action.iconColor ?? void 0,
|
|
1047
|
+
disabled,
|
|
1048
|
+
onSelect: () => void action.run(ctx)
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function registryTreeNodes(actions, ctx, aiExtras) {
|
|
1052
|
+
const tree = (0, import_menuStructure2.buildMenuTree)(actions, { aiSlot: aiExtras.length > 0 });
|
|
1053
|
+
const nodes = tree.topLevel.map((a) => richActionNode(a, ctx));
|
|
1054
|
+
for (const sub of (0, import_menuStructure2.withAiSlot)(tree.submenus, aiExtras.length > 0)) {
|
|
1055
|
+
const children = sub.actions.map((a) => richActionNode(a, ctx));
|
|
1056
|
+
const isAi = sub.label === import_menuStructure2.AI_SUBMENU_LABEL;
|
|
1057
|
+
if (isAi && aiExtras.length > 0) {
|
|
1058
|
+
if (children.length > 0) children.push({ kind: "separator", id: "rich-ai:sep" });
|
|
1059
|
+
children.push(...aiExtras);
|
|
1060
|
+
}
|
|
1061
|
+
nodes.push({
|
|
1062
|
+
kind: "submenu",
|
|
1063
|
+
id: `rich-sub:${sub.label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`,
|
|
1064
|
+
label: sub.label,
|
|
1065
|
+
icon: sub.icon,
|
|
1066
|
+
width: isAi ? "w-64" : void 0,
|
|
1067
|
+
children
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
nodes.push(...tree.extras.map((a) => richActionNode(a, ctx)));
|
|
1071
|
+
return nodes;
|
|
1072
|
+
}
|
|
1073
|
+
function categoryGroupNode(group, m) {
|
|
1074
|
+
const { category, items, children } = group;
|
|
1075
|
+
const children_ = items.map((entry) => {
|
|
1076
|
+
const isDisabled = entry.entryType === "agent_shortcut" && !entry.agentId;
|
|
1077
|
+
const hint = (
|
|
1078
|
+
// Advertised only when the shell's key listener can run it (key-combo.ts).
|
|
1079
|
+
entry.entryType === "agent_shortcut" && entry.keyboardShortcut && parseKeyCombo(entry.keyboardShortcut) ? entry.keyboardShortcut : isDisabled ? "Not configured" : void 0
|
|
1080
|
+
);
|
|
1081
|
+
return {
|
|
1082
|
+
kind: "item",
|
|
1083
|
+
id: `entry:${entry.id}`,
|
|
1084
|
+
label: entry.label,
|
|
1085
|
+
icon: resolveIcon(entry.iconName),
|
|
1086
|
+
// Phase 6.7: there is no "legacy match" row any more. Availability is
|
|
1087
|
+
// derived (requirement-gate.ts) — an item either QUALIFIES here or is
|
|
1088
|
+
// absent. Nothing renders red to advertise a backfill backlog.
|
|
1089
|
+
hint,
|
|
1090
|
+
disabled: isDisabled,
|
|
1091
|
+
onSelect: () => m.handleEntrySelect(entry)
|
|
1092
|
+
};
|
|
1093
|
+
});
|
|
1094
|
+
if (children.length > 0) {
|
|
1095
|
+
if (items.length > 0) children_.push({ kind: "separator", id: `${category.id}:sep` });
|
|
1096
|
+
for (const child of children) children_.push(categoryGroupNode(child, m));
|
|
1097
|
+
}
|
|
1098
|
+
return {
|
|
1099
|
+
kind: "submenu",
|
|
1100
|
+
id: `cat:${category.id}`,
|
|
1101
|
+
label: category.label,
|
|
1102
|
+
icon: resolveIcon(category.iconName),
|
|
1103
|
+
iconStyle: { color: category.color || "currentColor" },
|
|
1104
|
+
disabled: items.length === 0 && children.length === 0,
|
|
1105
|
+
emptyLabel: `No items in ${category.label}`,
|
|
1106
|
+
width: "w-64",
|
|
1107
|
+
children: children_
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function placementNode(placementType, m) {
|
|
1111
|
+
const mode = m.resolvedPlacementMode[placementType];
|
|
1112
|
+
if (mode === "hide") return null;
|
|
1113
|
+
const groups = m.grouped[placementType] || [];
|
|
1114
|
+
const hasCategories = groups.length > 0;
|
|
1115
|
+
const label = getPlacementLabel(placementType);
|
|
1116
|
+
const color = PLACEMENT_COLOR[placementType];
|
|
1117
|
+
if (!hasCategories && m.librariesError && mode !== "disable") {
|
|
1118
|
+
return failedLibraryNode(`placement:${placementType}`, label, getPlacementIcon(placementType), m, placementType);
|
|
1119
|
+
}
|
|
1120
|
+
return {
|
|
1121
|
+
kind: "submenu",
|
|
1122
|
+
id: `placement:${placementType}`,
|
|
1123
|
+
label,
|
|
1124
|
+
icon: getPlacementIcon(placementType),
|
|
1125
|
+
iconStyle: color ? { color } : void 0,
|
|
1126
|
+
disabled: mode === "disable" || !hasCategories,
|
|
1127
|
+
loading: m.loading && !hasCategories,
|
|
1128
|
+
emptyLabel: `No ${label}`,
|
|
1129
|
+
width: "w-64",
|
|
1130
|
+
placement: placementType,
|
|
1131
|
+
children: groups.map((g) => categoryGroupNode(g, m))
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function failedLibraryNode(id, label, icon4, m, placement) {
|
|
1135
|
+
return {
|
|
1136
|
+
kind: "submenu",
|
|
1137
|
+
id,
|
|
1138
|
+
label,
|
|
1139
|
+
icon: icon4,
|
|
1140
|
+
width: "w-64",
|
|
1141
|
+
placement,
|
|
1142
|
+
children: [
|
|
1143
|
+
{
|
|
1144
|
+
kind: "item",
|
|
1145
|
+
id: `${id}:retry`,
|
|
1146
|
+
label: "Couldn't load. Retry",
|
|
1147
|
+
icon: import_lucide_react2.RotateCcw,
|
|
1148
|
+
keepsMenuOpen: true,
|
|
1149
|
+
onSelect: m.retryLibraries
|
|
1150
|
+
}
|
|
1151
|
+
]
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function boundAgentsNode(m) {
|
|
1155
|
+
const mode = m.resolvedPlacementMode["bound-agent"];
|
|
1156
|
+
if (mode === "hide") return null;
|
|
1157
|
+
const sections = m.boundAgentSections.filter((s) => s.agents.length > 0);
|
|
1158
|
+
const hasAgents = sections.length > 0;
|
|
1159
|
+
if (!hasAgents && m.boundAgentsError && !m.boundAgentsLoading && mode !== "disable") {
|
|
1160
|
+
return failedLibraryNode("placement:bound-agent", "Agents", import_domain2.AGENT_ICON, m, "bound-agent");
|
|
1161
|
+
}
|
|
1162
|
+
const children = [];
|
|
1163
|
+
sections.forEach((section, idx) => {
|
|
1164
|
+
if (idx > 0) children.push({ kind: "separator", id: `agents:${section.key}:sep` });
|
|
1165
|
+
children.push({
|
|
1166
|
+
kind: "label",
|
|
1167
|
+
id: `agents:${section.key}:label`,
|
|
1168
|
+
label: section.label.toUpperCase()
|
|
1169
|
+
});
|
|
1170
|
+
for (const agent of section.agents) {
|
|
1171
|
+
children.push({
|
|
1172
|
+
kind: "item",
|
|
1173
|
+
id: `agent:${section.key}:${agent.agentId}`,
|
|
1174
|
+
label: agent.name,
|
|
1175
|
+
icon: import_domain2.AGENT_ICON,
|
|
1176
|
+
iconClassName: "text-indigo-500/80",
|
|
1177
|
+
onSelect: () => void m.handleBoundAgentExecute(agent)
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
return {
|
|
1182
|
+
kind: "submenu",
|
|
1183
|
+
id: "placement:bound-agent",
|
|
1184
|
+
label: "Agents",
|
|
1185
|
+
icon: import_domain2.AGENT_ICON,
|
|
1186
|
+
iconClassName: "text-indigo-500",
|
|
1187
|
+
disabled: mode === "disable" || !hasAgents && !m.boundAgentsLoading,
|
|
1188
|
+
loading: m.boundAgentsLoading,
|
|
1189
|
+
emptyLabel: m.boundAgentsLoading && !hasAgents ? "Loading\u2026" : "No agents available",
|
|
1190
|
+
width: "w-64",
|
|
1191
|
+
placement: "bound-agent",
|
|
1192
|
+
children
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
function buildMenuModel(m, props) {
|
|
1196
|
+
const {
|
|
1197
|
+
extraSections,
|
|
1198
|
+
isEditable,
|
|
1199
|
+
onSave,
|
|
1200
|
+
onDelete,
|
|
1201
|
+
onUndo,
|
|
1202
|
+
onRedo,
|
|
1203
|
+
canUndo,
|
|
1204
|
+
canRedo,
|
|
1205
|
+
undoHint,
|
|
1206
|
+
redoHint,
|
|
1207
|
+
onViewHistory,
|
|
1208
|
+
hasHistory,
|
|
1209
|
+
selectedText,
|
|
1210
|
+
entity,
|
|
1211
|
+
selectionRange,
|
|
1212
|
+
getTextarea,
|
|
1213
|
+
recordActionsOnly = false
|
|
1214
|
+
} = props;
|
|
1215
|
+
const { actionText, quickActions } = m;
|
|
1216
|
+
const header = actionText.source === "selection" ? { label: "Selected", text: actionText.text } : actionText.source === "content" ? { label: "Content", text: actionText.text } : null;
|
|
1217
|
+
const copy = {
|
|
1218
|
+
kind: "item",
|
|
1219
|
+
id: "copy",
|
|
1220
|
+
label: "Copy",
|
|
1221
|
+
icon: import_lucide_react2.Copy,
|
|
1222
|
+
iconClassName: "text-emerald-500",
|
|
1223
|
+
disabled: actionText.source === "none",
|
|
1224
|
+
onSelect: () => void m.handleCopy()
|
|
1225
|
+
};
|
|
1226
|
+
const speak = {
|
|
1227
|
+
kind: "item",
|
|
1228
|
+
id: "speak",
|
|
1229
|
+
label: "Speak",
|
|
1230
|
+
icon: import_lucide_react2.Volume2,
|
|
1231
|
+
iconClassName: "text-sky-500",
|
|
1232
|
+
disabled: actionText.source === "none",
|
|
1233
|
+
onSelect: m.handleSpeak
|
|
1234
|
+
};
|
|
1235
|
+
const listenDisabled = actionText.source === "none" || !m.spokenSummaryAvailable;
|
|
1236
|
+
const hasRegistry = m.registryActions.length > 0;
|
|
1237
|
+
const spokenSummary = {
|
|
1238
|
+
kind: "item",
|
|
1239
|
+
id: "spoken-summary",
|
|
1240
|
+
label: "Summarize without playing",
|
|
1241
|
+
icon: import_lucide_react2.Headphones,
|
|
1242
|
+
iconClassName: "text-violet-500",
|
|
1243
|
+
disabled: listenDisabled,
|
|
1244
|
+
onSelect: m.handleSpokenSummary
|
|
1245
|
+
};
|
|
1246
|
+
const spokenSummaryLive = {
|
|
1247
|
+
kind: "item",
|
|
1248
|
+
id: "spoken-summary-live",
|
|
1249
|
+
label: "Summarize & listen",
|
|
1250
|
+
icon: import_lucide_react2.AudioLines,
|
|
1251
|
+
iconClassName: "text-violet-500",
|
|
1252
|
+
disabled: listenDisabled,
|
|
1253
|
+
onSelect: m.handleSpokenSummaryLive
|
|
1254
|
+
};
|
|
1255
|
+
const listen = hasRegistry ? null : {
|
|
1256
|
+
kind: "submenu",
|
|
1257
|
+
id: "listen",
|
|
1258
|
+
label: "Listen",
|
|
1259
|
+
icon: import_lucide_react2.Headphones,
|
|
1260
|
+
iconClassName: "text-violet-500",
|
|
1261
|
+
disabled: listenDisabled,
|
|
1262
|
+
children: [spokenSummary, spokenSummaryLive]
|
|
1263
|
+
};
|
|
1264
|
+
const copyAs = !hasRegistry && m.copyVariantActions.length > 0 ? {
|
|
1265
|
+
kind: "submenu",
|
|
1266
|
+
id: "copy-as",
|
|
1267
|
+
label: "Copy as",
|
|
1268
|
+
icon: import_lucide_react2.Copy,
|
|
1269
|
+
iconClassName: "text-emerald-500",
|
|
1270
|
+
children: m.copyVariantActions.map((a) => richActionNode(a, m.richDocCtx))
|
|
1271
|
+
} : null;
|
|
1272
|
+
const json = m.jsonSection ? {
|
|
1273
|
+
kind: "submenu",
|
|
1274
|
+
id: "json",
|
|
1275
|
+
label: jsonSectionLabel(m.jsonSection),
|
|
1276
|
+
icon: import_lucide_react2.Braces,
|
|
1277
|
+
iconClassName: "text-amber-500",
|
|
1278
|
+
width: "w-72",
|
|
1279
|
+
children: m.jsonSection.actions.map((action) => ({
|
|
1280
|
+
kind: "item",
|
|
1281
|
+
id: `json:${action.id}`,
|
|
1282
|
+
label: action.label,
|
|
1283
|
+
hint: action.hint,
|
|
1284
|
+
disabled: action.disabled,
|
|
1285
|
+
onSelect: () => void action.run()
|
|
1286
|
+
}))
|
|
1287
|
+
} : null;
|
|
1288
|
+
const cut = {
|
|
1289
|
+
kind: "item",
|
|
1290
|
+
id: "cut",
|
|
1291
|
+
label: "Cut",
|
|
1292
|
+
icon: import_lucide_react2.Scissors,
|
|
1293
|
+
iconClassName: "text-emerald-500",
|
|
1294
|
+
disabled: !isEditable || !selectedText,
|
|
1295
|
+
onSelect: () => void m.handleCut()
|
|
1296
|
+
};
|
|
1297
|
+
const paste = {
|
|
1298
|
+
kind: "item",
|
|
1299
|
+
id: "paste",
|
|
1300
|
+
label: "Paste",
|
|
1301
|
+
icon: import_lucide_react2.Clipboard,
|
|
1302
|
+
iconClassName: "text-emerald-500",
|
|
1303
|
+
disabled: !isEditable,
|
|
1304
|
+
onSelect: () => void m.handlePaste()
|
|
1305
|
+
};
|
|
1306
|
+
const selectAll = {
|
|
1307
|
+
kind: "item",
|
|
1308
|
+
id: "select-all",
|
|
1309
|
+
label: "Select All",
|
|
1310
|
+
icon: import_lucide_react2.Type,
|
|
1311
|
+
iconClassName: "text-muted-foreground",
|
|
1312
|
+
// Select All acts on a captured selection's container, or an editable
|
|
1313
|
+
// surface's own field. With neither (right-click on a table row or on
|
|
1314
|
+
// read-only content without selecting) it would do nothing — a dead row,
|
|
1315
|
+
// so it is absent (R1 b), not offered.
|
|
1316
|
+
disabled: !selectionRange && !(isEditable && getTextarea),
|
|
1317
|
+
onSelect: m.handleSelectAll
|
|
1318
|
+
};
|
|
1319
|
+
const find = {
|
|
1320
|
+
kind: "item",
|
|
1321
|
+
id: "find",
|
|
1322
|
+
label: "Find & Replace",
|
|
1323
|
+
icon: import_lucide_react2.Search,
|
|
1324
|
+
iconClassName: "text-muted-foreground",
|
|
1325
|
+
onSelect: m.handleFind
|
|
1326
|
+
};
|
|
1327
|
+
const insertReference = {
|
|
1328
|
+
kind: "item",
|
|
1329
|
+
id: "insert-reference",
|
|
1330
|
+
label: isEditable ? "Insert reference\u2026" : "Copy reference\u2026",
|
|
1331
|
+
icon: import_lucide_react2.AtSign,
|
|
1332
|
+
iconClassName: "text-primary",
|
|
1333
|
+
onSelect: m.handleInsertReference
|
|
1334
|
+
};
|
|
1335
|
+
const chat = {
|
|
1336
|
+
kind: "item",
|
|
1337
|
+
id: "chat",
|
|
1338
|
+
label: "Chat",
|
|
1339
|
+
icon: import_lucide_react2.MessageSquare,
|
|
1340
|
+
iconClassName: "text-primary",
|
|
1341
|
+
onSelect: () => quickActions.openChatWindow()
|
|
1342
|
+
};
|
|
1343
|
+
const undo = {
|
|
1344
|
+
kind: "item",
|
|
1345
|
+
id: "undo",
|
|
1346
|
+
label: "Undo",
|
|
1347
|
+
icon: import_lucide_react2.Undo2,
|
|
1348
|
+
iconClassName: "text-sky-500",
|
|
1349
|
+
hint: undoHint,
|
|
1350
|
+
disabled: onUndo ? !canUndo : !m.canNativeUndo,
|
|
1351
|
+
onSelect: m.handleUndo
|
|
1352
|
+
};
|
|
1353
|
+
const redo = {
|
|
1354
|
+
kind: "item",
|
|
1355
|
+
id: "redo",
|
|
1356
|
+
label: "Redo",
|
|
1357
|
+
icon: import_lucide_react2.Redo2,
|
|
1358
|
+
iconClassName: "text-sky-500",
|
|
1359
|
+
hint: redoHint,
|
|
1360
|
+
disabled: onRedo ? !canRedo : !m.canNativeUndo,
|
|
1361
|
+
onSelect: m.handleRedo
|
|
1362
|
+
};
|
|
1363
|
+
const viewHistory = {
|
|
1364
|
+
kind: "item",
|
|
1365
|
+
id: "view-history",
|
|
1366
|
+
label: "View History",
|
|
1367
|
+
icon: import_lucide_react2.History,
|
|
1368
|
+
iconClassName: "text-violet-500",
|
|
1369
|
+
disabled: !onViewHistory || !hasHistory,
|
|
1370
|
+
onSelect: () => onViewHistory?.()
|
|
1371
|
+
};
|
|
1372
|
+
const compare = hasRegistry ? null : {
|
|
1373
|
+
kind: "submenu",
|
|
1374
|
+
id: "compare",
|
|
1375
|
+
label: "Compare",
|
|
1376
|
+
icon: import_lucide_react2.GitCompareArrows,
|
|
1377
|
+
iconClassName: "text-amber-500",
|
|
1378
|
+
children: [
|
|
1379
|
+
{
|
|
1380
|
+
kind: "item",
|
|
1381
|
+
id: "compare:clipboard",
|
|
1382
|
+
label: "Compare with clipboard",
|
|
1383
|
+
icon: import_lucide_react2.Clipboard,
|
|
1384
|
+
onSelect: () => void m.handleCompareClipboard()
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
kind: "item",
|
|
1388
|
+
id: "compare:set-base",
|
|
1389
|
+
label: "Set as compare base",
|
|
1390
|
+
icon: import_lucide_react2.Pin,
|
|
1391
|
+
description: actionText.source === "selection" ? "Use selection" : "Use content",
|
|
1392
|
+
onSelect: m.handleSetCompareBase
|
|
1393
|
+
},
|
|
1394
|
+
{
|
|
1395
|
+
kind: "item",
|
|
1396
|
+
id: "compare:with-base",
|
|
1397
|
+
label: "Compare with base",
|
|
1398
|
+
icon: import_lucide_react2.GitCompareArrows,
|
|
1399
|
+
description: m.hasCompareBase ? void 0 : "No base set yet",
|
|
1400
|
+
disabled: !m.hasCompareBase,
|
|
1401
|
+
onSelect: () => void m.handleCompareWithBase()
|
|
1402
|
+
}
|
|
1403
|
+
]
|
|
1404
|
+
};
|
|
1405
|
+
const exportMenu = !hasRegistry && m.exportActions.length > 0 ? {
|
|
1406
|
+
kind: "submenu",
|
|
1407
|
+
id: "export",
|
|
1408
|
+
label: "Export",
|
|
1409
|
+
icon: import_lucide_react2.Download,
|
|
1410
|
+
iconClassName: "text-amber-500",
|
|
1411
|
+
children: m.exportActions.map((a) => richActionNode(a, m.richDocCtx))
|
|
1412
|
+
} : null;
|
|
1413
|
+
const convert = !hasRegistry && m.convertActions.length > 0 ? {
|
|
1414
|
+
kind: "submenu",
|
|
1415
|
+
id: "convert",
|
|
1416
|
+
label: "Convert",
|
|
1417
|
+
icon: import_lucide_react2.Replace,
|
|
1418
|
+
iconClassName: "text-violet-500",
|
|
1419
|
+
children: m.convertActions.map((a) => richActionNode(a, m.richDocCtx))
|
|
1420
|
+
} : null;
|
|
1421
|
+
const attach = entity ? {
|
|
1422
|
+
kind: "item",
|
|
1423
|
+
id: "attach",
|
|
1424
|
+
label: "Attach To",
|
|
1425
|
+
icon: import_lucide_react2.Link2,
|
|
1426
|
+
iconClassName: "text-sky-500",
|
|
1427
|
+
onSelect: m.handleAttach
|
|
1428
|
+
} : null;
|
|
1429
|
+
const linkRecord = entity ? {
|
|
1430
|
+
kind: "item",
|
|
1431
|
+
id: "link-record",
|
|
1432
|
+
label: "Link a record\u2026",
|
|
1433
|
+
icon: import_lucide_react2.Link,
|
|
1434
|
+
iconClassName: "text-sky-500",
|
|
1435
|
+
onSelect: m.handleLinkRecord
|
|
1436
|
+
} : null;
|
|
1437
|
+
const share = entity?.resourceType ? {
|
|
1438
|
+
kind: "item",
|
|
1439
|
+
id: "share",
|
|
1440
|
+
label: "Share",
|
|
1441
|
+
icon: import_lucide_react2.Share2,
|
|
1442
|
+
iconClassName: "text-emerald-500",
|
|
1443
|
+
onSelect: m.handleShare
|
|
1444
|
+
} : null;
|
|
1445
|
+
const libraryNodes = [
|
|
1446
|
+
placementNode(import_constants2.PLACEMENT_TYPES.AI_ACTION, m),
|
|
1447
|
+
boundAgentsNode(m),
|
|
1448
|
+
placementNode(import_constants2.PLACEMENT_TYPES.CONTENT_BLOCK, m),
|
|
1449
|
+
placementNode(import_constants2.PLACEMENT_TYPES.USER_TOOL, m),
|
|
1450
|
+
placementNode(import_constants2.PLACEMENT_TYPES.ORGANIZATION_TOOL, m)
|
|
1451
|
+
].filter((n) => n !== null);
|
|
1452
|
+
const registry2 = hasRegistry ? registryTreeNodes(m.registryActions, m.richDocCtx, libraryNodes) : null;
|
|
1453
|
+
const placements = hasRegistry ? [] : libraryNodes;
|
|
1454
|
+
const quickMode = m.resolvedPlacementMode["quick-action"];
|
|
1455
|
+
const quick = quickMode === "hide" ? null : {
|
|
1456
|
+
kind: "submenu",
|
|
1457
|
+
id: "quick-actions",
|
|
1458
|
+
label: "Quick Actions",
|
|
1459
|
+
icon: import_lucide_react2.Zap,
|
|
1460
|
+
iconClassName: "text-pink-500",
|
|
1461
|
+
disabled: quickMode === "disable",
|
|
1462
|
+
width: "w-56",
|
|
1463
|
+
children: [
|
|
1464
|
+
{ kind: "item", id: "qa:notes", label: "Notes", icon: import_lucide_react2.StickyNote, onSelect: () => quickActions.openQuickNotes() },
|
|
1465
|
+
{ kind: "item", id: "qa:tasks", label: "Tasks", icon: import_lucide_react2.CheckSquare, onSelect: () => quickActions.openQuickTasks() },
|
|
1466
|
+
{ kind: "item", id: "qa:chat", label: "Chat", icon: import_lucide_react2.MessageSquare, onSelect: () => quickActions.openQuickChat() },
|
|
1467
|
+
{ kind: "item", id: "qa:data", label: "Data", icon: import_lucide_react2.Database, onSelect: () => quickActions.openQuickData() },
|
|
1468
|
+
{ kind: "item", id: "qa:files", label: "Files", icon: import_lucide_react2.FolderOpen, onSelect: () => quickActions.openQuickFiles() },
|
|
1469
|
+
{ kind: "item", id: "qa:voice", label: "Voice Input", icon: import_lucide_react2.Mic, onSelect: () => quickActions.openVoicePad() }
|
|
1470
|
+
]
|
|
1471
|
+
};
|
|
1472
|
+
const save = isEditable && onSave ? {
|
|
1473
|
+
kind: "item",
|
|
1474
|
+
id: "save",
|
|
1475
|
+
label: "Save",
|
|
1476
|
+
icon: import_lucide_react2.Save,
|
|
1477
|
+
iconClassName: "text-emerald-500",
|
|
1478
|
+
onSelect: () => onSave()
|
|
1479
|
+
} : null;
|
|
1480
|
+
const del = isEditable && onDelete ? {
|
|
1481
|
+
kind: "item",
|
|
1482
|
+
id: "delete",
|
|
1483
|
+
label: "Delete",
|
|
1484
|
+
icon: import_lucide_react2.Trash2,
|
|
1485
|
+
destructive: true,
|
|
1486
|
+
onSelect: () => void m.handleDelete()
|
|
1487
|
+
} : null;
|
|
1488
|
+
const admin = m.isAdmin ? {
|
|
1489
|
+
kind: "submenu",
|
|
1490
|
+
id: "admin",
|
|
1491
|
+
label: "Admin Tools",
|
|
1492
|
+
icon: import_lucide_react2.Shield,
|
|
1493
|
+
iconClassName: "text-rose-500",
|
|
1494
|
+
width: "w-56",
|
|
1495
|
+
children: [
|
|
1496
|
+
{
|
|
1497
|
+
kind: "item",
|
|
1498
|
+
id: "admin:debug",
|
|
1499
|
+
label: `${m.isDebugMode ? "Disable" : "Enable"} Debug Mode`,
|
|
1500
|
+
icon: m.isDebugMode ? import_lucide_react2.EyeOff : import_lucide_react2.Eye,
|
|
1501
|
+
iconClassName: m.isDebugMode ? "text-amber-600 dark:text-amber-400" : void 0,
|
|
1502
|
+
onSelect: m.handleToggleDebugMode
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
kind: "item",
|
|
1506
|
+
id: "admin:values",
|
|
1507
|
+
label: "Context Values",
|
|
1508
|
+
icon: import_lucide_react2.Bug,
|
|
1509
|
+
className: "text-amber-600 dark:text-amber-400",
|
|
1510
|
+
onSelect: m.handleInspectValues
|
|
1511
|
+
},
|
|
1512
|
+
...m.isDebugMode ? [
|
|
1513
|
+
{
|
|
1514
|
+
kind: "item",
|
|
1515
|
+
id: "admin:state",
|
|
1516
|
+
label: "Redux State",
|
|
1517
|
+
icon: import_lucide_react2.Database,
|
|
1518
|
+
className: "text-amber-600 dark:text-amber-400",
|
|
1519
|
+
onSelect: m.handleInspectState
|
|
1520
|
+
}
|
|
1521
|
+
] : [],
|
|
1522
|
+
{ kind: "separator", id: "admin:sep" },
|
|
1523
|
+
{
|
|
1524
|
+
kind: "item",
|
|
1525
|
+
id: "admin:indicator",
|
|
1526
|
+
label: `${m.isAdminIndicatorOpen ? "Hide" : "Show"} Admin Indicator`,
|
|
1527
|
+
icon: m.isAdminIndicatorOpen ? import_lucide_react2.Eye : import_lucide_react2.EyeOff,
|
|
1528
|
+
iconClassName: m.isAdminIndicatorOpen ? "text-green-600 dark:text-green-400" : void 0,
|
|
1529
|
+
onSelect: m.handleToggleAdminIndicator
|
|
1530
|
+
}
|
|
1531
|
+
]
|
|
1532
|
+
} : null;
|
|
1533
|
+
const extras = extrasByAnchor(extraSections);
|
|
1534
|
+
const surfaceInfo = {
|
|
1535
|
+
id: "surface-info",
|
|
1536
|
+
group: "surface-info",
|
|
1537
|
+
nodes: m.surfaceSection.items.map(fromExtraItem)
|
|
1538
|
+
};
|
|
1539
|
+
if (recordActionsOnly) {
|
|
1540
|
+
const recordSections = [];
|
|
1541
|
+
const recordHasDocument = hasRegistry && m.richDocCtx?.source?.type !== "raw";
|
|
1542
|
+
const clipboardRecord = compactNodes([recordHasDocument ? copy : null, recordHasDocument ? copyAs : null, recordHasDocument ? json : null]);
|
|
1543
|
+
if (clipboardRecord.length > 0) {
|
|
1544
|
+
recordSections.push({ id: "clipboard", group: "clipboard", nodes: clipboardRecord });
|
|
1545
|
+
}
|
|
1546
|
+
if (registry2 && recordHasDocument) recordSections.push({ id: "registry", group: "document", nodes: registry2 });
|
|
1547
|
+
recordSections.push(...extras["after-clipboard"]);
|
|
1548
|
+
if (recordHasDocument && (exportMenu || convert)) {
|
|
1549
|
+
recordSections.push({ id: "document-out", group: "document", nodes: compactNodes([exportMenu, convert]) });
|
|
1550
|
+
}
|
|
1551
|
+
if (attach || linkRecord || share) {
|
|
1552
|
+
recordSections.push({ id: "share", group: "share", nodes: compactNodes([attach, linkRecord, share]) });
|
|
1553
|
+
}
|
|
1554
|
+
recordSections.push(...extras["after-compare"]);
|
|
1555
|
+
recordSections.push(...extras["after-placements"]);
|
|
1556
|
+
if (save || del) recordSections.push({ id: "editable", group: "editable", nodes: compactNodes([save, del]) });
|
|
1557
|
+
if (admin) recordSections.push({ id: "admin", group: "admin", nodes: [admin] });
|
|
1558
|
+
recordSections.push(surfaceInfo);
|
|
1559
|
+
return {
|
|
1560
|
+
header,
|
|
1561
|
+
sections: withEditInPlace(liftPrimarySections(recordSections)),
|
|
1562
|
+
roles: {
|
|
1563
|
+
copy,
|
|
1564
|
+
speak,
|
|
1565
|
+
listen: null,
|
|
1566
|
+
copyAs,
|
|
1567
|
+
json,
|
|
1568
|
+
cut,
|
|
1569
|
+
paste,
|
|
1570
|
+
selectAll,
|
|
1571
|
+
find,
|
|
1572
|
+
insertReference,
|
|
1573
|
+
chat,
|
|
1574
|
+
undo,
|
|
1575
|
+
redo,
|
|
1576
|
+
viewHistory,
|
|
1577
|
+
compare: null,
|
|
1578
|
+
exportMenu,
|
|
1579
|
+
convert,
|
|
1580
|
+
attach,
|
|
1581
|
+
linkRecord,
|
|
1582
|
+
share,
|
|
1583
|
+
placements: [],
|
|
1584
|
+
registry: registry2,
|
|
1585
|
+
quickActions: null,
|
|
1586
|
+
save,
|
|
1587
|
+
del,
|
|
1588
|
+
admin,
|
|
1589
|
+
extras,
|
|
1590
|
+
surfaceInfo
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
const sections = [];
|
|
1595
|
+
sections.push({
|
|
1596
|
+
id: "clipboard",
|
|
1597
|
+
group: "clipboard",
|
|
1598
|
+
nodes: compactNodes([
|
|
1599
|
+
copy,
|
|
1600
|
+
speak,
|
|
1601
|
+
listen,
|
|
1602
|
+
copyAs,
|
|
1603
|
+
json,
|
|
1604
|
+
cut,
|
|
1605
|
+
paste,
|
|
1606
|
+
selectAll,
|
|
1607
|
+
find,
|
|
1608
|
+
insertReference
|
|
1609
|
+
])
|
|
1610
|
+
});
|
|
1611
|
+
if (registry2) sections.push({ id: "registry", group: "document", nodes: registry2 });
|
|
1612
|
+
sections.push(...extras["after-clipboard"]);
|
|
1613
|
+
sections.push({ id: "tools", group: "tools", nodes: [chat] });
|
|
1614
|
+
sections.push({
|
|
1615
|
+
id: "history",
|
|
1616
|
+
group: "history",
|
|
1617
|
+
nodes: compactNodes([undo, redo, viewHistory])
|
|
1618
|
+
});
|
|
1619
|
+
if (compare) sections.push({ id: "compare", group: "tools", nodes: [compare] });
|
|
1620
|
+
if (exportMenu || convert) {
|
|
1621
|
+
sections.push({ id: "document-out", group: "document", nodes: compactNodes([exportMenu, convert]) });
|
|
1622
|
+
}
|
|
1623
|
+
if (attach || linkRecord || share) {
|
|
1624
|
+
sections.push({ id: "share", group: "share", nodes: compactNodes([attach, linkRecord, share]) });
|
|
1625
|
+
}
|
|
1626
|
+
sections.push(...extras["after-compare"]);
|
|
1627
|
+
if (placements.length > 0) sections.push({ id: "placements", group: "ai", nodes: placements });
|
|
1628
|
+
sections.push(...extras["after-placements"]);
|
|
1629
|
+
if (quick) {
|
|
1630
|
+
sections.push({
|
|
1631
|
+
id: "quick",
|
|
1632
|
+
group: "quick",
|
|
1633
|
+
joinPrevious: extras["after-placements"].length === 0,
|
|
1634
|
+
nodes: [quick]
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
if (save || del) {
|
|
1638
|
+
sections.push({
|
|
1639
|
+
id: "editable",
|
|
1640
|
+
group: "editable",
|
|
1641
|
+
nodes: compactNodes([save, del])
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
if (admin) sections.push({ id: "admin", group: "admin", nodes: [admin] });
|
|
1645
|
+
sections.push(surfaceInfo);
|
|
1646
|
+
return {
|
|
1647
|
+
header,
|
|
1648
|
+
sections: withEditInPlace(liftPrimarySections(sections)),
|
|
1649
|
+
roles: {
|
|
1650
|
+
copy,
|
|
1651
|
+
speak,
|
|
1652
|
+
listen,
|
|
1653
|
+
copyAs,
|
|
1654
|
+
json,
|
|
1655
|
+
cut,
|
|
1656
|
+
paste,
|
|
1657
|
+
selectAll,
|
|
1658
|
+
find,
|
|
1659
|
+
insertReference,
|
|
1660
|
+
chat,
|
|
1661
|
+
undo,
|
|
1662
|
+
redo,
|
|
1663
|
+
viewHistory,
|
|
1664
|
+
compare,
|
|
1665
|
+
exportMenu,
|
|
1666
|
+
convert,
|
|
1667
|
+
attach,
|
|
1668
|
+
linkRecord,
|
|
1669
|
+
share,
|
|
1670
|
+
placements,
|
|
1671
|
+
registry: registry2,
|
|
1672
|
+
quickActions: quick,
|
|
1673
|
+
save,
|
|
1674
|
+
del,
|
|
1675
|
+
admin,
|
|
1676
|
+
extras,
|
|
1677
|
+
surfaceInfo
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// src/engine/table-row-item.ts
|
|
1683
|
+
var import_declare = require("@ai-matrx/alchemy/declare");
|
|
1684
|
+
var import_surface = require("@ai-matrx/alchemy/surface");
|
|
1685
|
+
var TableRowMenu = class {
|
|
1686
|
+
constructor(descriptor) {
|
|
1687
|
+
this.descriptor = descriptor;
|
|
1688
|
+
}
|
|
1689
|
+
descriptor;
|
|
1690
|
+
};
|
|
1691
|
+
function createTableRowMenuDescriptor(descriptor) {
|
|
1692
|
+
return new TableRowMenu(descriptor);
|
|
1693
|
+
}
|
|
1694
|
+
function isCommand(value) {
|
|
1695
|
+
return typeof value === "function";
|
|
1696
|
+
}
|
|
1697
|
+
function tableRowEditCommands(controls) {
|
|
1698
|
+
if (typeof controls !== "object" || controls === null) return {};
|
|
1699
|
+
const read = (key) => {
|
|
1700
|
+
const value = Reflect.get(controls, key);
|
|
1701
|
+
return isCommand(value) ? value : void 0;
|
|
1702
|
+
};
|
|
1703
|
+
return compact({
|
|
1704
|
+
beginEdit: read("beginEdit"),
|
|
1705
|
+
saveEdits: read("saveEdits"),
|
|
1706
|
+
cancelEdits: read("cancelEdits")
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
function createDefaultTableRowMenuDescriptor(payload) {
|
|
1710
|
+
if (payload.level !== "row") return null;
|
|
1711
|
+
return createTableRowMenuDescriptor(
|
|
1712
|
+
buildDefaultTableRowMenuDescriptor(
|
|
1713
|
+
payload.row,
|
|
1714
|
+
tableRowEditCommands(payload.controls)
|
|
1715
|
+
)
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
function buildDefaultTableRowMenuDescriptor(row, controls) {
|
|
1719
|
+
const items = [
|
|
1720
|
+
...controls.beginEdit ? [{ kind: "item", id: "table-edit-row", label: "Edit row", onSelect: controls.beginEdit }] : [],
|
|
1721
|
+
...controls.saveEdits ? [{ kind: "item", id: "table-save-row", label: "Save changes", onSelect: () => void controls.saveEdits?.() }] : [],
|
|
1722
|
+
...controls.cancelEdits ? [{ kind: "item", id: "table-discard-row", label: "Discard changes", onSelect: controls.cancelEdits }] : []
|
|
1723
|
+
];
|
|
1724
|
+
return {
|
|
1725
|
+
context: {
|
|
1726
|
+
content: JSON.stringify(row, null, 2) ?? String(row),
|
|
1727
|
+
context: row,
|
|
1728
|
+
__entity: null
|
|
1729
|
+
},
|
|
1730
|
+
extraSections: items.length === 0 ? [] : [{ id: "table-row", label: "Row", primary: true, anchor: "after-clipboard", items }]
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function rowElement(tableId, rowId) {
|
|
1734
|
+
if (typeof document === "undefined") return null;
|
|
1735
|
+
const table = Array.from(document.querySelectorAll("[data-matrx-table-id]")).find((el) => el.dataset.matrxTableId === tableId);
|
|
1736
|
+
return Array.from(table?.querySelectorAll("[data-row-id]") ?? []).find((el) => el.dataset.rowId === rowId) ?? null;
|
|
1737
|
+
}
|
|
1738
|
+
function rowIsReadOnly(descriptor) {
|
|
1739
|
+
return !descriptor.extraSections.some((section) => section.items.some((item) => "id" in item && item.id === "table-edit-row"));
|
|
1740
|
+
}
|
|
1741
|
+
function registerTableRowContextResolver(tableId, resolve) {
|
|
1742
|
+
return import_surface.itemSources.register(tableId, (target) => {
|
|
1743
|
+
const token = resolve({
|
|
1744
|
+
tableId,
|
|
1745
|
+
level: target.level,
|
|
1746
|
+
...target.itemId ? { rowId: target.itemId } : {},
|
|
1747
|
+
...target.partId ? { columnId: target.partId } : {}
|
|
1748
|
+
});
|
|
1749
|
+
if (!(token instanceof TableRowMenu)) return null;
|
|
1750
|
+
const descriptor = token.descriptor;
|
|
1751
|
+
const rowId = target.itemId ?? "";
|
|
1752
|
+
return {
|
|
1753
|
+
item: {
|
|
1754
|
+
itemType: import_declare.TABLE_ROW_ITEM,
|
|
1755
|
+
identity: { table_id: tableId, row_id: rowId },
|
|
1756
|
+
level: target.level,
|
|
1757
|
+
readOnly: rowIsReadOnly(descriptor),
|
|
1758
|
+
readValues: async () => ({
|
|
1759
|
+
table_id: tableId,
|
|
1760
|
+
row_id: rowId,
|
|
1761
|
+
shown: shownWords(rowElement(tableId, rowId)),
|
|
1762
|
+
row: descriptor.context.context
|
|
1763
|
+
})
|
|
1764
|
+
},
|
|
1765
|
+
host: descriptor
|
|
1766
|
+
};
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
function resolveTableRowItem(target) {
|
|
1770
|
+
const row = target?.closest("[data-row-id]");
|
|
1771
|
+
const tableId = row?.closest("[data-matrx-table-id]")?.dataset.matrxTableId;
|
|
1772
|
+
const rowId = row?.dataset.rowId;
|
|
1773
|
+
if (!tableId || !rowId) return null;
|
|
1774
|
+
const hit = import_surface.itemSources.resolve({ containerId: tableId, level: "row", itemId: rowId });
|
|
1775
|
+
const descriptor = hit?.host ?? null;
|
|
1776
|
+
if (!hit || !descriptor) return null;
|
|
1777
|
+
const { readValues: _raw, ...item } = hit.item;
|
|
1778
|
+
return { item, menu: headedMenu(descriptor, target, row) };
|
|
1779
|
+
}
|
|
1780
|
+
function resolveTableRowMenuDescriptor(target) {
|
|
1781
|
+
return resolveTableRowItem(target)?.menu ?? null;
|
|
1782
|
+
}
|
|
1783
|
+
function headedMenu(descriptor, target, row) {
|
|
1784
|
+
const cell = target?.closest("td, [role='gridcell']");
|
|
1785
|
+
const shown = shownWords(cell ?? row);
|
|
1786
|
+
const ownHeading = descriptor.context[types_exports.CONTEXT_MENU_HEADING_KEY];
|
|
1787
|
+
const recordName = ownHeading ? "" : rowRecordName(row);
|
|
1788
|
+
const context = {
|
|
1789
|
+
...descriptor.context,
|
|
1790
|
+
...shown ? { content: shown } : {},
|
|
1791
|
+
...recordName ? { [types_exports.CONTEXT_MENU_HEADING_KEY]: { label: "Row", text: recordName } } : {}
|
|
1792
|
+
};
|
|
1793
|
+
const headed = ownHeading?.text ?? recordName;
|
|
1794
|
+
const same = (label) => typeof headed === "string" && !!label && label.trim().toLowerCase() === headed.trim().toLowerCase();
|
|
1795
|
+
const extraSections = descriptor.extraSections.map(
|
|
1796
|
+
(section) => section.primary && same(section.label) ? { ...section, label: "Row" } : section
|
|
1797
|
+
);
|
|
1798
|
+
const changedSections = extraSections.some((section, i) => section !== descriptor.extraSections[i]);
|
|
1799
|
+
return shown || recordName || changedSections ? { ...descriptor, context, extraSections } : descriptor;
|
|
1800
|
+
}
|
|
1801
|
+
function rowRecordName(row) {
|
|
1802
|
+
if (!row) return "";
|
|
1803
|
+
for (const cell of Array.from(row.querySelectorAll("td[data-matrx-table-column-id], [role='gridcell'][data-matrx-table-column-id]"))) {
|
|
1804
|
+
const words = primaryLine(cell);
|
|
1805
|
+
if (words) return words.length > 120 ? `${words.slice(0, 119)}\u2026` : words;
|
|
1806
|
+
}
|
|
1807
|
+
return "";
|
|
1808
|
+
}
|
|
1809
|
+
var LINE_BREAKING = "div, p, li, br, tr, h1, h2, h3, h4, h5, h6, section, article, header, footer, dd, dt";
|
|
1810
|
+
function primaryLine(element) {
|
|
1811
|
+
const copy = element.cloneNode(true);
|
|
1812
|
+
copy.querySelectorAll("button, [aria-hidden='true'], input, textarea, select").forEach((node) => node.remove());
|
|
1813
|
+
copy.querySelectorAll(LINE_BREAKING).forEach((node) => node.before("\n"));
|
|
1814
|
+
const lines = (copy.textContent ?? "").split("\n").map((line) => line.replace(/\s+/g, " ").trim()).filter((line) => line && line !== "\u2014");
|
|
1815
|
+
return lines[0] ?? "";
|
|
1816
|
+
}
|
|
1817
|
+
function shownWords(element) {
|
|
1818
|
+
if (!element) return "";
|
|
1819
|
+
const cells = element.querySelectorAll("td, [role='gridcell']");
|
|
1820
|
+
const text = cells.length > 1 ? Array.from(cells).map(primaryLine).filter(Boolean).join(" \xB7 ") : primaryLine(element);
|
|
1821
|
+
return text.length > 200 ? `${text.slice(0, 199)}\u2026` : text;
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// src/engine/regroup/grouping.ts
|
|
1825
|
+
var import_menu = require("@ai-matrx/alchemy/menu");
|
|
1826
|
+
var REGROUP_ID_PREFIX = "regroup:";
|
|
1827
|
+
function actionLabel(action, target) {
|
|
1828
|
+
try {
|
|
1829
|
+
return typeof action.label === "function" ? action.label(target) : action.label;
|
|
1830
|
+
} catch {
|
|
1831
|
+
return action.id;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
function isPageOwn(action) {
|
|
1835
|
+
const section = action.section;
|
|
1836
|
+
return Boolean(section && (section.primary || section.kind === "target"));
|
|
1837
|
+
}
|
|
1838
|
+
function labelOf(action, target) {
|
|
1839
|
+
if (typeof action.label === "string") return action.label;
|
|
1840
|
+
return target ? actionLabel(action, target) : "";
|
|
1841
|
+
}
|
|
1842
|
+
function ruleMatches(rule, action, target) {
|
|
1843
|
+
const w = rule.when;
|
|
1844
|
+
if (w.pageOwnLabels && isPageOwn(action)) {
|
|
1845
|
+
const label = labelOf(action, target);
|
|
1846
|
+
if (label && w.pageOwnLabels.some((p) => new RegExp(p, "i").test(label))) return true;
|
|
1847
|
+
}
|
|
1848
|
+
if (w.ids?.includes(action.id)) return true;
|
|
1849
|
+
if (w.idPrefixes?.some((p) => action.id.startsWith(p))) return true;
|
|
1850
|
+
if (w.categories?.includes(action.category)) return true;
|
|
1851
|
+
if (w.pageOwn && isPageOwn(action)) return true;
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
function matchRule(grouping, action, target) {
|
|
1855
|
+
return grouping.rules.find((r) => ruleMatches(r, action, target)) ?? null;
|
|
1856
|
+
}
|
|
1857
|
+
function destinationFor(grouping, action, target) {
|
|
1858
|
+
return matchRule(grouping, action, target)?.to ?? grouping.fallback;
|
|
1859
|
+
}
|
|
1860
|
+
var sameName = (label) => label.toLowerCase().replace(/[….]+$/g, "").replace(/\s+/g, " ").trim();
|
|
1861
|
+
function drawnOrder(model) {
|
|
1862
|
+
const order = /* @__PURE__ */ new Map();
|
|
1863
|
+
const walk = (nodes) => {
|
|
1864
|
+
for (const n of nodes) {
|
|
1865
|
+
if (!order.has(n.id)) order.set(n.id, order.size);
|
|
1866
|
+
if (n.kind === "submenu") walk(n.children);
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
for (const s of model.sections) walk(s.nodes);
|
|
1870
|
+
return order;
|
|
1871
|
+
}
|
|
1872
|
+
function currentModel(target, resolved) {
|
|
1873
|
+
return (0, import_menu.buildMenuModel)(target, resolved, { headingPolicy: "inline" });
|
|
1874
|
+
}
|
|
1875
|
+
function regroupResolved(target, resolved, grouping, options) {
|
|
1876
|
+
const order = drawnOrder(currentModel(target, resolved));
|
|
1877
|
+
const pos = (r) => order.get(r.action.id) ?? Number.MAX_SAFE_INTEGER;
|
|
1878
|
+
const groupDef = new Map(grouping.groups.map((g) => [g.key, g]));
|
|
1879
|
+
const top = [];
|
|
1880
|
+
const home = /* @__PURE__ */ new Map();
|
|
1881
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
1882
|
+
const present = new Map(resolved.map((r) => [r.action.id, r]));
|
|
1883
|
+
const twinOf = (r) => {
|
|
1884
|
+
const rule = matchRule(grouping, r.action, target);
|
|
1885
|
+
for (const id of rule?.mergeWithIds ?? []) {
|
|
1886
|
+
const twin = present.get(id);
|
|
1887
|
+
if (twin && twin !== r) return twin;
|
|
1888
|
+
}
|
|
1889
|
+
return null;
|
|
1890
|
+
};
|
|
1891
|
+
const mergedInto = /* @__PURE__ */ new Map();
|
|
1892
|
+
const pageVersionOf = /* @__PURE__ */ new Map();
|
|
1893
|
+
for (const r of resolved) {
|
|
1894
|
+
if (r.action.expand) continue;
|
|
1895
|
+
const twin = twinOf(r);
|
|
1896
|
+
if (!twin || pageVersionOf.has(twin.action.id)) continue;
|
|
1897
|
+
mergedInto.set(r.action.id, twin);
|
|
1898
|
+
pageVersionOf.set(twin.action.id, r);
|
|
1899
|
+
}
|
|
1900
|
+
const withPageHandler = (r) => {
|
|
1901
|
+
const page = pageVersionOf.get(r.action.id);
|
|
1902
|
+
if (!page) return r;
|
|
1903
|
+
const { href: _generalHref, ...general } = r.action;
|
|
1904
|
+
void _generalHref;
|
|
1905
|
+
const action = {
|
|
1906
|
+
...general,
|
|
1907
|
+
run: page.action.run,
|
|
1908
|
+
...page.action.href !== void 0 ? { href: page.action.href } : {}
|
|
1909
|
+
};
|
|
1910
|
+
return { ...r, action };
|
|
1911
|
+
};
|
|
1912
|
+
for (const original of resolved) {
|
|
1913
|
+
if (mergedInto.has(original.action.id)) continue;
|
|
1914
|
+
const r = withPageHandler(original);
|
|
1915
|
+
const to = destinationFor(grouping, r.action, target);
|
|
1916
|
+
if (to.kind === "group" && groupDef.has(to.key)) {
|
|
1917
|
+
if (r.eligibility.status !== "available") {
|
|
1918
|
+
top.push(r);
|
|
1919
|
+
home.set(r.action.id, { kind: "top", note: "greyed here, so it stays visible with its reason" });
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
const list = wanted.get(to.key) ?? [];
|
|
1923
|
+
list.push(r);
|
|
1924
|
+
wanted.set(to.key, list);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (to.kind === "page-first") {
|
|
1928
|
+
const section = r.action.section;
|
|
1929
|
+
top.push(
|
|
1930
|
+
section ? { ...r, action: { ...r.action, section: { ...section, primary: true } } } : r
|
|
1931
|
+
);
|
|
1932
|
+
home.set(r.action.id, { kind: "page-first" });
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
top.push(r);
|
|
1936
|
+
home.set(r.action.id, { kind: "top" });
|
|
1937
|
+
}
|
|
1938
|
+
const members = /* @__PURE__ */ new Map();
|
|
1939
|
+
const groupActions = [];
|
|
1940
|
+
for (const def of grouping.groups) {
|
|
1941
|
+
const list = (wanted.get(def.key) ?? []).slice().sort((a, b) => pos(a) - pos(b));
|
|
1942
|
+
if (list.length === 0) continue;
|
|
1943
|
+
if (list.length < grouping.minMembers) {
|
|
1944
|
+
for (const r of list) {
|
|
1945
|
+
top.push(r);
|
|
1946
|
+
home.set(r.action.id, { kind: "top", note: `the only row for ${def.label}` });
|
|
1947
|
+
}
|
|
1948
|
+
continue;
|
|
1949
|
+
}
|
|
1950
|
+
const kept = [];
|
|
1951
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1952
|
+
for (const r of list) {
|
|
1953
|
+
const name = sameName(actionLabel(r.action, target));
|
|
1954
|
+
const first = options.mergeSameName ? byName.get(name) : void 0;
|
|
1955
|
+
if (first) {
|
|
1956
|
+
home.set(r.action.id, {
|
|
1957
|
+
kind: "merged",
|
|
1958
|
+
intoId: first.action.id,
|
|
1959
|
+
intoLabel: actionLabel(first.action, target),
|
|
1960
|
+
groupLabel: def.label
|
|
1961
|
+
});
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
byName.set(name, r);
|
|
1965
|
+
kept.push(r);
|
|
1966
|
+
home.set(r.action.id, { kind: "group", key: def.key, label: def.label });
|
|
1967
|
+
}
|
|
1968
|
+
members.set(def.key, kept);
|
|
1969
|
+
const children = kept.map((r) => r.action);
|
|
1970
|
+
const submenu2 = {
|
|
1971
|
+
id: `${REGROUP_ID_PREFIX}${def.key}`,
|
|
1972
|
+
label: def.label,
|
|
1973
|
+
category: def.category,
|
|
1974
|
+
order: def.order,
|
|
1975
|
+
...def.icon ? { icon: def.icon } : {},
|
|
1976
|
+
// Still loading at the source when any member is.
|
|
1977
|
+
pending: (t) => children.some((c) => Boolean(c.expand && c.pending?.(t))),
|
|
1978
|
+
eligible: () => ({ status: "available" }),
|
|
1979
|
+
expand: async () => children,
|
|
1980
|
+
run: () => void 0
|
|
1981
|
+
};
|
|
1982
|
+
groupActions.push({ action: submenu2, eligibility: { status: "available" } });
|
|
1983
|
+
}
|
|
1984
|
+
for (const [id, twin] of mergedInto) {
|
|
1985
|
+
const at = home.get(twin.action.id);
|
|
1986
|
+
home.set(id, {
|
|
1987
|
+
kind: "merged",
|
|
1988
|
+
intoId: twin.action.id,
|
|
1989
|
+
intoLabel: actionLabel(twin.action, target),
|
|
1990
|
+
groupLabel: at?.kind === "group" ? at.label : at?.kind === "merged" ? at.groupLabel : "",
|
|
1991
|
+
pageActionKept: true
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
return { resolved: [...top, ...groupActions], members, home };
|
|
1995
|
+
}
|
|
1996
|
+
function drawnPlaces(model, arrangement) {
|
|
1997
|
+
const arranged = (0, import_menu.context)(model, arrangement);
|
|
1998
|
+
const places = /* @__PURE__ */ new Map();
|
|
1999
|
+
for (const leaf of arranged.strip) places.set(leaf.actionId ?? leaf.id, "Icon strip");
|
|
2000
|
+
arranged.sections.forEach((section, index) => {
|
|
2001
|
+
const heading = section.label ? `"${section.label}" section` : "";
|
|
2002
|
+
for (const node of section.nodes) {
|
|
2003
|
+
if (node.kind === "separator" || node.kind === "label") continue;
|
|
2004
|
+
if (node.kind === "submenu" && node.id.endsWith(":fold")) {
|
|
2005
|
+
for (const child of node.children) {
|
|
2006
|
+
if (child.kind === "separator" || child.kind === "label") continue;
|
|
2007
|
+
places.set(child.actionId ?? child.id, `${node.label} \u25B8`);
|
|
2008
|
+
}
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
places.set(node.actionId ?? node.id, heading ? `Top level \xB7 ${heading}` : index === 0 && section.primary ? "Top level \xB7 first" : "Top level");
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
return places;
|
|
2015
|
+
}
|
|
2016
|
+
function topLevelRowCount(model, arrangement) {
|
|
2017
|
+
const arranged = (0, import_menu.context)(model, arrangement);
|
|
2018
|
+
let rows = arranged.strip.length > 0 ? 1 : 0;
|
|
2019
|
+
for (const section of arranged.sections) {
|
|
2020
|
+
rows += section.nodes.filter((n) => n.kind !== "separator" && n.kind !== "label").length;
|
|
2021
|
+
}
|
|
2022
|
+
return rows;
|
|
2023
|
+
}
|
|
2024
|
+
function auditRegroup(target, resolved, grouping, options, arrangement, regroup = regroupResolved) {
|
|
2025
|
+
const result = regroup(target, resolved, grouping, options);
|
|
2026
|
+
const current = currentModel(target, resolved);
|
|
2027
|
+
const proposed = currentModel(target, result.resolved);
|
|
2028
|
+
const currentPlaces = drawnPlaces(current, arrangement);
|
|
2029
|
+
const proposedPlaces = drawnPlaces(proposed, arrangement);
|
|
2030
|
+
const held = /* @__PURE__ */ new Set();
|
|
2031
|
+
for (const r of result.resolved) if (!r.action.id.startsWith(REGROUP_ID_PREFIX)) held.add(r.action.id);
|
|
2032
|
+
for (const list of result.members.values()) for (const r of list) held.add(r.action.id);
|
|
2033
|
+
const rows = resolved.map((r) => {
|
|
2034
|
+
const home = result.home.get(r.action.id) ?? null;
|
|
2035
|
+
let proposedPlace = "";
|
|
2036
|
+
if (held.has(r.action.id)) {
|
|
2037
|
+
proposedPlace = home?.kind === "group" ? `${home.label} \u25B8` : proposedPlaces.get(r.action.id) ?? "Top level";
|
|
2038
|
+
} else if (home?.kind === "merged" && held.has(home.intoId)) {
|
|
2039
|
+
proposedPlace = home.groupLabel ? `${home.groupLabel} \u25B8 ${home.intoLabel}` : `Top level \xB7 ${home.intoLabel}`;
|
|
2040
|
+
}
|
|
2041
|
+
return {
|
|
2042
|
+
id: r.action.id,
|
|
2043
|
+
label: actionLabel(r.action, target),
|
|
2044
|
+
category: r.action.category,
|
|
2045
|
+
isSubmenu: Boolean(r.action.expand),
|
|
2046
|
+
pageOwn: isPageOwn(r.action),
|
|
2047
|
+
current: currentPlaces.get(r.action.id) ?? "Top level",
|
|
2048
|
+
proposed: proposedPlace ? home : null,
|
|
2049
|
+
proposedPlace
|
|
2050
|
+
};
|
|
2051
|
+
});
|
|
2052
|
+
return {
|
|
2053
|
+
rows,
|
|
2054
|
+
lost: rows.filter((row) => !row.proposedPlace),
|
|
2055
|
+
currentRows: topLevelRowCount(current, arrangement),
|
|
2056
|
+
proposedRows: topLevelRowCount(proposed, arrangement)
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// src/engine/regroup/engine-rows.ts
|
|
2061
|
+
var CONTEXT_MENU_ENGINE_ROWS = [
|
|
2062
|
+
{ id: "cm:copy", label: "Copy", category: "clipboard" },
|
|
2063
|
+
{ id: "cm:cut", label: "Cut", category: "clipboard" },
|
|
2064
|
+
{ id: "cm:paste", label: "Paste", category: "clipboard" },
|
|
2065
|
+
{ id: "cm:undo", label: "Undo", category: "clipboard" },
|
|
2066
|
+
{ id: "cm:redo", label: "Redo", category: "clipboard" },
|
|
2067
|
+
{ id: "cm:find", label: "Find & Replace", category: "clipboard" },
|
|
2068
|
+
{ id: "cm:speak", label: "Speak", category: "clipboard" },
|
|
2069
|
+
{ id: "cm:listen", label: "Listen", category: "clipboard" },
|
|
2070
|
+
{ id: "cm:copy-as", label: "Copy as", category: "clipboard" },
|
|
2071
|
+
{ id: "cm:json", label: "JSON", category: "clipboard" },
|
|
2072
|
+
{ id: "cm:select-all", label: "Select All", category: "edit" },
|
|
2073
|
+
{ id: "cm:insert-reference", label: "Insert / Copy reference\u2026", category: "edit" },
|
|
2074
|
+
{ id: "cm:chat", label: "Chat", category: "app" },
|
|
2075
|
+
{ id: "cm:view-history", label: "View History", category: "history" },
|
|
2076
|
+
{ id: "cm:compare", label: "Compare", category: "app" },
|
|
2077
|
+
{ id: "cm:export", label: "Export", category: "save" },
|
|
2078
|
+
{ id: "cm:convert", label: "Convert", category: "save" },
|
|
2079
|
+
{ id: "cm:attach", label: "Attach To", category: "share" },
|
|
2080
|
+
{ id: "cm:share", label: "Share", category: "share" },
|
|
2081
|
+
{ id: "cm:placement:ai-action", label: "AI Actions", category: "ai" },
|
|
2082
|
+
{ id: "cm:placement:content-block", label: "Content blocks", category: "ai" },
|
|
2083
|
+
{ id: "cm:placement:organization-tool", label: "Org Items", category: "ai" },
|
|
2084
|
+
{ id: "cm:placement:user-tool", label: "My Items", category: "ai" },
|
|
2085
|
+
{ id: "cm:placement:quick-action", label: "Quick actions (placement)", category: "ai" },
|
|
2086
|
+
{ id: "cm:placement:bound-agent", label: "Agents", category: "ai" },
|
|
2087
|
+
{ id: "cm:quick-actions", label: "Quick Actions", category: "feedback" },
|
|
2088
|
+
{ id: "cm:save", label: "Save", category: "edit" },
|
|
2089
|
+
{ id: "cm:delete", label: "Delete", category: "edit" },
|
|
2090
|
+
{ id: "cm:admin", label: "Admin Tools", category: "admin" },
|
|
2091
|
+
{ id: "cm:x:surface", label: "This page (location, context, agents)", category: "surface-info" }
|
|
2092
|
+
];
|
|
2093
|
+
var CONTEXT_MENU_ENGINE_ID_PREFIXES = ["cm:placement:", "cm:cat:", "cm:agents", "cm:entry:"];
|
|
2094
|
+
|
|
2095
|
+
// src/engine/regroup/proposed-grouping.ts
|
|
2096
|
+
var import_lucide_react3 = require("lucide-react");
|
|
2097
|
+
var import_alchemy_icon_keys = require("@ai-matrx/rich-content/utils/alchemy-icon-keys");
|
|
2098
|
+
var import_domain3 = require("@ai-matrx/icons/domain");
|
|
2099
|
+
var icon = (c) => (0, import_alchemy_icon_keys.registerAlchemyIcon)(c);
|
|
2100
|
+
var PROPOSED_MENU_GROUPING = {
|
|
2101
|
+
name: "v2 \u2014 grouped by what you get",
|
|
2102
|
+
minMembers: 2,
|
|
2103
|
+
// `category` + `order` place each submenu row among the others (the package's
|
|
2104
|
+
// group order): Edit, Copy, Download, Convert to, Publish, Share, Organize,
|
|
2105
|
+
// History, AI, Read aloud, Compare, Feedback, Admin.
|
|
2106
|
+
groups: [
|
|
2107
|
+
{ key: "edit", label: "Edit", definition: "Change this item where it is: edit, rewrite, open in an editor, run, delete.", icon: icon(import_lucide_react3.Pencil), category: "edit", order: 0 },
|
|
2108
|
+
{ key: "copy", label: "Copy", definition: "It lands on your clipboard, in the form you pick: text, Markdown, rich text, HTML, a table, JSON, a reference.", icon: icon(import_lucide_react3.Copy), category: "copy", order: 0 },
|
|
2109
|
+
{ key: "download", label: "Download", definition: "It becomes a file outside the app: Markdown, PDF, Word, HTML \u2014 or a printout.", icon: icon(import_lucide_react3.Download), category: "export", order: 0 },
|
|
2110
|
+
{ key: "convert", label: "Convert to", definition: "It becomes a new thing inside the app: a note, document, task, file, flashcards, quiz, template, contact, data table.", icon: icon(import_lucide_react3.ArrowRightLeft), category: "save", order: 0 },
|
|
2111
|
+
{ key: "publish", label: "Publish", definition: "It goes out to the world: a public web page or published HTML anyone with the link can open.", icon: icon(import_lucide_react3.Globe), category: "share", order: 0 },
|
|
2112
|
+
{ key: "share", label: "Share", definition: "It goes to specific people or places: share with someone, send to Google Docs, email it.", icon: icon(import_lucide_react3.Share2), category: "share", order: 1 },
|
|
2113
|
+
{ key: "organize", label: "Organize", definition: "It stays what it is, but gets filed: attached to a project or context, pinned, annotated.", icon: icon(import_lucide_react3.Tags), category: "history", order: 0 },
|
|
2114
|
+
{ key: "history", label: "History", definition: "Earlier versions: view the history, fork from a point in it.", icon: icon(import_lucide_react3.History), category: "history", order: 1 },
|
|
2115
|
+
{ key: "ai", label: "AI", definition: "An AI works on it: ask, chat, run an agent or AI action, regenerate.", icon: icon(import_domain3.AGENT_ICON), category: "ai", order: 0 },
|
|
2116
|
+
{ key: "listen", label: "Read aloud", definition: "You hear it: read it aloud, a spoken summary, the voice settings.", icon: icon(import_lucide_react3.Volume2), category: "listen", order: 0 },
|
|
2117
|
+
{ key: "compare", label: "Compare", definition: "You see it side by side with something else: the clipboard or a saved base.", icon: icon(import_lucide_react3.GitCompareArrows), category: "study", order: 0 },
|
|
2118
|
+
{ key: "feedback", label: "Feedback", definition: "Tell us how it went: thumbs up or down, report a problem.", icon: icon(import_lucide_react3.ThumbsUp), category: "feedback", order: 1 },
|
|
2119
|
+
{ key: "admin", label: "Admin", definition: "Creator and admin tools: inspect, debug, server-side message surgery. Admins only.", icon: icon(import_lucide_react3.Shield), category: "admin", order: 0 }
|
|
2120
|
+
],
|
|
2121
|
+
rules: [
|
|
2122
|
+
// ── The page's own rows that DUPLICATE a category (Arman, 2026-09-28: "the
|
|
2123
|
+
// more of that we do, the better"): they move into that category, and merge
|
|
2124
|
+
// into the universal row that does the same thing when it is in the menu.
|
|
2125
|
+
// Only genuinely unique record actions (Open, Take, Duplicate, Move to Trash…)
|
|
2126
|
+
// stay at the top.
|
|
2127
|
+
{ when: { pageOwnLabels: ["^copy( to clipboard)?$"] }, to: { kind: "group", key: "copy" }, mergeWithIds: ["cm:copy", "copy"] },
|
|
2128
|
+
{ when: { pageOwnLabels: ["^copy\\b"] }, to: { kind: "group", key: "copy" } },
|
|
2129
|
+
{
|
|
2130
|
+
when: { pageOwnLabels: ["^(export|download)( as)? markdown"] },
|
|
2131
|
+
to: { kind: "group", key: "download" },
|
|
2132
|
+
mergeWithIds: ["save-as-file"]
|
|
2133
|
+
},
|
|
2134
|
+
{ when: { pageOwnLabels: ["^(export|download)\\b", "^print\\b"] }, to: { kind: "group", key: "download" } },
|
|
2135
|
+
{ when: { pageOwnLabels: ["^share( link)?\u2026?$"] }, to: { kind: "group", key: "share" }, mergeWithIds: ["cm:share"] },
|
|
2136
|
+
{ when: { pageOwnLabels: ["^(share|send|email)\\b"] }, to: { kind: "group", key: "share" } },
|
|
2137
|
+
{ when: { pageOwnLabels: ["^publish\\b"] }, to: { kind: "group", key: "publish" } },
|
|
2138
|
+
{ when: { pageOwnLabels: ["^(convert|save to|save as|create (a )?(task|note|document))\\b"] }, to: { kind: "group", key: "convert" } },
|
|
2139
|
+
{ when: { pageOwnLabels: ["^(move to folder|add tags?|tags?|attach)\\b"] }, to: { kind: "group", key: "organize" } },
|
|
2140
|
+
{ when: { pageOwnLabels: ["^(read aloud|listen)\\b"] }, to: { kind: "group", key: "listen" } },
|
|
2141
|
+
{ when: { pageOwnLabels: ["^compare\\b"] }, to: { kind: "group", key: "compare" } },
|
|
2142
|
+
{ when: { pageOwnLabels: ["^save$"] }, to: { kind: "top" }, mergeWithIds: ["cm:save"] },
|
|
2143
|
+
// A conversation's own rows, by id (their labels are computed at open).
|
|
2144
|
+
{
|
|
2145
|
+
when: { ids: ["conversation-copy-plain", "conversation-copy-markdown", "conversation-copy-formatted", "conversation-copy-for-ai", "conversation-copy-link"] },
|
|
2146
|
+
to: { kind: "group", key: "copy" }
|
|
2147
|
+
},
|
|
2148
|
+
{ when: { idPrefixes: ["conversation-download-"] }, to: { kind: "group", key: "download" } },
|
|
2149
|
+
{
|
|
2150
|
+
when: { ids: ["conversation-save-to-notes", "conversation-create-document", "conversation-create-task"] },
|
|
2151
|
+
to: { kind: "group", key: "convert" }
|
|
2152
|
+
},
|
|
2153
|
+
{ when: { ids: ["conversation-share", "conversation-email-to-me"] }, to: { kind: "group", key: "share" } },
|
|
2154
|
+
{ when: { ids: ["conversation-open-in-new-chat"] }, to: { kind: "group", key: "ai" } },
|
|
2155
|
+
{ when: { ids: ["conversation-rename", "conversation-duplicate"] }, to: { kind: "group", key: "edit" } },
|
|
2156
|
+
// The clicked thing's remaining own rows (a quiz row's Open / Take / Archive,
|
|
2157
|
+
// a note's Duplicate / Move to Trash) come first, as they are.
|
|
2158
|
+
{ when: { pageOwn: true }, to: { kind: "page-first" } },
|
|
2159
|
+
// The universal verb strip (icons across the top) stays exactly as it is.
|
|
2160
|
+
{
|
|
2161
|
+
when: { ids: ["cm:copy", "cm:cut", "cm:paste", "cm:undo", "cm:redo", "cm:find", "copy", "cm:select-all"] },
|
|
2162
|
+
to: { kind: "top" }
|
|
2163
|
+
},
|
|
2164
|
+
// Quick Actions is already one submenu of quick tools; the page submenu is last;
|
|
2165
|
+
// an editor's own Save / Delete stay one click away.
|
|
2166
|
+
{ when: { ids: ["cm:quick-actions", "cm:save", "cm:delete"], categories: ["surface-info"] }, to: { kind: "top" } },
|
|
2167
|
+
{
|
|
2168
|
+
when: {
|
|
2169
|
+
ids: ["cm:copy-as", "cm:json", "cm:insert-reference", "copy-html-page", "conversation-copy-link"],
|
|
2170
|
+
categories: ["copy"]
|
|
2171
|
+
},
|
|
2172
|
+
to: { kind: "group", key: "copy" }
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
when: { ids: ["cm:export", "save-as-file", "download-pdf", "download-docx", "download-html", "print", "full-print"] },
|
|
2176
|
+
to: { kind: "group", key: "download" }
|
|
2177
|
+
},
|
|
2178
|
+
{ when: { ids: ["html-preview", "share-webpage"] }, to: { kind: "group", key: "publish" } },
|
|
2179
|
+
{
|
|
2180
|
+
when: { ids: ["cm:share", "send-google-doc", "email-to-me", "conversation-share"] },
|
|
2181
|
+
to: { kind: "group", key: "share" }
|
|
2182
|
+
},
|
|
2183
|
+
{
|
|
2184
|
+
when: { ids: ["cm:attach", "set-context-value", "pin-message", "notes-and-comments"] },
|
|
2185
|
+
to: { kind: "group", key: "organize" }
|
|
2186
|
+
},
|
|
2187
|
+
{
|
|
2188
|
+
when: { ids: ["cm:view-history", "edit-history", "fork-at-message", "fork-and-regenerate"] },
|
|
2189
|
+
to: { kind: "group", key: "history" }
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
when: { ids: ["cm:compare", "compare-with-clipboard", "set-compare-base", "compare-with-base"] },
|
|
2193
|
+
to: { kind: "group", key: "compare" }
|
|
2194
|
+
},
|
|
2195
|
+
{
|
|
2196
|
+
when: { ids: ["cm:speak", "cm:listen", "tts-voice-settings"], categories: ["listen"] },
|
|
2197
|
+
to: { kind: "group", key: "listen" }
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
when: {
|
|
2201
|
+
ids: ["cm:chat", "regenerate-response", "regenerate-latest", "continue-in-chat", "send-to-agent", "code-block-chart"],
|
|
2202
|
+
idPrefixes: ["cm:placement:", "cm:cat:", "cm:agents"],
|
|
2203
|
+
categories: ["ai", "ask"]
|
|
2204
|
+
},
|
|
2205
|
+
to: { kind: "group", key: "ai" }
|
|
2206
|
+
},
|
|
2207
|
+
{
|
|
2208
|
+
when: {
|
|
2209
|
+
ids: [
|
|
2210
|
+
"edit",
|
|
2211
|
+
"edit-and-resubmit",
|
|
2212
|
+
"open-fullscreen-editor",
|
|
2213
|
+
"delete-message",
|
|
2214
|
+
"code-block-open-in-editor",
|
|
2215
|
+
"code-block-apply-to-file",
|
|
2216
|
+
"code-block-run",
|
|
2217
|
+
"replace-selection",
|
|
2218
|
+
"insert-below",
|
|
2219
|
+
"conversation-rename",
|
|
2220
|
+
"conversation-duplicate"
|
|
2221
|
+
]
|
|
2222
|
+
},
|
|
2223
|
+
to: { kind: "group", key: "edit" }
|
|
2224
|
+
},
|
|
2225
|
+
{ when: { ids: ["thumbs-up", "thumbs-down", "submit-feedback"], categories: ["feedback"] }, to: { kind: "group", key: "feedback" } },
|
|
2226
|
+
{ when: { ids: ["cm:admin"], categories: ["creator", "admin"] }, to: { kind: "group", key: "admin" } },
|
|
2227
|
+
// Everything that makes a new thing in the app — checked after the rows
|
|
2228
|
+
// above because the registry files several of them under "save".
|
|
2229
|
+
{ when: { ids: ["cm:convert", "add-to-rulebook"], categories: ["save", "study", "export"] }, to: { kind: "group", key: "convert" } }
|
|
2230
|
+
],
|
|
2231
|
+
// A row no rule names stays at the top level where the package places it —
|
|
2232
|
+
// and the demo's Categories section lists it under "No category yet".
|
|
2233
|
+
fallback: { kind: "top" }
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
// src/engine/record-menu-registry.ts
|
|
2237
|
+
var RECORD_MENU_ATTR = "data-record-menu";
|
|
2238
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2239
|
+
var revision = 0;
|
|
2240
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
2241
|
+
function recordMenuChanged() {
|
|
2242
|
+
revision += 1;
|
|
2243
|
+
for (const listener of listeners) listener();
|
|
2244
|
+
}
|
|
2245
|
+
function subscribeRecordMenus(listener) {
|
|
2246
|
+
listeners.add(listener);
|
|
2247
|
+
return () => {
|
|
2248
|
+
listeners.delete(listener);
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
function recordMenusRevision() {
|
|
2252
|
+
return revision;
|
|
2253
|
+
}
|
|
2254
|
+
function registerRecordMenu(key, get) {
|
|
2255
|
+
registry.set(key, get);
|
|
2256
|
+
return () => {
|
|
2257
|
+
if (registry.get(key) === get) registry.delete(key);
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2260
|
+
function resolveRecordMenu(target) {
|
|
2261
|
+
const key = target?.closest?.(`[${RECORD_MENU_ATTR}]`)?.getAttribute(RECORD_MENU_ATTR);
|
|
2262
|
+
if (!key) return null;
|
|
2263
|
+
return registry.get(key)?.() ?? null;
|
|
2264
|
+
}
|
|
2265
|
+
var TRIGGER = '[data-alchemy-trigger="context"]';
|
|
2266
|
+
function contentTrigger(root) {
|
|
2267
|
+
const inner = root.querySelector(`${TRIGGER}[data-content-source]`);
|
|
2268
|
+
if (inner) return inner;
|
|
2269
|
+
if (root.matches(TRIGGER)) return root;
|
|
2270
|
+
return root.querySelector(TRIGGER) ?? root.closest(TRIGGER);
|
|
2271
|
+
}
|
|
2272
|
+
function openRecordMenu(key, anchor) {
|
|
2273
|
+
const doc = anchor.ownerDocument;
|
|
2274
|
+
const root = [...doc.querySelectorAll(`[${RECORD_MENU_ATTR}]`)].find(
|
|
2275
|
+
(el) => el.getAttribute(RECORD_MENU_ATTR) === key
|
|
2276
|
+
);
|
|
2277
|
+
const trigger = root ? contentTrigger(root) : null;
|
|
2278
|
+
if (!trigger) return false;
|
|
2279
|
+
const rect = anchor.getBoundingClientRect();
|
|
2280
|
+
trigger.dispatchEvent(
|
|
2281
|
+
new MouseEvent("contextmenu", {
|
|
2282
|
+
bubbles: true,
|
|
2283
|
+
cancelable: true,
|
|
2284
|
+
view: doc.defaultView ?? window,
|
|
2285
|
+
button: 2,
|
|
2286
|
+
buttons: 2,
|
|
2287
|
+
clientX: rect.left + rect.width / 2,
|
|
2288
|
+
clientY: rect.bottom
|
|
2289
|
+
})
|
|
2290
|
+
);
|
|
2291
|
+
return true;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// src/engine/proposed/proposed-arrangement.ts
|
|
2295
|
+
var import_lucide_react4 = require("lucide-react");
|
|
2296
|
+
var import_domain4 = require("@ai-matrx/icons/domain");
|
|
2297
|
+
var import_alchemy_icon_keys2 = require("@ai-matrx/rich-content/utils/alchemy-icon-keys");
|
|
2298
|
+
var icon2 = (c) => (0, import_alchemy_icon_keys2.registerAlchemyIcon)(c);
|
|
2299
|
+
var STRIP_MAX = 6;
|
|
2300
|
+
var OWN_ROWS_MAX = 4;
|
|
2301
|
+
var SLOT2 = {
|
|
2302
|
+
copy: { key: "copy", ids: ["cm:copy", "copy"], ownLabels: [/^copy( to clipboard)?$/i] },
|
|
2303
|
+
cut: { key: "cut", ids: ["cm:cut"] },
|
|
2304
|
+
paste: { key: "paste", ids: ["cm:paste"] },
|
|
2305
|
+
undo: { key: "undo", ids: ["cm:undo"] },
|
|
2306
|
+
redo: { key: "redo", ids: ["cm:redo"] },
|
|
2307
|
+
find: { key: "find", ids: ["cm:find"] },
|
|
2308
|
+
speak: { key: "speak", ids: ["cm:speak"] },
|
|
2309
|
+
share: { key: "share", ownLabels: [/^share\b/i], ids: ["cm:share"] },
|
|
2310
|
+
duplicate: { key: "duplicate", ownLabels: [/^duplicate\b/i, /^make a copy\b/i] },
|
|
2311
|
+
favorite: { key: "favorite", ownLabels: [/favou?rite/i, /^(un)?star\b/i] },
|
|
2312
|
+
download: { key: "download", ownLabels: [/^(export|download)\b/i] },
|
|
2313
|
+
archive: { key: "archive", ownLabels: [/^archive\b/i, /^delete\b/i, /^move to trash\b/i] }
|
|
2314
|
+
};
|
|
2315
|
+
var STRIP_FOR = {
|
|
2316
|
+
thing: ["copy", "share", "duplicate", "favorite", "download", "archive"],
|
|
2317
|
+
editable: ["cut", "copy", "paste", "undo", "redo", "find"],
|
|
2318
|
+
text: ["copy", "speak", "find", "share"]
|
|
2319
|
+
};
|
|
2320
|
+
var PAGE_TEXT_FILE_IDS = /* @__PURE__ */ new Set(["save-as-pdf"]);
|
|
2321
|
+
var OPEN_ROW = /^open( record)?$/i;
|
|
2322
|
+
var LINK_A_RECORD = /^link a record/i;
|
|
2323
|
+
var FOLDS_INTO_SHARE = [/^copy link\b/i, /^share link\b/i];
|
|
2324
|
+
var SAVE_SHARE_GROUPS = ["copy", "download", "convert", "publish", "share"];
|
|
2325
|
+
var ORGANIZE_GROUPS = /* @__PURE__ */ new Set(["organize", "history", "compare"]);
|
|
2326
|
+
var AI_ORDER = [
|
|
2327
|
+
"cm:chat",
|
|
2328
|
+
"cm:placement:ai-action",
|
|
2329
|
+
"cm:placement:bound-agent",
|
|
2330
|
+
"cm:agents",
|
|
2331
|
+
"cm:placement:user-tool",
|
|
2332
|
+
"cm:placement:organization-tool",
|
|
2333
|
+
"cm:placement:content-block",
|
|
2334
|
+
/^send to (another )?agent/i,
|
|
2335
|
+
/^custom agent/i
|
|
2336
|
+
];
|
|
2337
|
+
var AI_LABELS = [/^summari[sz]e\b/i, /^send to (another )?agent/i, /^custom agent/i];
|
|
2338
|
+
function isOwn(action) {
|
|
2339
|
+
if (action.id.startsWith("cm:x:") && !action.id.startsWith("cm:x:surface")) return true;
|
|
2340
|
+
const s = action.section;
|
|
2341
|
+
return Boolean(s && (s.primary || s.kind === "target"));
|
|
2342
|
+
}
|
|
2343
|
+
function clickedKind(target, resolved) {
|
|
2344
|
+
if (!target.readOnly && resolved.some((r) => r.action.id === "cm:paste")) return "editable";
|
|
2345
|
+
if (resolved.some((r) => isOwn(r.action))) return "thing";
|
|
2346
|
+
return "text";
|
|
2347
|
+
}
|
|
2348
|
+
function submenu(id, label, category, iconKey, children, order = 0) {
|
|
2349
|
+
const action = {
|
|
2350
|
+
id: `proposed:${id}`,
|
|
2351
|
+
label,
|
|
2352
|
+
category,
|
|
2353
|
+
order,
|
|
2354
|
+
icon: iconKey,
|
|
2355
|
+
pending: (t) => children.some((c) => Boolean(c.expand && c.pending?.(t))),
|
|
2356
|
+
eligible: () => ({ status: "available" }),
|
|
2357
|
+
expand: async () => children,
|
|
2358
|
+
run: () => void 0
|
|
2359
|
+
};
|
|
2360
|
+
return { action, eligibility: { status: "available" } };
|
|
2361
|
+
}
|
|
2362
|
+
function plain(action, extra = {}) {
|
|
2363
|
+
const { section: _section, ...rest } = action;
|
|
2364
|
+
void _section;
|
|
2365
|
+
return { ...rest, ...extra };
|
|
2366
|
+
}
|
|
2367
|
+
function proposedArrangement(target, resolved, { noun }) {
|
|
2368
|
+
const kind = clickedKind(target, resolved);
|
|
2369
|
+
const label = (r) => actionLabel(r.action, target);
|
|
2370
|
+
const groupOf = (r) => {
|
|
2371
|
+
const to = matchRule(PROPOSED_MENU_GROUPING, r.action, target)?.to;
|
|
2372
|
+
return to?.kind === "group" ? to.key : null;
|
|
2373
|
+
};
|
|
2374
|
+
const used = /* @__PURE__ */ new Set();
|
|
2375
|
+
const ownsDownload = resolved.some((r) => isOwn(r.action) && SLOT2.download.ownLabels.some((re) => re.test(label(r))));
|
|
2376
|
+
if (ownsDownload) {
|
|
2377
|
+
for (const r of resolved) {
|
|
2378
|
+
if (!isOwn(r.action) && (groupOf(r) === "download" || PAGE_TEXT_FILE_IDS.has(r.action.id))) used.add(r.action.id);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
const strip = [];
|
|
2382
|
+
for (const key of STRIP_FOR[kind]) {
|
|
2383
|
+
if (strip.length >= STRIP_MAX) break;
|
|
2384
|
+
const slot2 = SLOT2[key];
|
|
2385
|
+
const pick = resolved.find(
|
|
2386
|
+
(r) => !used.has(r.action.id) && !r.action.expand && isOwn(r.action) && slot2.ownLabels?.some((re) => re.test(label(r)))
|
|
2387
|
+
) ?? resolved.find((r) => !used.has(r.action.id) && !r.action.expand && slot2.ids?.includes(r.action.id));
|
|
2388
|
+
if (!pick) continue;
|
|
2389
|
+
used.add(pick.action.id);
|
|
2390
|
+
if (key === "share") {
|
|
2391
|
+
for (const r of resolved) if (r.action.id === "cm:share") used.add(r.action.id);
|
|
2392
|
+
}
|
|
2393
|
+
if (key === "copy") {
|
|
2394
|
+
for (const r of resolved) if (r.action.id === "cm:copy") used.add(r.action.id);
|
|
2395
|
+
}
|
|
2396
|
+
strip.push({
|
|
2397
|
+
...pick,
|
|
2398
|
+
action: plain(pick.action, {
|
|
2399
|
+
category: "clipboard",
|
|
2400
|
+
order: strip.length,
|
|
2401
|
+
...key === "archive" ? { destructive: true, icon: pick.action.icon ?? icon2(import_lucide_react4.Archive) } : {},
|
|
2402
|
+
...key === "copy" && !pick.action.icon ? { icon: icon2(import_lucide_react4.Copy) } : {}
|
|
2403
|
+
})
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
const hasShare = strip.some((r) => /^share\b/i.test(label(r)));
|
|
2407
|
+
const ownCandidates = resolved.filter((r) => {
|
|
2408
|
+
if (used.has(r.action.id)) return false;
|
|
2409
|
+
const g = groupOf(r);
|
|
2410
|
+
if (g && SAVE_SHARE_GROUPS.includes(g) && !(hasShare && FOLDS_INTO_SHARE.some((re) => re.test(label(r))))) return false;
|
|
2411
|
+
if (isOwn(r.action)) return true;
|
|
2412
|
+
return kind === "editable" && (groupOf(r) === "edit" || r.action.id === "cm:save" || r.action.id === "cm:delete");
|
|
2413
|
+
});
|
|
2414
|
+
const lead = (re) => {
|
|
2415
|
+
const at = ownCandidates.findIndex((r) => re.test(label(r)));
|
|
2416
|
+
return at >= 0 ? ownCandidates.splice(at, 1)[0] : null;
|
|
2417
|
+
};
|
|
2418
|
+
const openRow = lead(OPEN_ROW);
|
|
2419
|
+
const linkRow = lead(LINK_A_RECORD);
|
|
2420
|
+
ownCandidates.unshift(...[openRow, linkRow].filter((r) => r !== null));
|
|
2421
|
+
const own = [];
|
|
2422
|
+
const more = [];
|
|
2423
|
+
for (const r of ownCandidates) {
|
|
2424
|
+
used.add(r.action.id);
|
|
2425
|
+
const folds = hasShare && FOLDS_INTO_SHARE.some((re) => re.test(label(r)));
|
|
2426
|
+
if (!folds && own.length < OWN_ROWS_MAX && !r.action.expand) {
|
|
2427
|
+
const destructive = SLOT2.archive.ownLabels.some((re) => re.test(label(r)));
|
|
2428
|
+
own.push({ ...r, action: plain(r.action, { category: "edit", order: own.length, ...destructive ? { destructive: true } : {} }) });
|
|
2429
|
+
} else {
|
|
2430
|
+
more.push(plain(r.action));
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
const top = [...strip, ...own];
|
|
2434
|
+
if (more.length) top.push(submenu("more", `More ${noun} options`, "edit", icon2(import_lucide_react4.Ellipsis), more, OWN_ROWS_MAX));
|
|
2435
|
+
const ai = [];
|
|
2436
|
+
const saveShare = /* @__PURE__ */ new Map();
|
|
2437
|
+
const organize = [];
|
|
2438
|
+
const tools = [];
|
|
2439
|
+
for (const r of resolved) {
|
|
2440
|
+
if (used.has(r.action.id)) continue;
|
|
2441
|
+
used.add(r.action.id);
|
|
2442
|
+
const g = groupOf(r);
|
|
2443
|
+
if (g === "ai" || r.action.category === "ai" || r.action.category === "ask" || AI_LABELS.some((re) => re.test(label(r)))) {
|
|
2444
|
+
ai.push(plain(r.action));
|
|
2445
|
+
} else if (g && SAVE_SHARE_GROUPS.includes(g)) {
|
|
2446
|
+
const list = saveShare.get(g) ?? [];
|
|
2447
|
+
list.push(plain(r.action));
|
|
2448
|
+
saveShare.set(g, list);
|
|
2449
|
+
} else if (g && ORGANIZE_GROUPS.has(g)) {
|
|
2450
|
+
organize.push(plain(r.action));
|
|
2451
|
+
} else {
|
|
2452
|
+
tools.push(plain(r.action));
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
const aiRank = (a) => {
|
|
2456
|
+
const i = AI_ORDER.findIndex((p) => typeof p === "string" ? a.id === p || a.id.startsWith(p) : p.test(String(a.label)));
|
|
2457
|
+
return i === -1 ? AI_ORDER.length : i;
|
|
2458
|
+
};
|
|
2459
|
+
ai.sort((a, b) => aiRank(a) - aiRank(b));
|
|
2460
|
+
top.push(submenu("intelligence", "Intelligence", "ai", icon2(import_domain4.INTELLIGENCE_ICON), ai));
|
|
2461
|
+
const saveShareRows = [];
|
|
2462
|
+
for (const g of SAVE_SHARE_GROUPS) {
|
|
2463
|
+
const rows = saveShare.get(g) ?? [];
|
|
2464
|
+
rows.forEach((a, i) => saveShareRows.push(i === 0 && saveShareRows.length > 0 ? { ...a, startsGroup: true } : a));
|
|
2465
|
+
}
|
|
2466
|
+
if (saveShareRows.length) top.push(submenu("save-share", "Save & share", "app", icon2(import_lucide_react4.Share2), saveShareRows));
|
|
2467
|
+
if (organize.length) top.push(submenu("organize", "Organize", "app", icon2(import_lucide_react4.FolderTree), organize, 1));
|
|
2468
|
+
if (tools.length) top.push(submenu("tools", "Tools", "feedback", icon2(import_lucide_react4.Wrench), tools));
|
|
2469
|
+
return top;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/engine/utils/per-row-entity.ts
|
|
2473
|
+
var isDev2 = process.env.NODE_ENV !== "production";
|
|
2474
|
+
function resolveEffectiveEntity(entityProp, resolved) {
|
|
2475
|
+
if (!resolved || !(types_exports.CONTEXT_MENU_ENTITY_KEY in resolved)) return entityProp;
|
|
2476
|
+
const perRow = resolved[types_exports.CONTEXT_MENU_ENTITY_KEY];
|
|
2477
|
+
if (perRow === null || perRow === void 0) return void 0;
|
|
2478
|
+
const candidate = perRow;
|
|
2479
|
+
if (typeof perRow !== "object" || typeof candidate.type !== "string" || !candidate.type || typeof candidate.id !== "string" || !candidate.id) {
|
|
2480
|
+
if (isDev2) {
|
|
2481
|
+
console.error(
|
|
2482
|
+
`%c[ContextMenuV3] MALFORMED PER-ROW ENTITY%c \u2014 resolveContextOnOpen returned "${types_exports.CONTEXT_MENU_ENTITY_KEY}" without a string type + id, so Attach To / Share would target nothing. Falling back to the menu-level entity.`,
|
|
2483
|
+
"color:#ef4444;font-weight:bold",
|
|
2484
|
+
"color:inherit",
|
|
2485
|
+
{ received: perRow }
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
return entityProp;
|
|
2489
|
+
}
|
|
2490
|
+
return candidate;
|
|
2491
|
+
}
|
|
2492
|
+
function mergeResolvedContextData(contextData, resolved) {
|
|
2493
|
+
const merged = {
|
|
2494
|
+
...contextData ?? {},
|
|
2495
|
+
...resolved ?? {}
|
|
2496
|
+
};
|
|
2497
|
+
delete merged[types_exports.CONTEXT_MENU_ENTITY_KEY];
|
|
2498
|
+
return merged;
|
|
2499
|
+
}
|
|
2500
|
+
function joinRowAndSurfaceContext(row, surface) {
|
|
2501
|
+
if (!row) return surface ?? null;
|
|
2502
|
+
if (!surface) return row;
|
|
2503
|
+
const rowNamesEntity = row[types_exports.CONTEXT_MENU_ENTITY_KEY] !== null && row[types_exports.CONTEXT_MENU_ENTITY_KEY] !== void 0;
|
|
2504
|
+
return {
|
|
2505
|
+
...surface,
|
|
2506
|
+
...row,
|
|
2507
|
+
...!rowNamesEntity && types_exports.CONTEXT_MENU_ENTITY_KEY in surface ? { [types_exports.CONTEXT_MENU_ENTITY_KEY]: surface[types_exports.CONTEXT_MENU_ENTITY_KEY] } : {}
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
var TOKEN_SHAPE = /^[a-z][a-z0-9_]*$/;
|
|
2511
|
+
function sniffEntityFromDom(target) {
|
|
2512
|
+
const host = target?.closest?.("[data-entity-type][data-entity-id]");
|
|
2513
|
+
if (!(host instanceof HTMLElement)) return null;
|
|
2514
|
+
const type = host.getAttribute("data-entity-type")?.trim() ?? "";
|
|
2515
|
+
const id = host.getAttribute("data-entity-id")?.trim() ?? "";
|
|
2516
|
+
if (!type || !id) return null;
|
|
2517
|
+
if (!TOKEN_SHAPE.test(type)) {
|
|
2518
|
+
if (isDev2) {
|
|
2519
|
+
console.error(
|
|
2520
|
+
`%c[ContextMenuV3] MALFORMED data-entity-type%c \u2014 "${type}" is not an entity token (expected snake_case, e.g. "seo_keyword"), so Attach To / Share would target nothing. Fix the attribute or drop it.`,
|
|
2521
|
+
"color:#ef4444;font-weight:bold",
|
|
2522
|
+
"color:inherit",
|
|
2523
|
+
{ element: host }
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
const title = host.getAttribute("data-entity-title")?.trim() || (host.textContent ?? "").trim().slice(0, 120) || id;
|
|
2529
|
+
const resourceType = host.getAttribute("data-entity-resource")?.trim();
|
|
2530
|
+
return {
|
|
2531
|
+
type,
|
|
2532
|
+
id,
|
|
2533
|
+
title,
|
|
2534
|
+
...resourceType ? { resourceType } : {}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function resolveEffectiveEntityWithDom(entityProp, resolved, target) {
|
|
2538
|
+
const surfaceSpoke = !!resolved && types_exports.CONTEXT_MENU_ENTITY_KEY in resolved;
|
|
2539
|
+
if (surfaceSpoke) return resolveEffectiveEntity(entityProp, resolved);
|
|
2540
|
+
return sniffEntityFromDom(target) ?? entityProp;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// src/engine/utils/join-extra-sections.ts
|
|
2544
|
+
function isRow(item) {
|
|
2545
|
+
return item.kind !== "separator";
|
|
2546
|
+
}
|
|
2547
|
+
function tidySeparators(items) {
|
|
2548
|
+
const out = [];
|
|
2549
|
+
for (const item of items) {
|
|
2550
|
+
if (!isRow(item) && (out.length === 0 || !isRow(out[out.length - 1]))) continue;
|
|
2551
|
+
out.push(item);
|
|
2552
|
+
}
|
|
2553
|
+
while (out.length > 0 && !isRow(out[out.length - 1])) out.pop();
|
|
2554
|
+
return out;
|
|
2555
|
+
}
|
|
2556
|
+
function joinExtraSections(...sources) {
|
|
2557
|
+
const present = sources.filter((s) => Array.isArray(s));
|
|
2558
|
+
if (present.length === 0) return void 0;
|
|
2559
|
+
if (present.length === 1) return present[0];
|
|
2560
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2561
|
+
const out = [];
|
|
2562
|
+
for (const source of present) {
|
|
2563
|
+
const ownIds = /* @__PURE__ */ new Set();
|
|
2564
|
+
for (const section of source) {
|
|
2565
|
+
const kept = section.items.filter((item) => !isRow(item) || !taken.has(item.id));
|
|
2566
|
+
for (const item of kept) if (isRow(item)) ownIds.add(item.id);
|
|
2567
|
+
if (kept.length === section.items.length) {
|
|
2568
|
+
out.push(section);
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
const items = tidySeparators(kept);
|
|
2572
|
+
if (items.some(isRow)) out.push({ ...section, items });
|
|
2573
|
+
}
|
|
2574
|
+
for (const id of ownIds) taken.add(id);
|
|
2575
|
+
}
|
|
2576
|
+
return out;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/engine/utils/widget-handle.ts
|
|
2580
|
+
function setFieldValue(el, next, caret) {
|
|
2581
|
+
const setter = Object.getOwnPropertyDescriptor(
|
|
2582
|
+
HTMLTextAreaElement.prototype,
|
|
2583
|
+
"value"
|
|
2584
|
+
)?.set;
|
|
2585
|
+
if (setter) setter.call(el, next);
|
|
2586
|
+
else el.value = next;
|
|
2587
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
2588
|
+
try {
|
|
2589
|
+
el.setSelectionRange(caret, caret);
|
|
2590
|
+
} catch {
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
function buildEditableWidgetHandle(args) {
|
|
2594
|
+
const {
|
|
2595
|
+
getTextarea,
|
|
2596
|
+
onTextReplace,
|
|
2597
|
+
onTextInsertBefore,
|
|
2598
|
+
onTextInsertAfter,
|
|
2599
|
+
getApplicationScope
|
|
2600
|
+
} = args;
|
|
2601
|
+
const field = () => getTextarea?.() ?? null;
|
|
2602
|
+
const readCurrent = () => {
|
|
2603
|
+
const el = field();
|
|
2604
|
+
if (el) return el.value;
|
|
2605
|
+
const content = getApplicationScope?.().content;
|
|
2606
|
+
return typeof content === "string" ? content : null;
|
|
2607
|
+
};
|
|
2608
|
+
const writeFull = (next) => {
|
|
2609
|
+
if (onTextReplace) {
|
|
2610
|
+
onTextReplace(next);
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
const el = field();
|
|
2614
|
+
if (!el) throw new Error("Surface has no writable target");
|
|
2615
|
+
setFieldValue(el, next, next.length);
|
|
2616
|
+
};
|
|
2617
|
+
const canWrite = Boolean(onTextReplace) || Boolean(getTextarea);
|
|
2618
|
+
const canRead = Boolean(getTextarea) || Boolean(getApplicationScope);
|
|
2619
|
+
if (!canWrite) return null;
|
|
2620
|
+
const handle = {
|
|
2621
|
+
onTextReplace: ({ text }) => writeFull(text),
|
|
2622
|
+
...canRead ? { readText: readCurrent } : {}
|
|
2623
|
+
};
|
|
2624
|
+
if (onTextInsertBefore || getTextarea) {
|
|
2625
|
+
handle.onTextInsertBefore = ({ text }) => {
|
|
2626
|
+
if (onTextInsertBefore) return onTextInsertBefore(text);
|
|
2627
|
+
const el = field();
|
|
2628
|
+
if (!el) throw new Error("Surface has no writable target");
|
|
2629
|
+
const at = el.selectionStart ?? 0;
|
|
2630
|
+
setFieldValue(
|
|
2631
|
+
el,
|
|
2632
|
+
el.value.slice(0, at) + text + el.value.slice(at),
|
|
2633
|
+
at + text.length
|
|
2634
|
+
);
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
if (onTextInsertAfter || getTextarea) {
|
|
2638
|
+
handle.onTextInsertAfter = ({ text }) => {
|
|
2639
|
+
if (onTextInsertAfter) return onTextInsertAfter(text);
|
|
2640
|
+
const el = field();
|
|
2641
|
+
if (!el) throw new Error("Surface has no writable target");
|
|
2642
|
+
const at = el.selectionEnd ?? el.value.length;
|
|
2643
|
+
setFieldValue(
|
|
2644
|
+
el,
|
|
2645
|
+
el.value.slice(0, at) + text + el.value.slice(at),
|
|
2646
|
+
at + text.length
|
|
2647
|
+
);
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
if (canRead) {
|
|
2651
|
+
handle.onTextPrepend = ({ text }) => {
|
|
2652
|
+
const current = readCurrent();
|
|
2653
|
+
if (current === null) throw new Error("Surface content is unreadable");
|
|
2654
|
+
writeFull(text + current);
|
|
2655
|
+
};
|
|
2656
|
+
handle.onTextAppend = ({ text }) => {
|
|
2657
|
+
const current = readCurrent();
|
|
2658
|
+
if (current === null) throw new Error("Surface content is unreadable");
|
|
2659
|
+
writeFull(current + text);
|
|
2660
|
+
};
|
|
2661
|
+
handle.onTextPatch = ({ search_text, replacement_text }) => {
|
|
2662
|
+
const current = readCurrent();
|
|
2663
|
+
if (current === null) throw new Error("Surface content is unreadable");
|
|
2664
|
+
const idx = current.indexOf(search_text);
|
|
2665
|
+
if (idx === -1)
|
|
2666
|
+
throw new Error("search_text not found in surface content");
|
|
2667
|
+
writeFull(
|
|
2668
|
+
current.slice(0, idx) + replacement_text + current.slice(idx + search_text.length)
|
|
2669
|
+
);
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
return handle;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// src/engine/utils/table-at-target.ts
|
|
2676
|
+
var import_table_shape = require("@ai-matrx/records-ui/table-shape");
|
|
2677
|
+
function tableTextAtTarget(target) {
|
|
2678
|
+
const table = target?.closest("table");
|
|
2679
|
+
if (!table) return null;
|
|
2680
|
+
const text = (0, import_table_shape.shapeTextOfNode)(table.cloneNode(true));
|
|
2681
|
+
return text.trim() ? text : null;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
// src/engine/region-trigger-attrs.ts
|
|
2685
|
+
var CONTEXT_REGION_TRIGGER_ATTRS = {
|
|
2686
|
+
"data-alchemy-trigger": "context"
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
// src/engine/item/itemMenuToV3.ts
|
|
2690
|
+
var import_item = require("@ai-matrx/design-system/item");
|
|
2691
|
+
var import_notify = require("@ai-matrx/chat/host/notify");
|
|
2692
|
+
var iconOf = (entry) => entry.icon;
|
|
2693
|
+
var toastPromise = (promise, messages) => {
|
|
2694
|
+
import_notify.toast.promise(promise, messages);
|
|
2695
|
+
};
|
|
2696
|
+
function runCommand(entry) {
|
|
2697
|
+
(0, import_item.runCommand)(entry, toastPromise);
|
|
2698
|
+
}
|
|
2699
|
+
function entryToExtraItem(entry) {
|
|
2700
|
+
const description = entry.disabled && entry.disabledReason ? entry.disabledReason : entry.description;
|
|
2701
|
+
if ((0, import_item.isSubmenu)(entry)) {
|
|
2702
|
+
return compact({
|
|
2703
|
+
kind: "submenu",
|
|
2704
|
+
id: entry.id,
|
|
2705
|
+
label: entry.label,
|
|
2706
|
+
icon: iconOf(entry),
|
|
2707
|
+
disabled: entry.disabled,
|
|
2708
|
+
children: submenuChildren(entry.sections)
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
if ((0, import_item.isCheckbox)(entry)) {
|
|
2712
|
+
return compact({
|
|
2713
|
+
kind: "checkbox",
|
|
2714
|
+
id: entry.id,
|
|
2715
|
+
label: entry.label,
|
|
2716
|
+
icon: iconOf(entry),
|
|
2717
|
+
description,
|
|
2718
|
+
checked: entry.checked,
|
|
2719
|
+
onCheckedChange: (next) => (0, import_item.runToggle)(entry, next),
|
|
2720
|
+
disabled: entry.disabled,
|
|
2721
|
+
hint: entry.shortcut ?? entry.shortcutKey?.toUpperCase()
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
if ((0, import_item.isLink)(entry)) {
|
|
2725
|
+
return compact({
|
|
2726
|
+
kind: "link",
|
|
2727
|
+
id: entry.id,
|
|
2728
|
+
label: entry.label,
|
|
2729
|
+
icon: iconOf(entry),
|
|
2730
|
+
description,
|
|
2731
|
+
href: entry.href,
|
|
2732
|
+
target: entry.target,
|
|
2733
|
+
disabled: entry.disabled
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
return compact({
|
|
2737
|
+
kind: "item",
|
|
2738
|
+
id: entry.id,
|
|
2739
|
+
label: entry.label,
|
|
2740
|
+
icon: iconOf(entry),
|
|
2741
|
+
description,
|
|
2742
|
+
destructive: entry.tone === "destructive",
|
|
2743
|
+
disabled: entry.disabled,
|
|
2744
|
+
hint: entry.shortcut ?? entry.shortcutKey?.toUpperCase(),
|
|
2745
|
+
onSelect: () => runCommand(entry)
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
function submenuChildren(sections) {
|
|
2749
|
+
const visible = sections.map((s) => ({ ...s, items: s.items.filter((e) => !e.hidden) })).filter((s) => s.items.length > 0);
|
|
2750
|
+
const out = [];
|
|
2751
|
+
visible.forEach((section, i) => {
|
|
2752
|
+
if (i > 0)
|
|
2753
|
+
out.push({
|
|
2754
|
+
kind: "separator",
|
|
2755
|
+
id: `sep-${section.id ?? section.label ?? i}`
|
|
2756
|
+
});
|
|
2757
|
+
out.push(...section.items.map(entryToExtraItem));
|
|
2758
|
+
});
|
|
2759
|
+
return out;
|
|
2760
|
+
}
|
|
2761
|
+
function itemMenuConfigToExtraSections(config) {
|
|
2762
|
+
const sections = config.sections.map((s, i) => compact({
|
|
2763
|
+
id: String(s.id ?? s.label ?? `im-${i}`),
|
|
2764
|
+
label: s.label,
|
|
2765
|
+
anchor: "after-clipboard",
|
|
2766
|
+
items: s.items.filter((e) => !e.hidden).map(entryToExtraItem)
|
|
2767
|
+
})).filter((s) => s.items.length > 0);
|
|
2768
|
+
const title = config.header?.title;
|
|
2769
|
+
const first = sections[0];
|
|
2770
|
+
if (title && first && !first.label) {
|
|
2771
|
+
sections[0] = { ...first, label: title };
|
|
2772
|
+
}
|
|
2773
|
+
return sections;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// src/engine/alchemy-provider.ts
|
|
2777
|
+
var import_alchemy_icon_keys3 = require("@ai-matrx/rich-content/utils/alchemy-icon-keys");
|
|
2778
|
+
var import_turn_trust_strip = require("@ai-matrx/chat/agents/conversation-export/turn-trust-strip");
|
|
2779
|
+
var import_source = require("@ai-matrx/content-ir/source");
|
|
2780
|
+
function contextMenuHostOf(target) {
|
|
2781
|
+
const host = target.host;
|
|
2782
|
+
if (!host) return null;
|
|
2783
|
+
if (host.kind === "context-menu" && typeof host.instanceId === "string") {
|
|
2784
|
+
return host;
|
|
2785
|
+
}
|
|
2786
|
+
return host.contextMenu ?? null;
|
|
2787
|
+
}
|
|
2788
|
+
var VERBS = {
|
|
2789
|
+
copy: { verb: "copy", sentence: "Select text, or right-click content, to copy it." },
|
|
2790
|
+
cut: { verb: "cut", sentence: "Cut works on selected text in an editable field." },
|
|
2791
|
+
paste: { verb: "paste", sentence: "Paste works in an editable field." },
|
|
2792
|
+
undo: { verb: "undo", sentence: "Nothing to undo here." },
|
|
2793
|
+
redo: { verb: "redo", sentence: "Nothing to redo here." },
|
|
2794
|
+
find: { verb: "find", sentence: "Find works where there is text." }
|
|
2795
|
+
};
|
|
2796
|
+
var EDIT_ONLY_VERBS = /* @__PURE__ */ new Set(["cut", "paste", "undo", "redo"]);
|
|
2797
|
+
var CATEGORY_OF = {
|
|
2798
|
+
clipboard: "clipboard",
|
|
2799
|
+
tools: "app",
|
|
2800
|
+
history: "history",
|
|
2801
|
+
share: "share",
|
|
2802
|
+
document: "save",
|
|
2803
|
+
surface: "edit",
|
|
2804
|
+
ai: "ai",
|
|
2805
|
+
quick: "feedback",
|
|
2806
|
+
editable: "edit",
|
|
2807
|
+
admin: "admin",
|
|
2808
|
+
"surface-info": "surface-info"
|
|
2809
|
+
};
|
|
2810
|
+
var NON_VERB_CLIPBOARD = {
|
|
2811
|
+
"select-all": "edit",
|
|
2812
|
+
"insert-reference": "edit"
|
|
2813
|
+
};
|
|
2814
|
+
var INSERTS_CONTENT = /* @__PURE__ */ new Set(["placement:content-block"]);
|
|
2815
|
+
function findNodeById(nodes, id) {
|
|
2816
|
+
for (const n of nodes) {
|
|
2817
|
+
if (n.id === id) return n;
|
|
2818
|
+
if (n.kind === "submenu") {
|
|
2819
|
+
const hit = findNodeById(n.children, id);
|
|
2820
|
+
if (hit) return hit;
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
return null;
|
|
2824
|
+
}
|
|
2825
|
+
var available = { status: "available" };
|
|
2826
|
+
var absent = { status: "absent" };
|
|
2827
|
+
function icon3(node) {
|
|
2828
|
+
return node.icon ? (0, import_alchemy_icon_keys3.registerAlchemyIcon)(node.icon) : void 0;
|
|
2829
|
+
}
|
|
2830
|
+
function startGroup(a) {
|
|
2831
|
+
Object.assign(a, { startsGroup: true });
|
|
2832
|
+
}
|
|
2833
|
+
function toAction(node, place, instanceId, opts = {}) {
|
|
2834
|
+
if (node.kind === "separator" || node.kind === "label") return null;
|
|
2835
|
+
const id = `cm:${node.id}`;
|
|
2836
|
+
const own = (t) => contextMenuHostOf(t)?.instanceId === instanceId;
|
|
2837
|
+
const base = {
|
|
2838
|
+
id,
|
|
2839
|
+
label: node.label,
|
|
2840
|
+
category: place.category,
|
|
2841
|
+
order: place.order,
|
|
2842
|
+
...place.section ? { section: place.section } : {},
|
|
2843
|
+
.../* @__PURE__ */ ((key) => key ? { icon: key } : {})(icon3(node)),
|
|
2844
|
+
..."iconClassName" in node && node.iconClassName ? { iconTone: node.iconClassName } : {},
|
|
2845
|
+
..."description" in node && node.description ? { description: node.description } : {},
|
|
2846
|
+
..."hint" in node && node.hint ? { hint: node.hint } : {}
|
|
2847
|
+
};
|
|
2848
|
+
if (node.kind === "submenu") {
|
|
2849
|
+
const inserts = INSERTS_CONTENT.has(node.id);
|
|
2850
|
+
return {
|
|
2851
|
+
...base,
|
|
2852
|
+
// Still loading at the source: every layout shows "Loading…" on the row.
|
|
2853
|
+
pending: () => Boolean(node.loading),
|
|
2854
|
+
eligible: (t) => {
|
|
2855
|
+
if (!own(t)) return absent;
|
|
2856
|
+
if (inserts && (t.readOnly || opts.editable === false)) return absent;
|
|
2857
|
+
if (node.loading) return available;
|
|
2858
|
+
return !node.disabled && hasRows(node.children) ? available : absent;
|
|
2859
|
+
},
|
|
2860
|
+
expand: async (t) => {
|
|
2861
|
+
let current = node;
|
|
2862
|
+
for (let i = 0; current.kind === "submenu" && current.loading && opts.nextModel && i < 20; i++) {
|
|
2863
|
+
const next = await opts.nextModel();
|
|
2864
|
+
const found = next.sections.map((s) => findNodeById(s.nodes, node.id)).find(Boolean);
|
|
2865
|
+
if (!found) return [];
|
|
2866
|
+
current = found;
|
|
2867
|
+
}
|
|
2868
|
+
if (current.kind !== "submenu") return [];
|
|
2869
|
+
void t;
|
|
2870
|
+
const rows = [];
|
|
2871
|
+
let startsNext = false;
|
|
2872
|
+
for (const child of current.children) {
|
|
2873
|
+
if (child.kind === "separator") {
|
|
2874
|
+
startsNext = rows.length > 0;
|
|
2875
|
+
continue;
|
|
2876
|
+
}
|
|
2877
|
+
if (unusable(child)) continue;
|
|
2878
|
+
const a = toAction(child, { ...place, section: void 0, order: rows.length }, instanceId, opts);
|
|
2879
|
+
if (!a) continue;
|
|
2880
|
+
if (startsNext) startGroup(a);
|
|
2881
|
+
startsNext = false;
|
|
2882
|
+
rows.push(a);
|
|
2883
|
+
}
|
|
2884
|
+
return rows;
|
|
2885
|
+
},
|
|
2886
|
+
run: () => void 0
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
if (node.kind === "link") {
|
|
2890
|
+
return { ...base, href: node.href, eligible: (t) => own(t) && !node.disabled ? available : absent, run: () => void 0 };
|
|
2891
|
+
}
|
|
2892
|
+
if (node.kind === "checkbox") {
|
|
2893
|
+
return {
|
|
2894
|
+
...base,
|
|
2895
|
+
toggle: true,
|
|
2896
|
+
pressed: () => node.checked,
|
|
2897
|
+
eligible: (t) => own(t) && !node.disabled ? available : absent,
|
|
2898
|
+
run: () => node.onCheckedChange(!node.checked)
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
const verb = place.category === "clipboard" ? VERBS[node.id] : void 0;
|
|
2902
|
+
return {
|
|
2903
|
+
...base,
|
|
2904
|
+
...verb ? { verb: verb.verb } : {},
|
|
2905
|
+
...node.destructive ? { destructive: true } : {},
|
|
2906
|
+
...node.keepsMenuOpen ? { keepsMenuOpen: true } : {},
|
|
2907
|
+
eligible: (t) => {
|
|
2908
|
+
if (!own(t)) return absent;
|
|
2909
|
+
if (!node.disabled) return available;
|
|
2910
|
+
if (verb && (t.readOnly || opts.editable === false) && EDIT_ONLY_VERBS.has(verb.verb)) return absent;
|
|
2911
|
+
if (verb) return { status: "unavailable-verb", verb: verb.verb, sentence: verb.sentence };
|
|
2912
|
+
const reason = "description" in node && typeof node.description === "string" ? node.description.trim() : "";
|
|
2913
|
+
return reason ? { status: "unavailable", sentence: reason } : absent;
|
|
2914
|
+
},
|
|
2915
|
+
run: () => node.onSelect()
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function unusable(node) {
|
|
2919
|
+
if (node.kind === "separator" || node.kind === "label") return false;
|
|
2920
|
+
if (node.kind === "submenu") return !node.loading && (Boolean(node.disabled) || !hasRows(node.children));
|
|
2921
|
+
if (!("disabled" in node) || !node.disabled) return false;
|
|
2922
|
+
return !("description" in node && typeof node.description === "string" && node.description.trim());
|
|
2923
|
+
}
|
|
2924
|
+
function hasRows(nodes) {
|
|
2925
|
+
return nodes.some((n) => n.kind === "submenu" ? n.loading || hasRows(n.children) : n.kind !== "separator" && n.kind !== "label");
|
|
2926
|
+
}
|
|
2927
|
+
function contextMenuActionsFromModel(model, instanceId, opts = {}) {
|
|
2928
|
+
const out = [];
|
|
2929
|
+
model.sections.forEach((section, sectionIndex) => {
|
|
2930
|
+
const base = sectionIndex * 100;
|
|
2931
|
+
if (section.id === "registry") {
|
|
2932
|
+
section.nodes.forEach((node, i) => {
|
|
2933
|
+
if (node.kind !== "submenu" || !node.id.startsWith("rich-sub:")) return;
|
|
2934
|
+
const libraries = node.children.filter((c) => !c.id.startsWith("rich") && c.kind !== "separator");
|
|
2935
|
+
libraries.forEach((lib, j) => {
|
|
2936
|
+
const a = toAction(
|
|
2937
|
+
lib,
|
|
2938
|
+
{
|
|
2939
|
+
category: "ai",
|
|
2940
|
+
order: 5e3 + i * 50 + j
|
|
2941
|
+
},
|
|
2942
|
+
instanceId,
|
|
2943
|
+
opts
|
|
2944
|
+
);
|
|
2945
|
+
if (a) out.push(a);
|
|
2946
|
+
});
|
|
2947
|
+
});
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
const named = section.group === "history" ? { id: "cm-history", label: "History" } : section.label ? {
|
|
2951
|
+
id: `cm-${section.id}`,
|
|
2952
|
+
label: section.label,
|
|
2953
|
+
...section.primary ? { primary: true } : {},
|
|
2954
|
+
// A surface section's label is the clicked thing's own name.
|
|
2955
|
+
...section.group === "surface" ? { kind: "target" } : {}
|
|
2956
|
+
} : void 0;
|
|
2957
|
+
let groupStarts = false;
|
|
2958
|
+
section.nodes.forEach((node, i) => {
|
|
2959
|
+
if (node.kind === "separator") {
|
|
2960
|
+
groupStarts = true;
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
const category = section.group === "clipboard" ? NON_VERB_CLIPBOARD[node.id] ?? "clipboard" : CATEGORY_OF[section.group];
|
|
2964
|
+
const isHistoryVerb = section.group === "history" && (node.id === "undo" || node.id === "redo");
|
|
2965
|
+
const a = toAction(
|
|
2966
|
+
node,
|
|
2967
|
+
{
|
|
2968
|
+
category: isHistoryVerb ? "clipboard" : category,
|
|
2969
|
+
...named && !isHistoryVerb ? { section: named } : {},
|
|
2970
|
+
order: base + i
|
|
2971
|
+
},
|
|
2972
|
+
instanceId,
|
|
2973
|
+
opts
|
|
2974
|
+
);
|
|
2975
|
+
if (a) {
|
|
2976
|
+
if (groupStarts) startGroup(a);
|
|
2977
|
+
groupStarts = false;
|
|
2978
|
+
out.push(a);
|
|
2979
|
+
}
|
|
2980
|
+
});
|
|
2981
|
+
});
|
|
2982
|
+
return out;
|
|
2983
|
+
}
|
|
2984
|
+
function contextMenuProvider(instanceId, actions) {
|
|
2985
|
+
return {
|
|
2986
|
+
id: `context-menu:${instanceId}`,
|
|
2987
|
+
tier: "T1",
|
|
2988
|
+
actions: (target) => contextMenuHostOf(target)?.instanceId === instanceId ? actions() : []
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
function modelRevision(model) {
|
|
2992
|
+
const walk = (nodes) => nodes.map(
|
|
2993
|
+
(n) => n.kind === "submenu" ? `${n.id}${n.disabled ? "!" : ""}${n.loading ? "~" : ""}[${walk(n.children)}]` : `${n.id}${"disabled" in n && n.disabled ? "!" : ""}${n.kind === "checkbox" && n.checked ? "*" : ""}${"label" in n ? `:${n.label}` : ""}`
|
|
2994
|
+
).join(",");
|
|
2995
|
+
return model.sections.map((s) => `${s.id}(${walk(s.nodes)})`).join("|");
|
|
2996
|
+
}
|
|
2997
|
+
function menuHeaderContent(actionText) {
|
|
2998
|
+
if (actionText.source === "none") return null;
|
|
2999
|
+
return headerPreview((0, import_turn_trust_strip.stripTurnTrust)(actionText.text));
|
|
3000
|
+
}
|
|
3001
|
+
function chatMessageSubject(args) {
|
|
3002
|
+
const { state, source, extensions, contextData } = args;
|
|
3003
|
+
const read = (conversationId, messageId) => typeof conversationId === "string" && typeof messageId === "string" ? state.messages?.byConversationId?.[conversationId]?.byId?.[messageId] : void 0;
|
|
3004
|
+
if (source?.type === "chat-message") {
|
|
3005
|
+
const record2 = read(source.conversationId, source.messageId);
|
|
3006
|
+
const role = record2?.role ?? (extensions?.type === "chat-message" ? extensions.role : null);
|
|
3007
|
+
return role ? { role: String(role), createdAt: record2?.createdAt ?? null } : null;
|
|
3008
|
+
}
|
|
3009
|
+
const record = read(contextData?.conversationId, contextData?.messageId);
|
|
3010
|
+
return record?.role ? { role: String(record.role), createdAt: record.createdAt ?? null } : null;
|
|
3011
|
+
}
|
|
3012
|
+
function menuHeader(actionText, fieldLabel, message) {
|
|
3013
|
+
if (message && actionText.source !== "selection" && actionText.source !== "none") {
|
|
3014
|
+
const who = message.role === "user" ? "Your message" : message.role === "assistant" ? "AI answer" : "Message";
|
|
3015
|
+
const at = message.createdAt ? new Date(message.createdAt) : null;
|
|
3016
|
+
const when = at && !Number.isNaN(at.getTime()) ? ` \xB7 ${at.toLocaleString(void 0, { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}` : "";
|
|
3017
|
+
return { content: "", contentLabel: `${who}${when}` };
|
|
3018
|
+
}
|
|
3019
|
+
if (fieldLabel && actionText.source !== "selection") {
|
|
3020
|
+
return { content: fieldPreview(headerPreview((0, import_turn_trust_strip.stripTurnTrust)(actionText.text))), contentLabel: fieldLabel };
|
|
3021
|
+
}
|
|
3022
|
+
return { content: menuHeaderContent(actionText), contentLabel: null };
|
|
3023
|
+
}
|
|
3024
|
+
function headerPreview(text) {
|
|
3025
|
+
const blocks = text.replace(/^\s{0,3}(?:```|~~~).*$/gm, "").replace(/^\s{0,3}#{1,6}\s+/gm, "").replace(/^\s{0,3}>\s?/gm, "").replace(/^\s{0,3}(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?/gm, "");
|
|
3026
|
+
return (0, import_source.markdownToPlainText)(blocks).replace(/\s*\n+\s*/g, " ").trim();
|
|
3027
|
+
}
|
|
3028
|
+
function namedHeader(heading, actionText) {
|
|
3029
|
+
if (!heading || actionText.source === "selection") return null;
|
|
3030
|
+
return { content: heading.text, contentLabel: heading.label };
|
|
3031
|
+
}
|
|
3032
|
+
function menuEngineRevision(drawn, header) {
|
|
3033
|
+
return `${drawn}\0${header.contentLabel ?? ""}\0${header.content ?? ""}`;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
// src/engine/selection-provider.ts
|
|
3037
|
+
var import_domain5 = require("@ai-matrx/icons/domain");
|
|
3038
|
+
var import_alchemy_icon_keys4 = require("@ai-matrx/rich-content/utils/alchemy-icon-keys");
|
|
3039
|
+
var import_selection_actions = require("@ai-matrx/rich-content/selection-toolbar/selection-actions");
|
|
3040
|
+
var CONTEXT_MENU_SELECTION_HOST_KEY = "contextMenuSelection";
|
|
3041
|
+
function menuOf(target) {
|
|
3042
|
+
const half = (0, import_selection_actions.hostHalf)(target, CONTEXT_MENU_SELECTION_HOST_KEY);
|
|
3043
|
+
return half?.kind === "context-menu-selection" ? half : null;
|
|
3044
|
+
}
|
|
3045
|
+
var ASK_AI = {
|
|
3046
|
+
id: "selection:ai",
|
|
3047
|
+
label: "Ask AI",
|
|
3048
|
+
description: "AI actions, agents, copy and export for the selected text",
|
|
3049
|
+
icon: (0, import_alchemy_icon_keys4.registerAlchemyIcon)(import_domain5.AGENT_ICON),
|
|
3050
|
+
category: "ai",
|
|
3051
|
+
order: 0,
|
|
3052
|
+
placement: "primary",
|
|
3053
|
+
preserveSelection: true,
|
|
3054
|
+
eligible: (t) => menuOf(t) && (0, import_selection_actions.shownInSelectionMode)(ASK_AI.id, t) ? { status: "available" } : { status: "absent" },
|
|
3055
|
+
run: (t) => {
|
|
3056
|
+
const menu = menuOf(t);
|
|
3057
|
+
if (!menu) return;
|
|
3058
|
+
(0, import_selection_actions.selectionToolbarHostOf)(t)?.ui.close();
|
|
3059
|
+
menu.open();
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
var contextMenuSelectionProvider = {
|
|
3063
|
+
id: "context-menu-selection",
|
|
3064
|
+
tier: "T0",
|
|
3065
|
+
declaredIds: () => [ASK_AI.id],
|
|
3066
|
+
actions: (target) => menuOf(target) ? (0, import_selection_actions.placeSelectionActions)([ASK_AI], target) : []
|
|
3067
|
+
};
|
|
3068
|
+
(0, import_selection_actions.declareSelectionProvider)(contextMenuSelectionProvider);
|
|
3069
|
+
//# sourceMappingURL=engine.cjs.map
|