flexdesk 0.17.17 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +0 -0
  3. package/README.md +1 -0
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/canvas.js +0 -0
  6. package/charts.js +0 -0
  7. package/core.js +0 -0
  8. package/css/flexdesk.css +7499 -5851
  9. package/css/reset.css +1 -1
  10. package/css/tokens.css +23 -1
  11. package/dist/canvas.js +0 -0
  12. package/dist/canvas.js.map +0 -0
  13. package/dist/charts.js +10 -6
  14. package/dist/charts.js.map +1 -1
  15. package/dist/chunk-3PHCPZHT.js +0 -0
  16. package/dist/chunk-3PHCPZHT.js.map +0 -0
  17. package/dist/chunk-5W4EEKSK.js +778 -0
  18. package/dist/chunk-5W4EEKSK.js.map +7 -0
  19. package/dist/chunk-DNJ3N4T4.js +82 -0
  20. package/dist/chunk-DNJ3N4T4.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +0 -0
  22. package/dist/chunk-FOOS3T5L.js.map +0 -0
  23. package/dist/chunk-GOQLR5WR.js +0 -0
  24. package/dist/chunk-GOQLR5WR.js.map +0 -0
  25. package/dist/{chunk-JKZLNKG6.js → chunk-HKPB6OFA.js} +90 -25
  26. package/dist/{chunk-JKZLNKG6.js.map → chunk-HKPB6OFA.js.map} +2 -2
  27. package/dist/chunk-HNU6MM2B.js +0 -0
  28. package/dist/chunk-HNU6MM2B.js.map +0 -0
  29. package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
  30. package/dist/chunk-JYWURG5T.js +0 -0
  31. package/dist/chunk-JYWURG5T.js.map +0 -0
  32. package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
  33. package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
  34. package/dist/chunk-N7HC3ADV.js +0 -0
  35. package/dist/chunk-N7HC3ADV.js.map +0 -0
  36. package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
  37. package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
  38. package/dist/chunk-PAI2ZUWV.js +260 -0
  39. package/dist/chunk-PAI2ZUWV.js.map +7 -0
  40. package/dist/chunk-Q7BALEPX.js +527 -0
  41. package/dist/chunk-Q7BALEPX.js.map +7 -0
  42. package/dist/chunk-QXAOTGCC.js +40 -0
  43. package/dist/chunk-QXAOTGCC.js.map +7 -0
  44. package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
  45. package/dist/chunk-RKVYIXDC.js.map +7 -0
  46. package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
  47. package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
  48. package/dist/chunk-UKP22RUG.js +219 -0
  49. package/dist/chunk-UKP22RUG.js.map +7 -0
  50. package/dist/chunk-VLOAI3HS.js +43 -0
  51. package/dist/chunk-VLOAI3HS.js.map +7 -0
  52. package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
  53. package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
  54. package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
  55. package/dist/chunk-ZL3VKINP.js.map +7 -0
  56. package/dist/editor.js +3 -2
  57. package/dist/editor.js.map +1 -1
  58. package/dist/flexdesk.css +7499 -5851
  59. package/dist/flow.js +29 -97
  60. package/dist/flow.js.map +3 -3
  61. package/dist/git.js +1356 -0
  62. package/dist/git.js.map +7 -0
  63. package/dist/host.js +0 -0
  64. package/dist/host.js.map +0 -0
  65. package/dist/reset.css +1 -1
  66. package/dist/tile_registry-6WZPTUZV.js +0 -0
  67. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  68. package/dist/tiles.js +20 -15
  69. package/dist/tiles.js.map +2 -2
  70. package/dist/tokens.css +23 -1
  71. package/dist/widgets.js +3969 -1189
  72. package/dist/widgets.js.map +4 -4
  73. package/dist/wm.js +121 -33
  74. package/dist/wm.js.map +2 -2
  75. package/docs/README.md +9 -4
  76. package/docs/best-practices.md +2 -2
  77. package/docs/components/chat-shell.md +198 -0
  78. package/docs/components/command-palette.md +0 -0
  79. package/docs/components/conversation.md +10 -0
  80. package/docs/components/data-table-grid.md +0 -0
  81. package/docs/components/data-table.md +0 -0
  82. package/docs/components/editor.md +0 -0
  83. package/docs/components/filter-builder.md +246 -0
  84. package/docs/components/flow-canvas-editor.md +0 -0
  85. package/docs/components/flow-editors.md +0 -0
  86. package/docs/components/flow-lane-editor.md +0 -0
  87. package/docs/components/flow-outline-editor.md +0 -0
  88. package/docs/components/forms-and-dialogs.md +22 -0
  89. package/docs/components/git.md +355 -0
  90. package/docs/components/input-fields.md +167 -2
  91. package/docs/components/keymap.md +8 -0
  92. package/docs/components/markdown-and-outline.md +0 -0
  93. package/docs/components/menus.md +120 -3
  94. package/docs/components/panels-and-desktops.md +16 -5
  95. package/docs/components/record-list-and-fact-table.md +0 -0
  96. package/docs/components/settings-view.md +0 -0
  97. package/docs/components/shell.md +104 -1
  98. package/docs/components/wizard.md +0 -0
  99. package/docs/components/workspace-picker.md +149 -0
  100. package/docs/concepts/building-and-packaging.md +2 -2
  101. package/docs/concepts/domain-neutrality.md +0 -0
  102. package/docs/concepts/host-port.md +0 -0
  103. package/docs/concepts/packages-and-entry-points.md +14 -13
  104. package/docs/concepts/peer-dependencies.md +0 -0
  105. package/docs/concepts/state-guard.md +3 -3
  106. package/docs/quickstart.md +1 -1
  107. package/editor.js +0 -0
  108. package/flow.js +0 -0
  109. package/git.js +13 -0
  110. package/host.js +0 -0
  111. package/package.json +6 -1
  112. package/src/canvas/canvas_adapter.js +0 -0
  113. package/src/canvas/edge_router.js +0 -0
  114. package/src/canvas/graph/connector_router.js +0 -0
  115. package/src/canvas/graph/history_service.js +0 -0
  116. package/src/canvas/graph/node_platform.js +0 -0
  117. package/src/canvas/index.js +0 -0
  118. package/src/canvas/layered_layout.js +0 -0
  119. package/src/charts/data_series_plot_window.js +0 -0
  120. package/src/charts/downsample.js +0 -0
  121. package/src/charts/plot_config.js +0 -0
  122. package/src/charts/plotly_wrapper.js +0 -0
  123. package/src/core/event_bus.js +0 -0
  124. package/src/core/logging.js +0 -0
  125. package/src/core/settings.js +0 -0
  126. package/src/core/state_guard.js +0 -0
  127. package/src/core/state_machine.js +0 -0
  128. package/src/editor/editor_pane.js +0 -0
  129. package/src/editor/monaco_editor_factory.js +0 -0
  130. package/src/editor/monaco_loader.js +0 -0
  131. package/src/editor/notebook_search.js +0 -0
  132. package/src/editor/notebook_undo_manager.js +0 -0
  133. package/src/editor/split_pane_container.js +0 -0
  134. package/src/flow/canvas_editor/connections.js +0 -0
  135. package/src/flow/canvas_editor/editor.js +0 -0
  136. package/src/flow/canvas_editor/index.js +0 -0
  137. package/src/flow/canvas_editor/layout.js +0 -0
  138. package/src/flow/canvas_editor/strings.js +0 -0
  139. package/src/flow/kit/always_before.js +0 -0
  140. package/src/flow/kit/catalogue.js +0 -0
  141. package/src/flow/kit/chip_input.js +0 -0
  142. package/src/flow/kit/dom.js +28 -119
  143. package/src/flow/kit/findings.js +0 -0
  144. package/src/flow/kit/history.js +0 -0
  145. package/src/flow/kit/index.js +0 -0
  146. package/src/flow/kit/keys.js +0 -0
  147. package/src/flow/kit/logic_graph.js +0 -0
  148. package/src/flow/kit/references.js +0 -0
  149. package/src/flow/kit/settings_panel.js +0 -0
  150. package/src/flow/kit/settings_schema.js +0 -0
  151. package/src/flow/kit/step_picker.js +1 -1
  152. package/src/flow/kit/strings.js +0 -0
  153. package/src/flow/kit/value_picker.js +1 -1
  154. package/src/flow/kit/widgets.js +0 -0
  155. package/src/flow/lanes/editor.js +0 -0
  156. package/src/flow/lanes/index.js +0 -0
  157. package/src/flow/lanes/layout.js +0 -0
  158. package/src/flow/lanes/pipeline.js +0 -0
  159. package/src/flow/lanes/preview.js +0 -0
  160. package/src/flow/lanes/strings.js +0 -0
  161. package/src/flow/outline/editor.js +0 -0
  162. package/src/flow/outline/index.js +0 -0
  163. package/src/flow/outline/mapping.js +0 -0
  164. package/src/flow/outline/menu.js +1 -1
  165. package/src/flow/outline/operations.js +0 -0
  166. package/src/flow/outline/recognise.js +0 -0
  167. package/src/flow/outline/strings.js +0 -0
  168. package/src/git/diff_viewer.js +212 -0
  169. package/src/git/diff_window.js +51 -0
  170. package/src/git/git_graph.js +401 -0
  171. package/src/git/git_ops.js +61 -0
  172. package/src/git/git_panel.js +755 -0
  173. package/src/git/index.js +9 -0
  174. package/src/git/line_diff.js +202 -0
  175. package/src/help/help_modal.js +0 -0
  176. package/src/help/help_registry.js +0 -0
  177. package/src/host/external_links.js +0 -0
  178. package/src/host/file_picker.js +0 -0
  179. package/src/host/host.js +0 -0
  180. package/src/host/photino_host.js +0 -0
  181. package/src/host/photino_host.md +0 -0
  182. package/src/host/pywebview_host.js +0 -0
  183. package/src/tiles/chart_tiles.js +0 -0
  184. package/src/tiles/config_schema.js +0 -0
  185. package/src/tiles/figure_tile.js +0 -0
  186. package/src/tiles/layout_persistence.js +0 -0
  187. package/src/tiles/list_tiles.js +0 -0
  188. package/src/tiles/scope_picker.js +0 -0
  189. package/src/tiles/tile_base.js +5 -4
  190. package/src/tiles/tile_config_form.js +0 -0
  191. package/src/tiles/tile_registry.js +0 -0
  192. package/src/tiling/bottom_panel_tabs.js +0 -0
  193. package/src/tiling/command_palette.js +0 -0
  194. package/src/tiling/content_registry.js +0 -0
  195. package/src/tiling/desktops.js +10 -8
  196. package/src/tiling/entity_sources.js +0 -0
  197. package/src/tiling/keymap.js +6 -5
  198. package/src/tiling/kind_taxonomy.js +15 -2
  199. package/src/tiling/landing_table.js +0 -0
  200. package/src/tiling/loading_overlay.js +0 -0
  201. package/src/tiling/nav_panel.js +0 -0
  202. package/src/tiling/page_factory.js +0 -0
  203. package/src/tiling/panel_availability.js +0 -0
  204. package/src/tiling/panel_keys.js +0 -0
  205. package/src/tiling/shell.js +124 -17
  206. package/src/tiling/tab_strip.js +0 -0
  207. package/src/tiling/tile_renderer.js +2 -3
  208. package/src/tiling/tile_tab_menu.js +0 -0
  209. package/src/tiling/zoom.js +0 -0
  210. package/src/ui/base/component_base.js +0 -0
  211. package/src/ui/base/controller_base.js +0 -0
  212. package/src/ui/base/page_base.js +0 -0
  213. package/src/ui/components/about_dialog.js +0 -0
  214. package/src/ui/components/action_dropdown.js +4 -78
  215. package/src/ui/components/alert_log.js +0 -0
  216. package/src/ui/components/alerts_pane.js +0 -0
  217. package/src/ui/components/anchored_layer.js +255 -0
  218. package/src/ui/components/attribute_list_editor.js +0 -0
  219. package/src/ui/components/autocomplete_field.js +0 -0
  220. package/src/ui/components/cell_editor_host.js +0 -0
  221. package/src/ui/components/cell_editors.js +0 -0
  222. package/src/ui/components/cell_grid.js +0 -0
  223. package/src/ui/components/cell_paste.js +0 -0
  224. package/src/ui/components/cell_selection.js +0 -0
  225. package/src/ui/components/chat_shell.js +920 -0
  226. package/src/ui/components/chat_state_machine.js +223 -0
  227. package/src/ui/components/choice_controls.js +45 -2
  228. package/src/ui/components/color_field.js +3 -2
  229. package/src/ui/components/color_list_field.js +3 -2
  230. package/src/ui/components/column_fit.js +0 -0
  231. package/src/ui/components/column_groups.js +0 -0
  232. package/src/ui/components/column_order.js +0 -0
  233. package/src/ui/components/combobox.js +422 -0
  234. package/src/ui/components/conversation_view.js +17 -2
  235. package/src/ui/components/crosstab.js +0 -0
  236. package/src/ui/components/data_table.js +2 -2
  237. package/src/ui/components/detail_header.js +0 -0
  238. package/src/ui/components/document_outline.js +0 -0
  239. package/src/ui/components/drag_reorder.js +0 -0
  240. package/src/ui/components/fact_table.js +0 -0
  241. package/src/ui/components/fill_handle.js +0 -0
  242. package/src/ui/components/filter_builder.js +766 -0
  243. package/src/ui/components/filter_model.js +614 -0
  244. package/src/ui/components/gallery_picker.js +0 -0
  245. package/src/ui/components/header_actions.js +0 -0
  246. package/src/ui/components/inline_renamer.js +0 -0
  247. package/src/ui/components/job_progress.js +0 -0
  248. package/src/ui/components/managed_window.js +24 -47
  249. package/src/ui/components/menu_bar.js +101 -24
  250. package/src/ui/components/modal.js +79 -109
  251. package/src/ui/components/option_list.js +160 -0
  252. package/src/ui/components/popover.js +94 -0
  253. package/src/ui/components/progress.js +0 -0
  254. package/src/ui/components/recent_items.js +169 -0
  255. package/src/ui/components/record_list.js +0 -0
  256. package/src/ui/components/row_activation.js +0 -0
  257. package/src/ui/components/section_stack.js +0 -0
  258. package/src/ui/components/select.js +322 -0
  259. package/src/ui/components/series.js +0 -0
  260. package/src/ui/components/settings_view.js +0 -0
  261. package/src/ui/components/slide_out_panel.js +0 -0
  262. package/src/ui/components/slider_field.js +0 -0
  263. package/src/ui/components/sortable_list.js +0 -0
  264. package/src/ui/components/staged_edits.js +0 -0
  265. package/src/ui/components/staged_history.js +0 -0
  266. package/src/ui/components/table_filter_chips.js +0 -0
  267. package/src/ui/components/table_state_store.js +0 -0
  268. package/src/ui/components/table_structure.js +0 -0
  269. package/src/ui/components/table_values.js +0 -0
  270. package/src/ui/components/tabs.js +0 -0
  271. package/src/ui/components/tag_input.js +77 -8
  272. package/src/ui/components/toast.js +0 -0
  273. package/src/ui/components/toggle_field.js +0 -0
  274. package/src/ui/components/tsv.js +0 -0
  275. package/src/ui/components/window_taskbar.js +0 -0
  276. package/src/ui/components/workspace_picker.js +1155 -0
  277. package/src/ui/controllers/window_chrome_controller.js +4 -1
  278. package/src/ui/utils/auto_scrollbars.js +0 -0
  279. package/src/ui/utils/fire_event.js +17 -0
  280. package/src/ui/utils/focusable.js +25 -0
  281. package/src/ui/utils/markdown.js +0 -0
  282. package/src/ui/utils/modal_host.js +27 -0
  283. package/src/ui/utils/native_context_menu.js +0 -0
  284. package/src/ui/utils/overlay_scrollbar.js +0 -0
  285. package/src/ui/utils/raf_resize_observer.js +0 -0
  286. package/src/ui/utils/scroll_lock.js +0 -0
  287. package/src/ui/utils/strip_scroll.js +0 -0
  288. package/src/ui/utils/tooltip_service.js +0 -0
  289. package/tiles.js +0 -0
  290. package/widgets.js +12 -2
  291. package/wm.js +0 -0
  292. package/css/base.css +0 -10527
  293. package/css/overrides.css +0 -2596
  294. package/dist/chunk-7JI7HHSY.js +0 -107
  295. package/dist/chunk-7JI7HHSY.js.map +0 -7
  296. package/dist/chunk-MIW7D6WC.js +0 -526
  297. package/dist/chunk-MIW7D6WC.js.map +0 -7
  298. package/dist/chunk-PGZ4EHSE.js.map +0 -7
  299. package/dist/chunk-XJNADRGR.js.map +0 -7
  300. /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
