@ai-matrx/media 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/share.cjs ADDED
@@ -0,0 +1,606 @@
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/share.ts
22
+ var share_exports = {};
23
+ __export(share_exports, {
24
+ MediaSharePopover: () => MediaSharePopover,
25
+ createMediaSharePopover: () => createMediaSharePopover
26
+ });
27
+ module.exports = __toCommonJS(share_exports);
28
+
29
+ // src/share/media-share-popover.tsx
30
+ var import_react2 = require("react");
31
+ var import_react3 = require("@ai-matrx/associations/react");
32
+
33
+ // src/core/context.ts
34
+ var import_react = require("react");
35
+ var CLIENT_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.media.client-context");
36
+ function clientContext() {
37
+ const host = globalThis;
38
+ host[CLIENT_SLOT] ??= (0, import_react.createContext)(null);
39
+ return host[CLIENT_SLOT];
40
+ }
41
+ function useMediaClient() {
42
+ const client = (0, import_react.useContext)(clientContext());
43
+ if (!client) {
44
+ throw new Error(
45
+ "@ai-matrx/media: no MediaClient. Mount <MediaProvider client={...}> above every media surface (canonically wired to @ai-matrx/data/files)."
46
+ );
47
+ }
48
+ return client;
49
+ }
50
+
51
+ // src/react/cn.ts
52
+ var import_tailwind_merge = require("tailwind-merge");
53
+ function cn(...inputs) {
54
+ return (0, import_tailwind_merge.twMerge)(inputs.filter(Boolean).join(" "));
55
+ }
56
+
57
+ // src/react/icons.tsx
58
+ var import_jsx_runtime = require("react/jsx-runtime");
59
+ function svgProps(props) {
60
+ return {
61
+ xmlns: "http://www.w3.org/2000/svg",
62
+ viewBox: "0 0 24 24",
63
+ width: 24,
64
+ height: 24,
65
+ fill: "none",
66
+ stroke: "currentColor",
67
+ strokeWidth: 2,
68
+ strokeLinecap: "round",
69
+ strokeLinejoin: "round",
70
+ "aria-hidden": true,
71
+ ...props
72
+ };
73
+ }
74
+ function CheckIcon(props) {
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 6 9 17l-5-5" }) });
76
+ }
77
+ function XIcon(props) {
78
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }),
80
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })
81
+ ] });
82
+ }
83
+ function GlobeIcon(props) {
84
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
85
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
86
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M2 12h20" })
88
+ ] });
89
+ }
90
+ function Link2Icon(props) {
91
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
92
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 17H7A5 5 0 0 1 7 7h2" }),
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M15 7h2a5 5 0 1 1 0 10h-2" }),
94
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "8", x2: "16", y1: "12", y2: "12" })
95
+ ] });
96
+ }
97
+ function Settings2Icon(props) {
98
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
99
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 7h-9" }),
100
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 17H5" }),
101
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "17", cy: "17", r: "3" }),
102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "7", cy: "7", r: "3" })
103
+ ] });
104
+ }
105
+ function SpinnerIcon(props) {
106
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
107
+ }
108
+ function PlusIcon(props) {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
110
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 12h14" }),
111
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 5v14" })
112
+ ] });
113
+ }
114
+ function SearchIcon(props) {
115
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
117
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 21-4.3-4.3" })
118
+ ] });
119
+ }
120
+
121
+ // src/share/media-share-popover.tsx
122
+ var import_jsx_runtime2 = require("react/jsx-runtime");
123
+ var DEFAULT_ATTACH_TOKENS = [
124
+ "organization",
125
+ "project",
126
+ "task",
127
+ "conversation",
128
+ "scope"
129
+ ];
130
+ var KIND_LABEL = {
131
+ image: "image",
132
+ video: "video",
133
+ audio: "audio",
134
+ file: "file"
135
+ };
136
+ function fileIdOf(ref) {
137
+ if (!ref || typeof ref === "string") return null;
138
+ return ref.file_id ?? null;
139
+ }
140
+ async function copyToClipboard(text) {
141
+ try {
142
+ if (typeof navigator === "undefined" || !navigator.clipboard) return false;
143
+ await navigator.clipboard.writeText(text);
144
+ return true;
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
149
+ function MediaSharePopover({
150
+ context,
151
+ onClose,
152
+ manageLinks,
153
+ AccessSummary,
154
+ notify,
155
+ entityToken = "file",
156
+ attachTokens = DEFAULT_ATTACH_TOKENS,
157
+ associations = true,
158
+ className
159
+ }) {
160
+ const fileId = fileIdOf(context.ref);
161
+ const kind = context.resolution?.kind ?? "file";
162
+ const associationsStore = useOptionalAssociationsStore();
163
+ const AccessSummarySlot = AccessSummary ?? null;
164
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
165
+ "div",
166
+ {
167
+ className: cn(
168
+ "w-80 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
169
+ className
170
+ ),
171
+ children: [
172
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "border-b px-3 py-2", children: [
173
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xs font-semibold", children: [
174
+ "Share ",
175
+ KIND_LABEL[kind]
176
+ ] }),
177
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-[11px] text-muted-foreground", children: "Public links use Matrx sharing \xB7 playback URLs stay private" })
178
+ ] }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1 p-2", children: [
180
+ fileId ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
181
+ MatrxQuickActions,
182
+ {
183
+ context,
184
+ notify,
185
+ onActionComplete: onClose
186
+ }
187
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
188
+ ExternalQuickActions,
189
+ {
190
+ context,
191
+ kind,
192
+ notify,
193
+ onActionComplete: onClose
194
+ }
195
+ ),
196
+ fileId && manageLinks ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
197
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "my-1 h-px bg-border" }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
199
+ QuickActionRow,
200
+ {
201
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Settings2Icon, { className: "h-4 w-4 text-muted-foreground", width: 16, height: 16 }),
202
+ label: "Manage all links",
203
+ sublabel: "Custom expiry \xB7 permissions \xB7 revoke",
204
+ onClick: () => {
205
+ manageLinks(context);
206
+ onClose();
207
+ },
208
+ muted: true
209
+ }
210
+ )
211
+ ] }) : null,
212
+ fileId && AccessSummarySlot ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ShareSection, { title: "Who can see this", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
213
+ AccessSummarySlot,
214
+ {
215
+ entityType: entityToken,
216
+ entityId: fileId,
217
+ context
218
+ }
219
+ ) }) : null,
220
+ fileId && associations && associationsStore ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
221
+ AssociationsSection,
222
+ {
223
+ entityToken,
224
+ entityId: fileId,
225
+ attachTokens,
226
+ notify
227
+ }
228
+ ) : null
229
+ ] })
230
+ ]
231
+ }
232
+ );
233
+ }
234
+ function createMediaSharePopover(options = {}) {
235
+ return function ConfiguredMediaSharePopover(props) {
236
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MediaSharePopover, { ...props, ...options });
237
+ };
238
+ }
239
+ function useOptionalAssociationsStore() {
240
+ try {
241
+ return (0, import_react3.useAssociationsStore)();
242
+ } catch {
243
+ return null;
244
+ }
245
+ }
246
+ function MatrxQuickActions({
247
+ context,
248
+ notify,
249
+ onActionComplete
250
+ }) {
251
+ const client = useMediaClient();
252
+ const [publicBusy, setPublicBusy] = (0, import_react2.useState)(false);
253
+ const [copied, setCopied] = (0, import_react2.useState)(false);
254
+ const [error, setError] = (0, import_react2.useState)(null);
255
+ const handleCopyPublic = (0, import_react2.useCallback)(async () => {
256
+ if (publicBusy) return;
257
+ setPublicBusy(true);
258
+ setError(null);
259
+ try {
260
+ const url = await client.shareableUrl(context.ref);
261
+ if (!url) {
262
+ setError("This file cannot be shared publicly");
263
+ notify?.error("Could not create public link", {
264
+ description: "This file cannot be shared publicly."
265
+ });
266
+ return;
267
+ }
268
+ const wrote = await copyToClipboard(url);
269
+ if (!wrote) {
270
+ setError("Could not copy link");
271
+ notify?.error("Could not copy link");
272
+ return;
273
+ }
274
+ notify?.success("Public link copied", {
275
+ description: "This link will never expire."
276
+ });
277
+ setCopied(true);
278
+ onActionComplete();
279
+ } catch (err) {
280
+ const message = err instanceof Error ? err.message : String(err);
281
+ setError(message);
282
+ notify?.error("Could not create public link", { description: message });
283
+ } finally {
284
+ setPublicBusy(false);
285
+ }
286
+ }, [publicBusy, client, context.ref, notify, onActionComplete]);
287
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
289
+ QuickActionRow,
290
+ {
291
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GlobeIcon, { className: "h-4 w-4 text-emerald-500", width: 16, height: 16 }),
292
+ label: "Copy public link",
293
+ sublabel: "Never expires \xB7 anyone with the link",
294
+ onClick: () => void handleCopyPublic(),
295
+ busy: publicBusy,
296
+ copied
297
+ }
298
+ ),
299
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "px-3 text-[11px] text-destructive", children: error }) : null
300
+ ] });
301
+ }
302
+ function ExternalQuickActions({
303
+ context,
304
+ kind,
305
+ notify,
306
+ onActionComplete
307
+ }) {
308
+ const client = useMediaClient();
309
+ const [externalUrl, setExternalUrl] = (0, import_react2.useState)(null);
310
+ const [checked, setChecked] = (0, import_react2.useState)(false);
311
+ const [error, setError] = (0, import_react2.useState)(null);
312
+ (0, import_react2.useEffect)(() => {
313
+ let cancelled = false;
314
+ setChecked(false);
315
+ void client.shareableUrl(context.ref).then((url) => {
316
+ if (cancelled) return;
317
+ setExternalUrl(url);
318
+ setChecked(true);
319
+ }).catch(() => {
320
+ if (cancelled) return;
321
+ setExternalUrl(null);
322
+ setChecked(true);
323
+ });
324
+ return () => {
325
+ cancelled = true;
326
+ };
327
+ }, [client, context.ref]);
328
+ const handleCopy = (0, import_react2.useCallback)(async () => {
329
+ if (!externalUrl) {
330
+ setError("This private playback URL cannot be shared");
331
+ notify?.error("This private playback URL cannot be shared");
332
+ return;
333
+ }
334
+ const wrote = await copyToClipboard(externalUrl);
335
+ if (!wrote) {
336
+ setError("Could not copy link");
337
+ notify?.error("Could not copy link");
338
+ return;
339
+ }
340
+ notify?.success("Link copied");
341
+ onActionComplete();
342
+ }, [externalUrl, notify, onActionComplete]);
343
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1", children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
345
+ QuickActionRow,
346
+ {
347
+ icon: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Link2Icon, { className: "h-4 w-4", width: 16, height: 16 }),
348
+ label: "Copy external link",
349
+ sublabel: externalUrl ?? "No safe external URL available",
350
+ onClick: () => void handleCopy(),
351
+ busy: !checked,
352
+ disabled: checked && !externalUrl
353
+ }
354
+ ),
355
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "px-3 text-[11px] text-destructive", children: error }) : null,
356
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "px-3 pt-2 text-[11px] text-muted-foreground", children: [
357
+ "This ",
358
+ KIND_LABEL[kind],
359
+ " is hosted externally \u2014 we can't create a permanent Matrx share link for it."
360
+ ] })
361
+ ] });
362
+ }
363
+ function AssociationsSection({
364
+ entityToken,
365
+ entityId,
366
+ attachTokens,
367
+ notify
368
+ }) {
369
+ const { edges, status, error, add, remove } = (0, import_react3.useAssociations)({
370
+ type: entityToken,
371
+ id: entityId
372
+ });
373
+ const [attachOpen, setAttachOpen] = (0, import_react2.useState)(false);
374
+ const [query, setQuery] = (0, import_react2.useState)("");
375
+ const [busyKey, setBusyKey] = (0, import_react2.useState)(null);
376
+ const [writeError, setWriteError] = (0, import_react2.useState)(null);
377
+ const outgoing = (0, import_react2.useMemo)(
378
+ () => edges.filter((edge) => edge.direction === "outgoing"),
379
+ [edges]
380
+ );
381
+ const titles = (0, import_react3.useEntityTitles)(
382
+ outgoing.map((edge) => ({
383
+ token: edge.otherType,
384
+ id: edge.otherId,
385
+ label: edge.label
386
+ }))
387
+ );
388
+ const attachedKeys = (0, import_react2.useMemo)(
389
+ () => new Set(outgoing.map((edge) => `${edge.otherType}:${edge.otherId}`)),
390
+ [outgoing]
391
+ );
392
+ const search = (0, import_react3.useUniversalEntitySearch)({
393
+ query,
394
+ tokens: [...attachTokens],
395
+ enabled: attachOpen
396
+ });
397
+ const runWrite = (0, import_react2.useCallback)(
398
+ async (key, verb, op) => {
399
+ if (busyKey) return;
400
+ setBusyKey(key);
401
+ setWriteError(null);
402
+ try {
403
+ const res = await op();
404
+ if (!res.ok) {
405
+ const message = `Couldn't ${verb}${res.error ? `: ${res.error}` : ""}`;
406
+ setWriteError(message);
407
+ notify?.error(message);
408
+ }
409
+ } finally {
410
+ setBusyKey(null);
411
+ }
412
+ },
413
+ [busyKey, notify]
414
+ );
415
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
416
+ ShareSection,
417
+ {
418
+ title: "Linked to",
419
+ action: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ onClick: () => setAttachOpen((open) => !open),
424
+ className: "inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground",
425
+ children: [
426
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PlusIcon, { className: "h-3 w-3", width: 12, height: 12 }),
427
+ "Attach to\u2026"
428
+ ]
429
+ }
430
+ ),
431
+ children: [
432
+ status === "loading" && outgoing.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5 text-[11px] text-muted-foreground", children: [
433
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin", width: 14, height: 14 }),
434
+ "Loading links\u2026"
435
+ ] }) : null,
436
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "px-3 text-[11px] text-destructive", children: error }) : null,
437
+ status !== "loading" && !error && outgoing.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "px-3 py-1 text-[11px] text-muted-foreground", children: "Not linked to anything yet." }) : null,
438
+ outgoing.map((edge) => {
439
+ const ref = { token: edge.otherType, id: edge.otherId, label: edge.label };
440
+ const unresolved = titles.isUnresolved(ref);
441
+ const key = `edge:${edge.id}`;
442
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
443
+ "div",
444
+ {
445
+ className: "flex items-center gap-2 rounded-md px-3 py-1.5 hover:bg-accent/50",
446
+ children: [
447
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
448
+ Link2Icon,
449
+ {
450
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
451
+ width: 14,
452
+ height: 14
453
+ }
454
+ ),
455
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col", children: [
456
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
457
+ "span",
458
+ {
459
+ className: cn(
460
+ "truncate text-xs",
461
+ unresolved && "text-destructive"
462
+ ),
463
+ children: unresolved ? "Unavailable" : titles.titleFor(ref)
464
+ }
465
+ ),
466
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: [
467
+ edge.otherType,
468
+ edge.role ? ` \xB7 ${edge.role}` : ""
469
+ ] })
470
+ ] }),
471
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
472
+ "button",
473
+ {
474
+ type: "button",
475
+ "aria-label": `Detach ${unresolved ? edge.otherType : titles.titleFor(ref)}`,
476
+ disabled: busyKey === key,
477
+ onClick: () => void runWrite(
478
+ key,
479
+ "detach",
480
+ () => remove({
481
+ targetType: edge.otherType,
482
+ targetId: edge.otherId,
483
+ ...edge.role != null ? { role: edge.role } : {}
484
+ })
485
+ ),
486
+ className: "flex h-6 w-6 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-destructive",
487
+ children: busyKey === key ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SpinnerIcon, { className: "h-3 w-3 animate-spin", width: 12, height: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(XIcon, { className: "h-3 w-3", width: 12, height: 12 })
488
+ }
489
+ )
490
+ ]
491
+ },
492
+ edge.id
493
+ );
494
+ }),
495
+ attachOpen ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1 border-t pt-2", children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "relative px-3", children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
498
+ SearchIcon,
499
+ {
500
+ className: "pointer-events-none absolute left-5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground",
501
+ width: 14,
502
+ height: 14
503
+ }
504
+ ),
505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
506
+ "input",
507
+ {
508
+ type: "text",
509
+ value: query,
510
+ onChange: (event) => setQuery(event.target.value),
511
+ placeholder: "Search to attach\u2026",
512
+ "aria-label": "Search to attach",
513
+ className: "w-full rounded-md border bg-background py-1 pl-7 pr-2 text-xs"
514
+ }
515
+ )
516
+ ] }),
517
+ search.loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 px-3 py-1.5 text-[11px] text-muted-foreground", children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SpinnerIcon, { className: "h-3.5 w-3.5 animate-spin", width: 14, height: 14 }),
519
+ "Searching\u2026"
520
+ ] }) : null,
521
+ search.results.map((candidate) => {
522
+ const candidateKey = `${candidate.token}:${candidate.id}`;
523
+ const attached = attachedKeys.has(candidateKey);
524
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ disabled: busyKey === candidateKey,
529
+ onClick: () => void runWrite(
530
+ candidateKey,
531
+ attached ? `detach "${candidate.title}"` : `attach "${candidate.title}"`,
532
+ () => attached ? remove({
533
+ targetType: candidate.token,
534
+ targetId: candidate.id
535
+ }) : add({
536
+ targetType: candidate.token,
537
+ targetId: candidate.id,
538
+ label: candidate.title
539
+ })
540
+ ),
541
+ className: "flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-left hover:bg-accent",
542
+ children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col", children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-xs", children: candidate.title }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: candidate.token })
546
+ ] }),
547
+ busyKey === candidateKey ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SpinnerIcon, { className: "h-3.5 w-3.5 shrink-0 animate-spin", width: 14, height: 14 }) : attached ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { className: "h-3.5 w-3.5 shrink-0 text-emerald-500", width: 14, height: 14 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PlusIcon, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", width: 14, height: 14 })
548
+ ]
549
+ },
550
+ candidateKey
551
+ );
552
+ }),
553
+ !search.loading && query.trim() && search.results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "px-3 py-1 text-[11px] text-muted-foreground", children: "No matches." }) : null
554
+ ] }) : null,
555
+ writeError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "px-3 text-[11px] text-destructive", children: writeError }) : null
556
+ ]
557
+ }
558
+ );
559
+ }
560
+ function ShareSection({
561
+ title,
562
+ action,
563
+ children
564
+ }) {
565
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-1 flex flex-col gap-1 border-t pt-2", children: [
566
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between px-3", children: [
567
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: title }),
568
+ action
569
+ ] }),
570
+ children
571
+ ] });
572
+ }
573
+ function QuickActionRow({
574
+ icon,
575
+ label,
576
+ sublabel,
577
+ onClick,
578
+ busy,
579
+ disabled,
580
+ copied,
581
+ muted
582
+ }) {
583
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
584
+ "button",
585
+ {
586
+ type: "button",
587
+ onClick,
588
+ disabled: busy || disabled,
589
+ className: cn(
590
+ "flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition-colors",
591
+ "hover:bg-accent focus-visible:bg-accent focus-visible:outline-none",
592
+ (busy || disabled) && "opacity-50 cursor-not-allowed",
593
+ muted && "text-muted-foreground"
594
+ ),
595
+ children: [
596
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted", children: busy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SpinnerIcon, { className: "h-4 w-4 animate-spin", width: 16, height: 16 }) : icon }),
597
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col", children: [
598
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm font-medium leading-tight", children: label }),
599
+ sublabel ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: sublabel }) : null
600
+ ] }),
601
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, { className: "h-4 w-4 shrink-0 text-emerald-500", width: 16, height: 16 }) : null
602
+ ]
603
+ }
604
+ );
605
+ }
606
+ //# sourceMappingURL=share.cjs.map