@openviking/react 0.1.2 → 0.1.4

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 (249) hide show
  1. package/LICENSE.arco-design +21 -0
  2. package/README.md +25 -15
  3. package/dist/0~0.js +19 -0
  4. package/dist/2~0.js +2360 -0
  5. package/dist/2~0.js.LICENSE.txt +23 -0
  6. package/dist/3c~0.js +2206 -0
  7. package/dist/3~0.js +1205 -1982
  8. package/dist/4~0.js +18 -0
  9. package/dist/9~0.js +43 -0
  10. package/dist/ae~0.js +487 -0
  11. package/dist/ark-message-list-view~0.js +530 -119
  12. package/dist/a~0.js +1289 -10
  13. package/dist/c7~0.js +38 -2
  14. package/dist/ck~0.js +23 -0
  15. package/dist/client/index.d.ts +2 -1
  16. package/dist/client/paths.d.ts +64 -0
  17. package/dist/client/provider.d.ts +5 -3
  18. package/dist/client/response.d.ts +31 -0
  19. package/dist/client/sse.d.ts +39 -0
  20. package/dist/client/stream.d.ts +26 -0
  21. package/dist/client/types.d.ts +109 -5
  22. package/dist/client.js +2 -239
  23. package/dist/components/agent-connect/agent-connect-copy.d.ts +19 -33
  24. package/dist/components/agent-connect/agent-connect-design.d.ts +1935 -167
  25. package/dist/components/agent-connect/agent-connect-instructions.d.ts +135 -0
  26. package/dist/components/agent-connect/locales/en-US.d.ts +20 -0
  27. package/dist/components/agent-connect/locales/zh-CN.d.ts +3 -0
  28. package/dist/components/agent-connect/style.css +14 -2
  29. package/dist/components/agent-connect/types.d.ts +2 -1
  30. package/dist/components/directory-tour/directory-tour.d.ts +3 -0
  31. package/dist/components/directory-tour/index.d.ts +3 -0
  32. package/dist/components/directory-tour/locales/en-US.d.ts +3 -0
  33. package/dist/components/directory-tour/locales/zh-CN.d.ts +3 -0
  34. package/dist/components/directory-tour/steps.d.ts +5 -0
  35. package/dist/components/directory-tour/style.css +237 -0
  36. package/dist/components/directory-tour/style.js +0 -0
  37. package/dist/components/directory-tour/types.d.ts +42 -0
  38. package/dist/components/directory-tour/use-tour-anchor.d.ts +5 -0
  39. package/dist/components/directory-tour/use-tour-position.d.ts +12 -0
  40. package/dist/components/directory-tour.js +40 -0
  41. package/dist/components/file-browser-entries/file-browser-entries-icons.d.ts +18 -0
  42. package/dist/components/file-browser-entries/file-browser-entries.d.ts +14 -0
  43. package/dist/components/file-browser-entries/index.d.ts +1 -0
  44. package/dist/components/file-browser-entries/labels.d.ts +3 -0
  45. package/dist/components/file-browser-entries/locales/en-US.d.ts +6 -0
  46. package/dist/components/file-browser-entries/locales/zh-CN.d.ts +6 -0
  47. package/dist/components/file-browser-entries/style.css +905 -0
  48. package/dist/components/file-browser-entries/style.js +0 -0
  49. package/dist/components/file-browser-entries/types.d.ts +63 -0
  50. package/dist/components/file-browser-entries.js +1 -0
  51. package/dist/components/file-preview/file-preview-adapters.d.ts +2 -1
  52. package/dist/components/file-preview/file-preview-body.d.ts +2 -1
  53. package/dist/components/file-preview/file-preview-breadcrumb-menu.d.ts +2 -1
  54. package/dist/components/file-preview/file-preview-breadcrumb.d.ts +2 -1
  55. package/dist/components/file-preview/file-preview-directory.d.ts +33 -0
  56. package/dist/components/file-preview/file-preview-handle-adapters.d.ts +2 -1
  57. package/dist/components/file-preview/file-preview-header.d.ts +25 -3
  58. package/dist/components/file-preview/file-preview-image-frame.d.ts +5 -0
  59. package/dist/components/file-preview/file-preview-labels.d.ts +2 -14
  60. package/dist/components/file-preview/file-preview-level-tabs.d.ts +7 -0
  61. package/dist/components/file-preview/file-preview-markdown-images.d.ts +13 -0
  62. package/dist/components/file-preview/file-preview-panels.d.ts +44 -0
  63. package/dist/components/file-preview/file-preview-state.d.ts +10 -3
  64. package/dist/components/file-preview/file-preview-viewer.d.ts +2 -1
  65. package/dist/components/file-preview/file-preview.d.ts +1 -1
  66. package/dist/components/file-preview/index.d.ts +2 -1
  67. package/dist/components/file-preview/level-selection.d.ts +25 -0
  68. package/dist/components/file-preview/locales/en-US.d.ts +16 -0
  69. package/dist/components/file-preview/locales/zh-CN.d.ts +16 -0
  70. package/dist/components/file-preview/style.css +234 -3
  71. package/dist/components/file-preview/types.d.ts +134 -11
  72. package/dist/components/file-preview/use-directory-level.d.ts +30 -0
  73. package/dist/components/file-preview/use-file-preview-actions-context.d.ts +6 -2
  74. package/dist/components/file-preview/use-file-preview-actions.d.ts +1 -1
  75. package/dist/components/file-preview/use-file-preview-levels.d.ts +7 -0
  76. package/dist/components/file-preview/use-file-preview-view-state.d.ts +15 -0
  77. package/dist/components/file-preview.js +1 -1
  78. package/dist/components/file-tree/file-tree-info-labels.d.ts +0 -15
  79. package/dist/components/file-tree/file-tree-info-tooltip.d.ts +7 -7
  80. package/dist/components/file-tree/file-tree-labels.d.ts +4 -0
  81. package/dist/components/file-tree/file-tree-row-context.d.ts +0 -5
  82. package/dist/components/file-tree/file-tree-row-shared.d.ts +1 -4
  83. package/dist/components/file-tree/file-tree-toolbar.d.ts +11 -0
  84. package/dist/components/file-tree/file-tree-tooltip-surface.d.ts +8 -8
  85. package/dist/components/file-tree/locales/en-US.d.ts +69 -0
  86. package/dist/components/file-tree/locales/zh-CN.d.ts +69 -0
  87. package/dist/components/file-tree/style.css +207 -42
  88. package/dist/components/file-tree/types.d.ts +58 -10
  89. package/dist/components/file-tree/use-file-tree-actions.d.ts +4 -1
  90. package/dist/components/scope-switcher/labels.d.ts +3 -0
  91. package/dist/components/scope-switcher/locales/en-US.d.ts +8 -0
  92. package/dist/components/scope-switcher/locales/zh-CN.d.ts +8 -0
  93. package/dist/components/scope-switcher/scope-panel.d.ts +1 -1
  94. package/dist/components/scope-switcher/scope-switcher.d.ts +1 -1
  95. package/dist/components/scope-switcher/style.css +14 -2
  96. package/dist/components/scope-switcher/types.d.ts +11 -5
  97. package/dist/components/style.css +10144 -6840
  98. package/dist/components/tui/locales/en-US.d.ts +35 -0
  99. package/dist/components/tui/locales/zh-CN.d.ts +35 -0
  100. package/dist/components/tui/style.css +310 -18
  101. package/dist/components/tui/tui-copy-button.d.ts +1 -1
  102. package/dist/components/tui/tui-empty.d.ts +25 -0
  103. package/dist/components/tui/tui-labels.d.ts +2 -2
  104. package/dist/components/tui/tui.d.ts +1 -1
  105. package/dist/components/tui/types.d.ts +35 -12
  106. package/dist/components/tui.js +1 -1
  107. package/dist/components/viking-bot/ark-adapter.d.ts +2 -0
  108. package/dist/components/viking-bot/ark-message-body.d.ts +1 -7
  109. package/dist/components/viking-bot/ark-message-list-view.d.ts +1 -1
  110. package/dist/components/viking-bot/ark-message-row.d.ts +3 -1
  111. package/dist/components/viking-bot/bot-popover.d.ts +9 -0
  112. package/dist/components/viking-bot/capabilities.d.ts +49 -0
  113. package/dist/components/viking-bot/composer-attachments.d.ts +13 -0
  114. package/dist/components/viking-bot/composer-controls.d.ts +10 -0
  115. package/dist/components/viking-bot/composer-footer.d.ts +17 -0
  116. package/dist/components/viking-bot/composer-references.d.ts +20 -0
  117. package/dist/components/viking-bot/composer-text.d.ts +10 -0
  118. package/dist/components/viking-bot/index.d.ts +3 -1
  119. package/dist/components/viking-bot/interaction/ask-human-fields.d.ts +1 -1
  120. package/dist/components/viking-bot/interaction/labels.d.ts +2 -1
  121. package/dist/components/viking-bot/interaction/locales/en-US.d.ts +17 -0
  122. package/dist/components/viking-bot/interaction/locales/zh-CN.d.ts +17 -0
  123. package/dist/components/viking-bot/interaction/types.d.ts +8 -3
  124. package/dist/components/viking-bot/interaction/viking-bot-interaction.d.ts +1 -1
  125. package/dist/components/viking-bot/labels.d.ts +2 -0
  126. package/dist/components/viking-bot/locales/en-US.d.ts +40 -0
  127. package/dist/components/viking-bot/locales/zh-CN.d.ts +40 -0
  128. package/dist/components/viking-bot/markdown-links.d.ts +27 -0
  129. package/dist/components/viking-bot/message-activity.d.ts +7 -0
  130. package/dist/components/viking-bot/message-code-block.d.ts +8 -0
  131. package/dist/components/viking-bot/message-footer.d.ts +8 -0
  132. package/dist/components/viking-bot/message-markdown.d.ts +9 -0
  133. package/dist/components/viking-bot/model.d.ts +37 -3
  134. package/dist/components/viking-bot/quick-input-tracker.d.ts +16 -0
  135. package/dist/components/viking-bot/quick-inputs.d.ts +6 -0
  136. package/dist/components/viking-bot/reference-picker.d.ts +16 -0
  137. package/dist/components/viking-bot/session-actions.d.ts +11 -0
  138. package/dist/components/viking-bot/style.css +1486 -48
  139. package/dist/components/viking-bot/tool-activity.d.ts +9 -0
  140. package/dist/components/viking-bot/tool-result-links.d.ts +6 -0
  141. package/dist/components/viking-bot/types.d.ts +21 -9
  142. package/dist/components/viking-bot/use-quick-input.d.ts +17 -0
  143. package/dist/components/viking-bot/viking-bot.d.ts +1 -6
  144. package/dist/components/viking-bot/welcome.d.ts +8 -0
  145. package/dist/components/viking-bot.js +1 -1
  146. package/dist/c~0.js +533 -66
  147. package/dist/e~0.js +333 -0
  148. package/dist/file-preview-viewer~0.js +90 -8
  149. package/dist/f~0.js +39 -47
  150. package/dist/h~0.js +5 -1
  151. package/dist/icons/console-icons.d.ts +12 -0
  152. package/dist/icons/index.d.ts +2 -0
  153. package/dist/icons/viking-bot-icons.d.ts +6 -0
  154. package/dist/icons.js +1 -0
  155. package/dist/index.d.ts +4 -0
  156. package/dist/index.js +3 -1
  157. package/dist/k~0.js +1 -0
  158. package/dist/lg~0.js +155 -0
  159. package/dist/lk~0.js +37 -8
  160. package/dist/l~0.js +1447 -25
  161. package/dist/modules/console/command-messages.d.ts +19 -0
  162. package/dist/modules/console/command-parser.d.ts +15 -0
  163. package/dist/modules/console/command-specs.d.ts +15 -0
  164. package/dist/modules/console/commands.d.ts +6 -0
  165. package/dist/modules/console/console-history.d.ts +10 -0
  166. package/dist/modules/console/console-toolbar.d.ts +18 -0
  167. package/dist/modules/console/console.d.ts +1 -3
  168. package/dist/modules/console/controller.d.ts +17 -0
  169. package/dist/modules/console/index.d.ts +2 -0
  170. package/dist/modules/console/labels.d.ts +61 -0
  171. package/dist/modules/console/locales/en-US.d.ts +49 -0
  172. package/dist/modules/console/locales/zh-CN.d.ts +3 -0
  173. package/dist/modules/console/localize-records.d.ts +11 -0
  174. package/dist/modules/console/results.d.ts +2 -0
  175. package/dist/modules/console/style.css +10905 -15
  176. package/dist/modules/console/types.d.ts +59 -80
  177. package/dist/modules/console/use-console.d.ts +3 -0
  178. package/dist/modules/console.js +1 -1
  179. package/dist/modules/file-browser/file-browser-detail-icons.d.ts +10 -0
  180. package/dist/modules/file-browser/file-browser-detail.d.ts +34 -0
  181. package/dist/modules/file-browser/file-browser-empty.d.ts +3 -0
  182. package/dist/modules/file-browser/file-browser-failure.d.ts +22 -0
  183. package/dist/modules/file-browser/file-browser-icons.d.ts +18 -0
  184. package/dist/modules/file-browser/file-browser-labels.d.ts +27 -0
  185. package/dist/modules/file-browser/file-browser-layout.d.ts +27 -0
  186. package/dist/modules/file-browser/file-browser-toolbar.d.ts +17 -0
  187. package/dist/modules/file-browser/file-browser-tree-pane.d.ts +32 -0
  188. package/dist/modules/file-browser/file-browser.d.ts +14 -5
  189. package/dist/modules/file-browser/find-result.d.ts +24 -0
  190. package/dist/modules/file-browser/fs-directories.d.ts +33 -0
  191. package/dist/modules/file-browser/fs-file-kind.d.ts +19 -0
  192. package/dist/modules/file-browser/fs-icon.d.ts +17 -0
  193. package/dist/modules/file-browser/fs-model.d.ts +106 -0
  194. package/dist/modules/file-browser/fs-page.d.ts +16 -0
  195. package/dist/modules/file-browser/fs-path.d.ts +16 -0
  196. package/dist/modules/file-browser/fs-read-directory.d.ts +16 -0
  197. package/dist/modules/file-browser/fs-scope.d.ts +22 -0
  198. package/dist/modules/file-browser/fs-stat.d.ts +25 -0
  199. package/dist/modules/file-browser/index.d.ts +3 -1
  200. package/dist/modules/file-browser/locales/en-US.d.ts +25 -0
  201. package/dist/modules/file-browser/locales/zh-CN.d.ts +25 -0
  202. package/dist/modules/file-browser/style.css +6388 -41
  203. package/dist/modules/file-browser/types.d.ts +206 -52
  204. package/dist/modules/file-browser/use-container-width.d.ts +17 -0
  205. package/dist/modules/file-browser/use-file-browser.d.ts +54 -0
  206. package/dist/modules/file-browser/use-fs-preview.d.ts +65 -0
  207. package/dist/modules/file-browser/use-fs-search.d.ts +35 -0
  208. package/dist/modules/file-browser/use-fs-tree.d.ts +43 -0
  209. package/dist/modules/file-browser/use-request-scope.d.ts +39 -0
  210. package/dist/modules/file-browser.js +1 -1
  211. package/dist/modules/scope-cascade/labels.d.ts +6 -0
  212. package/dist/modules/scope-cascade/locales/en-US.d.ts +6 -0
  213. package/dist/modules/scope-cascade/locales/zh-CN.d.ts +3 -0
  214. package/dist/modules/scope-cascade/scope-cascade.d.ts +1 -1
  215. package/dist/modules/scope-cascade/style.css +14 -2
  216. package/dist/modules/scope-cascade/types.d.ts +3 -0
  217. package/dist/modules/style.css +17338 -77
  218. package/dist/nx~0.js +15 -0
  219. package/dist/n~0.js +403 -144
  220. package/dist/o~0.js +106 -0
  221. package/dist/p~0.js +27 -0
  222. package/dist/rslib-runtime~0.js +42 -0
  223. package/dist/r~0.js +152 -0
  224. package/dist/shared/components/divider/divider.d.ts +18 -0
  225. package/dist/shared/components/divider/index.d.ts +1 -0
  226. package/dist/shared/components/divider/types.d.ts +35 -0
  227. package/dist/shared/components/drawer/drawer.d.ts +13 -0
  228. package/dist/shared/components/drawer/index.d.ts +1 -0
  229. package/dist/shared/components/overlay/types.d.ts +7 -0
  230. package/dist/shared/components/{tooltip/use-tooltip-position.d.ts → overlay/use-overlay-position.d.ts} +6 -6
  231. package/dist/shared/components/popover/index.d.ts +1 -0
  232. package/dist/shared/components/popover/popover.d.ts +23 -0
  233. package/dist/shared/components/popover/types.d.ts +50 -0
  234. package/dist/shared/components/resizable/clamp.d.ts +13 -0
  235. package/dist/shared/components/resizable/index.d.ts +2 -0
  236. package/dist/shared/components/resizable/resizable-handle.d.ts +25 -0
  237. package/dist/shared/components/resizable/types.d.ts +48 -0
  238. package/dist/shared/components/tooltip/types.d.ts +5 -5
  239. package/dist/shared/components/tooltip/use-portal-theme.d.ts +3 -0
  240. package/dist/shared/locale.d.ts +6 -0
  241. package/dist/style.css +27753 -7176
  242. package/dist/u~0.js +75 -58
  243. package/dist/viking-bot-composer~0.js +434 -77
  244. package/dist/viking-bot-interaction~0.js +11 -11
  245. package/package.json +18 -4
  246. package/dist/components/file-tree/file-tree-tooltip.d.ts +0 -17
  247. package/dist/components/tui/tui-labels-zh.d.ts +0 -2
  248. package/dist/g~0.js +0 -1207
  249. package/dist/modules/file-browser/file-browser-toolbar-button.d.ts +0 -10