package/dist/git.js ADDED
@@ -0,0 +1,1356 @@
1
+ import {
2
+ reportBridgeError,
3
+ showConfirmDialog,
4
+ showSelectDialog,
5
+ toastInfo
6
+ } from "./chunk-5W4EEKSK.js";
7
+ import {
8
+ ActionDropdown
9
+ } from "./chunk-PAI2ZUWV.js";
10
+ import {
11
+ openForm,
12
+ openModal,
13
+ showContextMenu
14
+ } from "./chunk-RKVYIXDC.js";
15
+ import "./chunk-ZL3VKINP.js";
16
+ import "./chunk-Q7BALEPX.js";
17
+ import "./chunk-HNU6MM2B.js";
18
+ import "./chunk-WVFGV5FT.js";
19
+ import "./chunk-QXAOTGCC.js";
20
+ import "./chunk-JYWURG5T.js";
21
+
22
+ // src/git/line_diff.js
23
+ var LINE_DIFF_LIMIT = 1500;
24
+ function splitLines(text) {
25
+ const s = String(text ?? "");
26
+ if (s === "") return [];
27
+ const lines = s.split(/\r?\n/);
28
+ if (lines[lines.length - 1] === "") lines.pop();
29
+ return lines;
30
+ }
31
+ function editScript(a, b) {
32
+ const n = a.length;
33
+ const m = b.length;
34
+ const lcs = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
35
+ for (let i2 = n - 1; i2 >= 0; i2--) {
36
+ for (let j2 = m - 1; j2 >= 0; j2--) {
37
+ lcs[i2][j2] = a[i2] === b[j2] ? lcs[i2 + 1][j2 + 1] + 1 : Math.max(lcs[i2 + 1][j2], lcs[i2][j2 + 1]);
38
+ }
39
+ }
40
+ const ops = [];
41
+ let i = 0;
42
+ let j = 0;
43
+ while (i < n || j < m) {
44
+ if (i < n && j < m && a[i] === b[j]) {
45
+ ops.push(["context", i, j]);
46
+ i++;
47
+ j++;
48
+ } else if (i < n && (j >= m || lcs[i + 1][j] >= lcs[i][j + 1])) {
49
+ ops.push(["del", i, -1]);
50
+ i++;
51
+ } else {
52
+ ops.push(["add", -1, j]);
53
+ j++;
54
+ }
55
+ }
56
+ return ops;
57
+ }
58
+ function computeLineDiff(before, after, {
59
+ context = 3,
60
+ maxLines = LINE_DIFF_LIMIT,
61
+ beforeStart = 1,
62
+ afterStart = 1
63
+ } = {}) {
64
+ const a = splitLines(before);
65
+ const b = splitLines(after);
66
+ let head = 0;
67
+ while (head < a.length && head < b.length && a[head] === b[head]) head++;
68
+ let tail = 0;
69
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++;
70
+ const midA = a.slice(head, a.length - tail);
71
+ const midB = b.slice(head, b.length - tail);
72
+ if (midA.length === 0 && midB.length === 0) return [];
73
+ if (midA.length > maxLines || midB.length > maxLines) return null;
74
+ const ops = [];
75
+ for (let k = 0; k < head; k++) ops.push(["context", k, k]);
76
+ for (const [kind, i, j] of editScript(midA, midB)) {
77
+ ops.push([kind, i < 0 ? -1 : i + head, j < 0 ? -1 : j + head]);
78
+ }
79
+ for (let k = tail; k > 0; k--) ops.push(["context", a.length - k, b.length - k]);
80
+ const keep = new Uint8Array(ops.length);
81
+ let reach = -1;
82
+ for (let k = 0; k < ops.length; k++) {
83
+ if (ops[k][0] !== "context") reach = k + context;
84
+ if (k <= reach) keep[k] = 1;
85
+ }
86
+ reach = ops.length;
87
+ for (let k = ops.length - 1; k >= 0; k--) {
88
+ if (ops[k][0] !== "context") reach = k - context;
89
+ if (k >= reach) keep[k] = 1;
90
+ }
91
+ const out = [];
92
+ let skipped = 0;
93
+ ops.forEach(([kind, i, j], k) => {
94
+ if (!keep[k]) {
95
+ skipped++;
96
+ return;
97
+ }
98
+ if (skipped > 0 && out.length > 0) {
99
+ out.push({ kind: "gap", oldLine: null, newLine: null, text: "", hidden: skipped });
100
+ }
101
+ skipped = 0;
102
+ out.push({
103
+ kind,
104
+ oldLine: i < 0 ? null : beforeStart + i,
105
+ newLine: j < 0 ? null : afterStart + j,
106
+ text: j < 0 ? a[i] : b[j]
107
+ });
108
+ });
109
+ return out;
110
+ }
111
+ var HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
112
+ function parseUnifiedDiff(text) {
113
+ const lines = String(text ?? "").split(/\r?\n/);
114
+ const fileHeaders = lines.filter((l) => l.startsWith("diff --git ")).length;
115
+ const out = [];
116
+ const header = (kind, line) => out.push({ kind, oldLine: null, newLine: null, text: line });
117
+ let oldNo = 0;
118
+ let newNo = 0;
119
+ let oldLeft = 0;
120
+ let newLeft = 0;
121
+ for (const line of lines) {
122
+ if (oldLeft > 0 || newLeft > 0) {
123
+ const sign = line[0];
124
+ const body = line.slice(1);
125
+ if (sign === "-") {
126
+ out.push({ kind: "del", oldLine: oldNo++, newLine: null, text: body });
127
+ oldLeft--;
128
+ } else if (sign === "+") {
129
+ out.push({ kind: "add", oldLine: null, newLine: newNo++, text: body });
130
+ newLeft--;
131
+ } else if (sign === "\\") {
132
+ header("meta", line);
133
+ } else {
134
+ out.push({ kind: "context", oldLine: oldNo++, newLine: newNo++, text: body });
135
+ oldLeft--;
136
+ newLeft--;
137
+ }
138
+ continue;
139
+ }
140
+ const hunk = HUNK_RE.exec(line);
141
+ if (hunk) {
142
+ oldNo = Number(hunk[1]);
143
+ newNo = Number(hunk[3]);
144
+ oldLeft = hunk[2] === void 0 ? 1 : Number(hunk[2]);
145
+ newLeft = hunk[4] === void 0 ? 1 : Number(hunk[4]);
146
+ header("hunk", line);
147
+ } else if (line.startsWith("\\") || /^Binary files .* differ$/.test(line) || /^rename (from|to) /.test(line)) {
148
+ header("meta", line);
149
+ } else if (line.startsWith("diff --git ") && fileHeaders > 1) {
150
+ header("file", / b\/(.*)$/.exec(line)?.[1] ?? line.slice("diff --git ".length));
151
+ }
152
+ }
153
+ return out;
154
+ }
155
+ function diffStats(lines) {
156
+ let added = 0;
157
+ let removed = 0;
158
+ for (const l of lines) {
159
+ if (l.kind === "add") added++;
160
+ else if (l.kind === "del") removed++;
161
+ }
162
+ return { added, removed };
163
+ }
164
+
165
+ // src/git/diff_viewer.js
166
+ var DEFAULT_LABELS = Object.freeze({
167
+ fields: "Fields",
168
+ text: "Text",
169
+ raw: "Raw",
170
+ noChanges: "No differences.",
171
+ noFields: "No field changes.",
172
+ tooLong: "Too long to compare here.",
173
+ gap: (hidden) => `${hidden} unchanged line${hidden === 1 ? "" : "s"}`
174
+ });
175
+ function el(tag, className, text) {
176
+ const n = document.createElement(tag);
177
+ if (className) n.className = className;
178
+ if (text !== void 0) n.textContent = text;
179
+ return n;
180
+ }
181
+ var shownField = (v) => {
182
+ const s = v === null || v === void 0 ? "" : String(v);
183
+ return s === "" ? "\u2014" : s;
184
+ };
185
+ function lineRow(line, labels) {
186
+ const row = el("div", `twm-diff__line twm-diff__line--${line.kind}`);
187
+ if (line.kind === "gap" || line.kind === "hunk" || line.kind === "meta" || line.kind === "file") {
188
+ row.textContent = line.kind === "gap" ? labels.gap(line.hidden) : line.text;
189
+ return row;
190
+ }
191
+ row.append(
192
+ el("span", "twm-diff__n", line.oldLine === null ? "" : String(line.oldLine)),
193
+ el("span", "twm-diff__n", line.newLine === null ? "" : String(line.newLine)),
194
+ el("span", "twm-diff__sign", line.kind === "add" ? "+" : line.kind === "del" ? "-" : ""),
195
+ el("span", "twm-diff__text", line.text)
196
+ );
197
+ return row;
198
+ }
199
+ function createDiffViewer(host, options = {}) {
200
+ const { context = 3, maxLines, onViewChange = null } = options;
201
+ const labels = { ...DEFAULT_LABELS, ...options.labels };
202
+ const root = el("div", "twm-diff");
203
+ const switcher = el("div", "twm-diff__views");
204
+ switcher.setAttribute("role", "group");
205
+ switcher.setAttribute("aria-label", "Diff view");
206
+ const body = el("div", "twm-diff__body");
207
+ root.append(switcher, body);
208
+ host.appendChild(root);
209
+ let data = {};
210
+ let chosen = options.view ?? null;
211
+ let textLines;
212
+ let current = null;
213
+ const hasPair = () => typeof data.before === "string" || typeof data.after === "string";
214
+ const linesOfText = () => {
215
+ if (textLines !== void 0) return textLines;
216
+ textLines = hasPair() ? computeLineDiff(data.before, data.after, {
217
+ context,
218
+ ...maxLines === void 0 ? {} : { maxLines },
219
+ beforeStart: data.beforeStart ?? 1,
220
+ afterStart: data.afterStart ?? 1
221
+ }) : parseUnifiedDiff(data.diff);
222
+ return textLines;
223
+ };
224
+ const offered = () => {
225
+ const out = [];
226
+ if (Array.isArray(data.fields)) out.push("fields");
227
+ if (hasPair() || typeof data.diff === "string" && linesOfText().length > 0) out.push("text");
228
+ if (typeof data.diff === "string") out.push("raw");
229
+ return out;
230
+ };
231
+ const pick = (views) => {
232
+ if (chosen && views.includes(chosen)) return chosen;
233
+ if (views.includes("fields") && data.fields.length > 0) return "fields";
234
+ if (views.includes("text")) return "text";
235
+ return views[0] ?? null;
236
+ };
237
+ const message = (text) => {
238
+ body.replaceChildren(el("div", "twm-diff__message", text));
239
+ };
240
+ const drawFields = () => {
241
+ if (data.fields.length === 0) {
242
+ message(labels.noFields);
243
+ return;
244
+ }
245
+ const grid = el("div", "twm-diff__fields");
246
+ for (const field of data.fields) {
247
+ const values = el("span", "twm-diff__field-values");
248
+ const arrow = el("span", "material-symbols-outlined twm-diff__field-arrow", "arrow_forward");
249
+ arrow.setAttribute("aria-hidden", "true");
250
+ values.append(
251
+ el("span", "twm-diff__field-from", shownField(field.from)),
252
+ arrow,
253
+ el("span", "twm-diff__field-to", shownField(field.to))
254
+ );
255
+ grid.append(el("span", "twm-diff__field-name", String(field.name)), values);
256
+ }
257
+ body.replaceChildren(grid);
258
+ };
259
+ const drawText = () => {
260
+ const lines = linesOfText();
261
+ if (lines === null) {
262
+ message(typeof data.diff === "string" ? `${labels.tooLong} See ${labels.raw}.` : labels.tooLong);
263
+ return;
264
+ }
265
+ if (lines.length === 0) {
266
+ message(labels.noChanges);
267
+ return;
268
+ }
269
+ const list = el("div", "twm-diff__lines");
270
+ list.append(...lines.map((line) => lineRow(line, labels)));
271
+ body.replaceChildren(list);
272
+ };
273
+ const drawRaw = () => {
274
+ if (data.diff === "") {
275
+ message(labels.noChanges);
276
+ return;
277
+ }
278
+ body.replaceChildren(el("pre", "twm-diff__raw", data.diff));
279
+ };
280
+ const draw = () => {
281
+ const views = offered();
282
+ current = pick(views);
283
+ switcher.replaceChildren(...views.length > 1 ? views.map((v) => {
284
+ const b = el("button", "twm-diff__view", labels[v]);
285
+ b.type = "button";
286
+ b.setAttribute("aria-pressed", String(v === current));
287
+ b.addEventListener("click", () => {
288
+ chosen = v;
289
+ draw();
290
+ onViewChange?.(v);
291
+ });
292
+ return b;
293
+ }) : []);
294
+ switcher.hidden = views.length <= 1;
295
+ if (current === "fields") drawFields();
296
+ else if (current === "text") drawText();
297
+ else if (current === "raw") drawRaw();
298
+ else message(labels.noChanges);
299
+ };
300
+ const update = (next) => {
301
+ data = next ?? {};
302
+ textLines = void 0;
303
+ draw();
304
+ };
305
+ update(options);
306
+ return {
307
+ el: root,
308
+ update,
309
+ setView(view) {
310
+ chosen = view;
311
+ draw();
312
+ },
313
+ view: () => current,
314
+ /** Words in place of the diff, while it loads or when it could not be read. The next update replaces them. */
315
+ showMessage(text) {
316
+ switcher.replaceChildren();
317
+ switcher.hidden = true;
318
+ current = null;
319
+ message(text);
320
+ },
321
+ destroy() {
322
+ root.remove();
323
+ }
324
+ };
325
+ }
326
+
327
+ // src/git/diff_window.js
328
+ function openDiffWindow({ title, load, icon: icon2 = "difference", width = 880, height = 600, labels } = {}) {
329
+ const content = document.createElement("div");
330
+ content.className = "twm-diff-window";
331
+ const viewer = createDiffViewer(content, { labels });
332
+ viewer.showMessage("Loading\u2026");
333
+ const closed = openModal({ title, icon: icon2, content, width, height, maximizable: true });
334
+ Promise.resolve().then(load).then((data) => {
335
+ if (data?.ok === false) viewer.showMessage(data.error || "The diff could not be read.");
336
+ else viewer.update(data);
337
+ }).catch((e) => viewer.showMessage(e?.message || String(e)));
338
+ return closed;
339
+ }
340
+ function openGitDiffWindow({ adapter, path, status, renamedFrom, ref }) {
341
+ return openDiffWindow({
342
+ title: renamedFrom ? `${renamedFrom} \u2192 ${path}` : path,
343
+ load: () => adapter.diffFile({ path, status, ...renamedFrom ? { renamedFrom } : {}, ...ref ? { ref } : {} })
344
+ });
345
+ }
346
+
347
+ // src/git/git_ops.js
348
+ async function callGit(opName, fn) {
349
+ let result;
350
+ try {
351
+ result = await fn();
352
+ } catch (e) {
353
+ reportBridgeError(`${opName} failed`, e instanceof Error ? e : new Error(String(e)));
354
+ return null;
355
+ }
356
+ const title = result?.conflicts ? `${opName} stopped on conflicts` : `${opName} failed`;
357
+ return reportBridgeError(title, result) ? result ?? { ok: true } : null;
358
+ }
359
+ async function askFor(title, submitLabel, fields) {
360
+ const values = await openForm({
361
+ title,
362
+ submitLabel,
363
+ fields: fields.map(([name, label]) => ({ name, label, type: "text", required: true }))
364
+ });
365
+ if (!values) return null;
366
+ const trimmed = Object.fromEntries(fields.map(([name]) => [name, String(values[name] ?? "").trim()]));
367
+ return Object.values(trimmed).every(Boolean) ? trimmed : null;
368
+ }
369
+ function openDiffHandler(getAdapter, onOpenDiff) {
370
+ if (onOpenDiff) return onOpenDiff;
371
+ return ({ path, status, renamedFrom, ref }) => {
372
+ const adapter = getAdapter();
373
+ if (adapter?.diffFile) openGitDiffWindow({ adapter, path, status, renamedFrom, ref });
374
+ };
375
+ }
376
+
377
+ // src/git/git_graph.js
378
+ var SVG_NS = "http://www.w3.org/2000/svg";
379
+ var LANE_COLOURS = 8;
380
+ var LANE_WIDTH = 11;
381
+ var ROW_HEIGHT = 22;
382
+ var NODE_RADIUS = 4;
383
+ var DEFAULT_PAGE_SIZE = 50;
384
+ function computeGraphLanes(commits) {
385
+ const active = [];
386
+ const allocate = () => {
387
+ const free = active.indexOf(null);
388
+ if (free !== -1) return free;
389
+ active.push(null);
390
+ return active.length - 1;
391
+ };
392
+ const rows = commits.map((commit) => {
393
+ const waiting = active.indexOf(commit.hash);
394
+ const hasChild = waiting !== -1;
395
+ const lane = hasChild ? waiting : allocate();
396
+ active[lane] = null;
397
+ const passLanes = [];
398
+ active.forEach((hash, l) => {
399
+ if (hash !== null) passLanes.push(l);
400
+ });
401
+ const outputLanes = (commit.parentHashes ?? []).map((parentHash, i) => {
402
+ let parentLane = active.indexOf(parentHash);
403
+ if (parentLane === -1) parentLane = i === 0 ? lane : allocate();
404
+ active[parentLane] = parentHash;
405
+ return { lane: parentLane, parentHash };
406
+ });
407
+ return { lane, hasChild, outputLanes, passLanes };
408
+ });
409
+ return { rows, laneCount: Math.max(active.length, 1) };
410
+ }
411
+ var REF_KINDS = [
412
+ ["HEAD -> refs/heads/", "head"],
413
+ ["tag: refs/tags/", "tag"],
414
+ ["refs/heads/", "branch"],
415
+ ["refs/remotes/", "remote"]
416
+ ];
417
+ function parseGitRefs(refs) {
418
+ if (!refs) return [];
419
+ return String(refs).split(",").map((s) => s.trim()).flatMap((part) => {
420
+ if (part === "HEAD") return [{ name: "HEAD", type: "head" }];
421
+ const kind = REF_KINDS.find(([prefix]) => part.startsWith(prefix));
422
+ return kind ? [{ name: part.slice(kind[0].length), type: kind[1] }] : [];
423
+ });
424
+ }
425
+ var laneX = (lane) => lane * LANE_WIDTH + LANE_WIDTH / 2;
426
+ function edge(d, lane) {
427
+ const path = document.createElementNS(SVG_NS, "path");
428
+ path.setAttribute("class", `twm-git-graph__edge twm-git-lane--${lane % LANE_COLOURS}`);
429
+ path.setAttribute("d", d);
430
+ return path;
431
+ }
432
+ function sizeGutter(svg, laneCount) {
433
+ const width = Math.max(laneCount * LANE_WIDTH, LANE_WIDTH * 2);
434
+ svg.setAttribute("width", String(width));
435
+ svg.setAttribute("viewBox", `0 0 ${width} ${ROW_HEIGHT}`);
436
+ }
437
+ function createGutter(row, laneCount) {
438
+ const svg = document.createElementNS(SVG_NS, "svg");
439
+ svg.setAttribute("class", "twm-git-graph__svg");
440
+ svg.setAttribute("height", String(ROW_HEIGHT));
441
+ svg.setAttribute("aria-hidden", "true");
442
+ sizeGutter(svg, laneCount);
443
+ const h = ROW_HEIGHT;
444
+ const cx = laneX(row.lane);
445
+ const cy = h / 2;
446
+ for (const lane of row.passLanes) {
447
+ const x = laneX(lane);
448
+ svg.appendChild(edge(`M${x},0 L${x},${h}`, lane));
449
+ }
450
+ if (row.hasChild) svg.appendChild(edge(`M${cx},0 L${cx},${cy}`, row.lane));
451
+ for (const { lane } of row.outputLanes) {
452
+ const x = laneX(lane);
453
+ svg.appendChild(edge(x === cx ? `M${cx},${cy} L${cx},${h}` : `M${cx},${cy} C${cx},${h * 0.8} ${x},${h * 0.6} ${x},${h}`, lane));
454
+ }
455
+ const node = document.createElementNS(SVG_NS, "circle");
456
+ node.setAttribute("class", `twm-git-graph__node twm-git-lane--${row.lane % LANE_COLOURS}`);
457
+ node.setAttribute("cx", String(cx));
458
+ node.setAttribute("cy", String(cy));
459
+ node.setAttribute("r", String(NODE_RADIUS));
460
+ svg.appendChild(node);
461
+ return svg;
462
+ }
463
+ function commitTitle(commit) {
464
+ const date = new Date(commit.timestamp * 1e3).toLocaleDateString();
465
+ return `${commit.shortHash} \u2014 ${commit.author}
466
+ ${date}
467
+
468
+ ${commit.subject}`;
469
+ }
470
+ function createGitGraph(host, options = {}) {
471
+ const { onChanged = null, pageSize = DEFAULT_PAGE_SIZE, showRemoteRefs = false } = options;
472
+ let adapter = options.adapter ?? null;
473
+ const openDiff = openDiffHandler(() => adapter, options.onOpenDiff);
474
+ const root = document.createElement("div");
475
+ root.className = "twm-git-graph";
476
+ host.appendChild(root);
477
+ let commits = [];
478
+ let drawnJson = "";
479
+ let allLoaded = false;
480
+ let generation = 0;
481
+ let observer = null;
482
+ let sentinel = null;
483
+ let destroyed = false;
484
+ const say = (text) => {
485
+ const m = document.createElement("div");
486
+ m.className = "twm-git-graph__message";
487
+ m.textContent = text;
488
+ root.replaceChildren(m);
489
+ };
490
+ const stopObserving = () => {
491
+ observer?.disconnect();
492
+ observer = null;
493
+ sentinel?.remove();
494
+ sentinel = null;
495
+ };
496
+ const openFiles = async (git, commit, x, y) => {
497
+ const result = await callGit("Git changes", () => git.commitFiles({ hash: commit.hash }));
498
+ if (!result || git !== adapter) return;
499
+ const files = result.files ?? [];
500
+ if (files.length === 0) {
501
+ toastInfo("No files changed", commit.shortHash);
502
+ } else if (files.length === 1) {
503
+ openDiff({ ...files[0], ref: commit.hash });
504
+ } else {
505
+ const items = files.map((f, i) => ({ label: f.path, icon: "difference", action: String(i) }));
506
+ showContextMenu(x, y, items, (action) => {
507
+ if (git === adapter) openDiff({ ...files[Number(action)], ref: commit.hash });
508
+ });
509
+ }
510
+ };
511
+ const menuItems = (git) => {
512
+ const groups = [
513
+ [
514
+ git.checkout && { label: "Checkout (detached)", icon: "logout", action: "checkout" },
515
+ git.createBranch && { label: "Create Branch\u2026", icon: "commit", action: "create-branch" },
516
+ { label: "Copy Commit Hash", icon: "content_copy", action: "copy-hash" }
517
+ ],
518
+ [
519
+ git.cherryPick && { label: "Cherry-pick", icon: "content_copy", action: "cherry-pick" },
520
+ git.createTag && { label: "Create Tag\u2026", icon: "sell", action: "create-tag" },
521
+ git.commitFiles && { label: "View Changes", icon: "difference", action: "view-changes" }
522
+ ]
523
+ ].map((group) => group.filter(Boolean)).filter((group) => group.length > 0);
524
+ return groups.flatMap((group, i) => i === 0 ? group : [{ separator: true }, ...group]);
525
+ };
526
+ const onMenuAction = async (git, action, commit, x, y) => {
527
+ const { hash } = commit;
528
+ const change = async (opName, fn) => {
529
+ await callGit(opName, fn);
530
+ await refresh();
531
+ onChanged?.();
532
+ };
533
+ switch (action) {
534
+ case "checkout":
535
+ await change("Git checkout", () => git.checkout({ ref: hash }));
536
+ break;
537
+ case "create-branch": {
538
+ const values = await askFor("Create Branch", "Create", [["name", "Branch name"]]);
539
+ if (values) await change("Git create branch", () => git.createBranch({ name: values.name, from: hash }));
540
+ break;
541
+ }
542
+ case "copy-hash":
543
+ try {
544
+ await navigator.clipboard.writeText(hash);
545
+ toastInfo("Commit hash copied", commit.shortHash);
546
+ } catch (e) {
547
+ reportBridgeError("Copy commit hash failed", e);
548
+ }
549
+ break;
550
+ case "cherry-pick":
551
+ await change("Git cherry-pick", () => git.cherryPick({ hash }));
552
+ break;
553
+ case "create-tag": {
554
+ const values = await askFor("Create Tag", "Create", [["name", "Tag name"]]);
555
+ if (values) await change("Git create tag", () => git.createTag({ name: values.name, hash }));
556
+ break;
557
+ }
558
+ case "view-changes":
559
+ await openFiles(git, commit, x, y);
560
+ break;
561
+ }
562
+ };
563
+ const createRow = (commit, laneRow, laneCount) => {
564
+ const row = document.createElement("div");
565
+ row.className = "twm-git-graph__row";
566
+ row.title = commitTitle(commit);
567
+ row.tabIndex = 0;
568
+ row.appendChild(createGutter(laneRow, laneCount));
569
+ const info = document.createElement("div");
570
+ info.className = "twm-git-graph__info";
571
+ for (const pill of parseGitRefs(commit.refs)) {
572
+ if (pill.type === "remote" && !showRemoteRefs) continue;
573
+ const span = document.createElement("span");
574
+ span.className = `twm-git-ref twm-git-ref--${pill.type}`;
575
+ span.textContent = pill.name;
576
+ span.title = pill.name;
577
+ info.appendChild(span);
578
+ }
579
+ const subject = document.createElement("span");
580
+ subject.className = "twm-git-graph__subject";
581
+ subject.textContent = commit.subject;
582
+ info.appendChild(subject);
583
+ row.appendChild(info);
584
+ const openMenu = (x, y) => {
585
+ const git = adapter;
586
+ showContextMenu(x, y, menuItems(git), (action) => onMenuAction(git, action, commit, x, y));
587
+ };
588
+ row.addEventListener("contextmenu", (e) => {
589
+ e.preventDefault();
590
+ openMenu(e.clientX, e.clientY);
591
+ });
592
+ row.addEventListener("keydown", (e) => {
593
+ if (e.key !== "ContextMenu" && !(e.key === "F10" && e.shiftKey)) return;
594
+ e.preventDefault();
595
+ const r = row.getBoundingClientRect();
596
+ openMenu(r.left, r.bottom);
597
+ });
598
+ return row;
599
+ };
600
+ const observeSentinel = () => {
601
+ stopObserving();
602
+ if (allLoaded || commits.length === 0) return;
603
+ sentinel = document.createElement("div");
604
+ sentinel.className = "twm-git-graph__sentinel";
605
+ root.appendChild(sentinel);
606
+ observer = new IntersectionObserver((entries) => {
607
+ if (entries[0]?.isIntersecting) loadMore();
608
+ }, { root });
609
+ observer.observe(sentinel);
610
+ };
611
+ const drawRows = (from) => {
612
+ stopObserving();
613
+ const { rows, laneCount } = computeGraphLanes(commits);
614
+ for (const svg of root.querySelectorAll(".twm-git-graph__svg")) sizeGutter(svg, laneCount);
615
+ const fragment = document.createDocumentFragment();
616
+ for (let i = from; i < commits.length; i++) fragment.appendChild(createRow(commits[i], rows[i], laneCount));
617
+ root.appendChild(fragment);
618
+ observeSentinel();
619
+ };
620
+ const clear = () => {
621
+ stopObserving();
622
+ commits = [];
623
+ drawnJson = "";
624
+ allLoaded = false;
625
+ root.replaceChildren();
626
+ };
627
+ const refresh = async () => {
628
+ const gen = ++generation;
629
+ if (!adapter?.log) {
630
+ clear();
631
+ return;
632
+ }
633
+ const count = Math.max(commits.length, pageSize);
634
+ let result;
635
+ try {
636
+ result = await adapter.log({ count, skip: 0 });
637
+ } catch (e) {
638
+ result = { ok: false, error: e?.message || String(e) };
639
+ }
640
+ if (destroyed || gen !== generation) return;
641
+ if (!result?.ok) {
642
+ clear();
643
+ say(result?.error || "The history could not be read.");
644
+ return;
645
+ }
646
+ const next = result.commits ?? [];
647
+ const json = JSON.stringify(next);
648
+ if (json === drawnJson) {
649
+ if (!observer) observeSentinel();
650
+ return;
651
+ }
652
+ const top = root.scrollTop;
653
+ clear();
654
+ drawnJson = json;
655
+ commits = next;
656
+ allLoaded = next.length < count;
657
+ if (commits.length === 0) {
658
+ say("No commits yet");
659
+ return;
660
+ }
661
+ drawRows(0);
662
+ root.scrollTop = top;
663
+ };
664
+ const loadMore = async () => {
665
+ const gen = ++generation;
666
+ stopObserving();
667
+ const skip = commits.length;
668
+ const result = await callGit("Git log", () => adapter.log({ count: pageSize, skip }));
669
+ if (destroyed || gen !== generation || !result) return;
670
+ const page = result.commits ?? [];
671
+ allLoaded = page.length < pageSize;
672
+ commits = commits.concat(page);
673
+ drawnJson = JSON.stringify(commits);
674
+ drawRows(skip);
675
+ };
676
+ refresh();
677
+ return {
678
+ el: root,
679
+ refresh,
680
+ setAdapter(next) {
681
+ adapter = next ?? null;
682
+ clear();
683
+ refresh();
684
+ },
685
+ destroy() {
686
+ destroyed = true;
687
+ stopObserving();
688
+ root.remove();
689
+ }
690
+ };
691
+ }
692
+
693
+ // src/git/git_panel.js
694
+ var DEFAULT_POLL_MS = 3e3;
695
+ var COMMIT_BOX_MAX_PX = 80;
696
+ function el2(tag, className, text) {
697
+ const n = document.createElement(tag);
698
+ if (className) n.className = className;
699
+ if (text !== void 0) n.textContent = text;
700
+ return n;
701
+ }
702
+ function icon(name) {
703
+ const span = el2("span", "material-symbols-outlined", name);
704
+ span.setAttribute("aria-hidden", "true");
705
+ return span;
706
+ }
707
+ function iconButton(className, iconName, label, onClick) {
708
+ const b = el2("button", className);
709
+ b.type = "button";
710
+ b.title = label;
711
+ b.setAttribute("aria-label", label);
712
+ b.appendChild(icon(iconName));
713
+ b.addEventListener("click", onClick);
714
+ return b;
715
+ }
716
+ var escapeLabel = (s) => String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
717
+ function splitPath(path) {
718
+ const parts = String(path).replace(/\\/g, "/").split("/").filter(Boolean);
719
+ return { name: parts[parts.length - 1] ?? String(path), dir: parts.slice(0, -1).join("/") };
720
+ }
721
+ function requireMethods(adapter) {
722
+ const missing = ["isRepo", "status"].filter((m) => typeof adapter[m] !== "function");
723
+ if (missing.length > 0) throw new TypeError(`createGitPanel: the adapter has no ${missing.join(" or ")} method`);
724
+ }
725
+ function createGitPanel(host, options = {}) {
726
+ const {
727
+ onCountChange = null,
728
+ pollInterval = DEFAULT_POLL_MS,
729
+ emptyLabel = "No repository",
730
+ showRemoteRefs = false
731
+ } = options;
732
+ let adapter = options.adapter ?? null;
733
+ if (adapter) requireMethods(adapter);
734
+ const openDiff = openDiffHandler(() => adapter, options.onOpenDiff);
735
+ let isRepo = false;
736
+ let status = null;
737
+ let statusJson = "";
738
+ let branches = [];
739
+ let busy = false;
740
+ let pollTimer = null;
741
+ let generation = 0;
742
+ let reading = false;
743
+ let destroyed = false;
744
+ let changesExpanded = options.changesExpanded ?? true;
745
+ let graphExpanded = options.graphExpanded ?? false;
746
+ let graph = null;
747
+ const root = el2("div", "twm-git-panel");
748
+ const messageView = el2("div", "twm-git-panel__empty");
749
+ const mainView = el2("div", "twm-git-panel__main");
750
+ const busyBar = el2("div", "twm-git-panel__busy-bar");
751
+ root.append(messageView, mainView);
752
+ const branchButton = el2("button", "twm-git-panel__branch");
753
+ branchButton.type = "button";
754
+ const branchLabel = el2("span", "twm-git-panel__branch-label");
755
+ branchButton.append(icon("commit"), branchLabel);
756
+ const badges = el2("div", "twm-git-panel__badges");
757
+ const barActions = el2("div", "twm-git-panel__bar-actions");
758
+ const bar = el2("div", "twm-git-panel__bar");
759
+ bar.append(branchButton, badges, barActions);
760
+ const fetchButton = iconButton(
761
+ "twm-git-panel__bar-btn",
762
+ "sync",
763
+ "Fetch",
764
+ () => runOp("fetch", () => adapter.fetch())
765
+ );
766
+ const pullButton = iconButton(
767
+ "twm-git-panel__bar-btn",
768
+ "arrow_downward",
769
+ "Pull",
770
+ () => runOp("pull", () => adapter.pull())
771
+ );
772
+ const pushButton = iconButton(
773
+ "twm-git-panel__bar-btn",
774
+ "arrow_upward",
775
+ "Push",
776
+ () => runOp("push", () => adapter.push({ setUpstream: !status?.hasUpstream }))
777
+ );
778
+ const overflowButton = iconButton("twm-git-panel__bar-btn", "more_horiz", "More actions", () => {
779
+ });
780
+ barActions.append(fetchButton, pullButton, pushButton, overflowButton);
781
+ const commitArea = el2("div", "twm-git-panel__commit");
782
+ const commitBox = el2("textarea", "twm-git-panel__commit-box");
783
+ commitBox.rows = 1;
784
+ commitBox.placeholder = "Commit message (Ctrl+Enter to commit)";
785
+ commitBox.setAttribute("aria-label", "Commit message");
786
+ const commitButton = el2("button", "twm-git-panel__commit-btn", "Commit");
787
+ commitButton.type = "button";
788
+ const commitMenuButton = iconButton("twm-git-panel__commit-menu", "expand_more", "Commit options", () => {
789
+ });
790
+ const commitActions = el2("div", "twm-git-panel__commit-actions");
791
+ commitActions.append(commitButton, commitMenuButton);
792
+ commitArea.append(commitBox, commitActions);
793
+ const section = (title, expanded, onToggle) => {
794
+ const arrow = el2("span", "material-symbols-outlined twm-collapsible-arrow twm-git-panel__arrow", "expand_more");
795
+ arrow.setAttribute("aria-hidden", "true");
796
+ const count = el2("span", "twm-git-panel__count");
797
+ const head = el2("button", "twm-collapsible-header twm-git-panel__section-head");
798
+ head.type = "button";
799
+ head.append(arrow, title, count);
800
+ const body = el2("div", "twm-git-panel__section-body");
801
+ const setExpanded = (on) => {
802
+ arrow.classList.toggle("twm-git-panel__arrow--collapsed", !on);
803
+ head.setAttribute("aria-expanded", String(on));
804
+ body.hidden = !on;
805
+ };
806
+ setExpanded(expanded);
807
+ head.addEventListener("click", () => onToggle(setExpanded));
808
+ const wrap = el2("div", "twm-git-panel__section");
809
+ wrap.append(head, body);
810
+ return { wrap, body, count };
811
+ };
812
+ const changes = section("Changes", changesExpanded, (setExpanded) => {
813
+ changesExpanded = !changesExpanded;
814
+ setExpanded(changesExpanded);
815
+ });
816
+ const history = section("Graph", graphExpanded, (setExpanded) => {
817
+ graphExpanded = !graphExpanded;
818
+ setExpanded(graphExpanded);
819
+ if (graphExpanded) ensureGraph();
820
+ });
821
+ mainView.append(busyBar, bar, commitArea, changes.wrap, history.wrap);
822
+ host.appendChild(root);
823
+ const showMessage = (iconName, text, action = null) => {
824
+ const parts = [icon(iconName), el2("span", null, text)];
825
+ if (action) parts.push(action);
826
+ messageView.replaceChildren(...parts);
827
+ messageView.hidden = false;
828
+ mainView.hidden = true;
829
+ };
830
+ const showNotRepo = () => {
831
+ let action = null;
832
+ if (adapter.init) {
833
+ action = el2("button", "twm-btn twm-btn--primary twm-git-panel__init", "Initialize Repository");
834
+ action.type = "button";
835
+ action.addEventListener("click", initRepo);
836
+ }
837
+ showMessage("source", "Not a git repository", action);
838
+ };
839
+ const fileRow = (file) => {
840
+ const { name, dir } = splitPath(file.path);
841
+ const row = el2("div", "twm-git-panel__file");
842
+ row.tabIndex = 0;
843
+ row.setAttribute("role", "button");
844
+ row.title = file.renamedFrom ? `${file.renamedFrom} \u2192 ${file.path}` : file.path;
845
+ row.append(
846
+ el2("span", `twm-git-panel__status twm-git-panel__status--${file.status}`, file.status),
847
+ el2("span", "twm-git-panel__file-name", name)
848
+ );
849
+ if (dir) row.appendChild(el2("span", "twm-git-panel__file-dir", dir));
850
+ const open = () => openDiff({
851
+ path: file.path,
852
+ status: file.status,
853
+ staged: file.staged,
854
+ ...file.renamedFrom ? { renamedFrom: file.renamedFrom } : {}
855
+ });
856
+ row.addEventListener("click", open);
857
+ row.addEventListener("keydown", (e) => {
858
+ if (e.target === row && (e.key === "Enter" || e.key === " ")) {
859
+ e.preventDefault();
860
+ open();
861
+ }
862
+ });
863
+ const actions = el2("div", "twm-git-panel__file-actions");
864
+ const action = (iconName, label, fn) => {
865
+ actions.appendChild(iconButton("twm-git-panel__file-btn", iconName, label, (e) => {
866
+ e.stopPropagation();
867
+ fn();
868
+ }));
869
+ };
870
+ if (!file.staged && adapter.stage) {
871
+ action("add", "Stage", () => runOp("stage", () => adapter.stage({ paths: [file.path] })));
872
+ }
873
+ if (file.staged && adapter.unstage) {
874
+ const paths = file.renamedFrom ? [file.renamedFrom, file.path] : [file.path];
875
+ action("remove", "Unstage", () => runOp("unstage", () => adapter.unstage({ paths })));
876
+ }
877
+ if (adapter.discard && file.status !== "C") action("undo", "Discard changes", () => discard(file));
878
+ row.appendChild(actions);
879
+ return row;
880
+ };
881
+ const drawStatus = () => {
882
+ branchLabel.textContent = status.branch ?? "";
883
+ const marks = [];
884
+ if (status.hasUpstream && status.ahead > 0) marks.push(el2("span", "twm-git-panel__badge", `\u2191${status.ahead}`));
885
+ if (status.hasUpstream && status.behind > 0) marks.push(el2("span", "twm-git-panel__badge", `\u2193${status.behind}`));
886
+ badges.replaceChildren(...marks);
887
+ const files = status.files ?? [];
888
+ changes.count.textContent = String(files.length);
889
+ changes.body.replaceChildren(...files.length > 0 ? files.map(fileRow) : [el2("div", "twm-git-panel__no-changes", "No changes")]);
890
+ overflowDropdown.setOptions(overflowOptions());
891
+ };
892
+ const drawBusy = () => {
893
+ busyBar.hidden = !busy;
894
+ for (const b of [branchButton, fetchButton, pullButton, pushButton, overflowButton, commitButton, commitMenuButton]) {
895
+ b.disabled = busy;
896
+ }
897
+ };
898
+ const drawCapabilities = () => {
899
+ if (!adapter) return;
900
+ fetchButton.hidden = !adapter.fetch;
901
+ pullButton.hidden = !adapter.pull;
902
+ pushButton.hidden = !adapter.push;
903
+ commitArea.hidden = !adapter.commit;
904
+ const overflow = overflowOptions();
905
+ overflowButton.hidden = overflow.every((o) => o.separator);
906
+ overflowDropdown.setOptions(overflow);
907
+ commitDropdown.setOptions(commitOptions());
908
+ branchDropdown.setOptions(branchOptions());
909
+ };
910
+ const commitOptions = () => [
911
+ { type: "commit", label: "Commit", icon: "check" },
912
+ { type: "amend", label: "Commit (Amend)", icon: "edit" },
913
+ { separator: true },
914
+ ...adapter?.push ? [{ type: "commitPush", label: "Commit &amp; Push", icon: "arrow_upward" }] : [],
915
+ ...adapter?.pull && adapter?.push ? [{ type: "commitSync", label: "Commit &amp; Sync", icon: "sync" }] : []
916
+ ];
917
+ const overflowOptions = () => {
918
+ if (!adapter) return [];
919
+ return [
920
+ ...adapter.stash ? [{ type: "stash", label: "Stash", icon: "archive" }] : [],
921
+ ...adapter.stashPop ? [{ type: "stashPop", label: "Pop Stash", icon: "unarchive" }] : [],
922
+ { separator: true },
923
+ ...adapter.merge ? [{ type: "merge", label: "Merge\u2026", icon: "merge" }] : [],
924
+ ...adapter.rebase ? [{ type: "rebase", label: "Rebase\u2026", icon: "rebase_edit" }] : [],
925
+ ...adapter.rebase && status?.rebasing ? [
926
+ { type: "rebaseContinue", label: "Continue Rebase", icon: "play_arrow" },
927
+ { type: "rebaseAbort", label: "Abort Rebase", icon: "cancel" }
928
+ ] : [],
929
+ { separator: true },
930
+ ...adapter.remotes ? [{ type: "remotes", label: "Remotes\u2026", icon: "cloud" }] : [],
931
+ ...adapter.tags ? [{ type: "tags", label: "Tags\u2026", icon: "sell" }] : adapter.createTag ? [{ type: "createTag", label: "Create Tag\u2026", icon: "sell" }] : []
932
+ ];
933
+ };
934
+ const branchOptions = () => {
935
+ if (!adapter) return [];
936
+ const ref = (b, iconName) => ({ type: "checkout", ref: b.name, label: escapeLabel(b.name), icon: iconName });
937
+ return [
938
+ ...adapter.checkout ? [
939
+ ...branches.filter((b) => !b.isRemote).map((b) => ref(b, b.isCurrent ? "check" : "commit")),
940
+ { separator: true },
941
+ ...branches.filter((b) => b.isRemote).map((b) => ref(b, "cloud"))
942
+ ] : [],
943
+ { separator: true },
944
+ ...adapter.createBranch ? [{ type: "createBranch", label: "Create Branch\u2026", icon: "add" }] : [],
945
+ ...adapter.deleteBranch ? [{ type: "deleteBranch", label: "Delete Branch\u2026", icon: "delete" }] : []
946
+ ];
947
+ };
948
+ const branchDropdown = new ActionDropdown({
949
+ trigger: branchButton,
950
+ options: [],
951
+ onSelect: (option) => onBranchOption(option)
952
+ });
953
+ const commitDropdown = new ActionDropdown({
954
+ trigger: commitMenuButton,
955
+ options: [],
956
+ onSelect: (option) => commit(option.type)
957
+ });
958
+ const overflowDropdown = new ActionDropdown({
959
+ trigger: overflowButton,
960
+ options: [],
961
+ onSelect: (option) => onOverflowOption(option)
962
+ });
963
+ const readBranches = async (gen) => {
964
+ if (!adapter?.branches) return;
965
+ let result;
966
+ try {
967
+ result = await adapter.branches();
968
+ } catch {
969
+ return;
970
+ }
971
+ if (gen !== generation || destroyed || !result?.ok) return;
972
+ branches = result.branches ?? [];
973
+ branchDropdown.setOptions(branchOptions());
974
+ };
975
+ const refresh = async () => {
976
+ const gen = ++generation;
977
+ if (!adapter) {
978
+ showMessage("folder_off", emptyLabel);
979
+ onCountChange?.(0);
980
+ return;
981
+ }
982
+ reading = true;
983
+ try {
984
+ if (!isRepo) {
985
+ const check = await adapter.isRepo();
986
+ if (gen !== generation || destroyed) return;
987
+ if (!check?.ok) {
988
+ showMessage("error", check?.error || "The repository could not be read.");
989
+ onCountChange?.(0);
990
+ return;
991
+ }
992
+ if (!check.isRepo) {
993
+ showNotRepo();
994
+ onCountChange?.(0);
995
+ return;
996
+ }
997
+ isRepo = true;
998
+ }
999
+ const result = await adapter.status();
1000
+ if (gen !== generation || destroyed) return;
1001
+ if (!result?.ok) {
1002
+ isRepo = false;
1003
+ statusJson = "";
1004
+ showMessage("error", result?.error || "The repository could not be read.");
1005
+ onCountChange?.(0);
1006
+ return;
1007
+ }
1008
+ messageView.hidden = true;
1009
+ mainView.hidden = false;
1010
+ const json = JSON.stringify(result);
1011
+ if (json === statusJson) return;
1012
+ statusJson = json;
1013
+ status = result;
1014
+ drawStatus();
1015
+ onCountChange?.((result.files ?? []).length);
1016
+ readBranches(gen);
1017
+ graph?.refresh();
1018
+ } catch (e) {
1019
+ if (gen !== generation || destroyed) return;
1020
+ isRepo = false;
1021
+ statusJson = "";
1022
+ showMessage("error", e?.message || String(e));
1023
+ onCountChange?.(0);
1024
+ } finally {
1025
+ if (gen === generation) reading = false;
1026
+ }
1027
+ };
1028
+ const poll = () => {
1029
+ if (!reading && !busy) refresh();
1030
+ };
1031
+ const startPolling = () => {
1032
+ if (pollTimer !== null || destroyed) return;
1033
+ refresh();
1034
+ if (pollInterval > 0) pollTimer = setInterval(poll, pollInterval);
1035
+ };
1036
+ const stopPolling = () => {
1037
+ if (pollTimer !== null) clearInterval(pollTimer);
1038
+ pollTimer = null;
1039
+ };
1040
+ async function runOp(opName, fn) {
1041
+ if (busy) {
1042
+ toastInfo("Git is busy", `The ${opName} did not run: another operation is still going.`);
1043
+ return;
1044
+ }
1045
+ busy = true;
1046
+ drawBusy();
1047
+ await callGit(`Git ${opName}`, fn);
1048
+ busy = false;
1049
+ drawBusy();
1050
+ statusJson = "";
1051
+ await refresh();
1052
+ }
1053
+ async function initRepo() {
1054
+ const result = await callGit("Git init", () => adapter.init());
1055
+ if (result) await refresh();
1056
+ }
1057
+ async function inSteps(steps) {
1058
+ let result;
1059
+ for (const step of steps) {
1060
+ result = await step();
1061
+ if (result && result.ok === false) return result;
1062
+ }
1063
+ return result;
1064
+ }
1065
+ async function commit(mode) {
1066
+ const message = commitBox.value.trim();
1067
+ const amend = mode === "amend";
1068
+ if (!message && !amend) {
1069
+ commitBox.focus();
1070
+ return;
1071
+ }
1072
+ const files = status?.files ?? [];
1073
+ if (!amend && files.length === 0) {
1074
+ toastInfo("Nothing to commit", "There are no changes.");
1075
+ return;
1076
+ }
1077
+ const git = adapter;
1078
+ const setUpstream = !status?.hasUpstream;
1079
+ const stageAll = !amend && git.stage && !files.some((f) => f.staged);
1080
+ await runOp("commit", () => inSteps([
1081
+ ...stageAll ? [() => git.stage({ paths: "all" })] : [],
1082
+ async () => {
1083
+ const result = await git.commit({ message: message || null, amend });
1084
+ if (git === adapter && (!result || result.ok !== false)) {
1085
+ commitBox.value = "";
1086
+ commitBox.style.height = "";
1087
+ }
1088
+ return result;
1089
+ },
1090
+ ...mode === "commitSync" ? [() => git.pull()] : [],
1091
+ ...mode === "commitPush" || mode === "commitSync" ? [() => git.push({ setUpstream })] : []
1092
+ ]));
1093
+ }
1094
+ const discardWords = ({ path, status: letter, renamedFrom }) => {
1095
+ const { name } = splitPath(path);
1096
+ if (letter === "U") {
1097
+ return { title: "Delete File", okLabel: "Delete", message: `Delete the untracked file "${name}"? This cannot be undone.` };
1098
+ }
1099
+ if (letter === "A") {
1100
+ return { title: "Delete File", okLabel: "Delete", message: `Delete the new file "${name}"? It was never committed, so this cannot be undone.` };
1101
+ }
1102
+ if (renamedFrom) {
1103
+ return { title: "Discard Changes", okLabel: "Discard", message: `Put "${splitPath(renamedFrom).name}" back and discard all changes to "${name}"?` };
1104
+ }
1105
+ return { title: "Discard Changes", okLabel: "Discard", message: `Discard all changes to "${name}"?` };
1106
+ };
1107
+ async function discard(file) {
1108
+ const git = adapter;
1109
+ const confirmed = await showConfirmDialog({ ...discardWords(file), icon: "warning", okVariant: "danger" });
1110
+ if (!confirmed) return;
1111
+ await runOp("discard", () => git.discard({
1112
+ path: file.path,
1113
+ status: file.status,
1114
+ ...file.renamedFrom ? { renamedFrom: file.renamedFrom } : {}
1115
+ }));
1116
+ }
1117
+ async function onBranchOption(option) {
1118
+ if (option.type === "checkout") {
1119
+ await runOp("checkout", () => adapter.checkout({ ref: option.ref }));
1120
+ } else if (option.type === "createBranch") {
1121
+ const git = adapter;
1122
+ const values = await askFor("Create Branch", "Create", [["name", "Branch name"]]);
1123
+ if (values) await runOp("create branch", () => git.createBranch({ name: values.name }));
1124
+ } else if (option.type === "deleteBranch") {
1125
+ await deleteBranch();
1126
+ }
1127
+ }
1128
+ const otherLocalBranches = () => branches.filter((b) => !b.isCurrent && !b.isRemote);
1129
+ async function deleteBranch() {
1130
+ const git = adapter;
1131
+ const others = otherLocalBranches();
1132
+ if (others.length === 0) {
1133
+ toastInfo("Nothing to delete", "There is no other local branch.");
1134
+ return;
1135
+ }
1136
+ const name = await showSelectDialog({
1137
+ title: "Delete Branch",
1138
+ okLabel: "Delete",
1139
+ items: others.map((b) => ({ value: b.name, label: b.name }))
1140
+ });
1141
+ if (!name) return;
1142
+ let notMerged = false;
1143
+ await runOp("delete branch", async () => {
1144
+ const result = await git.deleteBranch({ name, force: false });
1145
+ notMerged = result?.notMerged === true;
1146
+ return notMerged ? { ok: true } : result;
1147
+ });
1148
+ if (!notMerged) return;
1149
+ const force = await showConfirmDialog({
1150
+ title: "Force Delete Branch",
1151
+ message: `"${name}" is not fully merged. Delete it anyway? Commits only on it may be lost.`,
1152
+ icon: "warning",
1153
+ okLabel: "Force Delete",
1154
+ okVariant: "danger"
1155
+ });
1156
+ if (force) await runOp("delete branch", () => git.deleteBranch({ name, force: true }));
1157
+ }
1158
+ async function onOverflowOption(option) {
1159
+ switch (option.type) {
1160
+ case "stash":
1161
+ await runOp("stash", () => adapter.stash({}));
1162
+ break;
1163
+ case "stashPop":
1164
+ await runOp("stash pop", () => adapter.stashPop());
1165
+ break;
1166
+ case "merge":
1167
+ case "rebase":
1168
+ await mergeOrRebase(option.type);
1169
+ break;
1170
+ case "rebaseContinue":
1171
+ await runOp("rebase", () => adapter.rebase({ continue: true }));
1172
+ break;
1173
+ case "rebaseAbort":
1174
+ await runOp("rebase", () => adapter.rebase({ abort: true }));
1175
+ break;
1176
+ case "remotes":
1177
+ await showRemotesMenu();
1178
+ break;
1179
+ case "tags":
1180
+ await showTagsMenu();
1181
+ break;
1182
+ case "createTag":
1183
+ await createTag(adapter);
1184
+ break;
1185
+ }
1186
+ }
1187
+ async function mergeOrRebase(op) {
1188
+ const git = adapter;
1189
+ const others = otherLocalBranches();
1190
+ if (others.length === 0) {
1191
+ toastInfo(`Nothing to ${op}`, "There is no other local branch.");
1192
+ return;
1193
+ }
1194
+ const branch = await showSelectDialog({
1195
+ title: op === "merge" ? "Merge Branch" : "Rebase onto Branch",
1196
+ okLabel: op === "merge" ? "Merge" : "Rebase",
1197
+ items: others.map((b) => ({ value: b.name, label: b.name }))
1198
+ });
1199
+ if (!branch) return;
1200
+ await runOp(op, () => op === "merge" ? git.merge({ branch }) : git.rebase({ onto: branch }));
1201
+ }
1202
+ const barMenu = (items, emptyTitle, onAction) => {
1203
+ if (items.length === 0) {
1204
+ toastInfo(emptyTitle, "There are none in this repository.");
1205
+ return;
1206
+ }
1207
+ const anchor = barActions.getBoundingClientRect();
1208
+ showContextMenu(anchor.left, anchor.bottom + 4, items, onAction);
1209
+ };
1210
+ async function showRemotesMenu() {
1211
+ const git = adapter;
1212
+ const result = await callGit("Git remotes", () => git.remotes());
1213
+ if (!result || git !== adapter) return;
1214
+ const remotes = result.remotes ?? [];
1215
+ const items = git.removeRemote ? remotes.map((r, i) => ({
1216
+ label: `Remove ${r.name}`,
1217
+ title: r.url,
1218
+ icon: "cloud_off",
1219
+ action: String(i),
1220
+ danger: true
1221
+ })) : remotes.map((r) => ({ label: r.name, title: r.url, icon: "cloud", disabled: true }));
1222
+ if (git.addRemote) {
1223
+ if (items.length > 0) items.push({ separator: true });
1224
+ items.push({ label: "Add Remote\u2026", icon: "add", action: "add" });
1225
+ }
1226
+ barMenu(items, "No remotes", async (action) => {
1227
+ if (action === "add") {
1228
+ const values = await askFor("Add Remote", "Add", [["name", "Name"], ["url", "URL"]]);
1229
+ if (values) await runOp("add remote", () => git.addRemote(values));
1230
+ return;
1231
+ }
1232
+ const { name } = remotes[Number(action)];
1233
+ const confirmed = await showConfirmDialog({
1234
+ title: "Remove Remote",
1235
+ message: `Remove remote "${name}"?`,
1236
+ icon: "warning",
1237
+ okLabel: "Remove",
1238
+ okVariant: "danger"
1239
+ });
1240
+ if (confirmed) await runOp("remove remote", () => git.removeRemote({ name }));
1241
+ });
1242
+ }
1243
+ async function createTag(git) {
1244
+ const values = await askFor("Create Tag", "Create", [["name", "Tag name"]]);
1245
+ if (values) await runOp("create tag", () => git.createTag({ name: values.name }));
1246
+ }
1247
+ async function showTagsMenu() {
1248
+ const git = adapter;
1249
+ const result = await callGit("Git tags", () => git.tags());
1250
+ if (!result || git !== adapter) return;
1251
+ const tags = result.tags ?? [];
1252
+ const items = git.deleteTag ? tags.map((t, i) => ({
1253
+ label: `Delete ${t.name}`,
1254
+ title: t.hash,
1255
+ icon: "delete",
1256
+ action: String(i),
1257
+ danger: true
1258
+ })) : tags.map((t) => ({ label: t.name, title: t.hash, icon: "sell", disabled: true }));
1259
+ if (git.createTag) {
1260
+ if (items.length > 0) items.push({ separator: true });
1261
+ items.push({ label: "Create Tag\u2026", icon: "add", action: "add" });
1262
+ }
1263
+ barMenu(items, "No tags", async (action) => {
1264
+ if (action === "add") {
1265
+ await createTag(git);
1266
+ return;
1267
+ }
1268
+ const { name } = tags[Number(action)];
1269
+ const confirmed = await showConfirmDialog({
1270
+ title: "Delete Tag",
1271
+ message: `Delete tag "${name}"?`,
1272
+ icon: "warning",
1273
+ okLabel: "Delete",
1274
+ okVariant: "danger"
1275
+ });
1276
+ if (confirmed) await runOp("delete tag", () => git.deleteTag({ name }));
1277
+ });
1278
+ }
1279
+ function ensureGraph() {
1280
+ if (graph || !adapter?.log) return;
1281
+ graph = createGitGraph(history.body, {
1282
+ adapter,
1283
+ onOpenDiff: openDiff,
1284
+ showRemoteRefs,
1285
+ onChanged: () => {
1286
+ statusJson = "";
1287
+ refresh();
1288
+ }
1289
+ });
1290
+ }
1291
+ commitButton.addEventListener("click", () => commit("commit"));
1292
+ commitBox.addEventListener("keydown", (e) => {
1293
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
1294
+ e.preventDefault();
1295
+ commit("commit");
1296
+ }
1297
+ });
1298
+ commitBox.addEventListener("input", () => {
1299
+ commitBox.style.height = "auto";
1300
+ commitBox.style.height = `${Math.min(commitBox.scrollHeight, COMMIT_BOX_MAX_PX)}px`;
1301
+ });
1302
+ branchButton.addEventListener("click", () => readBranches(generation));
1303
+ drawBusy();
1304
+ drawCapabilities();
1305
+ if (graphExpanded) ensureGraph();
1306
+ showMessage("hourglass_empty", "Checking repository\u2026");
1307
+ startPolling();
1308
+ return {
1309
+ el: root,
1310
+ refresh,
1311
+ startPolling,
1312
+ stopPolling,
1313
+ setAdapter(next) {
1314
+ if (next) requireMethods(next);
1315
+ adapter = next ?? null;
1316
+ isRepo = false;
1317
+ status = null;
1318
+ statusJson = "";
1319
+ branches = [];
1320
+ reading = false;
1321
+ commitBox.value = "";
1322
+ commitBox.style.height = "";
1323
+ changes.body.replaceChildren();
1324
+ drawCapabilities();
1325
+ graph?.setAdapter(adapter);
1326
+ if (graphExpanded) ensureGraph();
1327
+ if (adapter) showMessage("hourglass_empty", "Checking repository\u2026");
1328
+ refresh();
1329
+ },
1330
+ destroy() {
1331
+ destroyed = true;
1332
+ generation++;
1333
+ stopPolling();
1334
+ branchDropdown.destroy();
1335
+ commitDropdown.destroy();
1336
+ overflowDropdown.destroy();
1337
+ graph?.destroy();
1338
+ root.remove();
1339
+ }
1340
+ };
1341
+ }
1342
+ export {
1343
+ LINE_DIFF_LIMIT,
1344
+ computeGraphLanes,
1345
+ computeLineDiff,
1346
+ createDiffViewer,
1347
+ createGitGraph,
1348
+ createGitPanel,
1349
+ diffStats,
1350
+ openDiffWindow,
1351
+ openGitDiffWindow,
1352
+ parseGitRefs,
1353
+ parseUnifiedDiff,
1354
+ splitLines
1355
+ };
1356
+ //# sourceMappingURL=git.js.map