flea-qml 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +511 -2
  3. package/THIRD-PARTY-NOTICES.md +29 -0
  4. package/demos/App.java +5 -0
  5. package/demos/Dockerfile +4 -0
  6. package/demos/Main.kt +3 -0
  7. package/demos/Program.cs +1 -0
  8. package/demos/README.md +12 -0
  9. package/demos/app.dart +3 -0
  10. package/demos/app.deb +0 -0
  11. package/demos/app.js +1 -0
  12. package/demos/backup.tar +0 -0
  13. package/demos/budget.xlsx +0 -0
  14. package/demos/build.sh +2 -0
  15. package/demos/bundle.zip +0 -0
  16. package/demos/clip.mp4 +0 -0
  17. package/demos/data.sqlite +0 -0
  18. package/demos/disk.iso +0 -0
  19. package/demos/engine.cpp +5 -0
  20. package/demos/firmware.bin +0 -0
  21. package/demos/flake.nix +4 -0
  22. package/demos/folder/inside.txt +1 -0
  23. package/demos/font.ttf +0 -0
  24. package/demos/index.html +5 -0
  25. package/demos/index.php +2 -0
  26. package/demos/init.lua +1 -0
  27. package/demos/letter.docx +0 -0
  28. package/demos/lib.rs +3 -0
  29. package/demos/main.c +6 -0
  30. package/demos/main.h +3 -0
  31. package/demos/math-and-diagrams.md +200 -0
  32. package/demos/notes.tar.gz +0 -0
  33. package/demos/notes.txt +3 -0
  34. package/demos/photo.png +0 -0
  35. package/demos/program +2 -0
  36. package/demos/report.pdf +32 -0
  37. package/demos/script.py +1 -0
  38. package/demos/server.go +7 -0
  39. package/demos/settings.json +4 -0
  40. package/demos/slides.pptx +0 -0
  41. package/demos/song.mp3 +0 -0
  42. package/demos/style.css +4 -0
  43. package/demos/tool.rb +1 -0
  44. package/demos/types.ts +2 -0
  45. package/docs/pele.md +23 -0
  46. package/docs/protocol.md +3420 -0
  47. package/docs/temml.md +63 -0
  48. package/index.js +107 -0
  49. package/package.json +34 -4
  50. package/src/archive.js +407 -0
  51. package/src/archiveops.js +728 -0
  52. package/src/backend.js +369 -0
  53. package/src/bulkrename.js +95 -0
  54. package/src/clip-host.js +76 -0
  55. package/src/clip.js +462 -0
  56. package/src/code.js +168 -0
  57. package/src/compare.js +74 -0
  58. package/src/create.js +128 -0
  59. package/src/dirsize.js +147 -0
  60. package/src/figure-svg.js +530 -0
  61. package/src/figure-thread.js +23 -0
  62. package/src/figure.js +132 -0
  63. package/src/find.js +55 -0
  64. package/src/flea.js +39 -0
  65. package/src/fsinfo.js +39 -0
  66. package/src/highlight.js +141 -0
  67. package/src/html.js +52 -0
  68. package/src/kinds.js +89 -0
  69. package/src/listing.js +130 -0
  70. package/src/markdown-html.js +317 -0
  71. package/src/mathml.js +186 -0
  72. package/src/menu.js +180 -0
  73. package/src/meta.js +135 -0
  74. package/src/office.js +810 -0
  75. package/src/order.js +100 -0
  76. package/src/peek.js +49 -0
  77. package/src/pele-wrapper.js +695 -0
  78. package/src/permissions.js +180 -0
  79. package/src/places.js +313 -0
  80. package/src/preview-language.js +10 -0
  81. package/src/terminal-server.js +95 -0
  82. package/src/terminal-shell.js +26 -0
  83. package/src/transfer.js +669 -0
  84. package/src/trash-record.js +41 -0
  85. package/src/trash.js +210 -0
  86. package/src/trashbrowse.js +327 -0
  87. package/src/uistate.js +393 -0
  88. package/src/uistore.js +168 -0
  89. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  90. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  91. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  92. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  93. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  94. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  95. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  96. package/src/vendor/pele.js +145 -0
  97. package/src/vendor/temml.mjs +14708 -0
  98. package/src/watch.js +98 -0
  99. package/src/wayland/client.js +109 -0
  100. package/src/wayland/protocol.js +55 -0
  101. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  102. package/src/wayland/protocols/wayland.xml +3248 -0
  103. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  104. package/src/wayland/resource-io.js +6 -0
  105. package/src/wayland/socket-discovery.js +26 -0
  106. package/src/wayland/transport-posix.js +120 -0
  107. package/src/wayland/transport-win32.js +11 -0
  108. package/src/wayland/transport.js +3 -0
  109. package/src/wayland/win32-resources.js +80 -0
  110. package/src/wayland/win32-wire.js +62 -0
  111. package/test/archive.test.js +80 -0
  112. package/test/bench-markdown-scroll.mjs +190 -0
  113. package/test/clip-host.test.js +113 -0
  114. package/test/code-preview.test.js +35 -0
  115. package/test/compare-ui-paths.test.js +24 -0
  116. package/test/compare.test.js +54 -0
  117. package/test/delete-trash.test.js +67 -0
  118. package/test/dirsize.test.js +138 -0
  119. package/test/file-ops.test.js +307 -0
  120. package/test/file-url.test.js +24 -0
  121. package/test/find-contents.test.js +161 -0
  122. package/test/find-input.test.js +66 -0
  123. package/test/fixtures/pele-parity.json +1006 -0
  124. package/test/highlight-launcher.test.js +35 -0
  125. package/test/html-preview.test.js +22 -0
  126. package/test/input.test.js +15 -0
  127. package/test/keymap.test.js +26 -0
  128. package/test/language-icons.test.js +36 -0
  129. package/test/launcher.test.js +51 -0
  130. package/test/manual-archive-here-ui.mjs +47 -0
  131. package/test/manual-code-scroll.mjs +28 -0
  132. package/test/manual-column-scroll.mjs +24 -0
  133. package/test/manual-compare-ui.mjs +20 -0
  134. package/test/manual-find-status.mjs +37 -0
  135. package/test/manual-folder-focus.mjs +63 -0
  136. package/test/manual-html-security.mjs +23 -0
  137. package/test/manual-html-ui.mjs +30 -0
  138. package/test/manual-syntax-preview.mjs +26 -0
  139. package/test/manual-terminal-lifecycle.mjs +42 -0
  140. package/test/manual-terminal-navigation.mjs +48 -0
  141. package/test/manual-terminal-ui.mjs +47 -0
  142. package/test/manual-trash-ui.mjs +86 -0
  143. package/test/markdown-diagrams.test.js +93 -0
  144. package/test/markdown-html.test.js +105 -0
  145. package/test/mathml.test.js +92 -0
  146. package/test/menu-transfer.test.js +57 -0
  147. package/test/office-preview.test.js +154 -0
  148. package/test/pele-parity.test.js +28 -0
  149. package/test/places.test.js +134 -0
  150. package/test/preview-keys.test.js +60 -0
  151. package/test/preview-language.test.js +22 -0
  152. package/test/status-filename.test.js +20 -0
  153. package/test/transfer-replace-merge.test.js +187 -0
  154. package/test/trash-address.test.js +23 -0
  155. package/test/trash-view.test.js +37 -0
  156. package/test/trashbrowse.test.js +135 -0
  157. package/ui/AboutFacts.qml +78 -0
  158. package/ui/ArchiveCards.qml +95 -0
  159. package/ui/ArchiveCompressDialog.qml +378 -0
  160. package/ui/ArchiveHereConfirm.qml +127 -0
  161. package/ui/ArchivePasswordDialog.qml +257 -0
  162. package/ui/Backend.qml +775 -0
  163. package/ui/BulkOldNames.qml +47 -0
  164. package/ui/BulkRenameDialog.qml +380 -0
  165. package/ui/CardFrame.qml +67 -0
  166. package/ui/CardScroll.qml +79 -0
  167. package/ui/CheckBox.qml +73 -0
  168. package/ui/ChromeBar.qml +360 -0
  169. package/ui/ChromeButton.qml +131 -0
  170. package/ui/ChromeModes.qml +43 -0
  171. package/ui/CodeLines.qml +256 -0
  172. package/ui/CodeRefusal.qml +52 -0
  173. package/ui/CollideConfirm.qml +147 -0
  174. package/ui/CollideHost.qml +167 -0
  175. package/ui/ColumnPane.qml +525 -0
  176. package/ui/ColumnRow.qml +380 -0
  177. package/ui/ColumnsArea.qml +594 -0
  178. package/ui/ColumnsLanding.qml +147 -0
  179. package/ui/CompareView.qml +321 -0
  180. package/ui/ContextMenu.qml +743 -0
  181. package/ui/ConvertDialog.qml +456 -0
  182. package/ui/CountedWorker.qml +7 -0
  183. package/ui/Crumb.qml +64 -0
  184. package/ui/CrumbDrop.qml +25 -0
  185. package/ui/CsvTable.qml +394 -0
  186. package/ui/CursorMark.qml +41 -0
  187. package/ui/DefaultClaim.qml +130 -0
  188. package/ui/DeviceLock.qml +47 -0
  189. package/ui/DeviceMounts.qml +540 -0
  190. package/ui/DialogButton.qml +150 -0
  191. package/ui/DialogField.qml +147 -0
  192. package/ui/DialogHeader.qml +31 -0
  193. package/ui/DialogScrim.qml +20 -0
  194. package/ui/DialogTitle.qml +37 -0
  195. package/ui/Divider.qml +9 -0
  196. package/ui/DropInto.qml +126 -0
  197. package/ui/DropTarget.qml +35 -0
  198. package/ui/DropboxMark.qml +60 -0
  199. package/ui/EmptyState.qml +150 -0
  200. package/ui/EscapeHint.qml +11 -0
  201. package/ui/FactsTable.qml +54 -0
  202. package/ui/FastScrollHandler.qml +476 -0
  203. package/ui/FatNamesConfirm.qml +157 -0
  204. package/ui/Favourites.qml +143 -0
  205. package/ui/FigureService.qml +120 -0
  206. package/ui/FileDrag.qml +267 -0
  207. package/ui/FilterStrip.qml +133 -0
  208. package/ui/FitMetrics.qml +22 -0
  209. package/ui/FlashDriveRows.qml +137 -0
  210. package/ui/FlashHost.qml +72 -0
  211. package/ui/FlashImageRow.qml +104 -0
  212. package/ui/FlashRunner.qml +119 -0
  213. package/ui/FlashStepRow.qml +172 -0
  214. package/ui/FlashStrip.qml +38 -0
  215. package/ui/FlashView.qml +397 -0
  216. package/ui/FleaMark.qml +88 -0
  217. package/ui/FlyoutFade.qml +23 -0
  218. package/ui/FontPane.qml +170 -0
  219. package/ui/Glass.qml +114 -0
  220. package/ui/GlassTagger.qml +59 -0
  221. package/ui/Glyph.qml +64 -0
  222. package/ui/GlyphSolid.qml +22 -0
  223. package/ui/GridArea.qml +664 -0
  224. package/ui/GridTile.qml +325 -0
  225. package/ui/GvfsBridge.qml +164 -0
  226. package/ui/Header.qml +433 -0
  227. package/ui/HoverTimers.qml +39 -0
  228. package/ui/HtmlClip.qml +30 -0
  229. package/ui/HtmlPane.qml +63 -0
  230. package/ui/HyprlandMark.qml +45 -0
  231. package/ui/Ipc.qml +1005 -0
  232. package/ui/IpcControls.qml +7 -0
  233. package/ui/IpcMenuState.qml +48 -0
  234. package/ui/IpcPreviewState.qml +185 -0
  235. package/ui/JumpPath.qml +51 -0
  236. package/ui/KeymapSheet.qml +561 -0
  237. package/ui/KeymapSheetResult.qml +89 -0
  238. package/ui/KindStack.qml +40 -0
  239. package/ui/LangMark.qml +51 -0
  240. package/ui/LangMarkLoader.qml +26 -0
  241. package/ui/List.qml +480 -0
  242. package/ui/LoadingState.qml +54 -0
  243. package/ui/LocalSend.qml +56 -0
  244. package/ui/LocalSendMark.qml +73 -0
  245. package/ui/MarkdownBlockView.qml +311 -0
  246. package/ui/MarkdownBlocks.qml +35 -0
  247. package/ui/MarkdownFigure.qml +254 -0
  248. package/ui/MarkdownImages.qml +72 -0
  249. package/ui/MarkdownList.qml +81 -0
  250. package/ui/MarkdownMathsText.qml +97 -0
  251. package/ui/MarkdownPane.qml +83 -0
  252. package/ui/MarkdownPictures.qml +41 -0
  253. package/ui/MarkdownQuote.qml +52 -0
  254. package/ui/MarkdownRemote.qml +82 -0
  255. package/ui/MarkdownSource.qml +155 -0
  256. package/ui/MarkdownTable.qml +222 -0
  257. package/ui/MarkdownTableScroll.qml +64 -0
  258. package/ui/MarkdownTableWheel.qml +126 -0
  259. package/ui/MarkdownText.qml +87 -0
  260. package/ui/MarkdownWorker.js +132 -0
  261. package/ui/MatchText.qml +99 -0
  262. package/ui/MediaSession.qml +38 -0
  263. package/ui/MediaSound.qml +26 -0
  264. package/ui/MediaStrip.qml +191 -0
  265. package/ui/MediaTrack.qml +36 -0
  266. package/ui/MediaVolume.qml +54 -0
  267. package/ui/MenuActionDialog.qml +396 -0
  268. package/ui/MenuEdgeFade.qml +24 -0
  269. package/ui/MenuHost.qml +227 -0
  270. package/ui/MenuRow.qml +351 -0
  271. package/ui/MountListing.qml +100 -0
  272. package/ui/NetworkDialog.qml +435 -0
  273. package/ui/NetworkForm.qml +426 -0
  274. package/ui/NetworkMounts.qml +913 -0
  275. package/ui/NetworkPlaces.qml +127 -0
  276. package/ui/OpenWithDialog.qml +613 -0
  277. package/ui/Opener.qml +95 -0
  278. package/ui/Pane.qml +1320 -0
  279. package/ui/PaneMenuActions.qml +527 -0
  280. package/ui/PanePath.qml +90 -0
  281. package/ui/PaneRail.qml +268 -0
  282. package/ui/PaneStates.qml +88 -0
  283. package/ui/PaneSwap.qml +339 -0
  284. package/ui/PaneWire.qml +601 -0
  285. package/ui/ParkedWait.qml +9 -0
  286. package/ui/PathEntry.qml +186 -0
  287. package/ui/PathJump.qml +400 -0
  288. package/ui/PdfViewer.qml +297 -0
  289. package/ui/PermissionsDialog.qml +658 -0
  290. package/ui/PermissionsHeadings.qml +31 -0
  291. package/ui/PhoneMounts.qml +67 -0
  292. package/ui/PickerAnswers.qml +75 -0
  293. package/ui/PickerBox.qml +26 -0
  294. package/ui/PickerChrome.qml +295 -0
  295. package/ui/PickerFilter.qml +97 -0
  296. package/ui/PickerGrid.qml +308 -0
  297. package/ui/PickerHeader.qml +28 -0
  298. package/ui/PickerLifecycle.qml +27 -0
  299. package/ui/PickerList.qml +218 -0
  300. package/ui/PickerListing.qml +149 -0
  301. package/ui/PickerPathBar.qml +127 -0
  302. package/ui/PickerPlaces.qml +189 -0
  303. package/ui/PickerRecent.qml +92 -0
  304. package/ui/PickerSave.qml +101 -0
  305. package/ui/PickerSelection.qml +155 -0
  306. package/ui/PickerWindow.qml +846 -0
  307. package/ui/PowerSectorsReader.qml +10 -0
  308. package/ui/PrebuildDriver.qml +146 -0
  309. package/ui/Preview.qml +1005 -0
  310. package/ui/PreviewAnimation.qml +24 -0
  311. package/ui/PreviewArchive.qml +91 -0
  312. package/ui/PreviewColumn.qml +553 -0
  313. package/ui/PreviewEdge.qml +63 -0
  314. package/ui/PreviewFacts.qml +30 -0
  315. package/ui/PreviewFactsLine.qml +34 -0
  316. package/ui/PreviewHtml.qml +233 -0
  317. package/ui/PreviewImage.qml +253 -0
  318. package/ui/PreviewImagePan.qml +13 -0
  319. package/ui/PreviewImageSharp.qml +32 -0
  320. package/ui/PreviewLines.qml +106 -0
  321. package/ui/PreviewMarkdown.qml +706 -0
  322. package/ui/PreviewMedia.qml +250 -0
  323. package/ui/PreviewPdf.qml +482 -0
  324. package/ui/PreviewScroll.qml +201 -0
  325. package/ui/PreviewSwap.qml +335 -0
  326. package/ui/PreviewText.qml +149 -0
  327. package/ui/ProtocolChip.qml +18 -0
  328. package/ui/QuickLookArchive.qml +41 -0
  329. package/ui/QuickLookBar.qml +103 -0
  330. package/ui/QuickLookPrepare.qml +355 -0
  331. package/ui/QuickLookSwap.qml +10 -0
  332. package/ui/QuickLookWarm.qml +65 -0
  333. package/ui/RailDrop.qml +155 -0
  334. package/ui/RailHeading.qml +19 -0
  335. package/ui/RailPlaces.qml +38 -0
  336. package/ui/RailTrashRow.qml +67 -0
  337. package/ui/RenameField.qml +353 -0
  338. package/ui/RendererRetry.qml +11 -0
  339. package/ui/ResizeHandle.qml +52 -0
  340. package/ui/Reveal.qml +54 -0
  341. package/ui/Row.qml +476 -0
  342. package/ui/RowDrag.qml +213 -0
  343. package/ui/RowDropTarget.qml +27 -0
  344. package/ui/RunCard.qml +286 -0
  345. package/ui/Scripts.qml +65 -0
  346. package/ui/SearchStrip.qml +232 -0
  347. package/ui/SelectionBand.qml +140 -0
  348. package/ui/SelectionPreview.qml +370 -0
  349. package/ui/SettingsFavourite.qml +122 -0
  350. package/ui/SettingsGroup.qml +96 -0
  351. package/ui/SettingsHint.qml +12 -0
  352. package/ui/SettingsMetrics.qml +29 -0
  353. package/ui/SettingsPane.qml +178 -0
  354. package/ui/SettingsPanel.qml +547 -0
  355. package/ui/SettingsRail.qml +128 -0
  356. package/ui/SettingsRow.qml +409 -0
  357. package/ui/SettingsRuler.qml +85 -0
  358. package/ui/SettingsSegment.qml +85 -0
  359. package/ui/ShareBrowser.qml +113 -0
  360. package/ui/ShareLink.qml +67 -0
  361. package/ui/ShelfPins.qml +101 -0
  362. package/ui/Sidebar.qml +728 -0
  363. package/ui/SidebarRow.qml +358 -0
  364. package/ui/Spinner.qml +60 -0
  365. package/ui/StableModel.qml +89 -0
  366. package/ui/StateMessage.qml +84 -0
  367. package/ui/StatusBar.qml +512 -0
  368. package/ui/StatusFileName.qml +27 -0
  369. package/ui/TabBar.qml +673 -0
  370. package/ui/TabDragGeometry.qml +60 -0
  371. package/ui/TagMenuMark.qml +52 -0
  372. package/ui/TagRailSection.qml +53 -0
  373. package/ui/TagSlot.qml +40 -0
  374. package/ui/TagState.qml +150 -0
  375. package/ui/TagSwatch.qml +28 -0
  376. package/ui/Taildrop.qml +86 -0
  377. package/ui/TailscaleMark.qml +50 -0
  378. package/ui/TerminalService.qml +69 -0
  379. package/ui/TerminalTab.qml +48 -0
  380. package/ui/TerminalTabs.qml +27 -0
  381. package/ui/Theme.qml +460 -0
  382. package/ui/ThemeColors.qml +43 -0
  383. package/ui/TransferCard.qml +239 -0
  384. package/ui/TrashConfirm.qml +129 -0
  385. package/ui/TrashDropHandler.qml +11 -0
  386. package/ui/TrashHost.qml +202 -0
  387. package/ui/TrashMonitor.qml +30 -0
  388. package/ui/TrashToolbar.qml +19 -0
  389. package/ui/TrashView.qml +492 -0
  390. package/ui/UnlockDialog.qml +450 -0
  391. package/ui/UnlockHost.qml +206 -0
  392. package/ui/UpdateCheck.qml +97 -0
  393. package/ui/UserDirs.qml +25 -0
  394. package/ui/ViewState.qml +565 -0
  395. package/ui/ViewportScrollBar.qml +195 -0
  396. package/ui/ViewportScrollBars.qml +25 -0
  397. package/ui/WebPreview.qml +33 -0
  398. package/ui/WebPreviewView.qml +26 -0
  399. package/ui/WindowBody.qml +829 -0
  400. package/ui/WindowDrag.qml +35 -0
  401. package/ui/boot/Commons/AudioNodes.qml +50 -0
  402. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  403. package/ui/boot/Commons/Border.qml +243 -0
  404. package/ui/boot/Commons/BorderGeometry.js +373 -0
  405. package/ui/boot/Commons/Color.qml +258 -0
  406. package/ui/boot/Commons/I18n.qml +118 -0
  407. package/ui/boot/Commons/InputDevices.qml +112 -0
  408. package/ui/boot/Commons/InputMethodState.qml +38 -0
  409. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  410. package/ui/boot/Commons/Numbers.js +19 -0
  411. package/ui/boot/Commons/Plural.js +91 -0
  412. package/ui/boot/Commons/ShellIpc.qml +12 -0
  413. package/ui/boot/Commons/Style.qml +561 -0
  414. package/ui/boot/Commons/Translations.qml +100 -0
  415. package/ui/boot/Commons/UntypedInput.qml +152 -0
  416. package/ui/boot/Commons/Util.qml +188 -0
  417. package/ui/boot/Commons/qmldir +13 -0
  418. package/ui/boot/LICENSE-omarchy +20 -0
  419. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  420. package/ui/boot/Ui/BarIconButton.qml +106 -0
  421. package/ui/boot/Ui/BarIconContent.qml +38 -0
  422. package/ui/boot/Ui/BarIndicator.qml +53 -0
  423. package/ui/boot/Ui/BarWidget.qml +50 -0
  424. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  425. package/ui/boot/Ui/BorderSurface.qml +40 -0
  426. package/ui/boot/Ui/Button.qml +224 -0
  427. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  428. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  429. package/ui/boot/Ui/CursorSurface.qml +42 -0
  430. package/ui/boot/Ui/Dropdown.qml +245 -0
  431. package/ui/boot/Ui/FilterInput.qml +26 -0
  432. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  433. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  434. package/ui/boot/Ui/MultiSelect.qml +624 -0
  435. package/ui/boot/Ui/NumberField.qml +86 -0
  436. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  437. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  438. package/ui/boot/Ui/Panel.qml +60 -0
  439. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  440. package/ui/boot/Ui/PanelController.qml +16 -0
  441. package/ui/boot/Ui/PanelHero.qml +112 -0
  442. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  443. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  444. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  445. package/ui/boot/Ui/PanelSlider.qml +156 -0
  446. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  447. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  448. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  449. package/ui/boot/Ui/PopupCard.qml +181 -0
  450. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  451. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  452. package/ui/boot/Ui/SizedImage.qml +125 -0
  453. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  454. package/ui/boot/Ui/TextField.qml +58 -0
  455. package/ui/boot/Ui/Toggle.qml +118 -0
  456. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  457. package/ui/boot/Ui/WidgetButton.qml +172 -0
  458. package/ui/boot/Ui/qmldir +41 -0
  459. package/ui/boot/fleatab.qml +50 -0
  460. package/ui/boot/picker.qml +39 -0
  461. package/ui/boot/shell.qml +131 -0
  462. package/ui/boot/tabtearoff.qml +51 -0
  463. package/ui/js/Anchor.js +379 -0
  464. package/ui/js/AnchorHold.js +162 -0
  465. package/ui/js/Archive.js +157 -0
  466. package/ui/js/ArchiveCard.js +44 -0
  467. package/ui/js/ArchiveOps.js +44 -0
  468. package/ui/js/BackReturn.js +118 -0
  469. package/ui/js/BulkRename.js +100 -0
  470. package/ui/js/Buttons.js +54 -0
  471. package/ui/js/ChromeKeys.js +116 -0
  472. package/ui/js/ClipMarks.js +73 -0
  473. package/ui/js/ClipSelection.js +67 -0
  474. package/ui/js/Clipboard.js +179 -0
  475. package/ui/js/Cloud.js +97 -0
  476. package/ui/js/CodeRuns.js +72 -0
  477. package/ui/js/Collide.js +212 -0
  478. package/ui/js/ColumnFit.js +28 -0
  479. package/ui/js/ColumnMenu.js +111 -0
  480. package/ui/js/Columns.js +298 -0
  481. package/ui/js/ColumnsCarry.js +103 -0
  482. package/ui/js/Compare.js +74 -0
  483. package/ui/js/Contrast.js +157 -0
  484. package/ui/js/ControlReader.js +42 -0
  485. package/ui/js/Convert.js +52 -0
  486. package/ui/js/CopyAs.js +52 -0
  487. package/ui/js/Crumbs.js +178 -0
  488. package/ui/js/Csv.js +231 -0
  489. package/ui/js/Density.js +44 -0
  490. package/ui/js/Devices.js +440 -0
  491. package/ui/js/DirSizes.js +180 -0
  492. package/ui/js/Drag.js +360 -0
  493. package/ui/js/DragOut.js +166 -0
  494. package/ui/js/DropDev.js +52 -0
  495. package/ui/js/Dropbox.js +41 -0
  496. package/ui/js/Eject.js +217 -0
  497. package/ui/js/Errors.js +188 -0
  498. package/ui/js/ExtThumbs.js +115 -0
  499. package/ui/js/Facts.js +402 -0
  500. package/ui/js/FigureDate.js +14 -0
  501. package/ui/js/Filter.js +285 -0
  502. package/ui/js/Find.js +46 -0
  503. package/ui/js/Flash.js +184 -0
  504. package/ui/js/FlashCard.js +79 -0
  505. package/ui/js/FlashEndings.js +125 -0
  506. package/ui/js/FlashPane.js +116 -0
  507. package/ui/js/FleaBin.js +12 -0
  508. package/ui/js/Focus.js +504 -0
  509. package/ui/js/FolderSorts.js +108 -0
  510. package/ui/js/FolderViews.js +44 -0
  511. package/ui/js/Format.js +314 -0
  512. package/ui/js/Fuzzy.js +141 -0
  513. package/ui/js/GlassTag.js +72 -0
  514. package/ui/js/Grid.js +33 -0
  515. package/ui/js/GridGeometry.js +90 -0
  516. package/ui/js/GridNames.js +153 -0
  517. package/ui/js/GvfsBridge.js +200 -0
  518. package/ui/js/HostClipboard.js +29 -0
  519. package/ui/js/Icons.js +218 -0
  520. package/ui/js/Input.js +50 -0
  521. package/ui/js/Jump.js +200 -0
  522. package/ui/js/KeyName.js +64 -0
  523. package/ui/js/Keymap.js +401 -0
  524. package/ui/js/Kinds.js +164 -0
  525. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  526. package/ui/js/LangMarkPaths.js +47 -0
  527. package/ui/js/LangMarks.js +120 -0
  528. package/ui/js/Lists.js +11 -0
  529. package/ui/js/LocalSend.js +39 -0
  530. package/ui/js/LockedMenu.js +63 -0
  531. package/ui/js/MakeDefault.js +186 -0
  532. package/ui/js/MarkBox.js +7 -0
  533. package/ui/js/Markdown.js +119 -0
  534. package/ui/js/MarkdownLists.js +86 -0
  535. package/ui/js/MarkdownMaths.js +58 -0
  536. package/ui/js/MarkdownPictures.js +32 -0
  537. package/ui/js/MarkdownPrepared.js +139 -0
  538. package/ui/js/MarkdownTableFit.js +34 -0
  539. package/ui/js/Marks.js +148 -0
  540. package/ui/js/Marquee.js +96 -0
  541. package/ui/js/Match.js +93 -0
  542. package/ui/js/Material.js +109 -0
  543. package/ui/js/MdBlocks.js +437 -0
  544. package/ui/js/MdBreak.js +71 -0
  545. package/ui/js/MdChunks.js +70 -0
  546. package/ui/js/MdContainer.js +137 -0
  547. package/ui/js/MdDocument.js +337 -0
  548. package/ui/js/MdEmph.js +97 -0
  549. package/ui/js/MdEntity.js +108 -0
  550. package/ui/js/MdEntityTable.js +223 -0
  551. package/ui/js/MdEscape.js +59 -0
  552. package/ui/js/MdFront.js +37 -0
  553. package/ui/js/MdHold.js +32 -0
  554. package/ui/js/MdHtml.js +307 -0
  555. package/ui/js/MdHtmlBlock.js +290 -0
  556. package/ui/js/MdHtmlHeading.js +53 -0
  557. package/ui/js/MdHtmlImage.js +179 -0
  558. package/ui/js/MdInline.js +154 -0
  559. package/ui/js/MdItems.js +212 -0
  560. package/ui/js/MdLeaf.js +309 -0
  561. package/ui/js/MdLink.js +196 -0
  562. package/ui/js/MdMath.js +67 -0
  563. package/ui/js/MdParagraphs.js +138 -0
  564. package/ui/js/MdRefs.js +232 -0
  565. package/ui/js/MdResolve.js +226 -0
  566. package/ui/js/MdRun.js +300 -0
  567. package/ui/js/MdUrl.js +226 -0
  568. package/ui/js/Menu.js +514 -0
  569. package/ui/js/MenuContext.js +85 -0
  570. package/ui/js/MenuFit.js +13 -0
  571. package/ui/js/MenuRefresh.js +74 -0
  572. package/ui/js/MenuWheel.js +35 -0
  573. package/ui/js/Messages.js +186 -0
  574. package/ui/js/Motion.js +16 -0
  575. package/ui/js/Mounts.js +320 -0
  576. package/ui/js/MouseNav.js +13 -0
  577. package/ui/js/Nav.js +329 -0
  578. package/ui/js/NetFs.js +26 -0
  579. package/ui/js/OpenWith.js +83 -0
  580. package/ui/js/Ops.js +585 -0
  581. package/ui/js/Palette.js +33 -0
  582. package/ui/js/PathBar.js +260 -0
  583. package/ui/js/Permissions.js +179 -0
  584. package/ui/js/Phones.js +112 -0
  585. package/ui/js/Picker.js +353 -0
  586. package/ui/js/PlaceMenu.js +96 -0
  587. package/ui/js/Places.js +265 -0
  588. package/ui/js/PreviewAsk.js +21 -0
  589. package/ui/js/PreviewKeys.js +155 -0
  590. package/ui/js/PreviewSettle.js +29 -0
  591. package/ui/js/PreviewSwap.js +101 -0
  592. package/ui/js/PropertyFacts.js +144 -0
  593. package/ui/js/Protocols.js +296 -0
  594. package/ui/js/QuickLookKinds.js +27 -0
  595. package/ui/js/QuickLookRect.js +24 -0
  596. package/ui/js/RailDrop.js +51 -0
  597. package/ui/js/RailKeys.js +87 -0
  598. package/ui/js/RailMenu.js +133 -0
  599. package/ui/js/Recent.js +135 -0
  600. package/ui/js/RecentMode.js +156 -0
  601. package/ui/js/Reload.js +60 -0
  602. package/ui/js/Renderer.js +9 -0
  603. package/ui/js/RunFile.js +143 -0
  604. package/ui/js/Scripts.js +28 -0
  605. package/ui/js/Scroll.js +304 -0
  606. package/ui/js/ScrollOff.js +69 -0
  607. package/ui/js/Search.js +478 -0
  608. package/ui/js/Selection.js +97 -0
  609. package/ui/js/Settings.js +476 -0
  610. package/ui/js/SettingsAbout.js +97 -0
  611. package/ui/js/SettingsShelf.js +52 -0
  612. package/ui/js/SheetKeys.js +42 -0
  613. package/ui/js/SheetQuery.js +281 -0
  614. package/ui/js/ShelfPile.js +27 -0
  615. package/ui/js/SlowClick.js +53 -0
  616. package/ui/js/SlowOp.js +6 -0
  617. package/ui/js/Sort.js +134 -0
  618. package/ui/js/Startup.js +63 -0
  619. package/ui/js/StateWords.js +17 -0
  620. package/ui/js/Status.js +238 -0
  621. package/ui/js/Swap.js +166 -0
  622. package/ui/js/TabMove.js +39 -0
  623. package/ui/js/Tabs.js +751 -0
  624. package/ui/js/Tags.js +134 -0
  625. package/ui/js/Taildrop.js +128 -0
  626. package/ui/js/Tap.js +180 -0
  627. package/ui/js/TextSize.js +107 -0
  628. package/ui/js/Thumbs.js +158 -0
  629. package/ui/js/Transfer.js +172 -0
  630. package/ui/js/Trash.js +84 -0
  631. package/ui/js/TrashDates.js +47 -0
  632. package/ui/js/TrashDrop.js +64 -0
  633. package/ui/js/UiState.js +438 -0
  634. package/ui/js/Unlock.js +152 -0
  635. package/ui/js/Update.js +130 -0
  636. package/ui/js/UserDirs.js +56 -0
  637. package/ui/qmldir +230 -0