package/dist/3~0.js CHANGED
@@ -1,2117 +1,1340 @@
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
+ function FilePreviewImageFrame({ children }) {
6
+ const [dimensions, setDimensions] = useState('');
71
7
  return /*#__PURE__*/ jsxs("div", {
72
- className: "ov-tui__hint",
8
+ className: "ov-file-preview__image-frame",
9
+ onLoadCapture: (event)=>{
10
+ const image = event.target;
11
+ if (image instanceof HTMLImageElement && image.closest('[data-file-viewer-image]')) setDimensions(`${image.naturalWidth} × ${image.naturalHeight}`);
12
+ },
73
13
  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
- })
14
+ children,
15
+ dimensions ? /*#__PURE__*/ jsx("span", {
16
+ className: "ov-file-preview__image-size",
17
+ children: dimensions
18
+ }) : null
177
19
  ]
178
20
  });
179
21
  }
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
22
+ function FilePreviewEditor({ draft, text, loading, failed, saveFailed, saving, onChange, onCancel }) {
23
+ if (loading) return /*#__PURE__*/ jsx("div", {
24
+ className: "ov-file-preview__state",
25
+ role: "status",
26
+ children: /*#__PURE__*/ jsx("p", {
27
+ className: "ov-file-preview__state-title",
28
+ children: text.loading
29
+ })
263
30
  });
264
- return /*#__PURE__*/ jsxs(Fragment, {
31
+ if (failed) return /*#__PURE__*/ jsxs("div", {
32
+ className: "ov-file-preview__state",
33
+ role: "alert",
265
34
  children: [
266
- text.slice(0, index),
267
- /*#__PURE__*/ jsx("mark", {
268
- className: "ov-tui__mark",
269
- children: text.slice(index, index + normalized.length)
35
+ /*#__PURE__*/ jsx("p", {
36
+ className: "ov-file-preview__state-title",
37
+ children: text.loadFailed
270
38
  }),
271
- text.slice(index + normalized.length)
39
+ /*#__PURE__*/ jsx("button", {
40
+ type: "button",
41
+ className: "ov-file-preview__button",
42
+ onClick: onCancel,
43
+ children: text.leaveEditing
44
+ })
272
45
  ]
273
46
  });
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
47
  return /*#__PURE__*/ jsxs("div", {
297
- ref: panelRef,
298
- className: "ov-tui__assist",
299
- popover: "manual",
300
- "data-details-only": !options.length || void 0,
48
+ className: "ov-file-preview__editor",
301
49
  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
- })
50
+ /*#__PURE__*/ jsx("textarea", {
51
+ className: "ov-file-preview__textarea",
52
+ value: draft,
53
+ readOnly: saving,
54
+ "aria-label": text.editor,
55
+ spellCheck: false,
56
+ onChange: (event)=>onChange(event.target.value)
57
+ }),
58
+ saveFailed ? /*#__PURE__*/ jsx("p", {
59
+ className: "ov-file-preview__notice",
60
+ role: "alert",
61
+ children: text.saveFailed
62
+ }) : null
361
63
  ]
362
64
  });
363
65
  }
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);
66
+ function encodeText(content) {
67
+ return new TextEncoder().encode(content);
406
68
  }
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)
69
+ function toFileSource(source) {
70
+ const { content, file, url, fileName, fileType } = source;
71
+ if ('string' == typeof content) return {
72
+ kind: 'bytes',
73
+ bytes: encodeText(content),
74
+ name: fileName,
75
+ ...fileType ? {
76
+ fileType
77
+ } : {}
450
78
  };
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
- }
79
+ if (file) return {
80
+ kind: 'file',
81
+ file: "u" < typeof File || file instanceof File ? file : new File([
82
+ file
83
+ ], fileName, {
84
+ type: file.type
85
+ }),
86
+ name: fileName,
87
+ ...fileType ? {
88
+ fileType
89
+ } : {}
605
90
  };
