@openviking/react 0.1.3 → 0.1.5

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.
Files changed (209) hide show
  1. package/LICENSE.arco-design +21 -0
  2. package/README.md +27 -15
  3. package/dist/0~0.js +19 -0
  4. package/dist/28~0.js +15 -0
  5. package/dist/2~0.js +2270 -29
  6. package/dist/2~0.js.LICENSE.txt +23 -0
  7. package/dist/3c~0.js +2208 -0
  8. package/dist/3~0.js +1207 -1982
  9. package/dist/4~0.js +18 -0
  10. package/dist/9~0.js +43 -0
  11. package/dist/{1~0.js → ae~0.js} +11 -26
  12. package/dist/ark-message-list-view~0.js +530 -119
  13. package/dist/a~0.js +1300 -14
  14. package/dist/c7~0.js +38 -2
  15. package/dist/ck~0.js +23 -0
  16. package/dist/client/provider.d.ts +15 -9
  17. package/dist/client/types.d.ts +5 -5
  18. package/dist/client.js +2 -1
  19. package/dist/components/agent-connect/agent-connect-code-block.d.ts +14 -0
  20. package/dist/components/agent-connect/agent-connect-copy.d.ts +22 -33
  21. package/dist/components/agent-connect/agent-connect-design.d.ts +1936 -168
  22. package/dist/components/agent-connect/agent-connect-installer.d.ts +1 -0
  23. package/dist/components/agent-connect/agent-connect-instructions-view.d.ts +4 -6
  24. package/dist/components/agent-connect/agent-connect-instructions.d.ts +148 -0
  25. package/dist/components/agent-connect/agent-connect-markdown.d.ts +8 -2
  26. package/dist/components/agent-connect/agent-connect-mask.d.ts +10 -0
  27. package/dist/components/agent-connect/agent-connect.d.ts +1 -1
  28. package/dist/components/agent-connect/locales/en-US.d.ts +23 -0
  29. package/dist/components/agent-connect/locales/zh-CN.d.ts +3 -0
  30. package/dist/components/agent-connect/style.css +129 -3
  31. package/dist/components/agent-connect/types.d.ts +7 -4
  32. package/dist/components/agent-connect/use-agent-connect-copy.d.ts +6 -0
  33. package/dist/components/directory-tour/directory-tour.d.ts +3 -0
  34. package/dist/components/directory-tour/index.d.ts +3 -0
  35. package/dist/components/directory-tour/locales/en-US.d.ts +3 -0
  36. package/dist/components/directory-tour/locales/zh-CN.d.ts +3 -0
  37. package/dist/components/directory-tour/steps.d.ts +5 -0
  38. package/dist/components/directory-tour/style.css +237 -0
  39. package/dist/components/directory-tour/style.js +0 -0
  40. package/dist/components/directory-tour/types.d.ts +43 -0
  41. package/dist/components/directory-tour/use-tour-anchor.d.ts +5 -0
  42. package/dist/components/directory-tour/use-tour-position.d.ts +12 -0
  43. package/dist/components/directory-tour.js +40 -0
  44. package/dist/components/file-browser-entries/file-browser-entries.d.ts +5 -5
  45. package/dist/components/file-browser-entries/labels.d.ts +3 -0
  46. package/dist/components/file-browser-entries/locales/en-US.d.ts +6 -0
  47. package/dist/components/file-browser-entries/locales/zh-CN.d.ts +6 -0
  48. package/dist/components/file-browser-entries/style.css +866 -2
  49. package/dist/components/file-browser-entries/types.d.ts +34 -16
  50. package/dist/components/file-preview/file-preview-adapters.d.ts +2 -1
  51. package/dist/components/file-preview/file-preview-body.d.ts +2 -1
  52. package/dist/components/file-preview/file-preview-breadcrumb-menu.d.ts +2 -1
  53. package/dist/components/file-preview/file-preview-breadcrumb.d.ts +2 -1
  54. package/dist/components/file-preview/file-preview-directory.d.ts +2 -1
  55. package/dist/components/file-preview/file-preview-handle-adapters.d.ts +2 -1
  56. package/dist/components/file-preview/file-preview-header.d.ts +6 -2
  57. package/dist/components/file-preview/file-preview-image-frame.d.ts +5 -0
  58. package/dist/components/file-preview/file-preview-labels.d.ts +2 -14
  59. package/dist/components/file-preview/file-preview-level-tabs.d.ts +7 -0
  60. package/dist/components/file-preview/file-preview-markdown-images.d.ts +13 -0
  61. package/dist/components/file-preview/file-preview-panels.d.ts +5 -2
  62. package/dist/components/file-preview/file-preview-viewer.d.ts +2 -1
  63. package/dist/components/file-preview/file-preview.d.ts +0 -1
  64. package/dist/components/file-preview/index.d.ts +2 -1
  65. package/dist/components/file-preview/locales/en-US.d.ts +16 -0
  66. package/dist/components/file-preview/locales/zh-CN.d.ts +16 -0
  67. package/dist/components/file-preview/style.css +160 -2
  68. package/dist/components/file-preview/types.d.ts +34 -10
  69. package/dist/components/file-preview/use-file-preview-levels.d.ts +7 -0
  70. package/dist/components/file-preview/use-file-preview-view-state.d.ts +15 -0
  71. package/dist/components/file-preview.js +1 -1
  72. package/dist/components/file-tree/file-tree-info-labels.d.ts +0 -15
  73. package/dist/components/file-tree/file-tree-labels.d.ts +4 -0
  74. package/dist/components/file-tree/file-tree-row-shared.d.ts +2 -12
  75. package/dist/components/file-tree/file-tree-toolbar.d.ts +11 -0
  76. package/dist/components/file-tree/locales/en-US.d.ts +69 -0
  77. package/dist/components/file-tree/locales/zh-CN.d.ts +69 -0
  78. package/dist/components/file-tree/style.css +143 -15
  79. package/dist/components/file-tree/types.d.ts +38 -10
  80. package/dist/components/file-tree/use-file-tree-actions.d.ts +4 -1
  81. package/dist/components/scope-switcher/labels.d.ts +3 -0
  82. package/dist/components/scope-switcher/locales/en-US.d.ts +8 -0
  83. package/dist/components/scope-switcher/locales/zh-CN.d.ts +8 -0
  84. package/dist/components/scope-switcher/scope-panel.d.ts +1 -1
  85. package/dist/components/scope-switcher/scope-switcher.d.ts +2 -2
  86. package/dist/components/scope-switcher/style.css +14 -2
  87. package/dist/components/scope-switcher/types.d.ts +12 -6
  88. package/dist/components/style.css +10030 -6763
  89. package/dist/components/tui/locales/en-US.d.ts +35 -0
  90. package/dist/components/tui/locales/zh-CN.d.ts +35 -0
  91. package/dist/components/tui/style.css +310 -18
  92. package/dist/components/tui/tui-copy-button.d.ts +1 -1
  93. package/dist/components/tui/tui-empty.d.ts +25 -0
  94. package/dist/components/tui/tui-labels.d.ts +2 -2
  95. package/dist/components/tui/tui.d.ts +1 -1
  96. package/dist/components/tui/types.d.ts +35 -12
  97. package/dist/components/tui.js +1 -1
  98. package/dist/components/viking-bot/ark-adapter.d.ts +2 -0
  99. package/dist/components/viking-bot/ark-message-body.d.ts +1 -7
  100. package/dist/components/viking-bot/ark-message-list-view.d.ts +4 -1
  101. package/dist/components/viking-bot/ark-message-row.d.ts +3 -1
  102. package/dist/components/viking-bot/bot-popover.d.ts +9 -0
  103. package/dist/components/viking-bot/capabilities.d.ts +49 -0
  104. package/dist/components/viking-bot/composer-attachments.d.ts +13 -0
  105. package/dist/components/viking-bot/composer-controls.d.ts +10 -0
  106. package/dist/components/viking-bot/composer-footer.d.ts +17 -0
  107. package/dist/components/viking-bot/composer-references.d.ts +20 -0
  108. package/dist/components/viking-bot/composer-text.d.ts +10 -0
  109. package/dist/components/viking-bot/index.d.ts +3 -1
  110. package/dist/components/viking-bot/interaction/ask-human-fields.d.ts +1 -1
  111. package/dist/components/viking-bot/interaction/labels.d.ts +2 -1
  112. package/dist/components/viking-bot/interaction/locales/en-US.d.ts +17 -0
  113. package/dist/components/viking-bot/interaction/locales/zh-CN.d.ts +17 -0
  114. package/dist/components/viking-bot/interaction/types.d.ts +8 -3
  115. package/dist/components/viking-bot/interaction/viking-bot-interaction.d.ts +1 -1
  116. package/dist/components/viking-bot/labels.d.ts +2 -0
  117. package/dist/components/viking-bot/locales/en-US.d.ts +40 -0
  118. package/dist/components/viking-bot/locales/zh-CN.d.ts +40 -0
  119. package/dist/components/viking-bot/markdown-links.d.ts +27 -0
  120. package/dist/components/viking-bot/message-activity.d.ts +7 -0
  121. package/dist/components/viking-bot/message-code-block.d.ts +8 -0
  122. package/dist/components/viking-bot/message-footer.d.ts +8 -0
  123. package/dist/components/viking-bot/message-markdown.d.ts +9 -0
  124. package/dist/components/viking-bot/model.d.ts +37 -3
  125. package/dist/components/viking-bot/quick-input-tracker.d.ts +16 -0
  126. package/dist/components/viking-bot/quick-inputs.d.ts +6 -0
  127. package/dist/components/viking-bot/reference-picker.d.ts +16 -0
  128. package/dist/components/viking-bot/session-actions.d.ts +11 -0
  129. package/dist/components/viking-bot/style.css +1486 -48
  130. package/dist/components/viking-bot/tool-activity.d.ts +9 -0
  131. package/dist/components/viking-bot/tool-result-links.d.ts +6 -0
  132. package/dist/components/viking-bot/types.d.ts +20 -8
  133. package/dist/components/viking-bot/use-quick-input.d.ts +17 -0
  134. package/dist/components/viking-bot/viking-bot.d.ts +1 -6
  135. package/dist/components/viking-bot/welcome.d.ts +8 -0
  136. package/dist/components/viking-bot.js +1 -1
  137. package/dist/c~0.js +550 -69
  138. package/dist/e~0.js +335 -0
  139. package/dist/file-preview-viewer~0.js +90 -8
  140. package/dist/f~0.js +49 -51
  141. package/dist/h~0.js +5 -1
  142. package/dist/icons/console-icons.d.ts +12 -0
  143. package/dist/icons/index.d.ts +2 -0
  144. package/dist/icons/viking-bot-icons.d.ts +6 -0
  145. package/dist/icons.js +1 -0
  146. package/dist/index.d.ts +2 -0
  147. package/dist/index.js +2 -1
  148. package/dist/k~0.js +1 -0
  149. package/dist/lg~0.js +155 -0
  150. package/dist/lk~0.js +40 -8
  151. package/dist/l~0.js +170 -181
  152. package/dist/modules/console/command-messages.d.ts +19 -0
  153. package/dist/modules/console/command-parser.d.ts +15 -0
  154. package/dist/modules/console/command-specs.d.ts +15 -0
  155. package/dist/modules/console/commands.d.ts +6 -0
  156. package/dist/modules/console/console-history.d.ts +10 -0
  157. package/dist/modules/console/console-toolbar.d.ts +18 -0
  158. package/dist/modules/console/console.d.ts +1 -3
  159. package/dist/modules/console/controller.d.ts +17 -0
  160. package/dist/modules/console/index.d.ts +2 -0
  161. package/dist/modules/console/labels.d.ts +61 -0
  162. package/dist/modules/console/locales/en-US.d.ts +49 -0
  163. package/dist/modules/console/locales/zh-CN.d.ts +3 -0
  164. package/dist/modules/console/localize-records.d.ts +11 -0
  165. package/dist/modules/console/results.d.ts +2 -0
  166. package/dist/modules/console/style.css +10905 -15
  167. package/dist/modules/console/types.d.ts +59 -80
  168. package/dist/modules/console/use-console.d.ts +3 -0
  169. package/dist/modules/console.js +1 -1
  170. package/dist/modules/file-browser/file-browser-detail.d.ts +2 -1
  171. package/dist/modules/file-browser/file-browser-labels.d.ts +11 -12
  172. package/dist/modules/file-browser/file-browser-toolbar.d.ts +3 -4
  173. package/dist/modules/file-browser/file-browser-tree-pane.d.ts +7 -5
  174. package/dist/modules/file-browser/file-browser.d.ts +1 -1
  175. package/dist/modules/file-browser/fs-read-directory.d.ts +2 -6
  176. package/dist/modules/file-browser/locales/en-US.d.ts +25 -0
  177. package/dist/modules/file-browser/locales/zh-CN.d.ts +25 -0
  178. package/dist/modules/file-browser/style.css +1695 -126
  179. package/dist/modules/file-browser/types.d.ts +38 -29
  180. package/dist/modules/file-browser/use-file-browser.d.ts +6 -1
  181. package/dist/modules/file-browser/use-fs-preview.d.ts +15 -1
  182. package/dist/modules/scope-cascade/labels.d.ts +6 -0
  183. package/dist/modules/scope-cascade/locales/en-US.d.ts +6 -0
  184. package/dist/modules/scope-cascade/locales/zh-CN.d.ts +3 -0
  185. package/dist/modules/scope-cascade/scope-cascade.d.ts +2 -2
  186. package/dist/modules/scope-cascade/style.css +14 -2
  187. package/dist/modules/scope-cascade/types.d.ts +3 -0
  188. package/dist/modules/style.css +15938 -3455
  189. package/dist/nx~0.js +15 -0
  190. package/dist/n~0.js +240 -83
  191. package/dist/o~0.js +10 -7
  192. package/dist/p~0.js +27 -0
  193. package/dist/rslib-runtime~0.js +42 -0
  194. package/dist/r~0.js +152 -0
  195. package/dist/shared/components/drawer/drawer.d.ts +13 -0
  196. package/dist/shared/components/drawer/index.d.ts +1 -0
  197. package/dist/shared/components/overlay/use-overlay-position.d.ts +2 -2
  198. package/dist/shared/components/tooltip/use-portal-theme.d.ts +3 -0
  199. package/dist/shared/locale-context.d.ts +8 -0
  200. package/dist/shared/locale.d.ts +6 -0
  201. package/dist/style.css +26339 -10577
  202. package/dist/u~0.js +397 -152
  203. package/dist/viking-bot-composer~0.js +436 -77
  204. package/dist/viking-bot-interaction~0.js +13 -11
  205. package/package.json +15 -4
  206. package/dist/30~0.js +0 -1332
  207. package/dist/components/tui/tui-labels-zh.d.ts +0 -2
  208. package/dist/modules/file-browser/tree-info-copy.d.ts +0 -15
  209. package/dist/modules/file-browser/tree-info-copy.zh-CN.d.ts +0 -15