@@ -0,0 +1,23 @@
1
+ import {test,expect} from 'bun:test';
2
+ async function load(name,names,deps={}) {
3
+ const source=(await Bun.file(new URL('../ui/js/'+name+'.js',import.meta.url)).text()).replace(/^\.(?:pragma|import).*$/gm,'');
4
+ return new Function(...Object.keys(deps),source+';return {'+names+'};')(...Object.values(deps));
5
+ }
6
+ const PathBar=await load('PathBar','isTrashRoot,addressTarget,editLine,shouldNavigate',{Format:{}});
7
+ const Crumbs=await load('Crumbs','crumbs');
8
+ test('Trash root aliases canonicalize only at the address input boundary',()=>{
9
+ for(const value of ['trash:/','trash://','trash:///',' TRASH:/// ']) {
10
+ expect(PathBar.isTrashRoot(value)).toBe(true);
11
+ expect(PathBar.addressTarget(value,'/home/test','/home/test')).toBe('trash:///');
12
+ }
13
+ for(const value of ['trash:','trash:////','trash://host/','trash:///file','/tmp/trash:','trash'])expect(PathBar.isTrashRoot(value)).toBe(false);
14
+ expect(PathBar.addressTarget('/tmp','trash:///','/home/test')).toBe('/tmp');
15
+ expect(PathBar.addressTarget('~','trash:///','/home/test')).toBe('/home/test');
16
+ expect(PathBar.addressTarget('tmp','trash:///','/home/test')).toBe('/tmp');
17
+ });
18
+ test('Trash root remains a single breadcrumb and does not gain a fourth slash',()=>{
19
+ expect(PathBar.editLine('trash:///')).toBe('trash:///');
20
+ expect(PathBar.editLine('/tmp')).toBe('/tmp/');
21
+ expect(Crumbs.crumbs('trash:///','/home/test',null)).toEqual([{text:'trash:///',path:'trash:///',last:true}]);
22
+ expect(PathBar.shouldNavigate('trash:///','trash:///',false)).toBe(false);
23
+ });
@@ -0,0 +1,37 @@
1
+ import {test,expect} from 'bun:test';
2
+ async function load(name,names,deps={}) {
3
+ const source=(await Bun.file(new URL('../ui/js/'+name+'.js',import.meta.url)).text()).replace(/^\.(?:pragma|import).*$/gm,'');
4
+ return new Function(...Object.keys(deps),source+';return {'+names+'};')(...Object.values(deps));
5
+ }
6
+ test('Back from a trashed directory restores its collection and cursor, not filesystem root',async()=>{
7
+ const Back=await load('BackReturn','capture,travel');
8
+ const pane={path:'/',recentMode:'trash',recentFrom:'/home/test',cursorIndex:3,rowFor:()=>({n:'tmp/Trash/files/folder'})};
9
+ const entry=Back.capture(pane);
10
+ let opened=0;
11
+ Object.assign(pane,{path:'/tmp/Trash/files/folder',recentMode:'',history:[entry],forwardHistory:[],
12
+ trash:{open(){opened++}},join:(base,name)=>base+name,
13
+ openWithoutHistory(){throw Error('must not navigate to /')}});
14
+ Back.travel(pane,'history','forwardHistory');
15
+ expect(opened).toBe(1);expect(pane.recentFrom).toBe('/home/test');expect(pane.pendingSelect).toBe('/tmp/Trash/files/folder');
16
+ expect(pane.forwardHistory[0].path).toBe('/tmp/Trash/files/folder');
17
+ });
18
+ test('Trash Filter uses the visible original name, not its collision-safe storage name',async()=>{
19
+ const Match=await load('Match','run,base,rowName');
20
+ const Filter=await load('Filter','shown',{Match});
21
+ const rows=[{n:'tmp/Trash/files/report.2',trash:{original:'/docs/report'}},{n:'tmp/Trash/files/elsewhere',trash:{original:'/docs/notes'}}];
22
+ expect(Filter.shown(rows,10,'report')).toEqual([10]);
23
+ expect(Filter.shown(rows,10,'report.2')).toEqual([]);
24
+ });
25
+ test('Find in the virtual Trash cannot silently search the previous directory',async()=>{
26
+ const Search=await load('Search','start');let message='';
27
+ const pane={recentMode:'trash',path:'/',recentFrom:'/private',message:m=>message=m};
28
+ Search.start(pane);
29
+ expect(pane.path).toBe('/');expect(pane.recentMode).toBe('trash');expect(message).toContain('filters');
30
+ });
31
+
32
+ test('Trash captions share original basenames in list, grid and columns',async()=>{
33
+ const Match=await load('Match','rowName');
34
+ expect(Match.rowName({n:'tmp/Trash/files/report.2',trash:{original:'/docs/report'}},false)).toBe('report');
35
+ expect(Match.rowName({n:'C:/Trash/files/report.2',trash:{original:'C:\\docs\\report',name:'report'}},true)).toBe('report');
36
+ expect(Match.rowName({n:'sub/file.txt'},false)).toBe('sub/file.txt');
37
+ });
@@ -0,0 +1,135 @@
1
+ import {test, expect, beforeEach, afterEach} from 'bun:test';
2
+ import {mkdtempSync,mkdirSync,writeFileSync,readFileSync,renameSync,symlinkSync,existsSync,rmSync} from 'node:fs';
3
+ import {join,dirname} from 'node:path';
4
+ import {tmpdir} from 'node:os';
5
+ import {trashOne,homeTrash,trashInfoPath,trashInfoOriginal} from '../src/trash.js';
6
+ import {trashbrowse,readTrash,watchTrashCount} from '../src/trashbrowse.js';
7
+ let root,saved,stop;
8
+ const call=(op,fields={})=>{const replies=[];trashbrowse({op,id:1,...fields},m=>replies.push(m));return replies.find(m=>m.t==='trashbrowse');};
9
+ beforeEach(()=>{root=mkdtempSync(join(tmpdir(),'flea-trashbrowse-'));saved=process.env.XDG_DATA_HOME;process.env.XDG_DATA_HOME=join(root,'data');mkdirSync(process.env.XDG_DATA_HOME);call('cancel',{clearSelection:true});});
10
+ afterEach(()=>{stop?.();stop=null;if(saved===undefined)delete process.env.XDG_DATA_HOME;else process.env.XDG_DATA_HOME=saved;rmSync(root,{recursive:true,force:true});});
11
+ function item(name='sample',directory=false){const path=join(root,name);if(directory){mkdirSync(path);writeFileSync(join(path,'keep'),'keep');}else writeFileSync(path,'payload');trashOne(path);return readTrash().find(i=>i.original===path);}
12
+ test('Trash list, restore, collisions and single-use deletion',()=>{
13
+ const a=item('a');expect(call('list',{count:20}).rows.some(r=>r.uri===a.uri)).toBe(true);
14
+ writeFileSync(a.original,'new');expect(call('restore',{uris:[a.uri],identities:[a.identity]}).failed).toBe(1);
15
+ expect(readFileSync(a.original,'utf8')).toBe('new');rmSync(a.original);
16
+ expect(call('restore',{uris:[a.uri],identities:[a.identity]}).done).toBe(1);expect(readFileSync(a.original,'utf8')).toBe('payload');
17
+ const b=item('b');const prepared=call('prepare',{uris:[b.uri],identities:[b.identity]});expect(prepared.name).toBe('b');const {token}=prepared;
18
+ expect(call('check',{token}).valid).toBe(true);expect(call('delete',{token}).done).toBe(1);expect(call('delete',{token}).ok).toBe(false);
19
+ });
20
+ test('cancel and failed replacement reviews invalidate deletion tokens',()=>{
21
+ const a=item();let {token}=call('prepare',{uris:[a.uri]});call('cancel');expect(call('delete',{token}).ok).toBe(false);
22
+ ({token}=call('prepare',{uris:[a.uri]}));expect(call('prepare',{uris:['missing']}).ok).toBe(false);
23
+ expect(call('delete',{token}).ok).toBe(false);expect(existsSync(a.file)).toBe(true);
24
+ });
25
+ test('new descendants invalidate the whole deletion review',()=>{
26
+ const a=item('folder',true),{token}=call('prepare',{uris:[a.uri]});
27
+ writeFileSync(join(a.file,'new'),'new');expect(call('check',{token}).valid).toBe(false);
28
+ expect(call('delete',{token}).stale).toBe(true);expect(existsSync(join(a.file,'keep'))).toBe(true);
29
+ });
30
+ test('bulk selection refuses replaced files and metadata',()=>{
31
+ const a=item();call('list',{count:20});const {selectionToken}=call('select');
32
+ renameSync(a.file,a.file+'.old');writeFileSync(a.file,'replacement');
33
+ expect(call('restore',{selectionToken}).stale).toBe(true);
34
+ call('list',{count:20});const selected=call('select');writeFileSync(a.info,'[Trash Info]\nPath=/another-place\n');
35
+ expect(call('prepare',{selectionToken:selected.selectionToken}).stale).toBe(true);
36
+ });
37
+ test('Trash ignores symlink directories and dot metadata names',()=>{
38
+ const a=item();writeFileSync(join(homeTrash(),'info','...trashinfo'),'[Trash Info]\nPath=/bad\n');
39
+ expect(readTrash().some(i=>i.name==='..')).toBe(false);
40
+ const files=join(homeTrash(),'files');renameSync(files,files+'.old');symlinkSync(files+'.old',files);
41
+ expect(readTrash().some(i=>i.uri===a.uri)).toBe(false);
42
+ });
43
+ test('metadata round trips punctuation and rejects escaping relative paths',()=>{
44
+ const path=join(root,'a:b #中文 % x');expect(trashInfoOriginal(trashInfoPath(path), '')).toBe(path);
45
+ expect(trashInfoOriginal(trashInfoPath(path,root),root)).toBe(path);
46
+ for(const bad of ['../outside','%2e%2e/outside','%2foutside','bad%00name','%ZZ',''])expect(trashInfoOriginal(bad,root)).toBe('');
47
+ });
48
+ test('count watcher reports same-count replacements and disposes',async()=>{
49
+ const a=item();let reports=0;stop=watchTrashCount(()=>reports++);expect(reports).toBe(1);
50
+ renameSync(a.file,a.file+'.old');writeFileSync(a.file,'replacement');
51
+ const deadline=Date.now()+3000;while(reports<2 && Date.now()<deadline)await Bun.sleep(20);
52
+ expect(reports).toBeGreaterThan(1);stop();stop=null;
53
+ });
54
+ test('backend routes Trash requests and exits cleanly',()=>{
55
+ const result=Bun.spawnSync([process.execPath,new URL('../src/flea.js',import.meta.url).pathname,'--backend'],{
56
+ env:{...process.env},stdin:Buffer.from('{"c":"trashbrowse","op":"list","id":27,"count":10}\n{"c":"quit"}\n'),stdout:'pipe',stderr:'pipe'});
57
+ expect(result.exitCode).toBe(0);
58
+ const messages=result.stdout.toString().trim().split('\n').map(line=>JSON.parse(line));
59
+ expect(messages.some(m=>m.t==='trashcount' && typeof m.revision==='number')).toBe(true);
60
+ expect(messages.some(m=>m.t==='trashbrowse' && m.id===27 && m.ok===true)).toBe(true);
61
+ });
62
+
63
+ test('Trash collection uses ordinary rows, real paths and metadata; stale row actions are refused',()=>{
64
+ const a=item('preview 中文.txt');
65
+ const input=[{c:'listtrash',first:10000},{c:'trashbrowse',op:'restore',id:31,rows:[0],listing:-1},{c:'quit'}].map(x=>JSON.stringify(x)).join('\n')+'\n';
66
+ const result=Bun.spawnSync([process.execPath,new URL('../src/flea.js',import.meta.url).pathname,'--backend'],{env:{...process.env},stdin:Buffer.from(input),stdout:'pipe',stderr:'pipe'});
67
+ expect(result.exitCode).toBe(0);expect(result.stderr.toString()).toBe('');
68
+ const messages=result.stdout.toString().trim().split('\n').map(JSON.parse);
69
+ const rows=messages.find(x=>x.t==='rows');const row=rows.rows.find(x=>x.trash?.original===a.original);
70
+ expect(join('/',row.n)).toBe(a.file);expect(row.s).toBe(7);expect(row.d).toBe(false);
71
+ expect(messages.find(x=>x.id===31).stale).toBe(true);expect(existsSync(a.file)).toBe(true);
72
+ });
73
+
74
+ test('renaming a Trash entry preserves restore metadata and refuses destination collisions',async()=>{
75
+ const {renameWithTrashRecord}=await import('../src/trash-record.js');
76
+ const a=item('original.txt'),dst=join(dirname(a.file),'renamed.txt');
77
+ renameWithTrashRecord(a.file,dst);
78
+ expect(existsSync(a.file)).toBe(false);expect(existsSync(a.info)).toBe(false);
79
+ const moved=readTrash().find(x=>x.file===dst);expect(moved.original).toBe(join(root,'renamed.txt'));
80
+ writeFileSync(join(dirname(dst),'occupied'),'keep');
81
+ expect(()=>renameWithTrashRecord(dst,join(dirname(dst),'occupied'))).toThrow('already exists');
82
+ expect(readFileSync(dst,'utf8')).toBe('payload');
83
+ expect(call('restore',{uris:[moved.uri],identities:[moved.identity]}).done).toBe(1);
84
+ expect(readFileSync(join(root,'renamed.txt'),'utf8')).toBe('payload');
85
+ });
86
+
87
+ test('moving out cleans only the captured Trash record; copy keeps its source metadata',async()=>{
88
+ const {transfer}=await import('../src/transfer.js');
89
+ const a=item('move-me.txt');const dest=join(root,'destination');mkdirSync(dest);
90
+ const run=moving=>new Promise(resolve=>transfer({id:40,paths:[a.file],dest,op:moving?'move':'copy'},m=>{if(m.t==='transferdone')resolve(m)},null,0));
91
+ const copied=await run(false);expect(copied.ok).toBe(1);expect(existsSync(a.info)).toBe(true);
92
+ rmSync(join(dest,'move-me.txt'));
93
+ const moved=await run(true);expect(moved.ok).toBe(1);expect(existsSync(a.info)).toBe(false);
94
+ expect(readFileSync(join(dest,'move-me.txt'),'utf8')).toBe('payload');
95
+ });
96
+
97
+ test('Trash rename preserves literal replacement tokens in file names',async()=>{
98
+ const {renameWithTrashRecord}=await import('../src/trash-record.js');
99
+ for (const name of ['$&.txt', '$$.txt', "$'.txt", '$`.txt']) {
100
+ const a=item('old-'+name),dst=join(dirname(a.file),name);
101
+ renameWithTrashRecord(a.file,dst);
102
+ const moved=readTrash().find(x=>x.file===dst);
103
+ expect(moved.original).toBe(join(root,name));
104
+ expect(call('restore',{uris:[moved.uri],identities:[moved.identity]}).done).toBe(1);
105
+ expect(readFileSync(join(root,name),'utf8')).toBe('payload');
106
+ }
107
+ });
108
+
109
+ test('Trash menu restores its captured item after sort, not the replacement cursor',()=>{
110
+ const a=item('menu-a.txt'),b=item('menu-b.txt');
111
+ const commands=[{c:'listtrash',first:10000},
112
+ {c:'menuaction',op:'snapshot',id:93,paths:[a.file],cursorPath:a.file},
113
+ {c:'sort',by:'name',desc:true},
114
+ {c:'trashbrowse',op:'restore',id:94,menuId:93,rows:[0],listing:-1},
115
+ {c:'trashbrowse',op:'restore',id:95,menuId:999,rows:[0]},
116
+ {c:'quit'}];
117
+ const result=Bun.spawnSync([process.execPath,new URL('../src/flea.js',import.meta.url).pathname,'--backend'],{
118
+ env:{...process.env},stdin:Buffer.from(commands.map(JSON.stringify).join('\n')+'\n'),stdout:'pipe',stderr:'pipe'});
119
+ expect(result.exitCode).toBe(0);
120
+ const messages=result.stdout.toString().trim().split('\n').map(JSON.parse);
121
+ expect(messages.find(x=>x.id===94).done).toBe(1);
122
+ expect(messages.find(x=>x.id===95).stale).toBe(true);
123
+ expect(existsSync(a.original)).toBe(true);expect(existsSync(b.file)).toBe(true);
124
+ });
125
+
126
+ test('Trash collision suffixes do not change visible names or media classification',async()=>{
127
+ const {Listing}=await import('../src/listing.js');
128
+ for(const name of ['image.png','music.mp3','video.mp4','source.js','Dockerfile']) {
129
+ item(name);item(name);const a=readTrash().find(x=>x.original===join(root,name) && x.file.endsWith('.2'));
130
+ expect(a.file.endsWith('.2')).toBe(true);
131
+ const row=new Listing('/',[{name:a.file.slice(1),dir:false,trash:a}]).window(0,1).rows[0];
132
+ const original=new Listing(root,[{name,dir:false}]).window(0,1).rows[0];
133
+ expect(row.i).toBe(original.i);expect(row.trash.name).toBe(name);
134
+ }
135
+ });
@@ -0,0 +1,78 @@
1
+ import QtQuick
2
+ import Quickshell
3
+ import Quickshell.Io
4
+ import "js/SettingsAbout.js" as About
5
+
6
+ // Installed facts are read only when About is shown; each failed query keeps an explicit unknown.
7
+ QtObject {
8
+ id: root
9
+ property bool active: false
10
+ property bool loaded: false
11
+ property var known: ({})
12
+ // What ui/js/SettingsAbout.js reads: the installed facts, with the updater's status and the default's claim beside them.
13
+ readonly property var facts: Object.assign({ update: UpdateCheck.status, handler: DefaultClaim.handler, claim: DefaultClaim.claim }, root.known)
14
+ readonly property string binary: Quickshell.env("FLEA_BIN") || "flea"
15
+
16
+ function setFact(key, value) {
17
+ var next = Object.assign({}, root.known)
18
+ next[key] = value
19
+ root.known = next
20
+ }
21
+
22
+ onActiveChanged: {
23
+ // About opening is one of the updater's two automatic triggers; ui/js/Update.js decides whether the last answer stands.
24
+ if (root.active) UpdateCheck.checkIfDue()
25
+ if (!root.active || root.loaded) return
26
+ root.loaded = true
27
+ version.running = true
28
+ owner.running = true
29
+ DefaultClaim.read()
30
+ }
31
+
32
+ property var versionQuery: Process {
33
+ id: version
34
+ command: [root.binary, "--version"]
35
+ property string answer: ""
36
+ stdout: StdioCollector { onStreamFinished: version.answer = this.text.trim() }
37
+ // Sample output: "0.2.0". src/main.rs prints CARGO_PKG_VERSION and nothing else, so the
38
+ // name is tolerated rather than required: demanding it left the Version row reading
39
+ // "Not reported" on every build Flea has ever shipped.
40
+ onExited: function (code) {
41
+ if (code !== 0) return
42
+ var text = version.answer.indexOf("flea ") === 0 ? version.answer.substring(5) : version.answer
43
+ if (text.length > 0) root.setFact("version", text)
44
+ }
45
+ }
46
+ property var ownerQuery: Process {
47
+ id: owner
48
+ command: ["pacman", "-Qqo", root.binary]
49
+ property string answer: ""
50
+ stdout: StdioCollector { onStreamFinished: owner.answer = this.text.trim() }
51
+ // Sample output: flea
52
+ onExited: function (code) {
53
+ if (code !== 0 || owner.answer.length === 0) {
54
+ root.setFact("source", About.installedFrom("", false))
55
+ root.setFact("package", "Not owned by a package")
56
+ return
57
+ }
58
+ packageVersion.command = ["pacman", "-Qi", owner.answer]
59
+ packageVersion.running = true
60
+ }
61
+ }
62
+ // -Qi rather than -Q: the same query carries the build date, and the Built row had nothing
63
+ // setting it at all, so every box read "Not recorded in this build" whatever it was running.
64
+ // Its Validated By line is the signature src/update.rs tells OPR's flea from a local build by.
65
+ property var packageQuery: Process {
66
+ id: packageVersion
67
+ environment: ({ LC_ALL: "C" })
68
+ property string answer: ""
69
+ stdout: StdioCollector { onStreamFinished: packageVersion.answer = this.text }
70
+ onExited: function (code) {
71
+ // A package pacman cannot describe reads as unsigned, the way src/update.rs reads it.
72
+ var facts = About.packageFacts(code === 0 ? packageVersion.answer : "")
73
+ root.setFact("source", About.installedFrom(owner.answer, facts.signed))
74
+ if (facts.package.length > 0) root.setFact("package", facts.package)
75
+ if (facts.built.length > 0) root.setFact("built", facts.built)
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,95 @@
1
+ import QtQuick
2
+ import "js/ArchiveCard.js" as ArchiveCard
3
+ import "js/ArchiveOps.js" as ArchiveOps
4
+
5
+ // The two password cards, built on their first open and kept like every overlay, so a launch that never opens one pays nothing.
6
+ Item {
7
+ id: root
8
+
9
+ property var compressOwner: null
10
+ property var extractOwner: null
11
+ readonly property var compressItem: compressDialog.item
12
+ readonly property var extractItem: extractDialog.item
13
+
14
+ function askCompressPassword(pane, paths, menuId) {
15
+ if (paths) ArchiveOps.compressPasswordResolved(root, pane, paths, menuId)
16
+ else ArchiveOps.openCompressPassword(pane, menuId, root)
17
+ }
18
+
19
+ // One card at a time: a second request is answered with a sentence and never dropped silently.
20
+ function cardBusy(holder) {
21
+ if (!compressDialog.opened && !extractDialog.opened && !hereDialog.opened)
22
+ return false
23
+ holder.message(ArchiveCard.CARD_BUSY, true)
24
+ return true
25
+ }
26
+
27
+ function openCompressDialog(source, parentLeaf, holder) {
28
+ if (cardBusy(holder)) return
29
+ compressOwner = holder
30
+ compressDialog.active = true
31
+ compressDialog.item.open(source, parentLeaf, holder)
32
+ }
33
+
34
+ function openExtractPassword(source, holder, err) {
35
+ if (cardBusy(holder)) return
36
+ extractOwner = holder
37
+ extractDialog.active = true
38
+ extractDialog.item.open(source, holder, err)
39
+ }
40
+
41
+ function askExtractHere(holder, path, entries) {
42
+ if (cardBusy(holder)) return
43
+ var dest = path.substring(0, path.lastIndexOf("/") + 1)
44
+ if (entries >= 0 && entries <= 5) {
45
+ holder.backend.extractHere(path, dest, false)
46
+ return
47
+ }
48
+ hereDialog.active = true
49
+ hereDialog.item.open({ path: path, dest: dest, entries: entries, holder: holder, focusItem: holder.preview && holder.preview.active ? holder.preview : holder })
50
+ }
51
+
52
+ Loader {
53
+ id: hereDialog
54
+ anchors.fill: parent
55
+ active: false
56
+ source: "ArchiveHereConfirm.qml"
57
+ readonly property bool opened: item !== null && item.opened
58
+ }
59
+ Connections {
60
+ target: hereDialog.item
61
+ function onConfirmed(source) {
62
+ source.holder.backend.extractHere(source.path, source.dest, true)
63
+ source.focusItem.forceActiveFocus()
64
+ }
65
+ function onCancelled() { if (hereDialog.item.snapshot.focusItem) hereDialog.item.snapshot.focusItem.forceActiveFocus() }
66
+ }
67
+
68
+ anchors.fill: parent
69
+
70
+ Loader {
71
+ id: compressDialog
72
+ anchors.fill: parent
73
+ active: false
74
+ source: "ArchiveCompressDialog.qml"
75
+ readonly property bool opened: item !== null && item.opened
76
+ }
77
+ Connections {
78
+ target: compressDialog.item
79
+ function onAccepted(source, format, name, password, encryptNames) {
80
+ ArchiveOps.submitCompressPassword(root.compressOwner, source, format, name, password, encryptNames)
81
+ }
82
+ }
83
+
84
+ Loader {
85
+ id: extractDialog
86
+ anchors.fill: parent
87
+ active: false
88
+ source: "ArchivePasswordDialog.qml"
89
+ readonly property bool opened: item !== null && item.opened
90
+ }
91
+ Connections {
92
+ target: extractDialog.item
93
+ function onAccepted(source, password) { ArchiveOps.submitExtractPassword(root.extractOwner, source, password) }
94
+ }
95
+ }