606
- return {
607
- change,
608
- handleKeyDown
91
+ if (url) return {
92
+ kind: 'url',
93
+ url,
94
+ name: fileName,
95
+ ...fileType ? {
96
+ fileType
97
+ } : {}
609
98
  };
610
99
  }
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
- });
100
+ function readCopyableText(source) {
101
+ const { content } = source;
102
+ return 'string' == typeof content && content.length > 0 ? content : void 0;
713
103
  }
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
- });
104
+ function isEmptyFile(source) {
105
+ return '' === source.content;
786
106
  }
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
- });
107
+ function StatePanel({ title, description, action, icon }) {
810
108
  return /*#__PURE__*/ jsxs("div", {
811
- ref: anchorRef,
812
- className: "ov-tui__composer",
813
- "data-disabled": disabled || void 0,
109
+ className: "ov-file-preview__state",
110
+ role: "status",
814
111
  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
- ]
112
+ icon,
113
+ /*#__PURE__*/ jsx("p", {
114
+ className: "ov-file-preview__state-title",
115
+ children: title
902
116
  }),
903
- disabled ? /*#__PURE__*/ jsx("div", {
904
- className: "ov-tui__unavailable",
905
- role: "status",
906
- children: labels.unavailableMessage
907
- }) : null
117
+ description ? /*#__PURE__*/ jsx("p", {
118
+ className: "ov-file-preview__state-description",
119
+ children: description
120
+ }) : null,
121
+ action
908
122
  ]
909
123
  });
910
124
  }
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
- ]
125
+ function EmptyFileState({ text }) {
126
+ return /*#__PURE__*/ jsx(StatePanel, {
127
+ title: text.fileEmpty
1053
128
  });
1054
129
  }
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
130
+ function LoadingState({ text }) {
131
+ return /*#__PURE__*/ jsx(StatePanel, {
132
+ title: text.loading ?? 'Loading…',
133
+ icon: /*#__PURE__*/ jsx("span", {
134
+ className: "ov-file-preview__spinner"
135
+ })
1096
136
  });
1097
137
  }
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
- ]
138
+ function LoadFailedState({ text, description, onRetry }) {
139
+ return /*#__PURE__*/ jsx(StatePanel, {
140
+ title: text.loadFailed,
141
+ description: description,
142
+ action: onRetry ? /*#__PURE__*/ jsx("button", {
143
+ type: "button",
144
+ className: "ov-file-preview__button",
145
+ onClick: onRetry,
146
+ children: text.retry
147
+ }) : void 0
1118
148
  });
1119
149
  }
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 '';
150
+ function UnsupportedState({ text, onDownload }) {
151
+ return /*#__PURE__*/ jsx(StatePanel, {
152
+ title: text.unsupported,
153
+ action: onDownload ? /*#__PURE__*/ jsx("button", {
154
+ type: "button",
155
+ className: "ov-file-preview__button",
156
+ onClick: onDownload,
157
+ children: text.download
158
+ }) : void 0
159
+ });
1127
160
  }
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)
161
+ function TruncatedNotice({ text }) {
162
+ return /*#__PURE__*/ jsx("p", {
163
+ className: "ov-file-preview__notice",
164
+ children: text.truncated
1137
165
  });
1138
166
  }
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
167
+ const Viewer = /*#__PURE__*/ lazy(async ()=>{
168
+ const { FilePreviewViewer } = await import("./file-preview-viewer~0.js");
169
+ return {
170
+ default: FilePreviewViewer
171
+ };
172
+ });
173
+ function LazyFilePreviewViewer(props) {
174
+ return /*#__PURE__*/ jsx(Suspense, {
175
+ fallback: /*#__PURE__*/ jsx(LoadingState, {
176
+ text: props.text
177
+ }),
178
+ children: /*#__PURE__*/ jsx(Viewer, {
179
+ ...props
1147
180
  })
1148
181
  });
1149
182
  }
1150
- function MarkdownPre({ children }) {
1151
- const options = useContext(TuiMarkdownContext);
1152
- return /*#__PURE__*/ jsx(TuiCodeBlock, {
1153
- text: textOf(children).replace(/\n$/, ''),
1154
- ...options
183
+ function FilePreviewBody({ fileTab, text, locale, viewMode, editing, onViewModeChange, onHandleChange, onViewModesChange, onDownload, onLinkClick, onCancelEditing }) {
184
+ if (editing.editing || editing.loadingEdit || editing.editFailed) return /*#__PURE__*/ jsx(FilePreviewEditor, {
185
+ draft: editing.draft,
186
+ text: text,
187
+ loading: editing.loadingEdit,
188
+ failed: editing.editFailed,
189
+ saveFailed: editing.saveFailed,
190
+ saving: editing.saving,
191
+ onChange: editing.updateDraft,
192
+ onCancel: onCancelEditing
193
+ });
194
+ if (fileTab.loading) return /*#__PURE__*/ jsx(LoadingState, {
195
+ text: text
196
+ });
197
+ if (isEmptyFile(fileTab.source)) return /*#__PURE__*/ jsx(EmptyFileState, {
198
+ text: text
1155
199
  });
200
+ return /*#__PURE__*/ jsx(FilePreviewImageFrame, {
201
+ children: /*#__PURE__*/ jsx(LazyFilePreviewViewer, {
202
+ tab: fileTab,
203
+ text: text,
204
+ locale: locale,
205
+ viewMode: viewMode,
206
+ onViewModeChange: onViewModeChange,
207
+ onDownload: onDownload,
208
+ onHandleChange: onHandleChange,
209
+ onViewModesChange: onViewModesChange,
210
+ onLinkClick: onLinkClick
211
+ })
212
+ }, JSON.stringify([
213
+ fileTab.source.path,
214
+ fileTab.source.fileName,
215
+ fileTab.source.url
216
+ ]));
1156
217
  }
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
218
+ function FilePreviewLevelTabs({ levels, activeKey, disabled, onSelect }) {
219
+ if (!levels?.length) return null;
220
+ return /*#__PURE__*/ jsx("div", {
221
+ className: "ov-file-preview__levels ov-file-preview__levels--file",
222
+ role: "group",
223
+ children: levels.map((level)=>/*#__PURE__*/ jsx("button", {
224
+ type: "button",
225
+ className: "ov-file-preview__level",
226
+ "aria-pressed": level.key === activeKey,
227
+ disabled: disabled || level.disabled,
228
+ onClick: ()=>onSelect(level.key),
229
+ children: level.label
230
+ }, level.key))
1175
231
  });
1176
232
  }
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
- })
233
+ function FilePreviewBreadcrumbMenu({ hiddenPathLabel, segments, canOpen, disabled, onOpen }) {
234
+ return /*#__PURE__*/ jsx(Tooltip, {
235
+ trigger: "click",
236
+ placement: "bottom",
237
+ className: "ov-file-preview__breadcrumb-menu-surface",
238
+ content: /*#__PURE__*/ jsx("ul", {
239
+ className: "ov-file-preview__breadcrumb-menu",
240
+ role: "menu",
241
+ children: segments.map((segment)=>/*#__PURE__*/ jsx("li", {
242
+ role: "none",
243
+ children: /*#__PURE__*/ jsxs("button", {
244
+ type: "button",
245
+ role: "menuitem",
246
+ disabled: !canOpen || disabled,
247
+ onClick: ()=>onOpen(segment.uri),
248
+ children: [
249
+ segment.label,
250
+ "/"
251
+ ]
252
+ })
253
+ }, segment.uri))
1244
254
  }),
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
- })
255
+ children: /*#__PURE__*/ jsx("button", {
256
+ type: "button",
257
+ className: "ov-file-preview__breadcrumb-more",
258
+ "aria-label": hiddenPathLabel,
259
+ "aria-haspopup": "menu",
260
+ children: "…"
1273
261
  })
1274
262
  });