package/dist/3~0.js CHANGED
@@ -1,2117 +1,1342 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Children, createContext, isValidElement, useCallback, useContext, useEffect, useId, useRef, useState, useSyncExternalStore } from "react";
3
- import react_markdown, { defaultUrlTransform } from "react-markdown";
4
- import remark_gfm from "remark-gfm";
5
- function getTuiCommandInputText(command) {
6
- return command.inputText?.trim() || command.name;
7
- }
8
- function matchTuiCommandInput(value, commands) {
9
- const content = value.trimStart();
10
- const leadingWhitespace = value.slice(0, value.length - content.length);
11
- const matches = commands.flatMap((command)=>{
12
- const names = [
13
- command.name,
14
- ...command.aliases ?? []
15
- ];
16
- return [
17
- getTuiCommandInputText(command),
18
- ...names,
19
- ...names.map((name)=>`/${name}`)
20
- ].flatMap((prefix)=>{
21
- const words = prefix.trim().split(/[ \t]+/);
22
- const pattern = words.map((word)=>word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('[ \t]+');
23
- const match = pattern && new RegExp(`^${pattern}(?=$|\\s)`, 'i').exec(content);
24
- return match ? [
25
- {
26
- command,
27
- leadingWhitespace,
28
- prefix: match[0],
29
- rest: content.slice(match[0].length)
30
- }
31
- ] : [];
32
- });
33
- });
34
- return matches.sort((left, right)=>right.prefix.length - left.prefix.length)[0];
35
- }
36
- function formatTuiCommandInput(value, commands) {
37
- const match = matchTuiCommandInput(value, commands);
38
- return match ? `${match.leadingWhitespace}${getTuiCommandInputText(match.command)}${match.rest}` : value;
39
- }
40
- function getTuiArgumentHints(command) {
41
- const params = command.params ?? [];
42
- const positional = params.filter((param)=>!param.label.startsWith('-'));
43
- if (positional.length) return positional;
44
- return [
45
- ...params.filter((param)=>param.required),
46
- ...(command.options ?? []).filter((option)=>option.required).map((option)=>({
47
- label: option.token,
48
- description: option.description
49
- }))
50
- ];
51
- }
52
- function TuiParamHint({ command, labels, onPick }) {
53
- const examples = command.examples?.map((example)=>'string' == typeof example ? {
54
- value: example,
55
- label: example
56
- } : example);
57
- const params = [
58
- ...(command.params ?? []).map((param)=>({
59
- name: param.label,
60
- description: param.description,
61
- required: param.required ?? false,
62
- defaultValue: param.defaultValue
63
- })),
64
- ...(command.options ?? []).map((option)=>({
65
- name: option.token,
66
- description: option.description,
67
- required: option.required ?? false,
68
- defaultValue: option.defaultValue
69
- }))
70
- ];
2
+ import { Fragment as external_react_Fragment, Suspense, forwardRef, lazy, useCallback, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { Tooltip } from "./r~0.js";
4
+ import { resolveLocale, mergeLabels } from "./nx~0.js";
5
+ import { useLocale } from "./28~0.js";
6
+ function FilePreviewImageFrame({ children }) {
7
+ const [dimensions, setDimensions] = useState('');
71
8
  return /*#__PURE__*/ jsxs("div", {
72
- className: "ov-tui__hint",
9
+ className: "ov-file-preview__image-frame",
10
+ onLoadCapture: (event)=>{
11
+ const image = event.target;
12
+ if (image instanceof HTMLImageElement && image.closest('[data-file-viewer-image]')) setDimensions(`${image.naturalWidth} × ${image.naturalHeight}`);
13
+ },
73
14
  children: [
74
- /*#__PURE__*/ jsxs("div", {
75
- className: "ov-tui__hint-head",
76
- children: [
77
- /*#__PURE__*/ jsxs("code", {
78
- className: "ov-tui__hint-name",
79
- children: [
80
- "/",
81
- command.name
82
- ]
83
- }),
84
- /*#__PURE__*/ jsx("span", {
85
- className: "ov-tui__hint-desc",
86
- children: command.description
87
- })
88
- ]
89
- }),
90
- examples?.length ? /*#__PURE__*/ jsxs("div", {
91
- className: "ov-tui__hint-group",
92
- children: [
93
- /*#__PURE__*/ jsx("div", {
94
- className: "ov-tui__hint-label",
95
- children: labels.examples
96
- }),
97
- examples.map((example, index)=>/*#__PURE__*/ jsxs("button", {
98
- type: "button",
99
- className: "ov-tui__hint-example",
100
- onMouseDown: (event)=>event.preventDefault(),
101
- onClick: ()=>onPick(example.value),
102
- children: [
103
- /*#__PURE__*/ jsx("span", {
104
- className: "ov-tui__hint-example-index",
105
- "aria-hidden": "true",
106
- children: index + 1
107
- }),
108
- /*#__PURE__*/ jsx("span", {
109
- children: example.label ?? example.value
110
- })
111
- ]
112
- }, example.value))
113
- ]
114
- }) : null,
115
- /*#__PURE__*/ jsxs("div", {
116
- className: "ov-tui__hint-group",
117
- children: [
118
- /*#__PURE__*/ jsx("div", {
119
- className: "ov-tui__hint-section-title",
120
- children: labels.parameters
121
- }),
122
- /*#__PURE__*/ jsx("code", {
123
- className: "ov-tui__hint-usage",
124
- children: command.usage
125
- }),
126
- params.length ? /*#__PURE__*/ jsx("div", {
127
- className: "ov-tui__hint-table-frame",
128
- children: /*#__PURE__*/ jsxs("table", {
129
- className: "ov-tui__hint-table",
130
- children: [
131
- /*#__PURE__*/ jsx("thead", {
132
- children: /*#__PURE__*/ jsxs("tr", {
133
- children: [
134
- /*#__PURE__*/ jsx("th", {
135
- children: labels.parameter
136
- }),
137
- /*#__PURE__*/ jsx("th", {
138
- children: labels.required
139
- }),
140
- /*#__PURE__*/ jsx("th", {
141
- children: labels.description
142
- }),
143
- /*#__PURE__*/ jsx("th", {
144
- children: labels.defaultValue
145
- })
146
- ]
147
- })
148
- }),
149
- /*#__PURE__*/ jsx("tbody", {
150
- children: params.map((param)=>/*#__PURE__*/ jsxs("tr", {
151
- children: [
152
- /*#__PURE__*/ jsx("td", {
153
- children: /*#__PURE__*/ jsx("code", {
154
- children: param.name
155
- })
156
- }),
157
- /*#__PURE__*/ jsx("td", {
158
- children: param.required ? labels.yes : labels.no
159
- }),
160
- /*#__PURE__*/ jsx("td", {
161
- children: param.description
162
- }),
163
- /*#__PURE__*/ jsx("td", {
164
- children: param.defaultValue ?? '—'
165
- })
166
- ]
167
- }, param.name))
168
- })
169
- ]
170
- })
171
- }) : /*#__PURE__*/ jsx("div", {
172
- className: "ov-tui__hint-empty",
173
- children: labels.noParameters
174
- })
175
- ]
176
- })
15
+ children,
16
+ dimensions ? /*#__PURE__*/ jsx("span", {
17
+ className: "ov-file-preview__image-size",
18
+ children: dimensions
19
+ }) : null
177
20
  ]
178
21
  });
179
22
  }
180
- function useTuiAssistPosition(anchorRef, open, onDismiss) {
181
- const panelRef = useRef(null);
182
- const anchorName = `--ov-tui-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
183
- useEffect(()=>{
184
- const panel = panelRef.current;
185
- const anchor = anchorRef.current;
186
- if (!open || !panel || !anchor) return;
187
- const dismiss = ()=>onDismiss(anchor.querySelector('textarea')?.value ?? '');
188
- const outside = (event)=>{
189
- if (event.target instanceof Node && !panel.contains(event.target) && !anchor.contains(event.target)) dismiss();
190
- };
191
- const escape = (event)=>{
192
- if (event.isComposing || 229 === event.keyCode) return;
193
- if ('Escape' === event.key && (anchor.contains(document.activeElement) || panel.contains(document.activeElement))) {
194
- event.preventDefault();
195
- dismiss();
196
- }
197
- };
198
- document.addEventListener('pointerdown', outside);
199
- document.addEventListener('keydown', escape);
200
- if (!('popover' in HTMLElement.prototype)) {
201
- panel.removeAttribute('popover');
202
- panel.dataset.inline = '';
203
- return ()=>{
204
- document.removeEventListener('pointerdown', outside);
205
- document.removeEventListener('keydown', escape);
206
- };
207
- }
208
- const nativeAnchors = CSS.supports('position-area', 'block-start span-inline-end');
209
- const previousAnchor = anchor.style.getPropertyValue('anchor-name');
210
- anchor.style.setProperty('anchor-name', anchorName);
211
- panel.style.setProperty('position-anchor', anchorName);
212
- panel.dataset.floating = '';
213
- const place = ()=>{
214
- const rect = anchor.getBoundingClientRect();
215
- const above = Math.max(0, rect.top - 20);
216
- const below = Math.max(0, window.innerHeight - rect.bottom - 20);
217
- const onTop = above >= 160 || above >= below;
218
- const available = Math.max(80, onTop ? above : below);
219
- const width = Math.min(rect.width, window.innerWidth - 24);
220
- const left = Math.max(12, Math.min(rect.left, window.innerWidth - width - 12));
221
- panel.style.width = `${width}px`;
222
- panel.style.setProperty('--ov-ui-tui-assist-height', `${Math.min(420, available)}px`);
223
- const right = window.innerWidth - left - width;
224
- panel.dataset.side = left >= 334 ? 'left' : right >= 334 ? 'right' : 'stack';
225
- panel.style.setProperty('--ov-ui-tui-assist-detail-width', `${Math.min(400, (left >= 334 ? left : right) - 14)}px`);
226
- panel.dataset.placement = onTop ? 'above' : 'below';
227
- if (nativeAnchors) panel.style.setProperty('position-area', `${onTop ? 'block-start' : 'block-end'} span-inline-end`);
228
- else {
229
- panel.style.left = `${left}px`;
230
- panel.style.top = onTop ? 'auto' : `${rect.bottom + 8}px`;
231
- panel.style.bottom = onTop ? `${window.innerHeight - rect.top + 8}px` : 'auto';
232
- }
233
- };
234
- place();
235
- panel.showPopover();
236
- const observer = new ResizeObserver(place);
237
- observer.observe(anchor);
238
- observer.observe(panel);
239
- window.addEventListener('resize', place);
240
- window.addEventListener('scroll', place, true);
241
- return ()=>{
242
- observer.disconnect();
243
- window.removeEventListener('resize', place);
244
- window.removeEventListener('scroll', place, true);
245
- document.removeEventListener('pointerdown', outside);
246
- document.removeEventListener('keydown', escape);
247
- anchor.style.setProperty('anchor-name', previousAnchor);
248
- if (panel.matches(':popover-open')) panel.hidePopover();
249
- };
250
- }, [
251
- anchorRef,
252
- anchorName,
253
- open,
254
- onDismiss
255
- ]);
256
- return panelRef;
257
- }
258
- function Highlighted({ text, keyword }) {
259
- const normalized = keyword.trim().toLowerCase();
260
- const index = normalized ? text.toLowerCase().indexOf(normalized) : -1;
261
- if (index < 0) return /*#__PURE__*/ jsx(Fragment, {
262
- children: text
23
+ function FilePreviewEditor({ draft, text, loading, failed, saveFailed, saving, onChange, onCancel }) {
24
+ if (loading) return /*#__PURE__*/ jsx("div", {
25
+ className: "ov-file-preview__state",
26
+ role: "status",
27
+ children: /*#__PURE__*/ jsx("p", {
28
+ className: "ov-file-preview__state-title",
29
+ children: text.loading
30
+ })
263
31
  });
264
- return /*#__PURE__*/ jsxs(Fragment, {
32
+ if (failed) return /*#__PURE__*/ jsxs("div", {
33
+ className: "ov-file-preview__state",
34
+ role: "alert",
265
35
  children: [
266
- text.slice(0, index),
267
- /*#__PURE__*/ jsx("mark", {
268
- className: "ov-tui__mark",
269
- children: text.slice(index, index + normalized.length)
36
+ /*#__PURE__*/ jsx("p", {
37
+ className: "ov-file-preview__state-title",
38
+ children: text.loadFailed
270
39
  }),
271
- text.slice(index + normalized.length)
40
+ /*#__PURE__*/ jsx("button", {
41
+ type: "button",
42
+ className: "ov-file-preview__button",
43
+ onClick: onCancel,
44
+ children: text.leaveEditing
45
+ })
272
46
  ]
273
47
  });
274
- }
275
- function TuiAssist({ anchorRef, assistId, labels, completion, activeIndex, onActiveIndexChange, onPick, onDismiss }) {
276
- const options = completion.options;
277
- const keyword = completion.query.keyword;
278
- const selected = options[activeIndex];
279
- const command = selected;
280
- const open = completion.showSuggestions;
281
- const panelRef = useTuiAssistPosition(anchorRef, open, onDismiss);
282
- const listRef = useRef(null);
283
- useEffect(()=>{
284
- const list = listRef.current;
285
- const option = list?.querySelector('[aria-selected="true"]');
286
- if (!list || !option) return;
287
- const listBox = list.getBoundingClientRect();
288
- const optionBox = option.getBoundingClientRect();
289
- if (optionBox.top < listBox.top) list.scrollTop -= listBox.top - optionBox.top;
290
- if (optionBox.bottom > listBox.bottom) list.scrollTop += optionBox.bottom - listBox.bottom;
291
- }, [
292
- activeIndex,
293
- options
294
- ]);
295
- if (!open) return null;
296
48
  return /*#__PURE__*/ jsxs("div", {
297
- ref: panelRef,
298
- className: "ov-tui__assist",
299
- popover: "manual",
300
- "data-details-only": !options.length || void 0,
49
+ className: "ov-file-preview__editor",
301
50
  children: [
302
- command ? /*#__PURE__*/ jsx("div", {
303
- className: "ov-tui__assist-detail",
304
- children: /*#__PURE__*/ jsx(TuiParamHint, {
305
- command: command,
306
- labels: labels,
307
- onPick: onPick
308
- })
309
- }) : null,
310
- options.length ? /*#__PURE__*/ jsx("div", {
311
- className: "ov-tui__suggestions",
312
- ref: listRef,
313
- role: "listbox",
314
- id: assistId,
315
- "aria-label": labels.suggestions,
316
- children: completion.suggestions.map((section)=>/*#__PURE__*/ jsxs("div", {
317
- className: "ov-tui__suggestion-group",
318
- children: [
319
- /*#__PURE__*/ jsx("div", {
320
- className: "ov-tui__suggestion-label",
321
- children: section.group.label
322
- }),
323
- section.commands.map((item)=>{
324
- const index = options.indexOf(item);
325
- return /*#__PURE__*/ jsxs("button", {
326
- id: `${assistId}-${index}`,
327
- type: "button",
328
- role: "option",
329
- "aria-selected": item === selected,
330
- className: "ov-tui__suggestion",
331
- onMouseEnter: ()=>onActiveIndexChange(index),
332
- onFocus: ()=>onActiveIndexChange(index),
333
- onMouseDown: (event)=>event.preventDefault(),
334
- onClick: ()=>onPick(`${getTuiCommandInputText(item)} `),
335
- children: [
336
- /*#__PURE__*/ jsx("code", {
337
- className: "ov-tui__suggestion-name",
338
- children: /*#__PURE__*/ jsx(Highlighted, {
339
- text: item.name,
340
- keyword: keyword
341
- })
342
- }),
343
- /*#__PURE__*/ jsx("span", {
344
- className: "ov-tui__suggestion-desc",
345
- title: item.summary ?? item.description,
346
- children: /*#__PURE__*/ jsx(Highlighted, {
347
- text: item.summary ?? item.description,
348
- keyword: keyword
349
- })
350
- })
351
- ]
352
- }, item.name);
353
- })
354
- ]
355
- }, section.group.key))
356
- }) : command ? null : /*#__PURE__*/ jsx("div", {
357
- className: "ov-tui__no-suggestions",
358
- role: "status",
359
- children: labels.noSuggestions
360
- })
51
+ /*#__PURE__*/ jsx("textarea", {
52
+ className: "ov-file-preview__textarea",
53
+ value: draft,
54
+ readOnly: saving,
55
+ "aria-label": text.editor,
56
+ spellCheck: false,
57
+ onChange: (event)=>onChange(event.target.value)
58
+ }),
59
+ saveFailed ? /*#__PURE__*/ jsx("p", {
60
+ className: "ov-file-preview__notice",
61
+ role: "alert",
62
+ children: text.saveFailed
63
+ }) : null
361
64
  ]
362
65
  });
363
66
  }
364
- const LEADING_SLASH = /^\s*\//;
365
- const COMMAND_NAME = /^([^\s]*)([\s\S]*)$/;
366
- function parseTuiCommandInput(value) {
367
- if (!LEADING_SLASH.test(value)) return null;
368
- const body = value.replace(LEADING_SLASH, '');
369
- const matched = COMMAND_NAME.exec(body);
370
- if (!matched) return null;
371
- const name = matched[1] ?? '';
372
- const rest = matched[2] ?? '';
373
- return {
374
- name,
375
- rest,
376
- hasTrailingSpace: /^\s/.test(rest)
377
- };
378
- }
379
- function resolveTuiSkeleton(command) {
380
- return command?.skeleton ?? 'generic';
381
- }
382
- function findTuiCommand(text, commands) {
383
- return matchTuiCommandInput(text, commands)?.command;
384
- }
385
- function filterTuiCommands(keyword, commands) {
386
- const normalized = keyword.trim().toLowerCase();
387
- if (!normalized) return commands;
388
- return commands.filter((command)=>command.name.toLowerCase().includes(normalized) || (command.summary ?? command.description).toLowerCase().includes(normalized) || command.aliases?.some((alias)=>alias.toLowerCase().includes(normalized)));
389
- }
390
- function groupTuiCommands(commands, groups = []) {
391
- const known = new Map(groups.map((group)=>[
392
- group.key,
393
- group
394
- ]));
395
- for (const command of commands){
396
- const key = command.group ?? '';
397
- if (!known.has(key)) known.set(key, {
398
- key,
399
- label: key
400
- });
401
- }
402
- return Array.from(known.values(), (group)=>({
403
- group,
404
- commands: commands.filter((command)=>(command.group ?? '') === group.key)
405
- })).filter((section)=>section.commands.length > 0);
67
+ function encodeText(content) {
68
+ return new TextEncoder().encode(content);
406
69
  }
407
- const EMPTY_QUERY = {
408
- keyword: '',
409
- scope: null,
410
- matches: [],
411
- automatic: false,
412
- canOpenPalette: false,
413
- colorInput: false
414
- };
415
- const normalize = (text)=>text.replace(/[ \t]+/g, ' ');
416
- function getTuiCommandQuery(value, commands) {
417
- if (!commands.length || /[\r\n]/.test(value)) return EMPTY_QUERY;
418
- const slash = parseTuiCommandInput(value);
419
- const content = normalize(value.trimStart().slice(slash ? 1 : 0));
420
- if (!slash && !content) return EMPTY_QUERY;
421
- const boundary = content.lastIndexOf(' ') + 1;
422
- const head = content.slice(0, boundary);
423
- const keyword = content.slice(boundary);
424
- const entries = commands.flatMap((command)=>(slash ? [
425
- command.name,
426
- ...command.aliases ?? []
427
- ] : [
428
- getTuiCommandInputText(command)
429
- ]).map((text)=>({
430
- command,
431
- text: normalize(text)
432
- })));
433
- const definitions = (items)=>[
434
- ...new Set(items.map((entry)=>entry.command))
435
- ];
436
- const scoped = entries.filter((entry)=>entry.text.toLowerCase().startsWith(head.toLowerCase()));
437
- if (!scoped.length) return EMPTY_QUERY;
438
- const matching = scoped.filter((entry)=>entry.text.toLowerCase().startsWith(content.toLowerCase()));
439
- const input = matchTuiCommandInput(value, commands);
440
- if (input && (!slash || input.rest) && !matching.some((entry)=>entry.text.length > content.trimEnd().length)) return EMPTY_QUERY;
441
- const base = {
442
- ...EMPTY_QUERY,
443
- keyword,
444
- scope: slash ? '/' : head.trimEnd(),
445
- automatic: Boolean(slash)
446
- };
447
- if (slash) return {
448
- ...base,
449
- matches: head ? matching.length ? definitions(matching) : filterTuiCommands(keyword, definitions(scoped)) : filterTuiCommands(keyword, commands)
70
+ function toFileSource(source) {
71
+ const { content, file, url, fileName, fileType } = source;
72
+ if ('string' == typeof content) return {
73
+ kind: 'bytes',
74
+ bytes: encodeText(content),
75
+ name: fileName,
76
+ ...fileType ? {
77
+ fileType
78
+ } : {}
450
79
  };
451
- if (!keyword) return {
452
- ...base,
453
- matches: matching.map((entry)=>entry.command),
454
- canOpenPalette: matching.length > 0,
455
- colorInput: matching.length > 0
456
- };
457
- const words = content.split(' ').length;
458
- if (matching.length && matching.every((entry)=>entry.text.split(' ').length > words)) {
459
- const targets = [
460
- ...new Set(matching.map((entry)=>entry.text.split(' ').slice(0, words).join(' ')))
461
- ];
462
- const target = 1 === targets.length ? targets[0] : void 0;
463
- return {
464
- ...base,
465
- matches: matching.map((entry)=>entry.command),
466
- colorInput: true,
467
- prefixTarget: target,
468
- canOpenPalette: !target || target.toLowerCase() === content.toLowerCase()
469
- };
470
- }
471
- return {
472
- ...base,
473
- matches: matching.length ? matching.map((entry)=>entry.command) : filterTuiCommands(keyword, scoped.map((entry)=>entry.command)),
474
- colorInput: matching.length > 0
475
- };
476
- }
477
- function getTuiInlineCompletion(value, query, selected, paletteOpen = false) {
478
- const leading = value.slice(0, value.length - value.trimStart().length);
479
- const content = normalize(value.trimStart());
480
- if (query.prefixTarget) {
481
- const suffix = query.prefixTarget.slice(content.length);
482
- return suffix ? {
483
- kind: 'prefix',
484
- suffix,
485
- value: `${leading}${query.prefixTarget}`
486
- } : void 0;
487
- }
488
- if (!selected || !query.keyword && (!paletteOpen || '/' === query.scope)) return;
489
- if ('/' === query.scope) {
490
- const typed = content.slice(1);
491
- const name = [
492
- selected.name,
493
- ...selected.aliases ?? []
494
- ].map(normalize).find((name)=>name.toLowerCase().startsWith(typed.toLowerCase()));
495
- const suffix = name?.slice(typed.length);
496
- return suffix ? {
497
- kind: 'command',
498
- suffix,
499
- value: `${leading}${getTuiCommandInputText(selected)} `
500
- } : void 0;
501
- }
502
- const text = getTuiCommandInputText(selected);
503
- const normalized = normalize(text);
504
- if (!normalized.toLowerCase().startsWith(content.toLowerCase())) return;
505
- const suffix = normalized.slice(content.length);
506
- return suffix ? {
507
- kind: 'command',
508
- suffix,
509
- value: `${leading}${text} `
510
- } : void 0;
511
- }
512
- function useTuiComposerKeyboard({ inputRef, value, history, completion, inline, disabled, readOnly, canSubmit, activeIndex, onActiveIndexChange, onValueChange, onSubmit, onDismiss, onRequestPalette }) {
513
- const historyRef = useRef({
514
- offset: -1,
515
- draft: '',
516
- value: ''
517
- });
518
- const change = (next)=>{
519
- historyRef.current.offset = -1;
520
- onValueChange?.(next);
521
- };
522
- const accept = (next)=>{
523
- change(next);
524
- inputRef.current?.focus({
525
- preventScroll: true
526
- });
527
- };
528
- const handleKeyDown = (event)=>{
529
- if (event.nativeEvent.isComposing || 229 === event.nativeEvent.keyCode) return;
530
- const input = event.currentTarget;
531
- const atEnd = input.selectionStart === input.selectionEnd && input.selectionEnd === value.length;
532
- if ('Tab' === event.key) {
533
- if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) return;
534
- event.preventDefault();
535
- if (!atEnd) return;
536
- }
537
- const options = completion.options;
538
- const listOpen = completion.showSuggestions;
539
- if ('Escape' === event.key && (inline || listOpen)) {
540
- event.preventDefault();
541
- onDismiss(value);
542
- return;
543
- }
544
- if (!disabled && !readOnly) {
545
- if ('Tab' === event.key && inline?.kind === 'prefix') {
546
- event.preventDefault();
547
- accept(inline.value);
548
- return;
549
- }
550
- if (' ' === event.key && atEnd && inline?.kind === 'prefix' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
551
- event.preventDefault();
552
- accept(`${inline.value} `);
553
- return;
554
- }
555
- if ('Tab' === event.key && completion.query.canOpenPalette && !listOpen) {
556
- event.preventDefault();
557
- onRequestPalette();
558
- return;
559
- }
560
- if (listOpen && options.length) {
561
- if ('ArrowDown' === event.key || 'ArrowUp' === event.key) {
562
- event.preventDefault();
563
- const delta = 'ArrowDown' === event.key ? 1 : -1;
564
- onActiveIndexChange((index)=>index < 0 ? delta > 0 ? 0 : options.length - 1 : (index + delta + options.length) % options.length);
565
- return;
566
- }
567
- const spaceCommand = ' ' === event.key && atEnd ? matchTuiCommandInput(value, options)?.command : void 0;
568
- if ('Tab' === event.key || 'Enter' === event.key && !event.shiftKey || spaceCommand) {
569
- event.preventDefault();
570
- const picked = spaceCommand ?? options[activeIndex];
571
- if (picked) accept(`${getTuiCommandInputText(picked)} `);
572
- else if ('Tab' === event.key && inline) accept(inline.value);
573
- return;
574
- }
575
- }
576
- if ('Tab' === event.key && inline) {
577
- event.preventDefault();
578
- accept(inline.value);
579
- return;
580
- }
581
- }
582
- const previous = historyRef.current;
583
- const browsing = previous.offset >= 0 && previous.value === value;
584
- if (!disabled && !readOnly && history.length > 0 && input.selectionStart === input.selectionEnd && !event.shiftKey && ('ArrowUp' === event.key && !value.slice(0, input.selectionStart).includes('\n') || 'ArrowDown' === event.key && browsing && !value.slice(input.selectionEnd).includes('\n'))) {
585
- event.preventDefault();
586
- const offset = Math.max(-1, Math.min(history.length - 1, (browsing ? previous.offset : -1) + ('ArrowUp' === event.key ? 1 : -1)));
587
- const draft = browsing ? previous.draft : value;
588
- const next = offset < 0 ? draft : history[history.length - 1 - offset];
589
- historyRef.current = {
590
- offset,
591
- draft,
592
- value: next
593
- };
594
- onValueChange?.(next);
595
- onDismiss(next);
596
- inputRef.current?.focus({
597
- preventScroll: true
598
- });
599
- return;
600
- }
601
- if ('Enter' === event.key && !event.shiftKey) {
602
- event.preventDefault();
603
- if (value.trim() && !disabled && !readOnly && canSubmit) onSubmit?.(value.trim());
604
- }
80
+ if (file) return {
81
+ kind: 'file',
82
+ file: "u" < typeof File || file instanceof File ? file : new File([
83
+ file
84
+ ], fileName, {
85
+ type: file.type
86
+ }),
87
+ name: fileName,
88
+ ...fileType ? {
89
+ fileType
90
+ } : {}
605
91
  };
606
- return {
607
- change,
608
- handleKeyDown
92
+ if (url) return {
93
+ kind: 'url',
94
+ url,
95
+ name: fileName,
96
+ ...fileType ? {
97
+ fileType
98
+ } : {}
609
99
  };
610
100
  }
611
- const TUI_URI_PATTERN = /viking:\/\/[^\s"'<>`]+/g;
612
- const TUI_PROSE_URI_PATTERN = /viking:\/\/[^\s,,。;;、"'<>()[\]]+/g;
613
- function getTuiSafeHref(uri) {
614
- const href = uri?.trim();
615
- return href && /^(?:viking|https?):\/\/[^\s]+$/i.test(href) ? href : void 0;
616
- }
617
- async function copyTuiText(text) {
618
- if ("u" < typeof navigator) return false;
619
- try {
620
- if (navigator.clipboard?.writeText) {
621
- await navigator.clipboard.writeText(text);
622
- return true;
623
- }
624
- } catch {}
625
- return false;
626
- }
627
- function formatTuiJson(text) {
628
- const trimmed = text.trim();
629
- if (!trimmed || !/^[[{]/.test(trimmed)) return text;
630
- try {
631
- JSON.parse(trimmed);
632
- } catch {
633
- return text;
634
- }
635
- const tokens = trimmed.match(/"(?:\\.|[^"\\])*"|[^\s]/g) ?? [];
636
- let depth = 0;
637
- let output = '';
638
- const newline = ()=>`\n${' '.repeat(depth)}`;
639
- for (const [index, token] of tokens.entries())if ('{' === token || '[' === token) {
640
- output += token;
641
- depth++;
642
- if ('}' !== tokens[index + 1] && ']' !== tokens[index + 1]) output += newline();
643
- } else if ('}' === token || ']' === token) {
644
- depth--;
645
- if ('{' !== tokens[index - 1] && '[' !== tokens[index - 1]) output += newline();
646
- output += token;
647
- } else if (',' === token) output += `,${newline()}`;
648
- else if (':' === token) output += ': ';
649
- else output += token;
650
- return output;
651
- }
652
- function TuiUriLink({ uri, children, onOpenUri, editor = false, className = 'ov-tui__link' }) {
653
- const href = getTuiSafeHref(uri);
654
- if (!href) return /*#__PURE__*/ jsx(Fragment, {
655
- children: children
656
- });
657
- return /*#__PURE__*/ jsx("a", {
658
- href: href,
659
- className: className,
660
- onMouseDown: editor ? (event)=>event.preventDefault() : void 0,
661
- onClick: onOpenUri ? (event)=>{
662
- event.preventDefault();
663
- onOpenUri(href);
664
- } : void 0,
665
- children: children
666
- });
667
- }
668
- function TuiUriText({ text, onOpenUri, editor = false, mode }) {
669
- const parts = [];
670
- let cursor = 0;
671
- const pattern = new RegExp(('prose' === mode ? TUI_PROSE_URI_PATTERN : TUI_URI_PATTERN).source, 'gi');
672
- const plain = (value, key)=>editor ? /*#__PURE__*/ jsx("span", {
673
- "aria-hidden": "true",
674
- children: value
675
- }, key) : value;
676
- for (const matched of text.matchAll(pattern)){
677
- const index = matched.index ?? 0;
678
- const uri = 'prose' === mode ? matched[0].replace(/[.!?;;::!?]+$/u, '') : matched[0];
679
- if (index > cursor) parts.push(plain(text.slice(cursor, index), cursor));
680
- parts.push(/*#__PURE__*/ jsx(TuiUriLink, {
681
- uri: uri,
682
- onOpenUri: onOpenUri,
683
- editor: editor,
684
- children: uri
685
- }, `${index}-${uri}`));
686
- cursor = index + uri.length;
687
- }
688
- if (!parts.length) return /*#__PURE__*/ jsx(Fragment, {
689
- children: plain(text, 0)
690
- });
691
- if (cursor < text.length) parts.push(plain(text.slice(cursor), cursor));
692
- return /*#__PURE__*/ jsx(Fragment, {
693
- children: parts
694
- });
695
- }
696
- function TuiCommandText({ text, name, namespaceOnly = false }) {
697
- const namedStart = name ? text.length - name.length : -1;
698
- const matchesName = namedStart >= 0 && text.slice(namedStart).toLowerCase() === name?.toLowerCase() && (0 === namedStart || /\s/.test(text[namedStart - 1]));
699
- const lastToken = text.search(/\S+$/);
700
- const start = namespaceOnly ? text.length : matchesName ? namedStart : lastToken < 0 ? text.length : lastToken;
701
- return /*#__PURE__*/ jsxs(Fragment, {
702
- children: [
703
- start > 0 ? /*#__PURE__*/ jsx("span", {
704
- className: "ov-tui__command-prefix",
705
- children: text.slice(0, start)
706
- }) : null,
707
- start < text.length ? /*#__PURE__*/ jsx("span", {
708
- className: "ov-tui__command-name",
709
- children: text.slice(start)
710
- }) : null
711
- ]
712
- });
101
+ function readCopyableText(source) {
102
+ const { content } = source;
103
+ return 'string' == typeof content && content.length > 0 ? content : void 0;
713
104
  }
714
- function TuiInputMirror({ value, input, inline, colorInput, onOpenUri, onScroll, mirrorRef, hintId }) {
715
- const hints = input && !input.rest.trim() ? getTuiArgumentHints(input.command) : [];
716
- return /*#__PURE__*/ jsxs(Fragment, {
717
- children: [
718
- /*#__PURE__*/ jsxs("span", {
719
- className: "ov-tui__input-mirror",
720
- ref: mirrorRef,
721
- onScroll: onScroll,
722
- children: [
723
- input ? /*#__PURE__*/ jsxs(Fragment, {
724
- children: [
725
- input.leadingWhitespace,
726
- /*#__PURE__*/ jsx("span", {
727
- "aria-hidden": "true",
728
- children: /*#__PURE__*/ jsx("span", {
729
- className: "ov-tui__input-command",
730
- children: /*#__PURE__*/ jsx(TuiCommandText, {
731
- text: input.prefix,
732
- name: input.command.name
733
- })
734
- })
735
- }),
736
- /*#__PURE__*/ jsx(TuiUriText, {
737
- text: input.rest,
738
- mode: "literal",
739
- onOpenUri: onOpenUri,
740
- editor: true
741
- })
742
- ]
743
- }) : colorInput ? /*#__PURE__*/ jsx("span", {
744
- className: "ov-tui__input-command",
745
- "aria-hidden": "true",
746
- children: /*#__PURE__*/ jsx(TuiCommandText, {
747
- text: value,
748
- namespaceOnly: inline?.kind !== 'command'
749
- })
750
- }) : /*#__PURE__*/ jsx(TuiUriText, {
751
- text: value,
752
- mode: "literal",
753
- onOpenUri: onOpenUri,
754
- editor: true
755
- }),
756
- /*#__PURE__*/ jsxs("span", {
757
- "aria-hidden": "true",
758
- children: [
759
- inline ? /*#__PURE__*/ jsx("span", {
760
- className: "ov-tui__input-completion",
761
- children: inline.suffix
762
- }) : null,
763
- hints.length ? /*#__PURE__*/ jsxs("span", {
764
- className: "ov-tui__input-arguments",
765
- children: [
766
- input?.rest.endsWith(' ') ? '' : ' ',
767
- hints.map((hint)=>hint.label).join(' ')
768
- ]
769
- }) : null,
770
- value.endsWith('\n') ? '\u200b' : null
771
- ]
772
- })
773
- ]
774
- }),
775
- /*#__PURE__*/ jsxs("span", {
776
- id: hintId,
777
- className: "ov-tui__input-hint-description",
778
- "aria-live": inline?.kind === 'prefix' ? 'polite' : void 0,
779
- children: [
780
- inline?.value,
781
- hints.map((hint)=>`${hint.label}: ${hint.description}`).join('; ')
782
- ]
783
- })
784
- ]
785
- });
105
+ function isEmptyFile(source) {
106
+ return '' === source.content;
786
107
  }
787
- function TuiComposer({ anchorRef, assistId, onFocus, onDismiss, onRequestPalette, value, history, labels, completion, disabled, readOnly, canSubmit, activeIndex, onActiveIndexChange, onValueChange, onSubmit, onOpenUri }) {
788
- const inputRef = useRef(null);
789
- const inputId = useId();
790
- const mirrorRef = useRef(null);
791
- const showSuggestions = completion.showSuggestions && completion.options.length > 0;
792
- const preview = completion.options[activeIndex] ?? (completion.query.keyword ? completion.options[0] : void 0);
793
- const inline = completion.enabled ? getTuiInlineCompletion(value, completion.query, preview, completion.showSuggestions) : void 0;
794
- const keyboard = useTuiComposerKeyboard({
795
- inputRef,
796
- value,
797
- history,
798
- completion,
799
- inline,
800
- disabled,
801
- readOnly,
802
- canSubmit,
803
- activeIndex,
804
- onActiveIndexChange,
805
- onValueChange,
806
- onSubmit,
807
- onDismiss,
808
- onRequestPalette
809
- });
108
+ function StatePanel({ title, description, action, icon }) {
810
109
  return /*#__PURE__*/ jsxs("div", {
811
- ref: anchorRef,
812
- className: "ov-tui__composer",
813
- "data-disabled": disabled || void 0,
110
+ className: "ov-file-preview__state",
111
+ role: "status",
814
112
  children: [
815
- /*#__PURE__*/ jsxs("div", {
816
- className: "ov-tui__input-row",
817
- "data-value": `${value}\n`,
818
- children: [
819
- /*#__PURE__*/ jsx("label", {
820
- className: "ov-tui__input-label",
821
- htmlFor: inputId,
822
- children: labels.placeholder
823
- }),
824
- /*#__PURE__*/ jsx(TuiInputMirror, {
825
- value: value,
826
- input: completion.activeInput,
827
- inline: inline,
828
- colorInput: completion.query.colorInput,
829
- onOpenUri: onOpenUri,
830
- onScroll: (event)=>{
831
- if (inputRef.current) {
832
- inputRef.current.scrollTop = event.currentTarget.scrollTop;
833
- inputRef.current.scrollLeft = event.currentTarget.scrollLeft;
834
- }
835
- },
836
- mirrorRef: mirrorRef,
837
- hintId: `${inputId}-hint`
838
- }),
839
- /*#__PURE__*/ jsx("textarea", {
840
- id: inputId,
841
- name: "command",
842
- ref: inputRef,
843
- className: "ov-tui__input",
844
- rows: 1,
845
- value: value,
846
- placeholder: labels.placeholder,
847
- readOnly: readOnly,
848
- disabled: disabled,
849
- autoCapitalize: "off",
850
- autoCorrect: "off",
851
- spellCheck: false,
852
- enterKeyHint: "send",
853
- onFocus: onFocus,
854
- "aria-describedby": `${inputId}-shortcuts ${inputId}-hint`,
855
- "aria-autocomplete": inline ? 'both' : 'list',
856
- "aria-haspopup": "listbox",
857
- "aria-controls": showSuggestions ? assistId : void 0,
858
- "aria-activedescendant": showSuggestions && activeIndex >= 0 ? `${assistId}-${activeIndex}` : void 0,
859
- onChange: (event)=>keyboard.change(event.target.value),
860
- onScroll: (event)=>{
861
- if (mirrorRef.current) {
862
- mirrorRef.current.scrollTop = event.currentTarget.scrollTop;
863
- mirrorRef.current.scrollLeft = event.currentTarget.scrollLeft;
864
- }
865
- },
866
- onKeyDown: keyboard.handleKeyDown
867
- })
868
- ]
869
- }),
870
- /*#__PURE__*/ jsxs("div", {
871
- className: "ov-tui__shortcuts",
872
- id: `${inputId}-shortcuts`,
873
- children: [
874
- /*#__PURE__*/ jsxs("span", {
875
- className: "ov-tui__shortcut",
876
- children: [
877
- /*#__PURE__*/ jsx("kbd", {
878
- children: "/"
879
- }),
880
- labels.allCommands
881
- ]
882
- }),
883
- /*#__PURE__*/ jsxs("span", {
884
- className: "ov-tui__shortcut",
885
- children: [
886
- /*#__PURE__*/ jsx("kbd", {
887
- children: "↑"
888
- }),
889
- labels.historyCommands
890
- ]
891
- }),
892
- /*#__PURE__*/ jsxs("span", {
893
- className: "ov-tui__shortcut",
894
- children: [
895
- /*#__PURE__*/ jsx("kbd", {
896
- children: "↵"
897
- }),
898
- labels.send
899
- ]
900
- })
901
- ]
113
+ icon,
114
+ /*#__PURE__*/ jsx("p", {
115
+ className: "ov-file-preview__state-title",
116
+ children: title
902
117
  }),
903
- disabled ? /*#__PURE__*/ jsx("div", {
904
- className: "ov-tui__unavailable",
905
- role: "status",
906
- children: labels.unavailableMessage
907
- }) : null
118
+ description ? /*#__PURE__*/ jsx("p", {
119
+ className: "ov-file-preview__state-description",
120
+ children: description
121
+ }) : null,
122
+ action
908
123
  ]
909
124
  });
910
125
  }
911
- const ZH_TUI_LABELS = {
912
- placeholder: '>_ 输入 CLI 命令,如 ov find OpenViking',
913
- unavailableMessage: '当前无法执行命令。',
914
- send: '发送',
915
- allCommands: '全部指令',
916
- historyCommands: '历史指令',
917
- suggestions: '命令建议',
918
- noSuggestions: '没有匹配的命令',
919
- pending: '执行中',
920
- success: '执行成功',
921
- error: '执行失败',
922
- copy: '复制',
923
- copied: '已复制',
924
- empty: '运行命令后将在此显示结果。',
925
- noResult: '暂无返回结果',
926
- justNow: '刚刚',
927
- formatMinutesAgo: (minutes)=>`${minutes} 分钟前`,
928
- formatHoursAgo: (hours)=>`${hours} 小时前`,
929
- availableCommands: '可用命令',
930
- helpHint: '输入 / 可打开命令面板。',
931
- parameters: '参数详情',
932
- examples: '快速体验',
933
- noParameters: '此命令不需要参数',
934
- loadMore: '加载更多',
935
- resource: '资源',
936
- parameter: '参数',
937
- required: '必填',
938
- description: '说明',
939
- defaultValue: '默认值',
940
- yes: '是',
941
- no: '否'
942
- };
943
- const DEFAULT_TUI_LABELS = {
944
- placeholder: '>_ Enter CLI commands, e.g. ov find OpenViking',
945
- unavailableMessage: 'Commands are unavailable right now.',
946
- send: 'Send',
947
- allCommands: 'All commands',
948
- historyCommands: 'History',
949
- suggestions: 'Command suggestions',
950
- noSuggestions: 'No matching commands',
951
- pending: 'Running',
952
- success: 'Succeeded',
953
- error: 'Failed',
954
- copy: 'Copy',
955
- copied: 'Copied',
956
- empty: 'Run a command to see its result here.',
957
- noResult: 'No result returned',
958
- justNow: 'Just now',
959
- formatMinutesAgo: (minutes)=>`${minutes} minute${1 === minutes ? '' : 's'} ago`,
960
- formatHoursAgo: (hours)=>`${hours} hour${1 === hours ? '' : 's'} ago`,
961
- availableCommands: 'Available commands',
962
- helpHint: 'Type / to open the command palette.',
963
- parameters: 'Parameters',
964
- examples: 'Examples',
965
- noParameters: 'No parameters',
966
- loadMore: 'Load more',
967
- resource: 'Resource',
968
- parameter: 'Parameter',
969
- required: 'Required',
970
- description: "Description",
971
- defaultValue: 'Default',
972
- yes: 'Yes',
973
- no: 'No'
974
- };
975
- function resolveTuiLabels(labels, locale = 'en') {
976
- const defaults = 'zh' === locale ? ZH_TUI_LABELS : DEFAULT_TUI_LABELS;
977
- if (!labels) return defaults;
978
- return {
979
- ...defaults,
980
- placeholder: labels.placeholder ?? defaults.placeholder,
981
- unavailableMessage: labels.unavailableMessage ?? defaults.unavailableMessage,
982
- send: labels.send ?? defaults.send,
983
- allCommands: labels.allCommands ?? defaults.allCommands,
984
- historyCommands: labels.historyCommands ?? defaults.historyCommands,
985
- suggestions: labels.suggestions ?? defaults.suggestions,
986
- noSuggestions: labels.noSuggestions ?? defaults.noSuggestions,
987
- pending: labels.pending ?? defaults.pending,
988
- success: labels.success ?? defaults.success,
989
- error: labels.error ?? defaults.error,
990
- copy: labels.copy ?? defaults.copy,
991
- copied: labels.copied ?? defaults.copied,
992
- empty: labels.empty ?? defaults.empty,
993
- noResult: labels.noResult ?? defaults.noResult,
994
- justNow: labels.justNow ?? defaults.justNow,
995
- formatMinutesAgo: labels.formatMinutesAgo ?? defaults.formatMinutesAgo,
996
- formatHoursAgo: labels.formatHoursAgo ?? defaults.formatHoursAgo,
997
- availableCommands: labels.availableCommands ?? defaults.availableCommands,
998
- helpHint: labels.helpHint ?? defaults.helpHint,
999
- parameters: labels.parameters ?? defaults.parameters,
1000
- examples: labels.examples ?? defaults.examples,
1001
- noParameters: labels.noParameters ?? defaults.noParameters,
1002
- loadMore: labels.loadMore ?? defaults.loadMore,
1003
- resource: labels.resource ?? defaults.resource,
1004
- parameter: labels.parameter ?? defaults.parameter,
1005
- required: labels.required ?? defaults.required,
1006
- description: labels.description ?? defaults.description,
1007
- defaultValue: labels.defaultValue ?? defaults.defaultValue,
1008
- yes: labels.yes ?? defaults.yes,
1009
- no: labels.no ?? defaults.no
1010
- };
1011
- }
1012
- function TuiCommandHelp({ content, onPick, labels }) {
1013
- return /*#__PURE__*/ jsxs("div", {
1014
- className: "ov-tui__help",
1015
- children: [
1016
- /*#__PURE__*/ jsx("div", {
1017
- className: "ov-tui__help-lead",
1018
- children: labels.availableCommands
1019
- }),
1020
- content.sections.map((section)=>/*#__PURE__*/ jsxs("section", {
1021
- className: "ov-tui__help-section",
1022
- children: [
1023
- /*#__PURE__*/ jsx("div", {
1024
- className: "ov-tui__help-section-label",
1025
- children: section.label
1026
- }),
1027
- /*#__PURE__*/ jsx("div", {
1028
- className: "ov-tui__help-items",
1029
- children: section.items.map((item)=>/*#__PURE__*/ jsxs("button", {
1030
- type: "button",
1031
- className: "ov-tui__help-item",
1032
- disabled: !onPick,
1033
- onClick: ()=>onPick?.(item.name),
1034
- children: [
1035
- /*#__PURE__*/ jsx("span", {
1036
- className: "ov-tui__help-name",
1037
- children: item.name
1038
- }),
1039
- item.description ? /*#__PURE__*/ jsx("span", {
1040
- className: "ov-tui__help-desc",
1041
- children: item.description
1042
- }) : null
1043
- ]
1044
- }, item.key))
1045
- })
1046
- ]
1047
- }, section.key)),
1048
- /*#__PURE__*/ jsx("div", {
1049
- className: "ov-tui__help-footer",
1050
- children: labels.helpHint
1051
- })
1052
- ]
126
+ function EmptyFileState({ text }) {
127
+ return /*#__PURE__*/ jsx(StatePanel, {
128
+ title: text.fileEmpty
1053
129
  });
1054
130
  }
1055
- function formatTuiRecordTime(record, labels, now = Date.now()) {
1056
- const timestamp = record.createdAtMs;
1057
- if ('number' != typeof timestamp || !Number.isFinite(timestamp)) return record.createdAt;
1058
- const date = new Date(timestamp);
1059
- if (!Number.isFinite(date.getTime())) return record.createdAt;
1060
- const elapsed = Math.max(0, now - timestamp);
1061
- if (elapsed < 60000) return labels.justNow;
1062
- if (elapsed < 3600000) return labels.formatMinutesAgo(Math.floor(elapsed / 60000));
1063
- if (elapsed < 86400000) return labels.formatHoursAgo(Math.floor(elapsed / 3600000));
1064
- const pad = (value)=>String(value).padStart(2, '0');
1065
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
1066
- }
1067
- const subscribe = ()=>()=>{};
1068
- const clientSnapshot = ()=>true;
1069
- const serverSnapshot = ()=>false;
1070
- function TuiRecordTime({ record, labels }) {
1071
- const client = useSyncExternalStore(subscribe, clientSnapshot, serverSnapshot);
1072
- const text = client ? formatTuiRecordTime(record, labels) : record.createdAt;
1073
- return text ? /*#__PURE__*/ jsx("span", {
1074
- className: "ov-tui__bubble-time",
1075
- children: text
1076
- }) : null;
1077
- }
1078
- function TuiCopyButton({ text, label, copiedLabel, onCopy, className }) {
1079
- const [copied, setCopied] = useState(false);
1080
- const handleCopy = ()=>{
1081
- copyTuiText(text).then((succeeded)=>{
1082
- if (!succeeded) return;
1083
- setCopied(true);
1084
- onCopy?.(text);
1085
- window.setTimeout(()=>setCopied(false), 1600);
1086
- });
1087
- };
1088
- return /*#__PURE__*/ jsx("button", {
1089
- type: "button",
1090
- className: [
1091
- 'ov-tui__copy',
1092
- className
1093
- ].filter(Boolean).join(' '),
1094
- onClick: handleCopy,
1095
- children: copied ? copiedLabel : label
131
+ function LoadingState({ text }) {
132
+ return /*#__PURE__*/ jsx(StatePanel, {
133
+ title: text.loading ?? 'Loading…',
134
+ icon: /*#__PURE__*/ jsx("span", {
135
+ className: "ov-file-preview__spinner"
136
+ })
1096
137
  });
1097
138
  }
1098
- function TuiCodeBlock({ text, labels, onCopy, onOpenUri }) {
1099
- return /*#__PURE__*/ jsxs("div", {
1100
- className: "ov-tui__code-block",
1101
- children: [
1102
- /*#__PURE__*/ jsx(TuiCopyButton, {
1103
- className: "ov-tui__copy--inline",
1104
- text: text,
1105
- label: labels.copy,
1106
- copiedLabel: labels.copied,
1107
- onCopy: onCopy
1108
- }),
1109
- /*#__PURE__*/ jsx("pre", {
1110
- className: "ov-tui__code-content",
1111
- children: /*#__PURE__*/ jsx(TuiUriText, {
1112
- text: formatTuiJson(text),
1113
- mode: "literal",
1114
- onOpenUri: onOpenUri
1115
- })
1116
- })
1117
- ]
139
+ function LoadFailedState({ text, description, onRetry }) {
140
+ return /*#__PURE__*/ jsx(StatePanel, {
141
+ title: text.loadFailed,
142
+ description: description,
143
+ action: onRetry ? /*#__PURE__*/ jsx("button", {
144
+ type: "button",
145
+ className: "ov-file-preview__button",
146
+ onClick: onRetry,
147
+ children: text.retry
148
+ }) : void 0
1118
149
  });
1119
150
  }
1120
- const TuiMarkdownContext = /*#__PURE__*/ createContext(null);
1121
- const InsideLink = /*#__PURE__*/ createContext(false);
1122
- function textOf(children) {
1123
- if ('string' == typeof children || 'number' == typeof children) return String(children);
1124
- if (Array.isArray(children)) return children.map(textOf).join('');
1125
- if (/*#__PURE__*/ isValidElement(children)) return textOf(children.props.children);
1126
- return '';
151
+ function UnsupportedState({ text, onDownload }) {
152
+ return /*#__PURE__*/ jsx(StatePanel, {
153
+ title: text.unsupported,
154
+ action: onDownload ? /*#__PURE__*/ jsx("button", {
155
+ type: "button",
156
+ className: "ov-file-preview__button",
157
+ onClick: onDownload,
158
+ children: text.download
159
+ }) : void 0
160
+ });
1127
161
  }
1128
- function LinkedText({ children }) {
1129
- const options = useContext(TuiMarkdownContext);
1130
- const insideLink = useContext(InsideLink);
1131
- return /*#__PURE__*/ jsx(Fragment, {
1132
- children: insideLink ? children : Children.map(children, (child)=>'string' == typeof child ? /*#__PURE__*/ jsx(TuiUriText, {
1133
- text: child,
1134
- mode: "prose",
1135
- onOpenUri: options.onOpenUri
1136
- }) : child)
162
+ function TruncatedNotice({ text }) {
163
+ return /*#__PURE__*/ jsx("p", {
164
+ className: "ov-file-preview__notice",
165
+ children: text.truncated
1137
166
  });
1138
167
  }
1139
- function MarkdownCode({ children }) {
1140
- const options = useContext(TuiMarkdownContext);
1141
- const insideLink = useContext(InsideLink);
1142
- return /*#__PURE__*/ jsx("code", {
1143
- children: insideLink ? children : /*#__PURE__*/ jsx(TuiUriText, {
1144
- text: textOf(children),
1145
- mode: "literal",
1146
- onOpenUri: options.onOpenUri
168
+ const Viewer = /*#__PURE__*/ lazy(async ()=>{
169
+ const { FilePreviewViewer } = await import("./file-preview-viewer~0.js");
170
+ return {
171
+ default: FilePreviewViewer
172
+ };
173
+ });
174
+ function LazyFilePreviewViewer(props) {
175
+ return /*#__PURE__*/ jsx(Suspense, {
176
+ fallback: /*#__PURE__*/ jsx(LoadingState, {
177
+ text: props.text
178
+ }),
179
+ children: /*#__PURE__*/ jsx(Viewer, {
180
+ ...props
1147
181
  })
1148
182
  });
1149
183
  }
1150
- function MarkdownPre({ children }) {
1151
- const options = useContext(TuiMarkdownContext);
1152
- return /*#__PURE__*/ jsx(TuiCodeBlock, {
1153
- text: textOf(children).replace(/\n$/, ''),
1154
- ...options
184
+ function FilePreviewBody({ fileTab, text, locale, viewMode, editing, onViewModeChange, onHandleChange, onViewModesChange, onDownload, onLinkClick, onCancelEditing }) {
185
+ if (editing.editing || editing.loadingEdit || editing.editFailed) return /*#__PURE__*/ jsx(FilePreviewEditor, {
186
+ draft: editing.draft,
187
+ text: text,
188
+ loading: editing.loadingEdit,
189
+ failed: editing.editFailed,
190
+ saveFailed: editing.saveFailed,
191
+ saving: editing.saving,
192
+ onChange: editing.updateDraft,
193
+ onCancel: onCancelEditing
194
+ });
195
+ if (fileTab.loading) return /*#__PURE__*/ jsx(LoadingState, {
196
+ text: text
197
+ });
198
+ if (isEmptyFile(fileTab.source)) return /*#__PURE__*/ jsx(EmptyFileState, {
199
+ text: text
1155
200
  });
201
+ return /*#__PURE__*/ jsx(FilePreviewImageFrame, {
202
+ children: /*#__PURE__*/ jsx(LazyFilePreviewViewer, {
203
+ tab: fileTab,
204
+ text: text,
205
+ locale: locale,
206
+ viewMode: viewMode,
207
+ onViewModeChange: onViewModeChange,
208
+ onDownload: onDownload,
209
+ onHandleChange: onHandleChange,
210
+ onViewModesChange: onViewModesChange,
211
+ onLinkClick: onLinkClick
212
+ })
213
+ }, JSON.stringify([
214
+ fileTab.source.path,
215
+ fileTab.source.fileName,
216
+ fileTab.source.url
217
+ ]));
1156
218
  }
1157
- function MarkdownLink({ children, href, title }) {
1158
- const { onOpenUri } = useContext(TuiMarkdownContext);
1159
- const isViking = /^viking:/i.test(href ?? '');
1160
- const target = isViking ? getTuiSafeHref(href) : href;
1161
- return /*#__PURE__*/ jsx(InsideLink.Provider, {
1162
- value: true,
1163
- children: target ? /*#__PURE__*/ jsx("a", {
1164
- href: target,
1165
- title: title,
1166
- className: isViking ? 'ov-tui__link' : void 0,
1167
- target: isViking ? void 0 : '_blank',
1168
- rel: isViking ? void 0 : 'noreferrer',
1169
- onClick: isViking && onOpenUri ? (event)=>{
1170
- event.preventDefault();
1171
- onOpenUri(target);
1172
- } : void 0,
1173
- children: children
1174
- }) : children
219
+ function FilePreviewLevelTabs({ levels, activeKey, disabled, onSelect }) {
220
+ if (!levels?.length) return null;
221
+ return /*#__PURE__*/ jsx("div", {
222
+ className: "ov-file-preview__levels ov-file-preview__levels--file",
223
+ role: "group",
224
+ children: levels.map((level)=>/*#__PURE__*/ jsx("button", {
225
+ type: "button",
226
+ className: "ov-file-preview__level",
227
+ "aria-pressed": level.key === activeKey,
228
+ disabled: disabled || level.disabled,
229
+ onClick: ()=>onSelect(level.key),
230
+ children: level.label
231
+ }, level.key))
1175
232
  });
1176
233
  }
1177
- const TUI_MARKDOWN_COMPONENTS = {
1178
- p: ({ children })=>/*#__PURE__*/ jsx("p", {
1179
- children: /*#__PURE__*/ jsx(LinkedText, {
1180
- children: children
1181
- })
1182
- }),
1183
- li: ({ children })=>/*#__PURE__*/ jsx("li", {
1184
- children: /*#__PURE__*/ jsx(LinkedText, {
1185
- children: children
1186
- })
1187
- }),
1188
- h1: ({ children })=>/*#__PURE__*/ jsx("h1", {
1189
- children: /*#__PURE__*/ jsx(LinkedText, {
1190
- children: children
1191
- })
1192
- }),
1193
- h2: ({ children })=>/*#__PURE__*/ jsx("h2", {
1194
- children: /*#__PURE__*/ jsx(LinkedText, {
1195
- children: children
1196
- })
1197
- }),
1198
- h3: ({ children })=>/*#__PURE__*/ jsx("h3", {
1199
- children: /*#__PURE__*/ jsx(LinkedText, {
1200
- children: children
1201
- })
1202
- }),
1203
- h4: ({ children })=>/*#__PURE__*/ jsx("h4", {
1204
- children: /*#__PURE__*/ jsx(LinkedText, {
1205
- children: children
1206
- })
1207
- }),
1208
- h5: ({ children })=>/*#__PURE__*/ jsx("h5", {
1209
- children: /*#__PURE__*/ jsx(LinkedText, {
1210
- children: children
1211
- })
1212
- }),
1213
- h6: ({ children })=>/*#__PURE__*/ jsx("h6", {
1214
- children: /*#__PURE__*/ jsx(LinkedText, {
1215
- children: children
1216
- })
1217
- }),
1218
- strong: ({ children })=>/*#__PURE__*/ jsx("strong", {
1219
- children: /*#__PURE__*/ jsx(LinkedText, {
1220
- children: children
1221
- })
1222
- }),
1223
- em: ({ children })=>/*#__PURE__*/ jsx("em", {
1224
- children: /*#__PURE__*/ jsx(LinkedText, {
1225
- children: children
1226
- })
1227
- }),
1228
- del: ({ children })=>/*#__PURE__*/ jsx("del", {
1229
- children: /*#__PURE__*/ jsx(LinkedText, {
1230
- children: children
1231
- })
1232
- }),
1233
- td: ({ children, style })=>/*#__PURE__*/ jsx("td", {
1234
- style: style,
1235
- children: /*#__PURE__*/ jsx(LinkedText, {
1236
- children: children
1237
- })
1238
- }),
1239
- th: ({ children, style })=>/*#__PURE__*/ jsx("th", {
1240
- style: style,
1241
- children: /*#__PURE__*/ jsx(LinkedText, {
1242
- children: children
1243
- })
234
+ function FilePreviewBreadcrumbMenu({ hiddenPathLabel, segments, canOpen, disabled, onOpen }) {
235
+ return /*#__PURE__*/ jsx(Tooltip, {
236
+ trigger: "click",
237
+ placement: "bottom",
238
+ className: "ov-file-preview__breadcrumb-menu-surface",
239
+ content: /*#__PURE__*/ jsx("ul", {
240
+ className: "ov-file-preview__breadcrumb-menu",
241
+ role: "menu",
242
+ children: segments.map((segment)=>/*#__PURE__*/ jsx("li", {
243
+ role: "none",
244
+ children: /*#__PURE__*/ jsxs("button", {
245
+ type: "button",
246
+ role: "menuitem",
247
+ disabled: !canOpen || disabled,
248
+ onClick: ()=>onOpen(segment.uri),
249
+ children: [
250
+ segment.label,
251
+ "/"
252
+ ]
253
+ })
254
+ }, segment.uri))
1244
255
  }),
1245
- code: MarkdownCode,
1246
- pre: MarkdownPre,
1247
- a: MarkdownLink,
1248
- table: ({ children })=>/*#__PURE__*/ jsx("div", {
1249
- className: "ov-tui__table-frame",
1250
- children: /*#__PURE__*/ jsx("table", {
1251
- children: children
1252
- })
1253
- })
1254
- };
1255
- function TuiMarkdown({ text, ...options }) {
1256
- return /*#__PURE__*/ jsx(TuiMarkdownContext.Provider, {
1257
- value: options,
1258
- children: /*#__PURE__*/ jsx("div", {
1259
- className: "ov-tui__markdown",
1260
- children: /*#__PURE__*/ jsx(react_markdown, {
1261
- remarkPlugins: [
1262
- [
1263
- remark_gfm,
1264
- {
1265
- singleTilde: false
1266
- }
1267
- ]
1268
- ],
1269
- urlTransform: (url)=>/^viking:/i.test(url) ? getTuiSafeHref(url) ?? '' : defaultUrlTransform(url),
1270
- components: TUI_MARKDOWN_COMPONENTS,
1271
- children: text
1272
- })
256
+ children: /*#__PURE__*/ jsx("button", {
257
+ type: "button",
258
+ className: "ov-file-preview__breadcrumb-more",
259
+ "aria-label": hiddenPathLabel,
260
+ "aria-haspopup": "menu",
261
+ children: "…"
1273
262
  })
1274
263
  });
1275
264
  }
1276
- function getVisibleTuiResultTitle(title, command) {
1277
- if (!title || !command) return title;
1278
- const candidate = title.trim().replace(/^\*\*([\s\S]*)\*\*$/, '$1').replace(/^\/+/, '').trim();
1279
- const match = matchTuiCommandInput(candidate, [
1280
- command
1281
- ]);
1282
- return match && !match.rest.trim() ? void 0 : title;
1283
- }
1284
- function getTuiResultText(text, command) {
1285
- const heading = text.match(/^\s*(\*\*\/?[^\r\n]+\*\*|\/[^\r\n]+)\r?\n(?:[ \t]*\r?\n)?/);
1286
- return heading && void 0 === getVisibleTuiResultTitle(heading[1], command) ? text.slice(heading[0].length) : text;
265
+ const MAX_ANCESTOR_WIDTH = 160;
266
+ const OVERFLOW_BUTTON_WIDTH = 36;
267
+ const SHIFT_DURATION = 160;
268
+ function prefersReducedMotion() {
269
+ return "u" > typeof window && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1287
270
  }
1288
- function isTuiJson(text) {
1289
- const first = text.trimStart().charAt(0);
1290
- if ('{' !== first && '[' !== first) return false;
1291
- try {
1292
- JSON.parse(text);
1293
- return true;
1294
- } catch {
1295
- return false;
1296
- }
271
+ function measureAvailable(root) {
272
+ const row = root.parentElement;
273
+ if (!row) return root.clientWidth;
274
+ const style = window.getComputedStyle(row);
275
+ const gap = Number.parseFloat(style.columnGap) || 0;
276
+ const siblings = Array.from(row.children).filter((child)=>child !== root && child instanceof HTMLElement);
277
+ const siblingWidth = siblings.reduce((sum, child)=>sum + child.getBoundingClientRect().width, 0);
278
+ const siblingGaps = gap * siblings.length;
279
+ return Math.max(0, row.clientWidth - siblingWidth - siblingGaps);
1297
280
  }
1298
- function TuiResourcePreview({ content, onOpenUri, onCopy, labels }) {
1299
- const metaLabel = content.meta?.filter(Boolean).join(' · ') || content.description;
1300
- const target = content.navigationUri ?? content.uri;
281
+ function FilePreviewBreadcrumb({ hiddenPathLabel, segments, title, canOpen, disabled, onOpen }) {
282
+ const rootRef = useRef(null);
283
+ const measureRef = useRef(null);
284
+ const currentRef = useRef(null);
285
+ const previousRef = useRef(void 0);
286
+ const [hiddenCount, setHiddenCount] = useState(0);
287
+ const [rootHidden, setRootHidden] = useState(false);
288
+ const [overflowing, setOverflowing] = useState(false);
289
+ const signature = JSON.stringify([
290
+ segments.map((s)=>s.uri),
291
+ title
292
+ ]);
293
+ useLayoutEffect(()=>{
294
+ const root = rootRef.current;
295
+ const measure = measureRef.current;
296
+ if (!root || !measure) return;
297
+ const run = ()=>{
298
+ const widths = Array.from(measure.children).map((child)=>child.getBoundingClientRect().width);
299
+ const available = measureAvailable(root);
300
+ const isOverflowing = widths.reduce((sum, width)=>sum + width, 0) > available;
301
+ const capped = isOverflowing ? widths.map((width, index)=>index < widths.length - 1 ? Math.min(width, MAX_ANCESTOR_WIDTH) : width) : widths;
302
+ const ancestors = capped.slice(0, -1);
303
+ const trailing = capped[capped.length - 1] ?? 0;
304
+ const total = capped.reduce((sum, width)=>sum + width, 0);
305
+ setOverflowing(isOverflowing);
306
+ let count = 0;
307
+ let used = total;
308
+ while(used > available && count < ancestors.length - 1){
309
+ used -= ancestors[count + 1] ?? 0;
310
+ if (0 === count) used += OVERFLOW_BUTTON_WIDTH;
311
+ count += 1;
312
+ }
313
+ let hideRoot = false;
314
+ if (used > available && ancestors.length > 0) {
315
+ const rootCollapsed = OVERFLOW_BUTTON_WIDTH + trailing;
316
+ hideRoot = rootCollapsed < used;
317
+ }
318
+ setHiddenCount(hideRoot ? ancestors.length : count);
319
+ setRootHidden(hideRoot);
320
+ };
321
+ run();
322
+ const observer = new ResizeObserver(run);
323
+ observer.observe(root);
324
+ observer.observe(measure);
325
+ if (root.parentElement) observer.observe(root.parentElement);
326
+ return ()=>observer.disconnect();
327
+ }, [
328
+ segments,
329
+ title
330
+ ]);
331
+ useLayoutEffect(()=>{
332
+ const current = currentRef.current;
333
+ const previous = previousRef.current;
334
+ if (!current) return;
335
+ const left = current.offsetLeft;
336
+ previousRef.current = {
337
+ signature,
338
+ left
339
+ };
340
+ if (!previous || previous.signature !== signature || previous.left === left || prefersReducedMotion()) return;
341
+ const animation = current.animate([
342
+ {
343
+ transform: `translateX(${previous.left - left}px)`,
344
+ opacity: 0.65
345
+ },
346
+ {
347
+ transform: 'translateX(0)',
348
+ opacity: 1
349
+ }
350
+ ], {
351
+ duration: SHIFT_DURATION,
352
+ easing: 'cubic-bezier(0.2, 0, 0, 1)'
353
+ });
354
+ return ()=>animation.cancel();
355
+ }, [
356
+ hiddenCount,
357
+ rootHidden,
358
+ signature
359
+ ]);
360
+ const hiddenSegments = segments.filter((_, index)=>rootHidden ? index < hiddenCount : index > 0 && index <= hiddenCount);
361
+ const menu = hiddenSegments.length ? /*#__PURE__*/ jsx(FilePreviewBreadcrumbMenu, {
362
+ hiddenPathLabel: hiddenPathLabel,
363
+ segments: hiddenSegments,
364
+ canOpen: canOpen,
365
+ disabled: disabled,
366
+ onOpen: onOpen
367
+ }) : null;
1301
368
  return /*#__PURE__*/ jsxs("div", {
1302
- className: "ov-tui__preview",
369
+ className: [
370
+ 'ov-file-preview__breadcrumb',
371
+ overflowing ? 'ov-file-preview__breadcrumb--overflowing' : ''
372
+ ].filter(Boolean).join(' '),
373
+ ref: rootRef,
1303
374
  children: [
1304
- metaLabel ? /*#__PURE__*/ jsx("div", {
1305
- className: "ov-tui__result-meta",
1306
- children: /*#__PURE__*/ jsx(TuiUriText, {
1307
- text: metaLabel,
1308
- mode: "prose",
1309
- onOpenUri: onOpenUri
1310
- })
1311
- }) : null,
1312
- content.title ? /*#__PURE__*/ jsx("div", {
1313
- className: "ov-tui__card-title",
1314
- children: /*#__PURE__*/ jsx(TuiUriText, {
1315
- text: content.title,
1316
- mode: "literal",
1317
- onOpenUri: onOpenUri
1318
- })
1319
- }) : null,
1320
375
  /*#__PURE__*/ jsxs("div", {
1321
- className: "ov-tui__preview-body",
376
+ className: "ov-file-preview__breadcrumb-measure",
377
+ ref: measureRef,
378
+ "aria-hidden": "true",
1322
379
  children: [
1323
- content.uri ? /*#__PURE__*/ jsx(TuiUriLink, {
1324
- uri: target,
1325
- onOpenUri: onOpenUri,
1326
- className: "ov-tui__link ov-tui__preview-uri",
1327
- children: content.uri
1328
- }) : null,
1329
- isTuiJson(content.content) ? /*#__PURE__*/ jsx(TuiCodeBlock, {
1330
- text: content.content,
1331
- labels: labels,
1332
- onCopy: onCopy,
1333
- onOpenUri: onOpenUri
1334
- }) : /*#__PURE__*/ jsx("pre", {
1335
- className: "ov-tui__preview-content",
1336
- children: /*#__PURE__*/ jsx(TuiUriText, {
1337
- text: content.content,
1338
- mode: "literal",
1339
- onOpenUri: onOpenUri
1340
- })
380
+ segments.map((segment)=>/*#__PURE__*/ jsxs("span", {
381
+ className: "ov-file-preview__breadcrumb-part",
382
+ children: [
383
+ /*#__PURE__*/ jsx("span", {
384
+ children: segment.isFirst ? `${segment.uri}` : segment.label
385
+ }),
386
+ /*#__PURE__*/ jsx("span", {
387
+ className: "ov-file-preview__breadcrumb-separator",
388
+ children: "/"
389
+ })
390
+ ]
391
+ }, segment.uri)),
392
+ /*#__PURE__*/ jsx("span", {
393
+ children: title
1341
394
  })
1342
395
  ]
1343
- })
1344
- ]
1345
- });
1346
- }
1347
- const PAGE_SIZE = 20;
1348
- function TreeIdentity({ item, onOpenUri }) {
1349
- return /*#__PURE__*/ jsxs(Fragment, {
1350
- children: [
1351
- /*#__PURE__*/ jsx("span", {
1352
- className: "ov-tui__tree-title",
1353
- children: item.uri ? /*#__PURE__*/ jsx(TuiUriLink, {
1354
- uri: item.uri,
1355
- onOpenUri: onOpenUri,
1356
- children: item.title
1357
- }) : /*#__PURE__*/ jsx(TuiUriText, {
1358
- text: item.title,
1359
- mode: "literal",
1360
- onOpenUri: onOpenUri
1361
- })
1362
396
  }),
1363
- item.metaLabel ? /*#__PURE__*/ jsx("span", {
1364
- className: "ov-tui__tree-meta",
1365
- children: /*#__PURE__*/ jsx(TuiUriText, {
1366
- text: item.metaLabel,
1367
- mode: "prose",
1368
- onOpenUri: onOpenUri
1369
- })
1370
- }) : null
1371
- ]
1372
- });
1373
- }
1374
- function TuiResourceTree({ content, onOpenUri, loadMoreLabel, noResultLabel, resourceLabel }) {
1375
- const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
1376
- const columns = Array.from(new Set(content.items.flatMap((item)=>item.detailRows?.map((row)=>row.label) ?? [])));
1377
- const visibleItems = content.items.slice(0, visibleCount);
1378
- const hiddenCount = content.items.length - visibleItems.length;
1379
- return /*#__PURE__*/ jsxs("div", {
1380
- className: "ov-tui__tree",
1381
- children: [
1382
- content.title ? /*#__PURE__*/ jsx("div", {
1383
- className: "ov-tui__card-title",
1384
- children: /*#__PURE__*/ jsx(TuiUriText, {
1385
- text: content.title,
1386
- mode: "literal",
1387
- onOpenUri: onOpenUri
1388
- })
397
+ rootHidden && menu ? /*#__PURE__*/ jsxs("span", {
398
+ className: "ov-file-preview__breadcrumb-part",
399
+ children: [
400
+ menu,
401
+ /*#__PURE__*/ jsx("span", {
402
+ className: "ov-file-preview__breadcrumb-separator",
403
+ children: "/"
404
+ })
405
+ ]
1389
406
  }) : null,
1390
- content.items.length ? null : /*#__PURE__*/ jsx("div", {
1391
- className: "ov-tui__no-result",
1392
- children: noResultLabel
1393
- }),
1394
- columns.length ? /*#__PURE__*/ jsx("div", {
1395
- className: "ov-tui__table-frame",
1396
- children: /*#__PURE__*/ jsxs("table", {
1397
- className: "ov-tui__table",
407
+ segments.map((segment, index)=>{
408
+ if (hiddenSegments.includes(segment)) return null;
409
+ return /*#__PURE__*/ jsxs(external_react_Fragment, {
1398
410
  children: [
1399
- /*#__PURE__*/ jsx("thead", {
1400
- children: /*#__PURE__*/ jsxs("tr", {
1401
- children: [
1402
- /*#__PURE__*/ jsx("th", {
1403
- scope: "col",
1404
- children: resourceLabel
1405
- }),
1406
- columns.map((column)=>/*#__PURE__*/ jsx("th", {
1407
- scope: "col",
1408
- children: column
1409
- }, column))
1410
- ]
1411
- })
411
+ /*#__PURE__*/ jsxs("span", {
412
+ className: "ov-file-preview__breadcrumb-part",
413
+ children: [
414
+ /*#__PURE__*/ jsx("button", {
415
+ type: "button",
416
+ className: "ov-file-preview__breadcrumb-link",
417
+ disabled: !canOpen || disabled,
418
+ title: segment.uri,
419
+ onClick: ()=>onOpen(segment.uri),
420
+ children: segment.isFirst ? `${segment.uri}` : segment.label
421
+ }),
422
+ /*#__PURE__*/ jsx("span", {
423
+ className: "ov-file-preview__breadcrumb-separator",
424
+ children: "/"
425
+ })
426
+ ]
1412
427
  }),
1413
- /*#__PURE__*/ jsx("tbody", {
1414
- children: visibleItems.map((item)=>/*#__PURE__*/ jsxs("tr", {
1415
- children: [
1416
- /*#__PURE__*/ jsx("td", {
1417
- children: /*#__PURE__*/ jsx(TreeIdentity, {
1418
- item: item,
1419
- onOpenUri: onOpenUri
1420
- })
1421
- }),
1422
- columns.map((column)=>{
1423
- const value = item.detailRows?.find((row)=>row.label === column)?.value ?? '';
1424
- return /*#__PURE__*/ jsx("td", {
1425
- children: /*#__PURE__*/ jsx(TuiUriText, {
1426
- text: value,
1427
- mode: "literal",
1428
- onOpenUri: onOpenUri
1429
- })
1430
- }, column);
1431
- })
1432
- ]
1433
- }, item.id))
1434
- })
428
+ 0 === index && !rootHidden && menu ? /*#__PURE__*/ jsxs("span", {
429
+ className: "ov-file-preview__breadcrumb-part",
430
+ children: [
431
+ menu,
432
+ /*#__PURE__*/ jsx("span", {
433
+ className: "ov-file-preview__breadcrumb-separator",
434
+ children: "/"
435
+ })
436
+ ]
437
+ }) : null
1435
438
  ]
1436
- })
1437
- }) : /*#__PURE__*/ jsx("div", {
1438
- className: "ov-tui__tree-items",
1439
- children: visibleItems.map((item)=>/*#__PURE__*/ jsx("div", {
1440
- className: "ov-tui__tree-item",
1441
- children: /*#__PURE__*/ jsx(TreeIdentity, {
1442
- item: item,
1443
- onOpenUri: onOpenUri
1444
- })
1445
- }, item.id))
1446
- }),
1447
- hiddenCount > 0 ? /*#__PURE__*/ jsxs("button", {
1448
- type: "button",
1449
- className: "ov-tui__load-more",
1450
- onClick: ()=>setVisibleCount((count)=>count + PAGE_SIZE),
1451
- children: [
1452
- loadMoreLabel,
1453
- " (",
1454
- hiddenCount,
1455
- ")"
1456
- ]
1457
- }) : null
1458
- ]
1459
- });
1460
- }
1461
- function TuiResultHeader({ command, status, label }) {
1462
- return /*#__PURE__*/ jsxs("header", {
1463
- className: "ov-tui__result-head",
1464
- children: [
1465
- /*#__PURE__*/ jsx("span", {
1466
- className: "ov-tui__result-state",
1467
- "data-status": status,
1468
- role: "img",
1469
- "aria-label": label
439
+ }, segment.uri);
1470
440
  }),
1471
441
  /*#__PURE__*/ jsx("span", {
1472
- className: "ov-tui__result-command",
1473
- children: command
442
+ ref: currentRef,
443
+ className: "ov-file-preview__breadcrumb-current",
444
+ "aria-current": "page",
445
+ title: title,
446
+ children: title
1474
447
  })
1475
448
  ]
1476
449
  });
1477
450
  }
1478
- function ScoreDetails({ item }) {
1479
- const [hovered, setHovered] = useState(false);
1480
- const [pinned, setPinned] = useState(false);
1481
- if (!item.scoreDetails) return /*#__PURE__*/ jsx("span", {
1482
- className: "ov-tui__search-score",
1483
- children: item.scoreLabel
1484
- });
1485
- const open = hovered || pinned;
1486
- return /*#__PURE__*/ jsxs("span", {
1487
- className: "ov-tui__search-score-wrap",
1488
- children: [
1489
- /*#__PURE__*/ jsx("button", {
1490
- type: "button",
1491
- className: "ov-tui__search-score",
1492
- "aria-expanded": open,
1493
- onClick: ()=>setPinned((current)=>!current),
1494
- onMouseEnter: ()=>setHovered(true),
1495
- onMouseLeave: ()=>setHovered(false),
1496
- onFocus: ()=>setHovered(true),
1497
- onBlur: ()=>{
1498
- setHovered(false);
1499
- setPinned(false);
1500
- },
1501
- children: item.scoreLabel
1502
- }),
1503
- open ? /*#__PURE__*/ jsx("span", {
1504
- role: "tooltip",
1505
- className: "ov-tui__score-details",
1506
- children: item.scoreDetails
1507
- }) : null
1508
- ]
451
+ function FilePreviewCopyPath({ label, disabled, onClick }) {
452
+ return /*#__PURE__*/ jsx(Tooltip, {
453
+ content: label,
454
+ children: /*#__PURE__*/ jsx("button", {
455
+ type: "button",
456
+ className: "ov-file-preview__action",
457
+ "aria-label": label,
458
+ disabled: disabled,
459
+ onClick: onClick,
460
+ children: /*#__PURE__*/ jsx("svg", {
461
+ width: "14",
462
+ height: "14",
463
+ viewBox: "0 0 14 14",
464
+ fill: "none",
465
+ stroke: "currentColor",
466
+ strokeWidth: "1.2",
467
+ strokeLinecap: "round",
468
+ strokeLinejoin: "round",
469
+ "aria-hidden": "true",
470
+ children: /*#__PURE__*/ jsx("path", {
471
+ d: "M4.667 4.667V3.033c0-.653 0-.98.127-1.229a1.167 1.167 0 0 1 .51-.51c.25-.127.576-.127 1.23-.127h4.433c.653 0 .98 0 1.229.127.22.112.398.29.51.51.127.25.127.576.127 1.23v4.433c0 .653 0 .98-.127 1.229a1.167 1.167 0 0 1-.51.51c-.25.127-.576.127-1.23.127H9.333M3.033 12.833h4.434c.653 0 .98 0 1.229-.127.22-.112.398-.29.51-.51.127-.25.127-.576.127-1.229V6.533c0-.653 0-.98-.127-1.229a1.167 1.167 0 0 0-.51-.51c-.25-.127-.576-.127-1.23-.127H3.034c-.653 0-.98 0-1.23.127a1.167 1.167 0 0 0-.51.51c-.126.25-.126.576-.126 1.23v4.433c0 .653 0 .98.127 1.229.112.22.29.398.51.51.25.127.576.127 1.23.127Z"
472
+ })
473
+ })
474
+ })
1509
475
  });
1510
476
  }
1511
- function ResourceUri({ text }) {
1512
- const boundary = text.lastIndexOf('/');
1513
- return boundary > 9 ? /*#__PURE__*/ jsxs("span", {
1514
- className: "ov-tui__search-uri",
1515
- children: [
1516
- /*#__PURE__*/ jsx("span", {
1517
- className: "ov-tui__search-uri-prefix",
1518
- children: text.slice(0, boundary + 1)
1519
- }),
1520
- /*#__PURE__*/ jsx("span", {
1521
- className: "ov-tui__search-uri-suffix",
1522
- children: text.slice(boundary + 1)
1523
- })
1524
- ]
1525
- }) : /*#__PURE__*/ jsx("span", {
1526
- className: "ov-tui__search-uri-full",
1527
- children: text
477
+ const SEPARATOR = '/';
478
+ function splitPathSegments(path) {
479
+ const trimmed = path?.trim();
480
+ if (!trimmed) return [];
481
+ const schemeIndex = trimmed.indexOf('://');
482
+ const scheme = schemeIndex >= 0 ? trimmed.slice(0, schemeIndex + 3) : '';
483
+ const body = schemeIndex >= 0 ? trimmed.slice(schemeIndex + 3) : trimmed;
484
+ const parts = body.split(SEPARATOR).filter(Boolean);
485
+ if (parts.length <= 1) return [];
486
+ const root = '' === scheme && body.startsWith(SEPARATOR) ? SEPARATOR : '';
487
+ const segments = [];
488
+ let uri = scheme;
489
+ parts.slice(0, -1).forEach((part, index)=>{
490
+ uri = 0 === index ? `${scheme}${root}${part}` : `${uri}${SEPARATOR}${part}`;
491
+ segments.push({
492
+ label: part,
493
+ uri,
494
+ isFirst: 0 === index
495
+ });
1528
496
  });
497
+ return segments;
498
+ }
499
+ function getPathFileName(path) {
500
+ const trimmed = path?.trim();
501
+ if (!trimmed) return;
502
+ const parts = trimmed.split(SEPARATOR).filter(Boolean);
503
+ return parts[parts.length - 1];
1529
504
  }
1530
- function SearchRow({ item, onOpenUri }) {
505
+ function FilePreviewHeader({ levels, levelKey, onLevelChange, path, fileName, canOpenParent, titleExtra, slotActions, actions, text, onOpenParent }) {
506
+ const segments = splitPathSegments(path);
507
+ const title = getPathFileName(path) ?? fileName;
508
+ const actionNode = 'function' == typeof slotActions ? slotActions(actions) : slotActions;
1531
509
  return /*#__PURE__*/ jsxs(Fragment, {
1532
510
  children: [
1533
- item.scoreLabel || item.typeLabel ? /*#__PURE__*/ jsxs("span", {
1534
- className: "ov-tui__search-metrics",
1535
- children: [
1536
- item.scoreLabel ? /*#__PURE__*/ jsx(ScoreDetails, {
1537
- item: item
1538
- }) : null,
1539
- item.typeLabel ? /*#__PURE__*/ jsx("span", {
1540
- className: "ov-tui__search-type",
1541
- children: item.typeLabel
1542
- }) : null
1543
- ]
1544
- }) : null,
1545
- /*#__PURE__*/ jsxs("span", {
1546
- className: "ov-tui__search-body",
511
+ /*#__PURE__*/ jsxs("div", {
512
+ className: "ov-file-preview__header",
1547
513
  children: [
1548
- /*#__PURE__*/ jsxs("span", {
1549
- className: "ov-tui__search-main",
514
+ /*#__PURE__*/ jsxs("div", {
515
+ className: "ov-file-preview__path-row",
1550
516
  children: [
1551
- /*#__PURE__*/ jsx("span", {
1552
- className: "ov-tui__search-title",
1553
- title: item.uri ?? item.title,
1554
- children: item.uri ? /*#__PURE__*/ jsx(TuiUriLink, {
1555
- uri: item.uri,
1556
- onOpenUri: onOpenUri,
1557
- className: "ov-tui__link ov-tui__search-open",
1558
- children: /*#__PURE__*/ jsx(ResourceUri, {
1559
- text: item.uri
1560
- })
1561
- }) : /*#__PURE__*/ jsx(TuiUriText, {
1562
- text: item.title,
1563
- mode: "literal",
1564
- onOpenUri: onOpenUri
1565
- })
517
+ /*#__PURE__*/ jsx(FilePreviewBreadcrumb, {
518
+ hiddenPathLabel: text.showHiddenPath,
519
+ segments: segments,
520
+ title: title,
521
+ canOpen: canOpenParent,
522
+ disabled: actions.busy,
523
+ onOpen: onOpenParent
1566
524
  }),
1567
- item.levelLabel ? /*#__PURE__*/ jsx("span", {
1568
- className: "ov-tui__search-level",
1569
- children: item.levelLabel
1570
- }) : null
525
+ actions.canCopyPath ? /*#__PURE__*/ jsx(FilePreviewCopyPath, {
526
+ label: text.copyPath,
527
+ disabled: actions.busy,
528
+ onClick: ()=>void actions.copyPath()
529
+ }) : null,
530
+ titleExtra
1571
531
  ]
1572
532
  }),
1573
- item.preview ? /*#__PURE__*/ jsx("span", {
1574
- className: "ov-tui__search-preview",
1575
- children: /*#__PURE__*/ jsx(TuiUriText, {
1576
- text: item.preview,
1577
- mode: "prose",
1578
- onOpenUri: onOpenUri
1579
- })
1580
- }) : null,
1581
- item.metaLabel ? /*#__PURE__*/ jsx("span", {
1582
- className: "ov-tui__search-meta",
1583
- children: /*#__PURE__*/ jsx(TuiUriText, {
1584
- text: item.metaLabel,
1585
- mode: "prose",
1586
- onOpenUri: onOpenUri
1587
- })
533
+ actionNode ? /*#__PURE__*/ jsx("div", {
534
+ className: "ov-file-preview__header-actions",
535
+ children: actionNode
1588
536
  }) : null
1589
537
  ]
1590
- })
538
+ }),
539
+ onLevelChange ? /*#__PURE__*/ jsx(FilePreviewLevelTabs, {
540
+ levels: levels,
541
+ activeKey: levelKey,
542
+ disabled: actions.busy,
543
+ onSelect: onLevelChange
544
+ }) : null
1591
545
  ]
1592
546
  });
1593
547
  }
1594
- function TuiSearchList({ content, onOpenUri, noResultLabel }) {
1595
- return /*#__PURE__*/ jsxs("div", {
1596
- className: "ov-tui__search",
548
+ function levelToFileTab(tab, key) {
549
+ const level = tab.levels.find((candidate)=>candidate.key === key);
550
+ if (!level) return;
551
+ return {
552
+ kind: 'file',
553
+ key: `${tab.key}::${level.key}`,
554
+ source: level.source,
555
+ resources: level.resources ?? tab.resources,
556
+ resolveImage: tab.resolveImage,
557
+ operations: tab.operations,
558
+ slots: tab.slots
559
+ };
560
+ }
561
+ function FilePreviewDirectoryBody({ tab, text, locale, actions, editing, viewMode, onViewModeChange, onHandleChange, onViewModesChange, onDownload, onCancelEditing }) {
562
+ const [internalKey, setInternalKey] = useState(()=>tab.defaultLevelKey ?? tab.levels[0].key);
563
+ const activeKey = tab.activeLevelKey ?? internalKey;
564
+ const fileTab = levelToFileTab(tab, activeKey) ?? levelToFileTab(tab, tab.levels[0].key);
565
+ const tabsRef = useRef(null);
566
+ const [indicator, setIndicator] = useState({
567
+ left: 0,
568
+ width: 0
569
+ });
570
+ useLayoutEffect(()=>{
571
+ const root = tabsRef.current;
572
+ if (!root) return;
573
+ const measure = ()=>{
574
+ const active = root.querySelector('[aria-selected="true"]');
575
+ if (!active) return;
576
+ setIndicator({
577
+ left: active.offsetLeft,
578
+ width: active.offsetWidth
579
+ });
580
+ };
581
+ measure();
582
+ const observer = new ResizeObserver(measure);
583
+ observer.observe(root);
584
+ return ()=>observer.disconnect();
585
+ }, [
586
+ activeKey,
587
+ tab.levels
588
+ ]);
589
+ const changeLevel = (key)=>{
590
+ if (void 0 === tab.activeLevelKey) setInternalKey(key);
591
+ tab.onActiveLevelChange?.(key);
592
+ };
593
+ const onLevelKeyDown = (event)=>{
594
+ const keys = [
595
+ 'ArrowLeft',
596
+ 'ArrowRight',
597
+ 'Home',
598
+ 'End'
599
+ ];
600
+ if (!keys.includes(event.key)) return;
601
+ const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]:not(:disabled)'));
602
+ if (0 === buttons.length) return;
603
+ const current = buttons.indexOf(event.target);
604
+ const next = 'Home' === event.key ? 0 : 'End' === event.key ? buttons.length - 1 : (current + ('ArrowRight' === event.key ? 1 : -1) + buttons.length) % buttons.length;
605
+ const target = buttons[next];
606
+ if (!target) return;
607
+ event.preventDefault();
608
+ target.focus();
609
+ const key = tab.levels[next]?.key;
610
+ if (void 0 !== key) changeLevel(key);
611
+ };
612
+ if (!fileTab) return null;
613
+ if (tab.loading) return /*#__PURE__*/ jsxs(Fragment, {
1597
614
  children: [
1598
- content.summary ? /*#__PURE__*/ jsx("div", {
1599
- className: "ov-tui__search-summary",
1600
- children: 'string' == typeof content.summary ? /*#__PURE__*/ jsx(TuiUriText, {
1601
- text: content.summary,
1602
- mode: "prose",
1603
- onOpenUri: onOpenUri
1604
- }) : content.summary
1605
- }) : null,
1606
- content.items.length ? /*#__PURE__*/ jsx("div", {
1607
- className: "ov-tui__search-items",
1608
- children: content.items.map((item)=>/*#__PURE__*/ jsx("article", {
1609
- className: "ov-tui__search-item",
1610
- children: /*#__PURE__*/ jsx(SearchRow, {
1611
- item: item,
1612
- onOpenUri: onOpenUri
1613
- })
1614
- }, item.id))
1615
- }) : /*#__PURE__*/ jsx("div", {
1616
- className: "ov-tui__no-result",
1617
- children: noResultLabel
615
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
616
+ path: fileTab.source.path,
617
+ fileName: fileTab.source.fileName,
618
+ canOpenParent: Boolean(tab.operations?.openParent),
619
+ titleExtra: tab.slots?.titleExtra,
620
+ slotActions: tab.slots?.actions,
621
+ actions: actions,
622
+ text: text,
623
+ onOpenParent: (uri)=>void tab.operations?.openParent?.(uri)
624
+ }),
625
+ /*#__PURE__*/ jsx("div", {
626
+ className: "ov-file-preview__body",
627
+ children: /*#__PURE__*/ jsx(LoadingState, {
628
+ text: text
629
+ })
1618
630
  })
1619
631
  ]
1620
632
  });
1621
- }
1622
- function Line({ width }) {
1623
- return /*#__PURE__*/ jsx("span", {
1624
- className: "ov-tui__skeleton-line",
1625
- style: {
1626
- width
1627
- }
1628
- });
1629
- }
1630
- function TuiSkeleton({ kind }) {
1631
- if ('search' === kind) return /*#__PURE__*/ jsxs("div", {
1632
- className: "ov-tui__skeleton",
1633
- "aria-hidden": "true",
633
+ return /*#__PURE__*/ jsxs(Fragment, {
1634
634
  children: [
1635
- /*#__PURE__*/ jsxs("div", {
1636
- className: "ov-tui__skeleton-summary",
635
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
636
+ path: fileTab.source.path,
637
+ fileName: fileTab.source.fileName,
638
+ canOpenParent: Boolean(tab.operations?.openParent),
639
+ titleExtra: tab.slots?.titleExtra,
640
+ slotActions: tab.slots?.actions,
641
+ actions: actions,
642
+ text: text,
643
+ onOpenParent: (uri)=>void tab.operations?.openParent?.(uri)
644
+ }),
645
+ tab.levels.length > 1 ? /*#__PURE__*/ jsxs("div", {
646
+ className: "ov-file-preview__levels",
647
+ role: "tablist",
648
+ ref: tabsRef,
649
+ tabIndex: -1,
650
+ onKeyDown: onLevelKeyDown,
1637
651
  children: [
1638
- /*#__PURE__*/ jsx(Line, {
1639
- width: "100%"
1640
- }),
1641
- /*#__PURE__*/ jsx(Line, {
1642
- width: "56%"
652
+ tab.levels.map((level)=>/*#__PURE__*/ jsx("button", {
653
+ type: "button",
654
+ role: "tab",
655
+ "aria-selected": level.key === activeKey,
656
+ tabIndex: level.key === activeKey ? 0 : -1,
657
+ className: "ov-file-preview__level",
658
+ "data-active": level.key === activeKey || void 0,
659
+ onClick: ()=>changeLevel(level.key),
660
+ children: level.label
661
+ }, level.key)),
662
+ /*#__PURE__*/ jsx("span", {
663
+ "aria-hidden": "true",
664
+ className: "ov-file-preview__level-indicator",
665
+ style: {
666
+ transform: `translateX(${indicator.left}px)`,
667
+ width: indicator.width
668
+ }
1643
669
  })
1644
670
  ]
671
+ }) : null,
672
+ /*#__PURE__*/ jsx("div", {
673
+ className: "ov-file-preview__body",
674
+ children: /*#__PURE__*/ jsx(FilePreviewBody, {
675
+ fileTab: fileTab,
676
+ text: text,
677
+ locale: locale,
678
+ viewMode: viewMode,
679
+ editing: editing,
680
+ onViewModeChange: onViewModeChange,
681
+ onHandleChange: onHandleChange,
682
+ onViewModesChange: onViewModesChange,
683
+ onDownload: onDownload,
684
+ onCancelEditing: onCancelEditing
685
+ }, fileTab.key)
1645
686
  }),
1646
- [
1647
- 0,
1648
- 1,
1649
- 2
1650
- ].map((index)=>/*#__PURE__*/ jsxs("div", {
1651
- className: "ov-tui__skeleton-item",
1652
- children: [
1653
- /*#__PURE__*/ jsx("span", {
1654
- className: "ov-tui__skeleton-icon"
1655
- }),
1656
- /*#__PURE__*/ jsxs("span", {
1657
- className: "ov-tui__skeleton-lines",
1658
- children: [
1659
- /*#__PURE__*/ jsx(Line, {
1660
- width: "72%"
1661
- }),
1662
- /*#__PURE__*/ jsx(Line, {
1663
- width: "44%"
1664
- })
1665
- ]
1666
- })
1667
- ]
1668
- }, index))
687
+ tab.slots?.footer ? /*#__PURE__*/ jsx("div", {
688
+ className: "ov-file-preview__footer",
689
+ children: tab.slots.footer
690
+ }) : null
1669
691
  ]
1670
692
  });
1671
- if ('tree' === kind) return /*#__PURE__*/ jsx("div", {
1672
- className: "ov-tui__skeleton",
1673
- "aria-hidden": "true",
1674
- children: [
1675
- 0,
1676
- 1,
1677
- 2,
1678
- 3
1679
- ].map((index)=>/*#__PURE__*/ jsx("div", {
1680
- className: "ov-tui__skeleton-row",
1681
- children: /*#__PURE__*/ jsx(Line, {
1682
- width: index % 2 ? '52%' : '68%'
1683
- })
1684
- }, index))
693
+ }
694
+ function FilePreviewPanels({ levelKey, onLevelChange, contentTab, directoryTab, fileTab, panelProps, panelClass, text, locale, actions, actionContext, editing, viewMode, setViewMode, onHandleChange, onViewModesChange, linkClickHandler, activeTab }) {
695
+ if (contentTab) return /*#__PURE__*/ jsx("div", {
696
+ ...panelProps,
697
+ className: `${panelClass} ${panelClass}--content`,
698
+ children: contentTab.content
1685
699
  });
1686
- if ('status' === kind) return /*#__PURE__*/ jsx("div", {
1687
- className: "ov-tui__skeleton",
1688
- "aria-hidden": "true",
1689
- children: [
1690
- 0,
1691
- 1,
1692
- 2
1693
- ].map((index)=>/*#__PURE__*/ jsxs("div", {
1694
- className: "ov-tui__skeleton-row",
1695
- children: [
1696
- /*#__PURE__*/ jsx(Line, {
1697
- width: "28%"
1698
- }),
1699
- /*#__PURE__*/ jsx(Line, {
1700
- width: "46%"
1701
- })
1702
- ]
1703
- }, index))
700
+ if (directoryTab) return /*#__PURE__*/ jsx("div", {
701
+ ...panelProps,
702
+ className: panelClass,
703
+ children: /*#__PURE__*/ jsx(FilePreviewDirectoryBody, {
704
+ tab: directoryTab,
705
+ text: text,
706
+ locale: locale,
707
+ actions: actionContext,
708
+ editing: editing,
709
+ viewMode: viewMode,
710
+ onViewModeChange: setViewMode,
711
+ onHandleChange: onHandleChange,
712
+ onViewModesChange: onViewModesChange,
713
+ onDownload: ()=>{},
714
+ onCancelEditing: actionContext.cancelEditing
715
+ })
1704
716
  });
1705
- if ('resource' === kind) return /*#__PURE__*/ jsxs("div", {
1706
- className: "ov-tui__skeleton",
1707
- "aria-hidden": "true",
717
+ if (!fileTab) return null;
718
+ return /*#__PURE__*/ jsxs("div", {
719
+ ...panelProps,
720
+ className: panelClass,
1708
721
  children: [
1709
- /*#__PURE__*/ jsx(Line, {
1710
- width: "38%"
722
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
723
+ levels: fileTab.levels,
724
+ levelKey: levelKey,
725
+ onLevelChange: onLevelChange,
726
+ path: fileTab.source.path,
727
+ fileName: fileTab.source.fileName,
728
+ canOpenParent: Boolean(fileTab.operations?.openParent),
729
+ titleExtra: fileTab.slots?.titleExtra,
730
+ slotActions: fileTab.slots?.actions,
731
+ actions: actionContext,
732
+ text: text,
733
+ onOpenParent: (uri)=>void actions.openParent(activeTab, uri)
1711
734
  }),
1712
- /*#__PURE__*/ jsxs("div", {
1713
- className: "ov-tui__skeleton-block",
1714
- children: [
1715
- /*#__PURE__*/ jsx(Line, {
1716
- width: "100%"
1717
- }),
1718
- /*#__PURE__*/ jsx(Line, {
1719
- width: "92%"
1720
- }),
1721
- /*#__PURE__*/ jsx(Line, {
1722
- width: "76%"
1723
- })
1724
- ]
1725
- })
1726
- ]
1727
- });
1728
- if ('write' === kind) return /*#__PURE__*/ jsxs("div", {
1729
- className: "ov-tui__skeleton",
1730
- "aria-hidden": "true",
1731
- children: [
1732
735
  /*#__PURE__*/ jsx("div", {
1733
- className: "ov-tui__skeleton-row",
1734
- children: /*#__PURE__*/ jsx(Line, {
1735
- width: "64%"
1736
- })
736
+ className: "ov-file-preview__body",
737
+ children: /*#__PURE__*/ jsx(FilePreviewBody, {
738
+ fileTab: fileTab,
739
+ text: text,
740
+ locale: locale,
741
+ viewMode: viewMode,
742
+ editing: editing,
743
+ onViewModeChange: setViewMode,
744
+ onHandleChange: onHandleChange,
745
+ onViewModesChange: onViewModesChange,
746
+ onDownload: ()=>void actions.download(fileTab),
747
+ onLinkClick: linkClickHandler,
748
+ onCancelEditing: actionContext.cancelEditing
749
+ }, JSON.stringify([
750
+ fileTab.key,
751
+ levelKey
752
+ ]))
1737
753
  }),
1738
- /*#__PURE__*/ jsx("div", {
1739
- className: "ov-tui__skeleton-row",
1740
- children: /*#__PURE__*/ jsx(Line, {
1741
- width: "42%"
1742
- })
1743
- })
754
+ fileTab.source.truncated ? /*#__PURE__*/ jsx(TruncatedNotice, {
755
+ text: text
756
+ }) : null,
757
+ fileTab.slots?.footer ? /*#__PURE__*/ jsx("div", {
758
+ className: "ov-file-preview__footer",
759
+ children: fileTab.slots.footer
760
+ }) : null
1744
761
  ]
1745
762
  });
763
+ }
764
+ function useFilePreviewViewState(identity) {
765
+ const [previous, setPrevious] = useState(identity);
766
+ const [viewMode, setViewMode] = useState('preview');
767
+ const [viewModes, setViewModes] = useState([]);
768
+ const viewerHandle = useMemo(()=>({
769
+ identity,
770
+ current: null
771
+ }), [
772
+ identity
773
+ ]);
774
+ if (previous !== identity) {
775
+ setPrevious(identity);
776
+ setViewMode('preview');
777
+ setViewModes([]);
778
+ }
779
+ const onHandleChange = useCallback((handle)=>{
780
+ viewerHandle.current = handle;
781
+ }, [
782
+ viewerHandle
783
+ ]);
784
+ const onViewModesChange = useCallback((modes)=>{
785
+ const next = modes.map((mode)=>mode.value);
786
+ setViewModes((current)=>current.join() === next.join() ? current : next);
787
+ }, []);
788
+ return {
789
+ viewMode,
790
+ setViewMode,
791
+ viewModes,
792
+ setViewModes,
793
+ viewerHandle,
794
+ onHandleChange,
795
+ onViewModesChange
796
+ };
797
+ }
798
+ function useFilePreviewLevels(selected) {
799
+ const [keys, setKeys] = useState({});
800
+ const levels = 'file' === selected.kind ? selected.levels : void 0;
801
+ const requested = 'file' === selected.kind ? selected.activeLevelKey ?? keys[selected.key] : void 0;
802
+ const level = levels?.find((item)=>item.key === requested && !item.disabled) ?? levels?.find((item)=>!item.disabled);
803
+ const tab = 'file' === selected.kind && level ? {
804
+ ...selected,
805
+ source: level.source,
806
+ resources: level.resources ?? selected.resources,
807
+ editing: level.editing
808
+ } : selected;
809
+ function selectLevel(key) {
810
+ if ('file' !== selected.kind || key === level?.key || !levels?.some((item)=>item.key === key && !item.disabled)) return;
811
+ if (void 0 === selected.activeLevelKey) setKeys((current)=>({
812
+ ...current,
813
+ [selected.key]: key
814
+ }));
815
+ selected.onLevelChange?.(key);
816
+ }
817
+ return {
818
+ tab,
819
+ levelKey: level?.key,
820
+ selectLevel
821
+ };
822
+ }
823
+ const messages = {
824
+ showHiddenPath: 'Show hidden path levels',
825
+ copyPath: 'Copy path',
826
+ editor: 'Edit content',
827
+ leaveEditing: 'Cancel',
828
+ retry: 'Retry',
829
+ loadFailed: 'Failed to load the file',
830
+ saveFailed: 'Failed to save',
831
+ unsupported: 'Preview is not supported for this format',
832
+ loading: 'Loading…',
833
+ download: 'Download',
834
+ fileEmpty: 'This file is empty',
835
+ noContent: 'No content to show',
836
+ truncated: 'Only part of the content is shown'
837
+ };
838
+ const en_US = messages;
839
+ const zh_CN_messages = {
840
+ showHiddenPath: '显示隐藏的路径层级',
841
+ copyPath: '复制路径',
842
+ editor: '编辑正文',
843
+ leaveEditing: '取消',
844
+ retry: '重试',
845
+ loadFailed: '文件加载失败',
846
+ saveFailed: '保存失败',
847
+ unsupported: '不支持预览此格式',
848
+ loading: '加载中…',
849
+ download: '下载',
850
+ fileEmpty: '该文件为空',
851
+ noContent: '暂无可显示内容',
852
+ truncated: '仅显示部分内容'
853
+ };
854
+ const zh_CN = zh_CN_messages;
855
+ const LOCALES = {
856
+ 'en-US': en_US,
857
+ 'zh-CN': zh_CN
858
+ };
859
+ function resolveFilePreviewLabels(locale, labels) {
860
+ return resolveLocale(LOCALES, locale, labels);
861
+ }
862
+ const OUTLINE_RADIUS = 7;
863
+ const STROKE_INSET = 0.5;
864
+ function buildOutline(width, top, bottom, left, right) {
865
+ const radius = OUTLINE_RADIUS;
866
+ if (right - left < 2 * radius + 1) return `M0 ${bottom} H${width}`;
867
+ const lead = left > radius ? [
868
+ `M0 ${bottom}`,
869
+ `H${left - radius}`
870
+ ] : [
871
+ `M${left} ${bottom}`
872
+ ];
873
+ return [
874
+ ...lead,
875
+ `Q${left} ${bottom} ${left} ${bottom - radius}`,
876
+ `V${top + radius}`,
877
+ `Q${left} ${top} ${left + radius} ${top}`,
878
+ `H${right - radius}`,
879
+ `Q${right} ${top} ${right} ${top + radius}`,
880
+ `V${bottom - radius}`,
881
+ `Q${right} ${bottom} ${right + radius} ${bottom}`,
882
+ `H${width}`
883
+ ].join(' ');
884
+ }
885
+ function FilePreviewTabRow({ tabs, activeKey, disabled, tabId, panelId, onSelect }) {
886
+ const trackRef = useRef(null);
887
+ const [outline, setOutline] = useState('');
888
+ useLayoutEffect(()=>{
889
+ const track = trackRef.current;
890
+ if (!track) return;
891
+ const measure = ()=>{
892
+ const trackRect = track.getBoundingClientRect();
893
+ const bottom = trackRect.height - STROKE_INSET;
894
+ const active = track.querySelector('[aria-selected="true"]');
895
+ if (!active) return void setOutline(`M0 ${bottom} H${trackRect.width}`);
896
+ const rect = active.getBoundingClientRect();
897
+ setOutline(buildOutline(trackRect.width, rect.top - trackRect.top + STROKE_INSET, rect.bottom - trackRect.top - STROKE_INSET, rect.left - trackRect.left, rect.right - trackRect.left));
898
+ };
899
+ measure();
900
+ const observer = new ResizeObserver(measure);
901
+ observer.observe(track);
902
+ return ()=>observer.disconnect();
903
+ }, [
904
+ activeKey,
905
+ tabs
906
+ ]);
907
+ const onKeyDown = (event)=>{
908
+ const keys = [
909
+ 'ArrowLeft',
910
+ 'ArrowRight',
911
+ 'Home',
912
+ 'End'
913
+ ];
914
+ if (!keys.includes(event.key)) return;
915
+ const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]:not(:disabled)'));
916
+ if (0 === buttons.length) return;
917
+ const current = buttons.indexOf(event.target);
918
+ const next = 'Home' === event.key ? 0 : 'End' === event.key ? buttons.length - 1 : (current + ('ArrowRight' === event.key ? 1 : -1) + buttons.length) % buttons.length;
919
+ const target = buttons[next];
920
+ if (!target) return;
921
+ event.preventDefault();
922
+ target.focus();
923
+ onSelect(target.dataset.key ?? '');
924
+ };
1746
925
  return /*#__PURE__*/ jsx("div", {
1747
- className: "ov-tui__skeleton",
1748
- "aria-hidden": "true",
926
+ className: "ov-file-preview__tab-row",
1749
927
  children: /*#__PURE__*/ jsxs("div", {
1750
- className: "ov-tui__skeleton-summary",
928
+ role: "tablist",
929
+ className: "ov-file-preview__tab-track",
930
+ ref: trackRef,
931
+ tabIndex: -1,
932
+ onKeyDown: onKeyDown,
1751
933
  children: [
1752
- /*#__PURE__*/ jsx(Line, {
1753
- width: "100%"
934
+ tabs.map((tab)=>{
935
+ const selected = tab.key === activeKey;
936
+ return /*#__PURE__*/ jsxs("button", {
937
+ type: "button",
938
+ role: "tab",
939
+ "data-key": tab.key,
940
+ id: tabId(tab.key),
941
+ "aria-selected": selected,
942
+ "aria-controls": panelId(tab.key),
943
+ tabIndex: selected ? 0 : -1,
944
+ disabled: tab.disabled || disabled,
945
+ title: 'string' == typeof tab.title ? tab.title : void 0,
946
+ className: [
947
+ 'ov-file-preview__tab',
948
+ selected ? 'ov-file-preview__tab--active' : ''
949
+ ].filter(Boolean).join(' '),
950
+ onClick: ()=>onSelect(tab.key),
951
+ children: [
952
+ tab.icon ? /*#__PURE__*/ jsx("span", {
953
+ className: "ov-file-preview__tab-icon",
954
+ "aria-hidden": "true",
955
+ children: tab.icon
956
+ }) : null,
957
+ /*#__PURE__*/ jsx("span", {
958
+ className: "ov-file-preview__tab-title",
959
+ children: tab.title ?? ('file' === tab.kind ? tab.source.fileName : tab.key)
960
+ })
961
+ ]
962
+ }, tab.key);
1754
963
  }),
1755
- /*#__PURE__*/ jsx(Line, {
1756
- width: "64%"
964
+ /*#__PURE__*/ jsx("svg", {
965
+ className: "ov-file-preview__tab-outline",
966
+ "aria-hidden": "true",
967
+ children: /*#__PURE__*/ jsx("path", {
968
+ d: outline
969
+ })
1757
970
  })
1758
971
  ]
1759
972
  })
1760
973
  });
1761
974
  }
1762
- function TuiStatusCard({ content, onOpenUri }) {
1763
- return /*#__PURE__*/ jsxs("div", {
1764
- className: "ov-tui__status",
1765
- children: [
1766
- content.title ? /*#__PURE__*/ jsx("div", {
1767
- className: "ov-tui__card-title",
1768
- children: /*#__PURE__*/ jsx(TuiUriText, {
1769
- text: content.title,
1770
- mode: "prose",
1771
- onOpenUri: onOpenUri
1772
- })
1773
- }) : null,
1774
- /*#__PURE__*/ jsx("dl", {
1775
- className: "ov-tui__status-rows",
1776
- children: content.rows.map((row)=>/*#__PURE__*/ jsxs("div", {
1777
- className: "ov-tui__status-row",
1778
- "data-variant": row.variant ?? 'inline',
1779
- children: [
1780
- /*#__PURE__*/ jsx("dt", {
1781
- className: "ov-tui__status-label",
1782
- children: row.label
1783
- }),
1784
- /*#__PURE__*/ jsx("dd", {
1785
- className: "ov-tui__status-value",
1786
- children: 'string' == typeof row.value ? /*#__PURE__*/ jsx(TuiUriText, {
1787
- text: row.value,
1788
- mode: "literal",
1789
- onOpenUri: onOpenUri
1790
- }) : row.value
1791
- })
1792
- ]
1793
- }, row.label))
1794
- })
1795
- ]
1796
- });
1797
- }
1798
- function TuiWriteResult({ content, onOpenUri }) {
1799
- return /*#__PURE__*/ jsxs("div", {
1800
- className: "ov-tui__write",
1801
- children: [
1802
- content.title ? /*#__PURE__*/ jsx("div", {
1803
- className: "ov-tui__card-title",
1804
- children: /*#__PURE__*/ jsx(TuiUriText, {
1805
- text: content.title,
1806
- mode: "prose",
1807
- onOpenUri: onOpenUri
1808
- })
1809
- }) : null,
1810
- /*#__PURE__*/ jsx("div", {
1811
- className: "ov-tui__write-lines",
1812
- children: content.lines.map((line, index)=>/*#__PURE__*/ jsx("div", {
1813
- className: "ov-tui__write-line",
1814
- children: 'string' == typeof line ? /*#__PURE__*/ jsx(TuiUriText, {
1815
- text: line,
1816
- mode: "prose",
1817
- onOpenUri: onOpenUri
1818
- }) : line
1819
- }, index))
1820
- })
1821
- ]
1822
- });
975
+ async function copyText(value, notify) {
976
+ try {
977
+ await navigator.clipboard.writeText(value);
978
+ } catch {
979
+ return false;
980
+ }
981
+ notify?.(value);
982
+ return true;
1823
983
  }
1824
- function RecordBody({ record, command, labels, onOpenUri, onCopy, onPickCommand }) {
1825
- const { content } = record;
1826
- if ('pending' === record.status) return /*#__PURE__*/ jsx(TuiSkeleton, {
1827
- kind: resolveTuiSkeleton(command)
1828
- });
1829
- if (!content) return /*#__PURE__*/ jsx("div", {
1830
- className: "ov-tui__muted",
1831
- children: 'error' === record.status ? labels.error : labels.noResult
1832
- });
1833
- switch(content.variant){
1834
- case 'search-list':
1835
- return /*#__PURE__*/ jsx(TuiSearchList, {
1836
- content: content,
1837
- onOpenUri: onOpenUri,
1838
- noResultLabel: labels.noResult
1839
- });
1840
- case 'resource-preview':
1841
- return /*#__PURE__*/ jsx(TuiResourcePreview, {
1842
- content: {
1843
- ...content,
1844
- title: getVisibleTuiResultTitle(content.title, command) ?? ''
1845
- },
1846
- labels: labels,
1847
- onCopy: onCopy,
1848
- onOpenUri: onOpenUri
1849
- });
1850
- case 'resource-tree':
1851
- return /*#__PURE__*/ jsx(TuiResourceTree, {
1852
- content: {
1853
- ...content,
1854
- title: getVisibleTuiResultTitle(content.title, command)
1855
- },
1856
- onOpenUri: onOpenUri,
1857
- resourceLabel: labels.resource,
1858
- loadMoreLabel: labels.loadMore,
1859
- noResultLabel: labels.noResult
1860
- });
1861
- case 'status':
1862
- return /*#__PURE__*/ jsx(TuiStatusCard, {
1863
- content: {
1864
- ...content,
1865
- title: getVisibleTuiResultTitle(content.title, command)
1866
- },
1867
- onOpenUri: onOpenUri
1868
- });
1869
- case 'write-result':
1870
- return /*#__PURE__*/ jsx(TuiWriteResult, {
1871
- content: {
1872
- ...content,
1873
- title: getVisibleTuiResultTitle(content.title, command)
1874
- },
1875
- onOpenUri: onOpenUri
1876
- });
1877
- case 'command-help':
1878
- return /*#__PURE__*/ jsx(TuiCommandHelp, {
1879
- content: content,
1880
- onPick: onPickCommand,
1881
- labels: labels
1882
- });
1883
- default:
1884
- {
1885
- const text = getTuiResultText(formatTuiJson(content.text), command);
1886
- return /*#__PURE__*/ jsx("div", {
1887
- className: "ov-tui__text",
1888
- children: 'markdown' === content.format ? /*#__PURE__*/ jsx(TuiMarkdown, {
1889
- text: text,
1890
- labels: labels,
1891
- onCopy: onCopy,
1892
- onOpenUri: onOpenUri
1893
- }) : isTuiJson(text) ? /*#__PURE__*/ jsx(TuiCodeBlock, {
1894
- text: text,
1895
- labels: labels,
1896
- onCopy: onCopy,
1897
- onOpenUri: onOpenUri
1898
- }) : /*#__PURE__*/ jsx("pre", {
1899
- className: "ov-tui__plain",
1900
- children: /*#__PURE__*/ jsx(TuiUriText, {
1901
- text: text,
1902
- mode: "prose",
1903
- onOpenUri: onOpenUri
1904
- })
1905
- })
1906
- });
1907
- }
984
+ function useFilePreviewActions() {
985
+ const pending = useRef(new Set());
986
+ async function run(key, { execute, notify }, value) {
987
+ if (!execute || pending.current.has(key)) return;
988
+ pending.current.add(key);
989
+ try {
990
+ await execute(value);
991
+ } catch {
992
+ return;
993
+ } finally{
994
+ pending.current.delete(key);
995
+ }
996
+ notify?.(value);
1908
997
  }
998
+ return {
999
+ openParent: (tab, uri)=>'content' === tab.kind ? Promise.resolve() : run(`openParent:${tab.key}`, {
1000
+ execute: tab.operations?.openParent,
1001
+ notify: tab.onOpenParent
1002
+ }, uri),
1003
+ download: (tab)=>run(`download:${tab.key}`, {
1004
+ execute: tab.operations?.download,
1005
+ notify: tab.onDownload
1006
+ }, void 0),
1007
+ openUri: (tab, uri)=>run(`openUri:${tab.key}`, {
1008
+ execute: tab.operations?.openUri,
1009
+ notify: tab.onOpenUri
1010
+ }, uri),
1011
+ copyPath: (tab, path)=>copyText(path, tab.onCopyPath),
1012
+ copyContent: (tab, text)=>copyText(text, 'onCopyContent' in tab ? tab.onCopyContent : void 0)
1013
+ };
1909
1014
  }
1910
- const STATUS_TEXT = {
1911
- pending: 'pending',
1912
- success: 'success',
1913
- error: 'error'
1914
- };
1915
- function TuiRecordItem(props) {
1916
- const { record, labels } = props;
1917
- const statusLabel = labels[STATUS_TEXT[record.status]];
1918
- const input = props.command ? matchTuiCommandInput(record.command, [
1919
- props.command
1920
- ]) : void 0;
1921
- const prefix = input ? getTuiCommandInputText(input.command) : '';
1922
- return /*#__PURE__*/ jsxs("div", {
1923
- className: "ov-tui__record",
1924
- "data-status": record.status,
1925
- children: [
1926
- /*#__PURE__*/ jsxs("div", {
1927
- className: "ov-tui__bubble",
1928
- children: [
1929
- /*#__PURE__*/ jsx("code", {
1930
- className: "ov-tui__bubble-text",
1931
- children: input ? /*#__PURE__*/ jsxs(Fragment, {
1932
- children: [
1933
- input.leadingWhitespace,
1934
- /*#__PURE__*/ jsx("span", {
1935
- className: "ov-tui__bubble-command",
1936
- children: /*#__PURE__*/ jsx(TuiCommandText, {
1937
- text: prefix,
1938
- name: input.command.name
1939
- })
1940
- }),
1941
- /*#__PURE__*/ jsx(TuiUriText, {
1942
- text: input.rest,
1943
- mode: "literal",
1944
- onOpenUri: props.onOpenUri
1945
- })
1946
- ]
1947
- }) : /*#__PURE__*/ jsx(TuiUriText, {
1948
- text: record.command,
1949
- mode: "literal",
1950
- onOpenUri: props.onOpenUri
1951
- })
1952
- }),
1953
- record.createdAt || void 0 !== record.createdAtMs ? /*#__PURE__*/ jsx(TuiRecordTime, {
1954
- record: record,
1955
- labels: labels
1956
- }) : null
1957
- ]
1958
- }),
1959
- /*#__PURE__*/ jsxs("article", {
1960
- className: "ov-tui__result",
1961
- children: [
1962
- /*#__PURE__*/ jsx(TuiResultHeader, {
1963
- command: prefix || record.command,
1964
- status: record.status,
1965
- label: statusLabel
1966
- }),
1967
- /*#__PURE__*/ jsx("div", {
1968
- className: "ov-tui__result-body",
1969
- children: /*#__PURE__*/ jsx(RecordBody, {
1970
- ...props
1971
- })
1972
- })
1973
- ]
1974
- })
1975
- ]
1976
- });
1015
+ function useFilePreviewActionsContext({ activeTab, fileTab, currentPath, currentSource, viewMode, canSwitchView, setViewMode, editing, operations }) {
1016
+ const startEditing = useCallback(()=>{
1017
+ if (!fileTab) return;
1018
+ setViewMode('source');
1019
+ editing.start(fileTab);
1020
+ }, [
1021
+ editing,
1022
+ fileTab,
1023
+ setViewMode
1024
+ ]);
1025
+ const saveEditing = useCallback(async ()=>{
1026
+ if (!fileTab) return false;
1027
+ const saved = await editing.save(fileTab);
1028
+ if (saved) setViewMode('preview');
1029
+ return saved;
1030
+ }, [
1031
+ editing,
1032
+ fileTab,
1033
+ setViewMode
1034
+ ]);
1035
+ const cancelEditing = useCallback(()=>{
1036
+ editing.discard(activeTab);
1037
+ setViewMode('preview');
1038
+ }, [
1039
+ activeTab,
1040
+ editing,
1041
+ setViewMode
1042
+ ]);
1043
+ const copyable = currentSource ? readCopyableText(currentSource) : void 0;
1044
+ const copyContent = useCallback(async ()=>{
1045
+ if (!copyable) return false;
1046
+ return operations.copyContent(activeTab, copyable);
1047
+ }, [
1048
+ activeTab,
1049
+ copyable,
1050
+ operations
1051
+ ]);
1052
+ const copyPath = useCallback(async ()=>{
1053
+ const path = currentPath;
1054
+ if (!path) return false;
1055
+ return operations.copyPath(activeTab, path);
1056
+ }, [
1057
+ activeTab,
1058
+ currentPath,
1059
+ operations
1060
+ ]);
1061
+ const download = useCallback(()=>{
1062
+ if (fileTab) operations.download(fileTab);
1063
+ }, [
1064
+ fileTab,
1065
+ operations
1066
+ ]);
1067
+ return {
1068
+ viewMode,
1069
+ setViewMode,
1070
+ canSwitchView,
1071
+ editing: editing.editing,
1072
+ busy: editing.saving || editing.loadingEdit,
1073
+ canEdit: Boolean(fileTab?.editing) && !editing.editing,
1074
+ startEditing,
1075
+ saveEditing,
1076
+ cancelEditing,
1077
+ canCopyContent: Boolean(copyable),
1078
+ copyContent,
1079
+ canCopyPath: Boolean(currentPath),
1080
+ copyPath,
1081
+ canDownload: Boolean(fileTab?.operations?.download),
1082
+ download
1083
+ };
1977
1084
  }
1978
- function useTuiCompletion(value, enabled, commands, groups = [], paletteScope = null) {
1979
- const query = getTuiCommandQuery(value, commands);
1980
- const suggestions = groupTuiCommands(enabled ? query.matches : [], groups);
1085
+ function useFilePreviewIds(activeKey) {
1086
+ const instanceId = useId();
1087
+ const tabId = (key)=>`${instanceId}-tab-${key}`;
1088
+ const panelId = (key)=>`${instanceId}-panel-${key}`;
1981
1089
  return {
1982
- query,
1983
- suggestions,
1984
- options: suggestions.flatMap((section)=>section.commands),
1985
- enabled,
1986
- activeInput: '/' === query.scope ? void 0 : matchTuiCommandInput(value, commands),
1987
- showSuggestions: enabled && (query.automatic || null !== query.scope && paletteScope === query.scope)
1090
+ tabId,
1091
+ panelId,
1092
+ panelProps: {
1093
+ id: panelId(activeKey),
1094
+ role: 'tabpanel',
1095
+ 'aria-labelledby': tabId(activeKey)
1096
+ }
1988
1097
  };
1989
1098
  }
1990
- function Tui({ records, value, disabled = false, emptyState, locale = 'en', commands = [], commandGroups = [], onValueChange, onSubmit, onOpenUri, onCopy, labels, className, ...rest }) {
1991
- const text = resolveTuiLabels(labels, locale);
1992
- const bodyRef = useRef(null);
1993
- const followOutput = useRef(true);
1994
- const composerRef = useRef(null);
1995
- const assistId = useId();
1996
- const [dismissedValue, setDismissedValue] = useState(null);
1997
- const [paletteScope, setPaletteScope] = useState(null);
1998
- const [selection, setSelection] = useState({
1999
- queryKey: value,
2000
- index: -1
2001
- });
2002
- const dismissCompletion = useCallback((next)=>{
2003
- setDismissedValue(next);
2004
- setPaletteScope(null);
2005
- setSelection({
2006
- queryKey: next,
2007
- index: -1
2008
- });
2009
- }, []);
2010
- const updateValue = (next)=>{
2011
- setDismissedValue(null);
2012
- setSelection({
2013
- queryKey: next,
2014
- index: -1
2015
- });
2016
- if (getTuiCommandQuery(next, commands).scope !== paletteScope) setPaletteScope(null);
2017
- onValueChange?.(next);
1099
+ function initialLevelKey(tab, remembered, tabKey) {
1100
+ const preferred = tab.activeLevelKey ?? remembered[tabKey] ?? tab.defaultLevelKey;
1101
+ const first = tab.levels[0]?.key ?? '';
1102
+ if (void 0 === preferred) return first;
1103
+ return tab.levels.some((level)=>level.key === preferred) ? preferred : first;
1104
+ }
1105
+ function useDirectoryLevel(tab) {
1106
+ const [levelKeys, setLevelKeys] = useState({});
1107
+ if (!tab) return {};
1108
+ const activeLevel = tab.levels.find((level)=>level.key === initialLevelKey(tab, levelKeys, tab.key)) ?? tab.levels[0];
1109
+ return {
1110
+ activeLevel,
1111
+ directoryTab: {
1112
+ ...tab,
1113
+ activeLevelKey: activeLevel.key,
1114
+ onActiveLevelChange: (key)=>{
1115
+ if (void 0 === tab.activeLevelKey) setLevelKeys((previous)=>({
1116
+ ...previous,
1117
+ [tab.key]: key
1118
+ }));
1119
+ tab.onActiveLevelChange?.(key);
1120
+ }
1121
+ },
1122
+ source: activeLevel.source
2018
1123
  };
2019
- const readOnly = !onValueChange;
2020
- const fillCommand = (next)=>{
2021
- if (disabled || readOnly) return;
2022
- updateValue(formatTuiCommandInput(next, commands));
2023
- composerRef.current?.querySelector('textarea')?.focus({
2024
- preventScroll: true
2025
- });
1124
+ }
1125
+ function useFilePreviewEditing(activeTabKey) {
1126
+ const [editing, setEditing] = useState(false);
1127
+ const [loadingEdit, setLoadingEdit] = useState(false);
1128
+ const [editFailed, setEditFailed] = useState(false);
1129
+ const [saving, setSaving] = useState(false);
1130
+ const [saveFailed, setSaveFailed] = useState(false);
1131
+ const [draft, setDraft] = useState('');
1132
+ const requestId = useRef(0);
1133
+ const loading = useRef(false);
1134
+ const pendingSave = useRef(false);
1135
+ const lastKey = useRef(activeTabKey);
1136
+ if (lastKey.current !== activeTabKey) {
1137
+ lastKey.current = activeTabKey;
1138
+ requestId.current += 1;
1139
+ loading.current = false;
1140
+ if (editing) setEditing(false);
1141
+ if (loadingEdit) setLoadingEdit(false);
1142
+ if (editFailed) setEditFailed(false);
1143
+ if (saveFailed) setSaveFailed(false);
1144
+ if (draft) setDraft('');
1145
+ }
1146
+ function discard(tab) {
1147
+ requestId.current += 1;
1148
+ loading.current = false;
1149
+ const wasEditing = editing;
1150
+ const discarded = draft;
1151
+ setEditing(false);
1152
+ setLoadingEdit(false);
1153
+ setEditFailed(false);
1154
+ setSaveFailed(false);
1155
+ setDraft('');
1156
+ if (wasEditing && 'file' === tab.kind) tab.editing?.onCancel?.(discarded);
1157
+ }
1158
+ async function start(tab) {
1159
+ if (loading.current || pendingSave.current) return;
1160
+ const id = ++requestId.current;
1161
+ loading.current = true;
1162
+ setLoadingEdit(true);
1163
+ setEditFailed(false);
1164
+ setSaveFailed(false);
1165
+ let content;
1166
+ try {
1167
+ content = tab.editing?.loadContent ? await tab.editing.loadContent(tab.source) : tab.source.content ?? await tab.source.file?.text();
1168
+ } catch {
1169
+ if (id === requestId.current) setEditFailed(true);
1170
+ }
1171
+ if (id !== requestId.current) return;
1172
+ loading.current = false;
1173
+ setLoadingEdit(false);
1174
+ if (void 0 !== content) {
1175
+ setDraft(content);
1176
+ setEditing(true);
1177
+ }
1178
+ }
1179
+ async function save(tab) {
1180
+ if (!editing || !tab.editing || pendingSave.current) return false;
1181
+ pendingSave.current = true;
1182
+ setSaving(true);
1183
+ setSaveFailed(false);
1184
+ const value = draft;
1185
+ try {
1186
+ await tab.editing.save(value);
1187
+ } catch {
1188
+ setSaveFailed(true);
1189
+ return false;
1190
+ } finally{
1191
+ pendingSave.current = false;
1192
+ setSaving(false);
1193
+ }
1194
+ setEditing(false);
1195
+ setDraft('');
1196
+ tab.editing.onSave?.(value);
1197
+ return true;
1198
+ }
1199
+ return {
1200
+ editing,
1201
+ loadingEdit,
1202
+ editFailed,
1203
+ saving,
1204
+ saveFailed,
1205
+ draft,
1206
+ discard,
1207
+ start,
1208
+ save,
1209
+ updateDraft: (value)=>{
1210
+ setDraft(value);
1211
+ setSaveFailed(false);
1212
+ }
2026
1213
  };
2027
- const completion = useTuiCompletion(value, !disabled && !readOnly && dismissedValue !== value, commands, commandGroups, paletteScope);
2028
- const queryKey = value;
2029
- const activeIndex = selection.queryKey === queryKey && selection.index < completion.options.length ? selection.index : -1;
2030
- const setActiveIndexSafely = useCallback((next)=>{
2031
- setSelection((current)=>{
2032
- const index = current.queryKey === queryKey && current.index < completion.options.length ? current.index : -1;
2033
- return {
2034
- queryKey,
2035
- index: 'function' == typeof next ? next(index) : next
2036
- };
2037
- });
1214
+ }
1215
+ function isFileTab(tab) {
1216
+ return 'file' === tab.kind;
1217
+ }
1218
+ const file_preview_FilePreview = /*#__PURE__*/ forwardRef(function({ tabs, activeKey, defaultActiveKey, locale: localeProp, labels, onActiveKeyChange, className, ...rest }, ref) {
1219
+ const locale = useLocale(localeProp);
1220
+ const text = resolveFilePreviewLabels(locale, labels);
1221
+ const [internalKey, setInternalKey] = useState(()=>defaultActiveKey ?? tabs[0].key);
1222
+ const currentKey = activeKey ?? internalKey;
1223
+ const selectedTab = tabs.find((tab)=>tab.key === currentKey) ?? tabs[0];
1224
+ const { tab: activeTab, levelKey, selectLevel } = useFilePreviewLevels(selectedTab);
1225
+ const directory = useDirectoryLevel('directory' === activeTab.kind ? activeTab : void 0);
1226
+ const { viewMode, setViewMode, viewModes, setViewModes, viewerHandle, onHandleChange, onViewModesChange } = useFilePreviewViewState(JSON.stringify([
1227
+ activeTab.key,
1228
+ levelKey,
1229
+ directory.activeLevel?.key
1230
+ ]));
1231
+ const { tabId, panelId, panelProps } = useFilePreviewIds(activeTab.key);
1232
+ const editing = useFilePreviewEditing(JSON.stringify([
1233
+ activeTab.key,
1234
+ levelKey,
1235
+ directory.activeLevel?.key
1236
+ ]));
1237
+ const actions = useFilePreviewActions();
1238
+ const tabText = mergeLabels(text, activeTab.labels);
1239
+ const fileTab = isFileTab(activeTab) ? activeTab : void 0;
1240
+ const directoryTab = 'directory' === activeTab.kind ? activeTab : void 0;
1241
+ const contentTab = fileTab || directoryTab ? void 0 : activeTab;
1242
+ const switchTab = useCallback((key)=>{
1243
+ if (editing.saving) return false;
1244
+ if (key === currentKey && key === activeTab.key) return false;
1245
+ const target = tabs.find((tab)=>tab.key === key);
1246
+ if (!target || target.disabled) return false;
1247
+ const syncingOnly = target.key === activeTab.key;
1248
+ if (!syncingOnly) {
1249
+ editing.discard(activeTab);
1250
+ setViewMode('preview');
1251
+ setViewModes([]);
1252
+ viewerHandle.current = null;
1253
+ }
1254
+ if (void 0 === activeKey) setInternalKey(key);
1255
+ onActiveKeyChange?.(key);
1256
+ return true;
2038
1257
  }, [
2039
- queryKey,
2040
- completion.options.length
1258
+ activeKey,
1259
+ activeTab,
1260
+ currentKey,
1261
+ editing,
1262
+ onActiveKeyChange,
1263
+ tabs,
1264
+ setViewMode,
1265
+ setViewModes,
1266
+ viewerHandle
2041
1267
  ]);
2042
- useEffect(()=>{
2043
- const body = bodyRef.current;
2044
- if (body && followOutput.current) body.scrollTop = body.scrollHeight;
1268
+ useImperativeHandle(ref, ()=>({
1269
+ setActiveTab: switchTab,
1270
+ getActiveTab: ()=>activeTab.key,
1271
+ getViewerHandle: ()=>fileTab ? viewerHandle.current : null
1272
+ }), [
1273
+ activeTab.key,
1274
+ fileTab,
1275
+ switchTab,
1276
+ viewerHandle
1277
+ ]);
1278
+ const canSwitchView = Boolean(fileTab) && viewModes.includes('preview') && viewModes.includes('source');
1279
+ const onLinkClick = useCallback((_event, link)=>{
1280
+ if (fileTab) actions.openUri(fileTab, link.url);
2045
1281
  }, [
2046
- records
1282
+ actions,
1283
+ fileTab
2047
1284
  ]);
1285
+ const linkClickHandler = fileTab?.operations?.openUri ? onLinkClick : void 0;
1286
+ const level = directory;
1287
+ const currentPath = level.source?.path ?? fileTab?.source.path;
1288
+ const currentSource = level.source ?? fileTab?.source;
1289
+ const actionContext = useFilePreviewActionsContext({
1290
+ activeTab,
1291
+ fileTab,
1292
+ currentPath,
1293
+ currentSource,
1294
+ viewMode,
1295
+ canSwitchView,
1296
+ setViewMode,
1297
+ editing,
1298
+ operations: actions
1299
+ });
2048
1300
  return /*#__PURE__*/ jsxs("div", {
1301
+ ...rest,
2049
1302
  className: [
2050
- 'ov-tui',
1303
+ 'ov-file-preview',
2051
1304
  className
2052
1305
  ].filter(Boolean).join(' '),
2053
- ...rest,
2054
1306
  children: [
2055
- /*#__PURE__*/ jsx("div", {
2056
- ref: bodyRef,
2057
- className: "ov-tui__body",
2058
- onScroll: (event)=>{
2059
- const body = event.currentTarget;
2060
- followOutput.current = body.scrollHeight - body.scrollTop - body.clientHeight <= 24;
2061
- },
2062
- children: records.length ? records.map((record)=>/*#__PURE__*/ jsx(TuiRecordItem, {
2063
- record: record,
2064
- command: findTuiCommand(record.command, commands),
2065
- labels: text,
2066
- onOpenUri: onOpenUri,
2067
- onCopy: onCopy,
2068
- onPickCommand: disabled || readOnly ? void 0 : (command)=>fillCommand(`${command} `)
2069
- }, record.id)) : /*#__PURE__*/ jsx("div", {
2070
- className: "ov-tui__empty",
2071
- children: void 0 === emptyState ? text.empty : emptyState
2072
- })
2073
- }),
2074
- /*#__PURE__*/ jsx(TuiAssist, {
2075
- assistId: assistId,
2076
- anchorRef: composerRef,
2077
- onDismiss: dismissCompletion,
2078
- labels: text,
2079
- completion: completion,
2080
- activeIndex: activeIndex,
2081
- onActiveIndexChange: setActiveIndexSafely,
2082
- onPick: fillCommand
1307
+ /*#__PURE__*/ jsx(FilePreviewTabRow, {
1308
+ tabs: tabs,
1309
+ activeKey: activeTab.key,
1310
+ disabled: editing.saving,
1311
+ tabId: tabId,
1312
+ panelId: panelId,
1313
+ onSelect: (key)=>void switchTab(key)
2083
1314
  }),
2084
- /*#__PURE__*/ jsx(TuiComposer, {
2085
- onDismiss: dismissCompletion,
2086
- onRequestPalette: ()=>{
2087
- setDismissedValue(null);
2088
- setSelection({
2089
- queryKey: value,
2090
- index: -1
2091
- });
2092
- setPaletteScope(completion.query.scope);
1315
+ /*#__PURE__*/ jsx(FilePreviewPanels, {
1316
+ contentTab: contentTab,
1317
+ directoryTab: level.directoryTab,
1318
+ fileTab: fileTab,
1319
+ panelProps: panelProps,
1320
+ panelClass: "ov-file-preview__panel",
1321
+ levelKey: levelKey,
1322
+ onLevelChange: (key)=>{
1323
+ if (editing.saving || editing.loadingEdit || key === levelKey) return;
1324
+ if (fileTab?.activeLevelKey === void 0) editing.discard(activeTab);
1325
+ selectLevel(key);
2093
1326
  },
2094
- assistId: assistId,
2095
- anchorRef: composerRef,
2096
- onFocus: ()=>setDismissedValue(null),
2097
- value: value,
2098
- history: records.map((record)=>formatTuiCommandInput(record.command, commands)),
2099
- labels: text,
2100
- completion: completion,
2101
- disabled: disabled,
2102
- readOnly: readOnly,
2103
- canSubmit: Boolean(onSubmit) && !readOnly,
2104
- activeIndex: activeIndex,
2105
- onActiveIndexChange: setActiveIndexSafely,
2106
- onValueChange: onValueChange ? updateValue : void 0,
2107
- onSubmit: (command)=>{
2108
- if (disabled || readOnly) return;
2109
- followOutput.current = true;
2110
- onSubmit?.(command);
2111
- },
2112
- onOpenUri: onOpenUri
1327
+ text: tabText,
1328
+ locale: locale,
1329
+ actions: actions,
1330
+ actionContext: actionContext,
1331
+ editing: editing,
1332
+ viewMode: viewMode,
1333
+ setViewMode: setViewMode,
1334
+ onHandleChange: onHandleChange,
1335
+ onViewModesChange: onViewModesChange,
1336
+ linkClickHandler: linkClickHandler,
1337
+ activeTab: activeTab
2113
1338
  })
2114
1339
  ]
2115
1340
  });
2116
- }
2117
- export { Tui };
1341
+ });
1342
+ export { LoadFailedState, LoadingState, UnsupportedState, file_preview_FilePreview as FilePreview, toFileSource };