1275
263
  }
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;
264
+ const MAX_ANCESTOR_WIDTH = 160;
265
+ const OVERFLOW_BUTTON_WIDTH = 36;
266
+ const SHIFT_DURATION = 160;
267
+ function prefersReducedMotion() {
268
+ return "u" > typeof window && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1287
269
  }
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
- }
270
+ function measureAvailable(root) {
271
+ const row = root.parentElement;
272
+ if (!row) return root.clientWidth;
273
+ const style = window.getComputedStyle(row);
274
+ const gap = Number.parseFloat(style.columnGap) || 0;
275
+ const siblings = Array.from(row.children).filter((child)=>child !== root && child instanceof HTMLElement);
276
+ const siblingWidth = siblings.reduce((sum, child)=>sum + child.getBoundingClientRect().width, 0);
277
+ const siblingGaps = gap * siblings.length;
278
+ return Math.max(0, row.clientWidth - siblingWidth - siblingGaps);
1297
279
  }
1298
- function TuiResourcePreview({ content, onOpenUri, onCopy, labels }) {
1299
- const metaLabel = content.meta?.filter(Boolean).join(' · ') || content.description;
1300
- const target = content.navigationUri ?? content.uri;
280
+ function FilePreviewBreadcrumb({ hiddenPathLabel, segments, title, canOpen, disabled, onOpen }) {
281
+ const rootRef = useRef(null);
282
+ const measureRef = useRef(null);
283
+ const currentRef = useRef(null);
284
+ const previousRef = useRef(void 0);
285
+ const [hiddenCount, setHiddenCount] = useState(0);
286
+ const [rootHidden, setRootHidden] = useState(false);
287
+ const [overflowing, setOverflowing] = useState(false);
288
+ const signature = JSON.stringify([
289
+ segments.map((s)=>s.uri),
290
+ title
291
+ ]);
292
+ useLayoutEffect(()=>{
293
+ const root = rootRef.current;
294
+ const measure = measureRef.current;
295
+ if (!root || !measure) return;
296
+ const run = ()=>{
297
+ const widths = Array.from(measure.children).map((child)=>child.getBoundingClientRect().width);
298
+ const available = measureAvailable(root);
299
+ const isOverflowing = widths.reduce((sum, width)=>sum + width, 0) > available;
300
+ const capped = isOverflowing ? widths.map((width, index)=>index < widths.length - 1 ? Math.min(width, MAX_ANCESTOR_WIDTH) : width) : widths;
301
+ const ancestors = capped.slice(0, -1);
302
+ const trailing = capped[capped.length - 1] ?? 0;
303
+ const total = capped.reduce((sum, width)=>sum + width, 0);
304
+ setOverflowing(isOverflowing);
305
+ let count = 0;
306
+ let used = total;
307
+ while(used > available && count < ancestors.length - 1){
308
+ used -= ancestors[count + 1] ?? 0;
309
+ if (0 === count) used += OVERFLOW_BUTTON_WIDTH;
310
+ count += 1;
311
+ }
312
+ let hideRoot = false;
313
+ if (used > available && ancestors.length > 0) {
314
+ const rootCollapsed = OVERFLOW_BUTTON_WIDTH + trailing;
315
+ hideRoot = rootCollapsed < used;
316
+ }
317
+ setHiddenCount(hideRoot ? ancestors.length : count);
318
+ setRootHidden(hideRoot);
319
+ };
320
+ run();
321
+ const observer = new ResizeObserver(run);
322
+ observer.observe(root);
323
+ observer.observe(measure);
324
+ if (root.parentElement) observer.observe(root.parentElement);
325
+ return ()=>observer.disconnect();
326
+ }, [
327
+ segments,
328
+ title
329
+ ]);
330
+ useLayoutEffect(()=>{
331
+ const current = currentRef.current;
332
+ const previous = previousRef.current;
333
+ if (!current) return;
334
+ const left = current.offsetLeft;
335
+ previousRef.current = {
336
+ signature,
337
+ left
338
+ };
339
+ if (!previous || previous.signature !== signature || previous.left === left || prefersReducedMotion()) return;
340
+ const animation = current.animate([
341
+ {
342
+ transform: `translateX(${previous.left - left}px)`,
343
+ opacity: 0.65
344
+ },
345
+ {
346
+ transform: 'translateX(0)',
347
+ opacity: 1
348
+ }
349
+ ], {
350
+ duration: SHIFT_DURATION,
351
+ easing: 'cubic-bezier(0.2, 0, 0, 1)'
352
+ });
353
+ return ()=>animation.cancel();
354
+ }, [
355
+ hiddenCount,
356
+ rootHidden,
357
+ signature
358
+ ]);
359
+ const hiddenSegments = segments.filter((_, index)=>rootHidden ? index < hiddenCount : index > 0 && index <= hiddenCount);
360
+ const menu = hiddenSegments.length ? /*#__PURE__*/ jsx(FilePreviewBreadcrumbMenu, {
361
+ hiddenPathLabel: hiddenPathLabel,
362
+ segments: hiddenSegments,
363
+ canOpen: canOpen,
364
+ disabled: disabled,
365
+ onOpen: onOpen
366
+ }) : null;
1301
367
  return /*#__PURE__*/ jsxs("div", {
1302
- className: "ov-tui__preview",
368
+ className: [
369
+ 'ov-file-preview__breadcrumb',
370
+ overflowing ? 'ov-file-preview__breadcrumb--overflowing' : ''
371
+ ].filter(Boolean).join(' '),
372
+ ref: rootRef,
1303
373
  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
374
  /*#__PURE__*/ jsxs("div", {
1321
- className: "ov-tui__preview-body",
375
+ className: "ov-file-preview__breadcrumb-measure",
376
+ ref: measureRef,
377
+ "aria-hidden": "true",
1322
378
  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
- })
379
+ segments.map((segment)=>/*#__PURE__*/ jsxs("span", {
380
+ className: "ov-file-preview__breadcrumb-part",
381
+ children: [
382
+ /*#__PURE__*/ jsx("span", {
383
+ children: segment.isFirst ? `${segment.uri}` : segment.label
384
+ }),
385
+ /*#__PURE__*/ jsx("span", {
386
+ className: "ov-file-preview__breadcrumb-separator",
387
+ children: "/"
388
+ })
389
+ ]
390
+ }, segment.uri)),
391
+ /*#__PURE__*/ jsx("span", {
392
+ children: title
1341
393
  })
1342
394
  ]
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
395
  }),
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
- })
396
+ rootHidden && menu ? /*#__PURE__*/ jsxs("span", {
397
+ className: "ov-file-preview__breadcrumb-part",
398
+ children: [
399
+ menu,
400
+ /*#__PURE__*/ jsx("span", {
401
+ className: "ov-file-preview__breadcrumb-separator",
402
+ children: "/"
403
+ })
404
+ ]
1389
405
  }) : 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",
406
+ segments.map((segment, index)=>{
407
+ if (hiddenSegments.includes(segment)) return null;
408
+ return /*#__PURE__*/ jsxs(external_react_Fragment, {
1398
409
  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
- })
410
+ /*#__PURE__*/ jsxs("span", {
411
+ className: "ov-file-preview__breadcrumb-part",
412
+ children: [
413
+ /*#__PURE__*/ jsx("button", {
414
+ type: "button",
415
+ className: "ov-file-preview__breadcrumb-link",
416
+ disabled: !canOpen || disabled,
417
+ title: segment.uri,
418
+ onClick: ()=>onOpen(segment.uri),
419
+ children: segment.isFirst ? `${segment.uri}` : segment.label
420
+ }),
421
+ /*#__PURE__*/ jsx("span", {
422
+ className: "ov-file-preview__breadcrumb-separator",
423
+ children: "/"
424
+ })
425
+ ]
1412
426
  }),
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
- })
427
+ 0 === index && !rootHidden && menu ? /*#__PURE__*/ jsxs("span", {
428
+ className: "ov-file-preview__breadcrumb-part",
429
+ children: [
430
+ menu,
431
+ /*#__PURE__*/ jsx("span", {
432
+ className: "ov-file-preview__breadcrumb-separator",
433
+ children: "/"
434
+ })
435
+ ]
436
+ }) : null
1435
437
  ]
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
438
+ }, segment.uri);
1470
439
  }),
1471
440
  /*#__PURE__*/ jsx("span", {
1472
- className: "ov-tui__result-command",
1473
- children: command
441
+ ref: currentRef,
442
+ className: "ov-file-preview__breadcrumb-current",
443
+ "aria-current": "page",
444
+ title: title,
445
+ children: title
1474
446
  })
1475
447
  ]
1476
448
  });
1477
449
  }
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
- ]
450
+ function FilePreviewCopyPath({ label, disabled, onClick }) {
451
+ return /*#__PURE__*/ jsx(Tooltip, {
452
+ content: label,
453
+ children: /*#__PURE__*/ jsx("button", {
454
+ type: "button",
455
+ className: "ov-file-preview__action",
456
+ "aria-label": label,
457
+ disabled: disabled,
458
+ onClick: onClick,
459
+ children: /*#__PURE__*/ jsx("svg", {
460
+ width: "14",
461
+ height: "14",
462
+ viewBox: "0 0 14 14",
463
+ fill: "none",
464
+ stroke: "currentColor",
465
+ strokeWidth: "1.2",
466
+ strokeLinecap: "round",
467
+ strokeLinejoin: "round",
468
+ "aria-hidden": "true",
469
+ children: /*#__PURE__*/ jsx("path", {
470
+ 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"
471
+ })
472
+ })
473
+ })
1509
474
  });
1510
475
  }
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
476
+ const SEPARATOR = '/';
477
+ function splitPathSegments(path) {
478
+ const trimmed = path?.trim();
479
+ if (!trimmed) return [];
480
+ const schemeIndex = trimmed.indexOf('://');
481
+ const scheme = schemeIndex >= 0 ? trimmed.slice(0, schemeIndex + 3) : '';
482
+ const body = schemeIndex >= 0 ? trimmed.slice(schemeIndex + 3) : trimmed;
483
+ const parts = body.split(SEPARATOR).filter(Boolean);
484
+ if (parts.length <= 1) return [];
485
+ const root = '' === scheme && body.startsWith(SEPARATOR) ? SEPARATOR : '';
486
+ const segments = [];
487
+ let uri = scheme;
488
+ parts.slice(0, -1).forEach((part, index)=>{
489
+ uri = 0 === index ? `${scheme}${root}${part}` : `${uri}${SEPARATOR}${part}`;
490
+ segments.push({
491
+ label: part,
492
+ uri,
493
+ isFirst: 0 === index
494
+ });
1528
495
  });
496
+ return segments;
497
+ }
498
+ function getPathFileName(path) {
499
+ const trimmed = path?.trim();
500
+ if (!trimmed) return;
501
+ const parts = trimmed.split(SEPARATOR).filter(Boolean);
502
+ return parts[parts.length - 1];
1529
503
  }
1530
- function SearchRow({ item, onOpenUri }) {
504
+ function FilePreviewHeader({ levels, levelKey, onLevelChange, path, fileName, canOpenParent, titleExtra, slotActions, actions, text, onOpenParent }) {
505
+ const segments = splitPathSegments(path);
506
+ const title = getPathFileName(path) ?? fileName;
507
+ const actionNode = 'function' == typeof slotActions ? slotActions(actions) : slotActions;
1531
508
  return /*#__PURE__*/ jsxs(Fragment, {
1532
509
  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",
510
+ /*#__PURE__*/ jsxs("div", {
511
+ className: "ov-file-preview__header",
1547
512
  children: [
1548
- /*#__PURE__*/ jsxs("span", {
1549
- className: "ov-tui__search-main",
513
+ /*#__PURE__*/ jsxs("div", {
514
+ className: "ov-file-preview__path-row",
1550
515
  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
- })
516
+ /*#__PURE__*/ jsx(FilePreviewBreadcrumb, {
517
+ hiddenPathLabel: text.showHiddenPath,
518
+ segments: segments,
519
+ title: title,
520
+ canOpen: canOpenParent,
521
+ disabled: actions.busy,
522
+ onOpen: onOpenParent
1566
523
  }),
1567
- item.levelLabel ? /*#__PURE__*/ jsx("span", {
1568
- className: "ov-tui__search-level",
1569
- children: item.levelLabel
1570
- }) : null
524
+ actions.canCopyPath ? /*#__PURE__*/ jsx(FilePreviewCopyPath, {
525
+ label: text.copyPath,
526
+ disabled: actions.busy,
527
+ onClick: ()=>void actions.copyPath()
528
+ }) : null,
529
+ titleExtra
1571
530
  ]
1572
531
  }),
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
- })
532
+ actionNode ? /*#__PURE__*/ jsx("div", {
533
+ className: "ov-file-preview__header-actions",
534
+ children: actionNode
1588
535
  }) : null
1589
536
  ]
1590
- })
537
+ }),
538
+ onLevelChange ? /*#__PURE__*/ jsx(FilePreviewLevelTabs, {
539
+ levels: levels,
540
+ activeKey: levelKey,
541
+ disabled: actions.busy,
542
+ onSelect: onLevelChange
543
+ }) : null
1591
544
  ]
1592
545
  });
1593
546
  }
1594
- function TuiSearchList({ content, onOpenUri, noResultLabel }) {
1595
- return /*#__PURE__*/ jsxs("div", {
1596
- className: "ov-tui__search",
547
+ function levelToFileTab(tab, key) {
548
+ const level = tab.levels.find((candidate)=>candidate.key === key);
549
+ if (!level) return;
550
+ return {
551
+ kind: 'file',
552
+ key: `${tab.key}::${level.key}`,
553
+ source: level.source,
554
+ resources: level.resources ?? tab.resources,
555
+ resolveImage: tab.resolveImage,
556
+ operations: tab.operations,
557
+ slots: tab.slots
558
+ };
559
+ }
560
+ function FilePreviewDirectoryBody({ tab, text, locale, actions, editing, viewMode, onViewModeChange, onHandleChange, onViewModesChange, onDownload, onCancelEditing }) {
561
+ const [internalKey, setInternalKey] = useState(()=>tab.defaultLevelKey ?? tab.levels[0].key);
562
+ const activeKey = tab.activeLevelKey ?? internalKey;
563
+ const fileTab = levelToFileTab(tab, activeKey) ?? levelToFileTab(tab, tab.levels[0].key);
564
+ const tabsRef = useRef(null);
565
+ const [indicator, setIndicator] = useState({
566
+ left: 0,
567
+ width: 0
568
+ });
569
+ useLayoutEffect(()=>{
570
+ const root = tabsRef.current;
571
+ if (!root) return;
572
+ const measure = ()=>{
573
+ const active = root.querySelector('[aria-selected="true"]');
574
+ if (!active) return;
575
+ setIndicator({
576
+ left: active.offsetLeft,
577
+ width: active.offsetWidth
578
+ });
579
+ };
580
+ measure();
581
+ const observer = new ResizeObserver(measure);
582
+ observer.observe(root);
583
+ return ()=>observer.disconnect();
584
+ }, [
585
+ activeKey,
586
+ tab.levels
587
+ ]);
588
+ const changeLevel = (key)=>{
589
+ if (void 0 === tab.activeLevelKey) setInternalKey(key);
590
+ tab.onActiveLevelChange?.(key);
591
+ };
592
+ const onLevelKeyDown = (event)=>{
593
+ const keys = [
594
+ 'ArrowLeft',
595
+ 'ArrowRight',
596
+ 'Home',
597
+ 'End'
598
+ ];
599
+ if (!keys.includes(event.key)) return;
600
+ const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]:not(:disabled)'));
601
+ if (0 === buttons.length) return;
602
+ const current = buttons.indexOf(event.target);
603
+ const next = 'Home' === event.key ? 0 : 'End' === event.key ? buttons.length - 1 : (current + ('ArrowRight' === event.key ? 1 : -1) + buttons.length) % buttons.length;
604
+ const target = buttons[next];
605
+ if (!target) return;
606
+ event.preventDefault();
607
+ target.focus();
608
+ const key = tab.levels[next]?.key;
609
+ if (void 0 !== key) changeLevel(key);
610
+ };
611
+ if (!fileTab) return null;
612
+ if (tab.loading) return /*#__PURE__*/ jsxs(Fragment, {
1597
613
  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
614
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
615
+ path: fileTab.source.path,
616
+ fileName: fileTab.source.fileName,
617
+ canOpenParent: Boolean(tab.operations?.openParent),
618
+ titleExtra: tab.slots?.titleExtra,
619
+ slotActions: tab.slots?.actions,
620
+ actions: actions,
621
+ text: text,
622
+ onOpenParent: (uri)=>void tab.operations?.openParent?.(uri)
623
+ }),
624
+ /*#__PURE__*/ jsx("div", {
625
+ className: "ov-file-preview__body",
626
+ children: /*#__PURE__*/ jsx(LoadingState, {
627
+ text: text
628
+ })
1618
629
  })
1619
630
  ]
1620
631
  });
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",
632
+ return /*#__PURE__*/ jsxs(Fragment, {
1634
633
  children: [
1635
- /*#__PURE__*/ jsxs("div", {
1636
- className: "ov-tui__skeleton-summary",
634
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
635
+ path: fileTab.source.path,
636
+ fileName: fileTab.source.fileName,
637
+ canOpenParent: Boolean(tab.operations?.openParent),
638
+ titleExtra: tab.slots?.titleExtra,
639
+ slotActions: tab.slots?.actions,
640
+ actions: actions,
641
+ text: text,
642
+ onOpenParent: (uri)=>void tab.operations?.openParent?.(uri)
643
+ }),
644
+ tab.levels.length > 1 ? /*#__PURE__*/ jsxs("div", {
645
+ className: "ov-file-preview__levels",
646
+ role: "tablist",
647
+ ref: tabsRef,
648
+ tabIndex: -1,
649
+ onKeyDown: onLevelKeyDown,
1637
650
  children: [
1638
- /*#__PURE__*/ jsx(Line, {
1639
- width: "100%"
1640
- }),
1641
- /*#__PURE__*/ jsx(Line, {
1642
- width: "56%"
651
+ tab.levels.map((level)=>/*#__PURE__*/ jsx("button", {
652
+ type: "button",
653
+ role: "tab",
654
+ "aria-selected": level.key === activeKey,
655
+ tabIndex: level.key === activeKey ? 0 : -1,
656
+ className: "ov-file-preview__level",
657
+ "data-active": level.key === activeKey || void 0,
658
+ onClick: ()=>changeLevel(level.key),
659
+ children: level.label
660
+ }, level.key)),
661
+ /*#__PURE__*/ jsx("span", {
662
+ "aria-hidden": "true",
663
+ className: "ov-file-preview__level-indicator",
664
+ style: {
665
+ transform: `translateX(${indicator.left}px)`,
666
+ width: indicator.width
667
+ }
1643
668
  })
1644
669
  ]
670
+ }) : null,
671
+ /*#__PURE__*/ jsx("div", {
672
+ className: "ov-file-preview__body",
673
+ children: /*#__PURE__*/ jsx(FilePreviewBody, {
674
+ fileTab: fileTab,
675
+ text: text,
676
+ locale: locale,
677
+ viewMode: viewMode,
678
+ editing: editing,
679
+ onViewModeChange: onViewModeChange,
680
+ onHandleChange: onHandleChange,
681
+ onViewModesChange: onViewModesChange,
682
+ onDownload: onDownload,
683
+ onCancelEditing: onCancelEditing
684
+ }, fileTab.key)
1645
685
  }),
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))
686
+ tab.slots?.footer ? /*#__PURE__*/ jsx("div", {
687
+ className: "ov-file-preview__footer",
688
+ children: tab.slots.footer
689
+ }) : null
1669
690
  ]
1670
691
  });
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))
692
+ }
693
+ function FilePreviewPanels({ levelKey, onLevelChange, contentTab, directoryTab, fileTab, panelProps, panelClass, text, locale, actions, actionContext, editing, viewMode, setViewMode, onHandleChange, onViewModesChange, linkClickHandler, activeTab }) {
694
+ if (contentTab) return /*#__PURE__*/ jsx("div", {
695
+ ...panelProps,
696
+ className: `${panelClass} ${panelClass}--content`,
697
+ children: contentTab.content
1685
698
  });
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))
699
+ if (directoryTab) return /*#__PURE__*/ jsx("div", {
700
+ ...panelProps,
701
+ className: panelClass,
702
+ children: /*#__PURE__*/ jsx(FilePreviewDirectoryBody, {
703
+ tab: directoryTab,
704
+ text: text,
705
+ locale: locale,
706
+ actions: actionContext,
707
+ editing: editing,
708
+ viewMode: viewMode,
709
+ onViewModeChange: setViewMode,
710
+ onHandleChange: onHandleChange,
711
+ onViewModesChange: onViewModesChange,
712
+ onDownload: ()=>{},
713
+ onCancelEditing: actionContext.cancelEditing
714
+ })
1704
715
  });
1705
- if ('resource' === kind) return /*#__PURE__*/ jsxs("div", {
1706
- className: "ov-tui__skeleton",
1707
- "aria-hidden": "true",
716
+ if (!fileTab) return null;
717
+ return /*#__PURE__*/ jsxs("div", {
718
+ ...panelProps,
719
+ className: panelClass,
1708
720
  children: [
1709
- /*#__PURE__*/ jsx(Line, {
1710
- width: "38%"
721
+ /*#__PURE__*/ jsx(FilePreviewHeader, {
722
+ levels: fileTab.levels,
723
+ levelKey: levelKey,
724
+ onLevelChange: onLevelChange,
725
+ path: fileTab.source.path,
726
+ fileName: fileTab.source.fileName,
727
+ canOpenParent: Boolean(fileTab.operations?.openParent),
728
+ titleExtra: fileTab.slots?.titleExtra,
729
+ slotActions: fileTab.slots?.actions,
730
+ actions: actionContext,
731
+ text: text,
732
+ onOpenParent: (uri)=>void actions.openParent(activeTab, uri)
1711
733
  }),
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
734
  /*#__PURE__*/ jsx("div", {
1733
- className: "ov-tui__skeleton-row",
1734
- children: /*#__PURE__*/ jsx(Line, {
1735
- width: "64%"
1736
- })
735
+ className: "ov-file-preview__body",
736
+ children: /*#__PURE__*/ jsx(FilePreviewBody, {
737
+ fileTab: fileTab,
738
+ text: text,
739
+ locale: locale,
740
+ viewMode: viewMode,
741
+ editing: editing,
742
+ onViewModeChange: setViewMode,
743
+ onHandleChange: onHandleChange,
744
+ onViewModesChange: onViewModesChange,
745
+ onDownload: ()=>void actions.download(fileTab),
746
+ onLinkClick: linkClickHandler,
747
+ onCancelEditing: actionContext.cancelEditing
748
+ }, JSON.stringify([
749
+ fileTab.key,
750
+ levelKey
751
+ ]))
1737
752
  }),
1738
- /*#__PURE__*/ jsx("div", {
1739
- className: "ov-tui__skeleton-row",
1740
- children: /*#__PURE__*/ jsx(Line, {
1741
- width: "42%"
1742
- })
1743
- })
753
+ fileTab.source.truncated ? /*#__PURE__*/ jsx(TruncatedNotice, {
754
+ text: text
755
+ }) : null,
756
+ fileTab.slots?.footer ? /*#__PURE__*/ jsx("div", {
757
+ className: "ov-file-preview__footer",
758
+ children: fileTab.slots.footer
759
+ }) : null
1744
760
  ]
1745
761
  });
762
+ }
763
+ function useFilePreviewViewState(identity) {
764
+ const [previous, setPrevious] = useState(identity);
765
+ const [viewMode, setViewMode] = useState('preview');
766
+ const [viewModes, setViewModes] = useState([]);
767
+ const viewerHandle = useMemo(()=>({
768
+ identity,
769
+ current: null
770
+ }), [
771
+ identity
772
+ ]);
773
+ if (previous !== identity) {
774
+ setPrevious(identity);
775
+ setViewMode('preview');
776
+ setViewModes([]);
777
+ }
778
+ const onHandleChange = useCallback((handle)=>{
779
+ viewerHandle.current = handle;
780
+ }, [
781
+ viewerHandle
782
+ ]);
783
+ const onViewModesChange = useCallback((modes)=>{
784
+ const next = modes.map((mode)=>mode.value);
785
+ setViewModes((current)=>current.join() === next.join() ? current : next);
786
+ }, []);
787
+ return {
788
+ viewMode,
789
+ setViewMode,
790
+ viewModes,
791
+ setViewModes,
792
+ viewerHandle,
793
+ onHandleChange,
794
+ onViewModesChange
795
+ };
796
+ }
797
+ function useFilePreviewLevels(selected) {
798
+ const [keys, setKeys] = useState({});
799
+ const levels = 'file' === selected.kind ? selected.levels : void 0;
800
+ const requested = 'file' === selected.kind ? selected.activeLevelKey ?? keys[selected.key] : void 0;
801
+ const level = levels?.find((item)=>item.key === requested && !item.disabled) ?? levels?.find((item)=>!item.disabled);
802
+ const tab = 'file' === selected.kind && level ? {
803
+ ...selected,
804
+ source: level.source,
805
+ resources: level.resources ?? selected.resources,
806
+ editing: level.editing
807
+ } : selected;
808
+ function selectLevel(key) {
809
+ if ('file' !== selected.kind || key === level?.key || !levels?.some((item)=>item.key === key && !item.disabled)) return;
810
+ if (void 0 === selected.activeLevelKey) setKeys((current)=>({
811
+ ...current,
812
+ [selected.key]: key
813
+ }));
814
+ selected.onLevelChange?.(key);
815
+ }
816
+ return {
817
+ tab,
818
+ levelKey: level?.key,
819
+ selectLevel
820
+ };
821
+ }
822
+ const messages = {
823
+ showHiddenPath: 'Show hidden path levels',
824
+ copyPath: 'Copy path',
825
+ editor: 'Edit content',
826
+ leaveEditing: 'Cancel',
827
+ retry: 'Retry',
828
+ loadFailed: 'Failed to load the file',
829
+ saveFailed: 'Failed to save',
830
+ unsupported: 'Preview is not supported for this format',
831
+ loading: 'Loading…',
832
+ download: 'Download',
833
+ fileEmpty: 'This file is empty',
834
+ noContent: 'No content to show',
835
+ truncated: 'Only part of the content is shown'
836
+ };
837
+ const en_US = messages;
838
+ const zh_CN_messages = {
839
+ showHiddenPath: '显示隐藏的路径层级',
840
+ copyPath: '复制路径',
841
+ editor: '编辑正文',
842
+ leaveEditing: '取消',
843
+ retry: '重试',
844
+ loadFailed: '文件加载失败',
845
+ saveFailed: '保存失败',
846
+ unsupported: '不支持预览此格式',
847
+ loading: '加载中…',
848
+ download: '下载',
849
+ fileEmpty: '该文件为空',
850
+ noContent: '暂无可显示内容',
851
+ truncated: '仅显示部分内容'
852
+ };
853
+ const zh_CN = zh_CN_messages;
854
+ const LOCALES = {
855
+ 'en-US': en_US,
856
+ 'zh-CN': zh_CN
857
+ };
858
+ function resolveFilePreviewLabels(locale, labels) {
859
+ return resolveLocale(LOCALES, locale, labels);
860
+ }
861
+ const OUTLINE_RADIUS = 7;
862
+ const STROKE_INSET = 0.5;
863
+ function buildOutline(width, top, bottom, left, right) {
864
+ const radius = OUTLINE_RADIUS;
865
+ if (right - left < 2 * radius + 1) return `M0 ${bottom} H${width}`;
866
+ const lead = left > radius ? [
867
+ `M0 ${bottom}`,
868
+ `H${left - radius}`
869
+ ] : [
870
+ `M${left} ${bottom}`
871
+ ];
872
+ return [
873
+ ...lead,
874
+ `Q${left} ${bottom} ${left} ${bottom - radius}`,
875
+ `V${top + radius}`,
876
+ `Q${left} ${top} ${left + radius} ${top}`,
877
+ `H${right - radius}`,
878
+ `Q${right} ${top} ${right} ${top + radius}`,
879
+ `V${bottom - radius}`,
880
+ `Q${right} ${bottom} ${right + radius} ${bottom}`,
881
+ `H${width}`
882
+ ].join(' ');
883
+ }
884
+ function FilePreviewTabRow({ tabs, activeKey, disabled, tabId, panelId, onSelect }) {
885
+ const trackRef = useRef(null);
886
+ const [outline, setOutline] = useState('');
887
+ useLayoutEffect(()=>{
888
+ const track = trackRef.current;
889
+ if (!track) return;
890
+ const measure = ()=>{
891
+ const trackRect = track.getBoundingClientRect();
892
+ const bottom = trackRect.height - STROKE_INSET;
893
+ const active = track.querySelector('[aria-selected="true"]');
894
+ if (!active) return void setOutline(`M0 ${bottom} H${trackRect.width}`);
895
+ const rect = active.getBoundingClientRect();
896
+ setOutline(buildOutline(trackRect.width, rect.top - trackRect.top + STROKE_INSET, rect.bottom - trackRect.top - STROKE_INSET, rect.left - trackRect.left, rect.right - trackRect.left));
897
+ };
898
+ measure();
899
+ const observer = new ResizeObserver(measure);
900
+ observer.observe(track);
901
+ return ()=>observer.disconnect();
902
+ }, [
903
+ activeKey,
904
+ tabs
905
+ ]);
906
+ const onKeyDown = (event)=>{
907
+ const keys = [
908
+ 'ArrowLeft',
909
+ 'ArrowRight',
910
+ 'Home',
911
+ 'End'
912
+ ];
913
+ if (!keys.includes(event.key)) return;
914
+ const buttons = Array.from(event.currentTarget.querySelectorAll('[role="tab"]:not(:disabled)'));
915
+ if (0 === buttons.length) return;
916
+ const current = buttons.indexOf(event.target);
917
+ const next = 'Home' === event.key ? 0 : 'End' === event.key ? buttons.length - 1 : (current + ('ArrowRight' === event.key ? 1 : -1) + buttons.length) % buttons.length;
918
+ const target = buttons[next];
919
+ if (!target) return;
920
+ event.preventDefault();
921
+ target.focus();
922
+ onSelect(target.dataset.key ?? '');
923
+ };
1746
924
  return /*#__PURE__*/ jsx("div", {
1747
- className: "ov-tui__skeleton",
1748
- "aria-hidden": "true",
925
+ className: "ov-file-preview__tab-row",
1749
926
  children: /*#__PURE__*/ jsxs("div", {
1750
- className: "ov-tui__skeleton-summary",
927
+ role: "tablist",
928
+ className: "ov-file-preview__tab-track",
929
+ ref: trackRef,
930
+ tabIndex: -1,
931
+ onKeyDown: onKeyDown,
1751
932
  children: [
1752
- /*#__PURE__*/ jsx(Line, {
1753
- width: "100%"
933
+ tabs.map((tab)=>{
934
+ const selected = tab.key === activeKey;
935
+ return /*#__PURE__*/ jsxs("button", {
936
+ type: "button",
937
+ role: "tab",
938
+ "data-key": tab.key,
939
+ id: tabId(tab.key),
940
+ "aria-selected": selected,
941
+ "aria-controls": panelId(tab.key),
942
+ tabIndex: selected ? 0 : -1,
943
+ disabled: tab.disabled || disabled,
944
+ title: 'string' == typeof tab.title ? tab.title : void 0,
945
+ className: [
946
+ 'ov-file-preview__tab',
947
+ selected ? 'ov-file-preview__tab--active' : ''
948
+ ].filter(Boolean).join(' '),
949
+ onClick: ()=>onSelect(tab.key),
950
+ children: [
951
+ tab.icon ? /*#__PURE__*/ jsx("span", {
952
+ className: "ov-file-preview__tab-icon",
953
+ "aria-hidden": "true",
954
+ children: tab.icon
955
+ }) : null,
956
+ /*#__PURE__*/ jsx("span", {
957
+ className: "ov-file-preview__tab-title",
958
+ children: tab.title ?? ('file' === tab.kind ? tab.source.fileName : tab.key)
959
+ })
960
+ ]
961
+ }, tab.key);
1754
962
  }),
1755
- /*#__PURE__*/ jsx(Line, {
1756
- width: "64%"
963
+ /*#__PURE__*/ jsx("svg", {
964
+ className: "ov-file-preview__tab-outline",
965
+ "aria-hidden": "true",
966
+ children: /*#__PURE__*/ jsx("path", {
967
+ d: outline
968
+ })
1757
969
  })
1758
970
  ]
1759
971
  })
1760
972
  });
1761
973
  }
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
- });
974
+ async function copyText(value, notify) {
975
+ try {
976
+ await navigator.clipboard.writeText(value);
977
+ } catch {
978
+ return false;
979
+ }
980
+ notify?.(value);
981
+ return true;
1823
982
  }
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
- }
983
+ function useFilePreviewActions() {
984
+ const pending = useRef(new Set());
985
+ async function run(key, { execute, notify }, value) {
986
+ if (!execute || pending.current.has(key)) return;
987
+ pending.current.add(key);
988
+ try {
989
+ await execute(value);
990
+ } catch {
991
+ return;
992
+ } finally{
993
+ pending.current.delete(key);
994
+ }
995
+ notify?.(value);
1908
996
  }
997
+ return {
998
+ openParent: (tab, uri)=>'content' === tab.kind ? Promise.resolve() : run(`openParent:${tab.key}`, {
999
+ execute: tab.operations?.openParent,
1000
+ notify: tab.onOpenParent
1001
+ }, uri),
1002
+ download: (tab)=>run(`download:${tab.key}`, {
1003
+ execute: tab.operations?.download,
1004
+ notify: tab.onDownload
1005
+ }, void 0),
1006
+ openUri: (tab, uri)=>run(`openUri:${tab.key}`, {
1007
+ execute: tab.operations?.openUri,
1008
+ notify: tab.onOpenUri
1009
+ }, uri),
1010
+ copyPath: (tab, path)=>copyText(path, tab.onCopyPath),
1011
+ copyContent: (tab, text)=>copyText(text, 'onCopyContent' in tab ? tab.onCopyContent : void 0)
1012
+ };
1909
1013
  }
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
- });
1014
+ function useFilePreviewActionsContext({ activeTab, fileTab, currentPath, currentSource, viewMode, canSwitchView, setViewMode, editing, operations }) {
1015
+ const startEditing = useCallback(()=>{
1016
+ if (!fileTab) return;
1017
+ setViewMode('source');
1018
+ editing.start(fileTab);
1019
+ }, [
1020
+ editing,
1021
+ fileTab,
1022
+ setViewMode
1023
+ ]);
1024
+ const saveEditing = useCallback(async ()=>{
1025
+ if (!fileTab) return false;
1026
+ const saved = await editing.save(fileTab);
1027
+ if (saved) setViewMode('preview');
1028
+ return saved;
1029
+ }, [
1030
+ editing,
1031
+ fileTab,
1032
+ setViewMode
1033
+ ]);
1034
+ const cancelEditing = useCallback(()=>{
1035
+ editing.discard(activeTab);
1036
+ setViewMode('preview');
1037
+ }, [
1038
+ activeTab,
1039
+ editing,
1040
+ setViewMode
1041
+ ]);
1042
+ const copyable = currentSource ? readCopyableText(currentSource) : void 0;
1043
+ const copyContent = useCallback(async ()=>{
1044
+ if (!copyable) return false;
1045
+ return operations.copyContent(activeTab, copyable);
1046
+ }, [
1047
+ activeTab,
1048
+ copyable,
1049
+ operations
1050
+ ]);
1051
+ const copyPath = useCallback(async ()=>{
1052
+ const path = currentPath;
1053
+ if (!path) return false;
1054
+ return operations.copyPath(activeTab, path);
1055
+ }, [
1056
+ activeTab,
1057
+ currentPath,
1058
+ operations
1059
+ ]);
1060
+ const download = useCallback(()=>{
1061
+ if (fileTab) operations.download(fileTab);
1062
+ }, [
1063
+ fileTab,
1064
+ operations
1065
+ ]);
1066
+ return {
1067
+ viewMode,
1068
+ setViewMode,
1069
+ canSwitchView,
1070
+ editing: editing.editing,
1071
+ busy: editing.saving || editing.loadingEdit,
1072
+ canEdit: Boolean(fileTab?.editing) && !editing.editing,
1073
+ startEditing,
1074
+ saveEditing,
1075
+ cancelEditing,
1076
+ canCopyContent: Boolean(copyable),
1077
+ copyContent,
1078
+ canCopyPath: Boolean(currentPath),
1079
+ copyPath,
1080
+ canDownload: Boolean(fileTab?.operations?.download),
1081
+ download
1082
+ };
1977
1083
  }
1978
- function useTuiCompletion(value, enabled, commands, groups = [], paletteScope = null) {
1979
- const query = getTuiCommandQuery(value, commands);
1980
- const suggestions = groupTuiCommands(enabled ? query.matches : [], groups);
1084
+ function useFilePreviewIds(activeKey) {
1085
+ const instanceId = useId();
1086
+ const tabId = (key)=>`${instanceId}-tab-${key}`;
1087
+ const panelId = (key)=>`${instanceId}-panel-${key}`;
1981
1088
  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)
1089
+ tabId,
1090
+ panelId,
1091
+ panelProps: {
1092
+ id: panelId(activeKey),
1093
+ role: 'tabpanel',
1094
+ 'aria-labelledby': tabId(activeKey)
1095
+ }
1988
1096
  };
1989
1097
  }
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);
1098
+ function initialLevelKey(tab, remembered, tabKey) {
1099
+ const preferred = tab.activeLevelKey ?? remembered[tabKey] ?? tab.defaultLevelKey;
1100
+ const first = tab.levels[0]?.key ?? '';
1101
+ if (void 0 === preferred) return first;
1102
+ return tab.levels.some((level)=>level.key === preferred) ? preferred : first;
1103
+ }
1104
+ function useDirectoryLevel(tab) {
1105
+ const [levelKeys, setLevelKeys] = useState({});
1106
+ if (!tab) return {};
1107
+ const activeLevel = tab.levels.find((level)=>level.key === initialLevelKey(tab, levelKeys, tab.key)) ?? tab.levels[0];
1108
+ return {
1109
+ activeLevel,
1110
+ directoryTab: {
1111
+ ...tab,
1112
+ activeLevelKey: activeLevel.key,
1113
+ onActiveLevelChange: (key)=>{
1114
+ if (void 0 === tab.activeLevelKey) setLevelKeys((previous)=>({
1115
+ ...previous,
1116
+ [tab.key]: key
1117
+ }));
1118
+ tab.onActiveLevelChange?.(key);
1119
+ }
1120
+ },
1121
+ source: activeLevel.source
2018
1122
  };
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
- });
1123
+ }
1124
+ function useFilePreviewEditing(activeTabKey) {
1125
+ const [editing, setEditing] = useState(false);
1126
+ const [loadingEdit, setLoadingEdit] = useState(false);
1127
+ const [editFailed, setEditFailed] = useState(false);
1128
+ const [saving, setSaving] = useState(false);
1129
+ const [saveFailed, setSaveFailed] = useState(false);
1130
+ const [draft, setDraft] = useState('');
1131
+ const requestId = useRef(0);
1132
+ const loading = useRef(false);
1133
+ const pendingSave = useRef(false);
1134
+ const lastKey = useRef(activeTabKey);
1135
+ if (lastKey.current !== activeTabKey) {
1136
+ lastKey.current = activeTabKey;
1137
+ requestId.current += 1;
1138
+ loading.current = false;
1139
+ if (editing) setEditing(false);
1140
+ if (loadingEdit) setLoadingEdit(false);
1141
+ if (editFailed) setEditFailed(false);
1142
+ if (saveFailed) setSaveFailed(false);
1143
+ if (draft) setDraft('');
1144
+ }
1145
+ function discard(tab) {
1146
+ requestId.current += 1;
1147
+ loading.current = false;
1148
+ const wasEditing = editing;
1149
+ const discarded = draft;
1150
+ setEditing(false);
1151
+ setLoadingEdit(false);
1152
+ setEditFailed(false);
1153
+ setSaveFailed(false);
1154
+ setDraft('');
1155
+ if (wasEditing && 'file' === tab.kind) tab.editing?.onCancel?.(discarded);
1156
+ }
1157
+ async function start(tab) {
1158
+ if (loading.current || pendingSave.current) return;
1159
+ const id = ++requestId.current;
1160
+ loading.current = true;
1161
+ setLoadingEdit(true);
1162
+ setEditFailed(false);
1163
+ setSaveFailed(false);
1164
+ let content;
1165
+ try {
1166
+ content = tab.editing?.loadContent ? await tab.editing.loadContent(tab.source) : tab.source.content ?? await tab.source.file?.text();
1167
+ } catch {
1168
+ if (id === requestId.current) setEditFailed(true);
1169
+ }
1170
+ if (id !== requestId.current) return;
1171
+ loading.current = false;
1172
+ setLoadingEdit(false);
1173
+ if (void 0 !== content) {
1174
+ setDraft(content);
1175
+ setEditing(true);
1176
+ }
1177
+ }
1178
+ async function save(tab) {
1179
+ if (!editing || !tab.editing || pendingSave.current) return false;
1180
+ pendingSave.current = true;
1181
+ setSaving(true);
1182
+ setSaveFailed(false);
1183
+ const value = draft;
1184
+ try {
1185
+ await tab.editing.save(value);
1186
+ } catch {
1187
+ setSaveFailed(true);
1188
+ return false;
1189
+ } finally{
1190
+ pendingSave.current = false;
1191
+ setSaving(false);
1192
+ }
1193
+ setEditing(false);
1194
+ setDraft('');
1195
+ tab.editing.onSave?.(value);
1196
+ return true;
1197
+ }
1198
+ return {
1199
+ editing,
1200
+ loadingEdit,
1201
+ editFailed,
1202
+ saving,
1203
+ saveFailed,
1204
+ draft,
1205
+ discard,
1206
+ start,
1207
+ save,
1208
+ updateDraft: (value)=>{
1209
+ setDraft(value);
1210
+ setSaveFailed(false);
1211
+ }
2026
1212
  };
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
- });
1213
+ }
1214
+ function isFileTab(tab) {
1215
+ return 'file' === tab.kind;
1216
+ }
1217
+ const file_preview_FilePreview = /*#__PURE__*/ forwardRef(function({ tabs, activeKey, defaultActiveKey, locale, labels, onActiveKeyChange, className, ...rest }, ref) {
1218
+ const text = resolveFilePreviewLabels(locale, labels);
1219
+ const [internalKey, setInternalKey] = useState(()=>defaultActiveKey ?? tabs[0].key);
1220
+ const currentKey = activeKey ?? internalKey;
1221
+ const selectedTab = tabs.find((tab)=>tab.key === currentKey) ?? tabs[0];
1222
+ const { tab: activeTab, levelKey, selectLevel } = useFilePreviewLevels(selectedTab);
1223
+ const directory = useDirectoryLevel('directory' === activeTab.kind ? activeTab : void 0);
1224
+ const { viewMode, setViewMode, viewModes, setViewModes, viewerHandle, onHandleChange, onViewModesChange } = useFilePreviewViewState(JSON.stringify([
1225
+ activeTab.key,
1226
+ levelKey,
1227
+ directory.activeLevel?.key
1228
+ ]));
1229
+ const { tabId, panelId, panelProps } = useFilePreviewIds(activeTab.key);
1230
+ const editing = useFilePreviewEditing(JSON.stringify([
1231
+ activeTab.key,
1232
+ levelKey,
1233
+ directory.activeLevel?.key
1234
+ ]));
1235
+ const actions = useFilePreviewActions();
1236
+ const tabText = mergeLabels(text, activeTab.labels);
1237
+ const fileTab = isFileTab(activeTab) ? activeTab : void 0;
1238
+ const directoryTab = 'directory' === activeTab.kind ? activeTab : void 0;
1239
+ const contentTab = fileTab || directoryTab ? void 0 : activeTab;
1240
+ const switchTab = useCallback((key)=>{
1241
+ if (editing.saving) return false;
1242
+ if (key === currentKey && key === activeTab.key) return false;
1243
+ const target = tabs.find((tab)=>tab.key === key);
1244
+ if (!target || target.disabled) return false;
1245
+ const syncingOnly = target.key === activeTab.key;
1246
+ if (!syncingOnly) {
1247
+ editing.discard(activeTab);
1248
+ setViewMode('preview');
1249
+ setViewModes([]);
1250
+ viewerHandle.current = null;
1251
+ }
1252
+ if (void 0 === activeKey) setInternalKey(key);
1253
+ onActiveKeyChange?.(key);
1254
+ return true;
2038
1255
  }, [
2039
- queryKey,
2040
- completion.options.length
1256
+ activeKey,
1257
+ activeTab,
1258
+ currentKey,
1259
+ editing,
1260
+ onActiveKeyChange,
1261
+ tabs,
1262
+ setViewMode,
1263
+ setViewModes,
1264
+ viewerHandle
2041
1265
  ]);
2042
- useEffect(()=>{
2043
- const body = bodyRef.current;
2044
- if (body && followOutput.current) body.scrollTop = body.scrollHeight;
1266
+ useImperativeHandle(ref, ()=>({
1267
+ setActiveTab: switchTab,
1268
+ getActiveTab: ()=>activeTab.key,
1269
+ getViewerHandle: ()=>fileTab ? viewerHandle.current : null
1270
+ }), [
1271
+ activeTab.key,
1272
+ fileTab,
1273
+ switchTab,
1274
+ viewerHandle
1275
+ ]);
1276
+ const canSwitchView = Boolean(fileTab) && viewModes.includes('preview') && viewModes.includes('source');
1277
+ const onLinkClick = useCallback((_event, link)=>{
1278
+ if (fileTab) actions.openUri(fileTab, link.url);
2045
1279
  }, [
2046
- records
1280
+ actions,
1281
+ fileTab
2047
1282
  ]);
1283
+ const linkClickHandler = fileTab?.operations?.openUri ? onLinkClick : void 0;
1284
+ const level = directory;
1285
+ const currentPath = level.source?.path ?? fileTab?.source.path;
1286
+ const currentSource = level.source ?? fileTab?.source;
1287
+ const actionContext = useFilePreviewActionsContext({
1288
+ activeTab,
1289
+ fileTab,
1290
+ currentPath,
1291
+ currentSource,
1292
+ viewMode,
1293
+ canSwitchView,
1294
+ setViewMode,
1295
+ editing,
1296
+ operations: actions
1297
+ });
2048
1298
  return /*#__PURE__*/ jsxs("div", {
1299
+ ...rest,
2049
1300
  className: [
2050
- 'ov-tui',
1301
+ 'ov-file-preview',
2051
1302
  className
2052
1303
  ].filter(Boolean).join(' '),
2053
- ...rest,
2054
1304
  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
1305
+ /*#__PURE__*/ jsx(FilePreviewTabRow, {
1306
+ tabs: tabs,
1307
+ activeKey: activeTab.key,
1308
+ disabled: editing.saving,
1309
+ tabId: tabId,
1310
+ panelId: panelId,
1311
+ onSelect: (key)=>void switchTab(key)
2083
1312
  }),
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);
1313
+ /*#__PURE__*/ jsx(FilePreviewPanels, {
1314
+ contentTab: contentTab,
1315
+ directoryTab: level.directoryTab,
1316
+ fileTab: fileTab,
1317
+ panelProps: panelProps,
1318
+ panelClass: "ov-file-preview__panel",
1319
+ levelKey: levelKey,
1320
+ onLevelChange: (key)=>{
1321
+ if (editing.saving || editing.loadingEdit || key === levelKey) return;
1322
+ if (fileTab?.activeLevelKey === void 0) editing.discard(activeTab);
1323
+ selectLevel(key);
2093
1324
  },
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
1325
+ text: tabText,
1326
+ locale: locale,
1327
+ actions: actions,
1328
+ actionContext: actionContext,
1329
+ editing: editing,
1330
+ viewMode: viewMode,
1331
+ setViewMode: setViewMode,
1332
+ onHandleChange: onHandleChange,
1333
+ onViewModesChange: onViewModesChange,
1334
+ linkClickHandler: linkClickHandler,
1335
+ activeTab: activeTab
2113
1336
  })
2114
1337
  ]
2115
1338
  });
2116
- }
2117
- export { Tui };
1339
+ });
1340
+ export { LoadFailedState, LoadingState, UnsupportedState, file_preview_FilePreview as FilePreview, toFileSource };