flea-qml 0.0.0-stage → 0.1.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 (625) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +478 -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 +402 -0
  51. package/src/archiveops.js +697 -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 +129 -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/transfer.js +669 -0
  82. package/src/trash-record.js +41 -0
  83. package/src/trash.js +210 -0
  84. package/src/trashbrowse.js +326 -0
  85. package/src/uistate.js +393 -0
  86. package/src/uistore.js +168 -0
  87. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  88. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  89. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  90. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  91. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  92. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  93. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  94. package/src/vendor/pele.js +145 -0
  95. package/src/vendor/temml.mjs +14708 -0
  96. package/src/watch.js +98 -0
  97. package/src/wayland/client.js +109 -0
  98. package/src/wayland/protocol.js +55 -0
  99. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  100. package/src/wayland/protocols/wayland.xml +3248 -0
  101. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  102. package/src/wayland/resource-io.js +6 -0
  103. package/src/wayland/socket-discovery.js +26 -0
  104. package/src/wayland/transport-posix.js +120 -0
  105. package/src/wayland/transport-win32.js +11 -0
  106. package/src/wayland/transport.js +3 -0
  107. package/src/wayland/win32-resources.js +80 -0
  108. package/src/wayland/win32-wire.js +62 -0
  109. package/test/archive.test.js +80 -0
  110. package/test/bench-markdown-scroll.mjs +190 -0
  111. package/test/clip-host.test.js +113 -0
  112. package/test/code-preview.test.js +35 -0
  113. package/test/compare-ui-paths.test.js +24 -0
  114. package/test/compare.test.js +54 -0
  115. package/test/delete-trash.test.js +67 -0
  116. package/test/dirsize.test.js +138 -0
  117. package/test/file-ops.test.js +241 -0
  118. package/test/find-contents.test.js +161 -0
  119. package/test/find-input.test.js +66 -0
  120. package/test/fixtures/pele-parity.json +1006 -0
  121. package/test/highlight-launcher.test.js +35 -0
  122. package/test/html-preview.test.js +22 -0
  123. package/test/input.test.js +15 -0
  124. package/test/keymap.test.js +26 -0
  125. package/test/language-icons.test.js +36 -0
  126. package/test/launcher.test.js +51 -0
  127. package/test/manual-code-scroll.mjs +28 -0
  128. package/test/manual-column-scroll.mjs +24 -0
  129. package/test/manual-compare-ui.mjs +20 -0
  130. package/test/manual-find-status.mjs +37 -0
  131. package/test/manual-html-security.mjs +23 -0
  132. package/test/manual-html-ui.mjs +30 -0
  133. package/test/manual-syntax-preview.mjs +26 -0
  134. package/test/manual-trash-ui.mjs +86 -0
  135. package/test/markdown-diagrams.test.js +93 -0
  136. package/test/markdown-html.test.js +105 -0
  137. package/test/mathml.test.js +92 -0
  138. package/test/menu-transfer.test.js +57 -0
  139. package/test/office-preview.test.js +154 -0
  140. package/test/pele-parity.test.js +28 -0
  141. package/test/places.test.js +134 -0
  142. package/test/preview-keys.test.js +60 -0
  143. package/test/preview-language.test.js +22 -0
  144. package/test/status-filename.test.js +20 -0
  145. package/test/transfer-replace-merge.test.js +187 -0
  146. package/test/trash-address.test.js +23 -0
  147. package/test/trash-view.test.js +37 -0
  148. package/test/trashbrowse.test.js +135 -0
  149. package/ui/AboutFacts.qml +78 -0
  150. package/ui/ArchiveCards.qml +68 -0
  151. package/ui/ArchiveCompressDialog.qml +378 -0
  152. package/ui/ArchivePasswordDialog.qml +257 -0
  153. package/ui/Backend.qml +771 -0
  154. package/ui/BulkOldNames.qml +47 -0
  155. package/ui/BulkRenameDialog.qml +380 -0
  156. package/ui/CardFrame.qml +67 -0
  157. package/ui/CardScroll.qml +79 -0
  158. package/ui/CheckBox.qml +73 -0
  159. package/ui/ChromeBar.qml +360 -0
  160. package/ui/ChromeButton.qml +131 -0
  161. package/ui/ChromeModes.qml +43 -0
  162. package/ui/CodeLines.qml +256 -0
  163. package/ui/CodeRefusal.qml +52 -0
  164. package/ui/CollideConfirm.qml +147 -0
  165. package/ui/CollideHost.qml +167 -0
  166. package/ui/ColumnPane.qml +523 -0
  167. package/ui/ColumnRow.qml +380 -0
  168. package/ui/ColumnsArea.qml +594 -0
  169. package/ui/ColumnsLanding.qml +147 -0
  170. package/ui/CompareView.qml +321 -0
  171. package/ui/ContextMenu.qml +743 -0
  172. package/ui/ConvertDialog.qml +456 -0
  173. package/ui/CountedWorker.qml +7 -0
  174. package/ui/Crumb.qml +64 -0
  175. package/ui/CrumbDrop.qml +25 -0
  176. package/ui/CsvTable.qml +394 -0
  177. package/ui/CursorMark.qml +41 -0
  178. package/ui/DefaultClaim.qml +130 -0
  179. package/ui/DeviceLock.qml +47 -0
  180. package/ui/DeviceMounts.qml +540 -0
  181. package/ui/DialogButton.qml +150 -0
  182. package/ui/DialogField.qml +147 -0
  183. package/ui/DialogHeader.qml +31 -0
  184. package/ui/DialogScrim.qml +20 -0
  185. package/ui/DialogTitle.qml +37 -0
  186. package/ui/Divider.qml +9 -0
  187. package/ui/DropInto.qml +126 -0
  188. package/ui/DropTarget.qml +35 -0
  189. package/ui/DropboxMark.qml +60 -0
  190. package/ui/EmptyState.qml +150 -0
  191. package/ui/EscapeHint.qml +11 -0
  192. package/ui/FactsTable.qml +54 -0
  193. package/ui/FastScrollHandler.qml +476 -0
  194. package/ui/FatNamesConfirm.qml +157 -0
  195. package/ui/Favourites.qml +143 -0
  196. package/ui/FigureService.qml +120 -0
  197. package/ui/FileDrag.qml +267 -0
  198. package/ui/FilterStrip.qml +133 -0
  199. package/ui/FitMetrics.qml +22 -0
  200. package/ui/FlashDriveRows.qml +137 -0
  201. package/ui/FlashHost.qml +72 -0
  202. package/ui/FlashImageRow.qml +104 -0
  203. package/ui/FlashRunner.qml +119 -0
  204. package/ui/FlashStepRow.qml +172 -0
  205. package/ui/FlashStrip.qml +38 -0
  206. package/ui/FlashView.qml +397 -0
  207. package/ui/FleaMark.qml +88 -0
  208. package/ui/FlyoutFade.qml +23 -0
  209. package/ui/FontPane.qml +170 -0
  210. package/ui/Glass.qml +114 -0
  211. package/ui/GlassTagger.qml +59 -0
  212. package/ui/Glyph.qml +64 -0
  213. package/ui/GlyphSolid.qml +22 -0
  214. package/ui/GridArea.qml +664 -0
  215. package/ui/GridTile.qml +325 -0
  216. package/ui/GvfsBridge.qml +164 -0
  217. package/ui/Header.qml +433 -0
  218. package/ui/HoverTimers.qml +39 -0
  219. package/ui/HtmlClip.qml +30 -0
  220. package/ui/HtmlPane.qml +63 -0
  221. package/ui/HyprlandMark.qml +45 -0
  222. package/ui/Ipc.qml +1005 -0
  223. package/ui/IpcControls.qml +7 -0
  224. package/ui/IpcMenuState.qml +48 -0
  225. package/ui/IpcPreviewState.qml +185 -0
  226. package/ui/JumpPath.qml +51 -0
  227. package/ui/KeymapSheet.qml +561 -0
  228. package/ui/KeymapSheetResult.qml +89 -0
  229. package/ui/KindStack.qml +40 -0
  230. package/ui/LangMark.qml +51 -0
  231. package/ui/LangMarkLoader.qml +26 -0
  232. package/ui/List.qml +480 -0
  233. package/ui/LoadingState.qml +54 -0
  234. package/ui/LocalSend.qml +56 -0
  235. package/ui/LocalSendMark.qml +73 -0
  236. package/ui/MarkdownBlockView.qml +311 -0
  237. package/ui/MarkdownBlocks.qml +35 -0
  238. package/ui/MarkdownFigure.qml +254 -0
  239. package/ui/MarkdownImages.qml +72 -0
  240. package/ui/MarkdownList.qml +81 -0
  241. package/ui/MarkdownMathsText.qml +97 -0
  242. package/ui/MarkdownPane.qml +83 -0
  243. package/ui/MarkdownPictures.qml +41 -0
  244. package/ui/MarkdownQuote.qml +52 -0
  245. package/ui/MarkdownRemote.qml +82 -0
  246. package/ui/MarkdownSource.qml +155 -0
  247. package/ui/MarkdownTable.qml +222 -0
  248. package/ui/MarkdownTableScroll.qml +64 -0
  249. package/ui/MarkdownTableWheel.qml +126 -0
  250. package/ui/MarkdownText.qml +87 -0
  251. package/ui/MarkdownWorker.js +132 -0
  252. package/ui/MatchText.qml +99 -0
  253. package/ui/MediaSession.qml +38 -0
  254. package/ui/MediaSound.qml +26 -0
  255. package/ui/MediaStrip.qml +191 -0
  256. package/ui/MediaTrack.qml +36 -0
  257. package/ui/MediaVolume.qml +54 -0
  258. package/ui/MenuActionDialog.qml +396 -0
  259. package/ui/MenuEdgeFade.qml +24 -0
  260. package/ui/MenuHost.qml +227 -0
  261. package/ui/MenuRow.qml +351 -0
  262. package/ui/MountListing.qml +100 -0
  263. package/ui/NetworkDialog.qml +435 -0
  264. package/ui/NetworkForm.qml +426 -0
  265. package/ui/NetworkMounts.qml +913 -0
  266. package/ui/NetworkPlaces.qml +127 -0
  267. package/ui/OpenWithDialog.qml +613 -0
  268. package/ui/Opener.qml +115 -0
  269. package/ui/Pane.qml +1296 -0
  270. package/ui/PaneMenuActions.qml +527 -0
  271. package/ui/PanePath.qml +90 -0
  272. package/ui/PaneRail.qml +239 -0
  273. package/ui/PaneStates.qml +88 -0
  274. package/ui/PaneSwap.qml +339 -0
  275. package/ui/PaneWire.qml +605 -0
  276. package/ui/ParkedWait.qml +9 -0
  277. package/ui/PathEntry.qml +186 -0
  278. package/ui/PathJump.qml +400 -0
  279. package/ui/PdfViewer.qml +297 -0
  280. package/ui/PermissionsDialog.qml +658 -0
  281. package/ui/PermissionsHeadings.qml +31 -0
  282. package/ui/PhoneMounts.qml +67 -0
  283. package/ui/PickerAnswers.qml +75 -0
  284. package/ui/PickerBox.qml +26 -0
  285. package/ui/PickerChrome.qml +295 -0
  286. package/ui/PickerFilter.qml +97 -0
  287. package/ui/PickerGrid.qml +308 -0
  288. package/ui/PickerHeader.qml +28 -0
  289. package/ui/PickerLifecycle.qml +27 -0
  290. package/ui/PickerList.qml +218 -0
  291. package/ui/PickerListing.qml +149 -0
  292. package/ui/PickerPathBar.qml +127 -0
  293. package/ui/PickerPlaces.qml +189 -0
  294. package/ui/PickerRecent.qml +92 -0
  295. package/ui/PickerSave.qml +101 -0
  296. package/ui/PickerSelection.qml +155 -0
  297. package/ui/PickerWindow.qml +846 -0
  298. package/ui/PowerSectorsReader.qml +10 -0
  299. package/ui/PrebuildDriver.qml +146 -0
  300. package/ui/Preview.qml +1004 -0
  301. package/ui/PreviewAnimation.qml +24 -0
  302. package/ui/PreviewArchive.qml +66 -0
  303. package/ui/PreviewColumn.qml +551 -0
  304. package/ui/PreviewEdge.qml +63 -0
  305. package/ui/PreviewFacts.qml +30 -0
  306. package/ui/PreviewFactsLine.qml +34 -0
  307. package/ui/PreviewHtml.qml +233 -0
  308. package/ui/PreviewImage.qml +253 -0
  309. package/ui/PreviewImagePan.qml +13 -0
  310. package/ui/PreviewImageSharp.qml +32 -0
  311. package/ui/PreviewLines.qml +106 -0
  312. package/ui/PreviewMarkdown.qml +706 -0
  313. package/ui/PreviewMedia.qml +250 -0
  314. package/ui/PreviewPdf.qml +482 -0
  315. package/ui/PreviewScroll.qml +201 -0
  316. package/ui/PreviewSwap.qml +335 -0
  317. package/ui/PreviewText.qml +149 -0
  318. package/ui/ProtocolChip.qml +18 -0
  319. package/ui/QuickLookArchive.qml +38 -0
  320. package/ui/QuickLookBar.qml +103 -0
  321. package/ui/QuickLookPrepare.qml +355 -0
  322. package/ui/QuickLookSwap.qml +10 -0
  323. package/ui/QuickLookWarm.qml +65 -0
  324. package/ui/RailDrop.qml +155 -0
  325. package/ui/RailHeading.qml +19 -0
  326. package/ui/RailPlaces.qml +38 -0
  327. package/ui/RailTrashRow.qml +67 -0
  328. package/ui/RenameField.qml +353 -0
  329. package/ui/RendererRetry.qml +11 -0
  330. package/ui/ResizeHandle.qml +52 -0
  331. package/ui/Reveal.qml +54 -0
  332. package/ui/Row.qml +476 -0
  333. package/ui/RowDrag.qml +213 -0
  334. package/ui/RowDropTarget.qml +27 -0
  335. package/ui/RunCard.qml +286 -0
  336. package/ui/Scripts.qml +65 -0
  337. package/ui/SearchStrip.qml +232 -0
  338. package/ui/SelectionBand.qml +140 -0
  339. package/ui/SelectionPreview.qml +370 -0
  340. package/ui/SettingsFavourite.qml +122 -0
  341. package/ui/SettingsGroup.qml +96 -0
  342. package/ui/SettingsHint.qml +12 -0
  343. package/ui/SettingsMetrics.qml +29 -0
  344. package/ui/SettingsPane.qml +178 -0
  345. package/ui/SettingsPanel.qml +547 -0
  346. package/ui/SettingsRail.qml +128 -0
  347. package/ui/SettingsRow.qml +409 -0
  348. package/ui/SettingsRuler.qml +85 -0
  349. package/ui/SettingsSegment.qml +85 -0
  350. package/ui/ShareBrowser.qml +113 -0
  351. package/ui/ShareLink.qml +67 -0
  352. package/ui/ShelfPins.qml +101 -0
  353. package/ui/Sidebar.qml +732 -0
  354. package/ui/SidebarRow.qml +358 -0
  355. package/ui/Spinner.qml +60 -0
  356. package/ui/StableModel.qml +89 -0
  357. package/ui/StateMessage.qml +84 -0
  358. package/ui/StatusBar.qml +512 -0
  359. package/ui/StatusFileName.qml +27 -0
  360. package/ui/TabBar.qml +673 -0
  361. package/ui/TabDragGeometry.qml +60 -0
  362. package/ui/TagMenuMark.qml +52 -0
  363. package/ui/TagRailSection.qml +53 -0
  364. package/ui/TagSlot.qml +40 -0
  365. package/ui/TagState.qml +150 -0
  366. package/ui/TagSwatch.qml +28 -0
  367. package/ui/Taildrop.qml +86 -0
  368. package/ui/TailscaleMark.qml +50 -0
  369. package/ui/Theme.qml +460 -0
  370. package/ui/ThemeColors.qml +43 -0
  371. package/ui/TransferCard.qml +239 -0
  372. package/ui/TrashConfirm.qml +129 -0
  373. package/ui/TrashDropHandler.qml +11 -0
  374. package/ui/TrashHost.qml +202 -0
  375. package/ui/TrashMonitor.qml +30 -0
  376. package/ui/TrashToolbar.qml +19 -0
  377. package/ui/TrashView.qml +492 -0
  378. package/ui/UnlockDialog.qml +450 -0
  379. package/ui/UnlockHost.qml +206 -0
  380. package/ui/UpdateCheck.qml +97 -0
  381. package/ui/UserDirs.qml +25 -0
  382. package/ui/ViewState.qml +565 -0
  383. package/ui/ViewportScrollBar.qml +195 -0
  384. package/ui/ViewportScrollBars.qml +25 -0
  385. package/ui/WebPreview.qml +33 -0
  386. package/ui/WebPreviewView.qml +26 -0
  387. package/ui/WindowBody.qml +827 -0
  388. package/ui/WindowDrag.qml +35 -0
  389. package/ui/boot/Commons/AudioNodes.qml +50 -0
  390. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  391. package/ui/boot/Commons/Border.qml +243 -0
  392. package/ui/boot/Commons/BorderGeometry.js +373 -0
  393. package/ui/boot/Commons/Color.qml +258 -0
  394. package/ui/boot/Commons/I18n.qml +118 -0
  395. package/ui/boot/Commons/InputDevices.qml +112 -0
  396. package/ui/boot/Commons/InputMethodState.qml +38 -0
  397. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  398. package/ui/boot/Commons/Numbers.js +19 -0
  399. package/ui/boot/Commons/Plural.js +91 -0
  400. package/ui/boot/Commons/ShellIpc.qml +12 -0
  401. package/ui/boot/Commons/Style.qml +561 -0
  402. package/ui/boot/Commons/Translations.qml +100 -0
  403. package/ui/boot/Commons/UntypedInput.qml +152 -0
  404. package/ui/boot/Commons/Util.qml +188 -0
  405. package/ui/boot/Commons/qmldir +13 -0
  406. package/ui/boot/LICENSE-omarchy +20 -0
  407. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  408. package/ui/boot/Ui/BarIconButton.qml +106 -0
  409. package/ui/boot/Ui/BarIconContent.qml +38 -0
  410. package/ui/boot/Ui/BarIndicator.qml +53 -0
  411. package/ui/boot/Ui/BarWidget.qml +50 -0
  412. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  413. package/ui/boot/Ui/BorderSurface.qml +40 -0
  414. package/ui/boot/Ui/Button.qml +224 -0
  415. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  416. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  417. package/ui/boot/Ui/CursorSurface.qml +42 -0
  418. package/ui/boot/Ui/Dropdown.qml +245 -0
  419. package/ui/boot/Ui/FilterInput.qml +26 -0
  420. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  421. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  422. package/ui/boot/Ui/MultiSelect.qml +624 -0
  423. package/ui/boot/Ui/NumberField.qml +86 -0
  424. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  425. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  426. package/ui/boot/Ui/Panel.qml +60 -0
  427. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  428. package/ui/boot/Ui/PanelController.qml +16 -0
  429. package/ui/boot/Ui/PanelHero.qml +112 -0
  430. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  431. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  432. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  433. package/ui/boot/Ui/PanelSlider.qml +156 -0
  434. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  435. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  436. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  437. package/ui/boot/Ui/PopupCard.qml +181 -0
  438. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  439. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  440. package/ui/boot/Ui/SizedImage.qml +125 -0
  441. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  442. package/ui/boot/Ui/TextField.qml +58 -0
  443. package/ui/boot/Ui/Toggle.qml +118 -0
  444. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  445. package/ui/boot/Ui/WidgetButton.qml +172 -0
  446. package/ui/boot/Ui/qmldir +41 -0
  447. package/ui/boot/fleatab.qml +50 -0
  448. package/ui/boot/picker.qml +39 -0
  449. package/ui/boot/shell.qml +122 -0
  450. package/ui/boot/tabtearoff.qml +51 -0
  451. package/ui/js/Anchor.js +379 -0
  452. package/ui/js/AnchorHold.js +162 -0
  453. package/ui/js/Archive.js +157 -0
  454. package/ui/js/ArchiveCard.js +44 -0
  455. package/ui/js/ArchiveOps.js +44 -0
  456. package/ui/js/BackReturn.js +118 -0
  457. package/ui/js/BulkRename.js +100 -0
  458. package/ui/js/Buttons.js +54 -0
  459. package/ui/js/ChromeKeys.js +116 -0
  460. package/ui/js/ClipMarks.js +73 -0
  461. package/ui/js/ClipSelection.js +67 -0
  462. package/ui/js/Clipboard.js +179 -0
  463. package/ui/js/Cloud.js +97 -0
  464. package/ui/js/CodeRuns.js +72 -0
  465. package/ui/js/Collide.js +212 -0
  466. package/ui/js/ColumnFit.js +28 -0
  467. package/ui/js/ColumnMenu.js +111 -0
  468. package/ui/js/Columns.js +298 -0
  469. package/ui/js/ColumnsCarry.js +103 -0
  470. package/ui/js/Compare.js +74 -0
  471. package/ui/js/Contrast.js +157 -0
  472. package/ui/js/ControlReader.js +42 -0
  473. package/ui/js/Convert.js +52 -0
  474. package/ui/js/CopyAs.js +52 -0
  475. package/ui/js/Crumbs.js +178 -0
  476. package/ui/js/Csv.js +231 -0
  477. package/ui/js/Density.js +44 -0
  478. package/ui/js/Devices.js +440 -0
  479. package/ui/js/DirSizes.js +180 -0
  480. package/ui/js/Drag.js +364 -0
  481. package/ui/js/DragOut.js +166 -0
  482. package/ui/js/DropDev.js +52 -0
  483. package/ui/js/Dropbox.js +41 -0
  484. package/ui/js/Eject.js +217 -0
  485. package/ui/js/Errors.js +188 -0
  486. package/ui/js/ExtThumbs.js +115 -0
  487. package/ui/js/Facts.js +402 -0
  488. package/ui/js/FigureDate.js +14 -0
  489. package/ui/js/Filter.js +285 -0
  490. package/ui/js/Find.js +46 -0
  491. package/ui/js/Flash.js +184 -0
  492. package/ui/js/FlashCard.js +79 -0
  493. package/ui/js/FlashEndings.js +125 -0
  494. package/ui/js/FlashPane.js +116 -0
  495. package/ui/js/FleaBin.js +12 -0
  496. package/ui/js/Focus.js +502 -0
  497. package/ui/js/FolderSorts.js +108 -0
  498. package/ui/js/FolderViews.js +44 -0
  499. package/ui/js/Format.js +305 -0
  500. package/ui/js/Fuzzy.js +141 -0
  501. package/ui/js/GlassTag.js +72 -0
  502. package/ui/js/Grid.js +33 -0
  503. package/ui/js/GridGeometry.js +90 -0
  504. package/ui/js/GridNames.js +153 -0
  505. package/ui/js/GvfsBridge.js +200 -0
  506. package/ui/js/HostClipboard.js +29 -0
  507. package/ui/js/Icons.js +218 -0
  508. package/ui/js/Input.js +50 -0
  509. package/ui/js/Jump.js +200 -0
  510. package/ui/js/KeyName.js +64 -0
  511. package/ui/js/Keymap.js +401 -0
  512. package/ui/js/Kinds.js +164 -0
  513. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  514. package/ui/js/LangMarkPaths.js +47 -0
  515. package/ui/js/LangMarks.js +120 -0
  516. package/ui/js/Lists.js +11 -0
  517. package/ui/js/LocalSend.js +39 -0
  518. package/ui/js/LockedMenu.js +63 -0
  519. package/ui/js/MakeDefault.js +186 -0
  520. package/ui/js/MarkBox.js +7 -0
  521. package/ui/js/Markdown.js +119 -0
  522. package/ui/js/MarkdownLists.js +86 -0
  523. package/ui/js/MarkdownMaths.js +58 -0
  524. package/ui/js/MarkdownPictures.js +32 -0
  525. package/ui/js/MarkdownPrepared.js +139 -0
  526. package/ui/js/MarkdownTableFit.js +34 -0
  527. package/ui/js/Marks.js +148 -0
  528. package/ui/js/Marquee.js +96 -0
  529. package/ui/js/Match.js +93 -0
  530. package/ui/js/Material.js +109 -0
  531. package/ui/js/MdBlocks.js +437 -0
  532. package/ui/js/MdBreak.js +71 -0
  533. package/ui/js/MdChunks.js +70 -0
  534. package/ui/js/MdContainer.js +137 -0
  535. package/ui/js/MdDocument.js +337 -0
  536. package/ui/js/MdEmph.js +97 -0
  537. package/ui/js/MdEntity.js +108 -0
  538. package/ui/js/MdEntityTable.js +223 -0
  539. package/ui/js/MdEscape.js +59 -0
  540. package/ui/js/MdFront.js +37 -0
  541. package/ui/js/MdHold.js +32 -0
  542. package/ui/js/MdHtml.js +307 -0
  543. package/ui/js/MdHtmlBlock.js +290 -0
  544. package/ui/js/MdHtmlHeading.js +53 -0
  545. package/ui/js/MdHtmlImage.js +179 -0
  546. package/ui/js/MdInline.js +154 -0
  547. package/ui/js/MdItems.js +212 -0
  548. package/ui/js/MdLeaf.js +309 -0
  549. package/ui/js/MdLink.js +196 -0
  550. package/ui/js/MdMath.js +67 -0
  551. package/ui/js/MdParagraphs.js +138 -0
  552. package/ui/js/MdRefs.js +232 -0
  553. package/ui/js/MdResolve.js +226 -0
  554. package/ui/js/MdRun.js +300 -0
  555. package/ui/js/MdUrl.js +226 -0
  556. package/ui/js/Menu.js +514 -0
  557. package/ui/js/MenuContext.js +85 -0
  558. package/ui/js/MenuFit.js +13 -0
  559. package/ui/js/MenuRefresh.js +74 -0
  560. package/ui/js/MenuWheel.js +35 -0
  561. package/ui/js/Messages.js +186 -0
  562. package/ui/js/Motion.js +16 -0
  563. package/ui/js/Mounts.js +320 -0
  564. package/ui/js/MouseNav.js +13 -0
  565. package/ui/js/Nav.js +329 -0
  566. package/ui/js/NetFs.js +26 -0
  567. package/ui/js/OpenWith.js +83 -0
  568. package/ui/js/Ops.js +585 -0
  569. package/ui/js/Palette.js +33 -0
  570. package/ui/js/PathBar.js +260 -0
  571. package/ui/js/Permissions.js +179 -0
  572. package/ui/js/Phones.js +112 -0
  573. package/ui/js/Picker.js +353 -0
  574. package/ui/js/PlaceMenu.js +96 -0
  575. package/ui/js/Places.js +265 -0
  576. package/ui/js/PreviewAsk.js +21 -0
  577. package/ui/js/PreviewKeys.js +155 -0
  578. package/ui/js/PreviewSettle.js +29 -0
  579. package/ui/js/PreviewSwap.js +101 -0
  580. package/ui/js/PropertyFacts.js +144 -0
  581. package/ui/js/Protocols.js +296 -0
  582. package/ui/js/QuickLookKinds.js +27 -0
  583. package/ui/js/QuickLookRect.js +24 -0
  584. package/ui/js/RailDrop.js +51 -0
  585. package/ui/js/RailKeys.js +87 -0
  586. package/ui/js/RailMenu.js +133 -0
  587. package/ui/js/Recent.js +135 -0
  588. package/ui/js/RecentMode.js +156 -0
  589. package/ui/js/Reload.js +60 -0
  590. package/ui/js/Renderer.js +9 -0
  591. package/ui/js/RunFile.js +143 -0
  592. package/ui/js/Scripts.js +28 -0
  593. package/ui/js/Scroll.js +304 -0
  594. package/ui/js/ScrollOff.js +69 -0
  595. package/ui/js/Search.js +478 -0
  596. package/ui/js/Selection.js +97 -0
  597. package/ui/js/Settings.js +476 -0
  598. package/ui/js/SettingsAbout.js +97 -0
  599. package/ui/js/SettingsShelf.js +52 -0
  600. package/ui/js/SheetKeys.js +42 -0
  601. package/ui/js/SheetQuery.js +281 -0
  602. package/ui/js/ShelfPile.js +27 -0
  603. package/ui/js/SlowClick.js +53 -0
  604. package/ui/js/SlowOp.js +6 -0
  605. package/ui/js/Sort.js +134 -0
  606. package/ui/js/Startup.js +63 -0
  607. package/ui/js/StateWords.js +17 -0
  608. package/ui/js/Status.js +238 -0
  609. package/ui/js/Swap.js +166 -0
  610. package/ui/js/TabMove.js +39 -0
  611. package/ui/js/Tabs.js +736 -0
  612. package/ui/js/Tags.js +134 -0
  613. package/ui/js/Taildrop.js +128 -0
  614. package/ui/js/Tap.js +174 -0
  615. package/ui/js/TextSize.js +107 -0
  616. package/ui/js/Thumbs.js +158 -0
  617. package/ui/js/Transfer.js +172 -0
  618. package/ui/js/Trash.js +81 -0
  619. package/ui/js/TrashDates.js +47 -0
  620. package/ui/js/TrashDrop.js +64 -0
  621. package/ui/js/UiState.js +438 -0
  622. package/ui/js/Unlock.js +152 -0
  623. package/ui/js/Update.js +130 -0
  624. package/ui/js/UserDirs.js +56 -0
  625. package/ui/qmldir +228 -0
package/src/figure.js ADDED
@@ -0,0 +1,132 @@
1
+ // Mermaid diagrams for both Markdown previews: the passive HTML page (src/markdown-html.js) and
2
+ // the block view's figures (ui/FigureService.qml, through the backend's `figure` request).
3
+ //
4
+ // Pele runs in one Bun Worker (src/figure-thread.js), started on the first diagram and ended
5
+ // after a quiet spell. Diagrams go to it one at a time, each under a deadline: a layout still
6
+ // running then terminates the Worker, that diagram fails with a reason, and the next one starts
7
+ // a fresh Worker. The answers are SVG checked against an allowlist (src/figure-svg.js) and
8
+ // held in a small cache keyed by source and theme.
9
+ import { MERMAID_LIMIT } from "./figure-svg.js";
10
+
11
+ export const FIGURE_TIMEOUT_MS = 5000;
12
+ const IDLE_MS = 30000;
13
+ const CACHE_MAX = 64;
14
+ // A Gantt chart draws today's date: it is asked for this day's noon UTC, so every zone draws the day it is.
15
+ const MIDDAY_HOUR = 12;
16
+
17
+ const HEX = /^#[0-9a-f]{6}$/i;
18
+ const ADVANCES = 95; // printable ASCII, " " to "~"
19
+
20
+ /**
21
+ * The theme a diagram is drawn under, from what a preview sends: colours as #rrggbb, a font
22
+ * family, a body size, and the font's advance tables (thousandths of an em per character from
23
+ * " " to "~", regular and bold) so every label is sized from the font that draws it. Anything
24
+ * else is dropped, so a theme cannot carry markup into the SVG.
25
+ */
26
+ export function figureTheme(theme = {}, source = "", now = new Date()) {
27
+ const colour = (value, fallback) => (HEX.test(String(value ?? "")) ? String(value) : fallback);
28
+ const table = (value) => Array.isArray(value) && value.length === ADVANCES && value.every((n) => Number.isFinite(n) && n >= 0)
29
+ ? value.map(Number) : [];
30
+ const fg = colour(theme.fg ?? theme.foreground, "#d8dee9");
31
+ const out = {
32
+ bg: colour(theme.bg ?? theme.background, "#171b22"), fg,
33
+ accent: colour(theme.accent, fg), muted: colour(theme.muted, ""), surface: colour(theme.surface, ""),
34
+ font: /^[\p{L}\p{N} _.,'-]{1,80}$/u.test(String(theme.font ?? "")) ? String(theme.font) : "sans-serif",
35
+ bodyPx: Math.max(8, Math.min(40, Number(theme.bodyPx) || 14)),
36
+ advances: table(theme.advances), boldAdvances: table(theme.boldAdvances),
37
+ };
38
+ if (/(?:^|%%)\s*gantt\b/im.test(source))
39
+ out.today = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate(), MIDDAY_HOUR);
40
+ return out;
41
+ }
42
+
43
+ const cache = new Map();
44
+ const queue = [];
45
+ let worker = null, active = null, idleTimer = null, nextId = 0;
46
+
47
+ function stopWorker() {
48
+ clearTimeout(idleTimer);
49
+ idleTimer = null;
50
+ worker?.terminate();
51
+ worker = null;
52
+ }
53
+
54
+ function finish(job, answer) {
55
+ clearTimeout(job.timer);
56
+ if (answer.svg !== undefined) {
57
+ cache.set(job.key, answer);
58
+ if (cache.size > CACHE_MAX) cache.delete(cache.keys().next().value);
59
+ }
60
+ job.resolve(answer);
61
+ }
62
+
63
+ function pump() {
64
+ if (active || !queue.length) {
65
+ if (!active && worker && !idleTimer) idleTimer = setTimeout(stopWorker, IDLE_MS);
66
+ return;
67
+ }
68
+ clearTimeout(idleTimer);
69
+ idleTimer = null;
70
+ active = queue.shift();
71
+ if (!worker) {
72
+ worker = new Worker(new URL("./figure-thread.js", import.meta.url).href);
73
+ // The Worker never keeps the backend (or a test run) alive by itself.
74
+ worker.unref?.();
75
+ worker.onmessage = ({ data }) => {
76
+ if (!active || data.id !== active.id) return;
77
+ const job = active;
78
+ active = null;
79
+ finish(job, data.svg !== undefined ? { svg: data.svg } : { error: data.error || "render failed" });
80
+ pump();
81
+ };
82
+ worker.onerror = (event) => {
83
+ event.preventDefault?.();
84
+ const job = active;
85
+ active = null;
86
+ stopWorker();
87
+ if (job) finish(job, { error: String(event.message || "diagram engine failed") });
88
+ pump();
89
+ };
90
+ }
91
+ const job = active;
92
+ job.timer = setTimeout(() => {
93
+ if (active !== job) return;
94
+ active = null;
95
+ stopWorker();
96
+ finish(job, { error: `diagram took over ${job.timeout / 1000} s` });
97
+ pump();
98
+ }, job.timeout);
99
+ worker.postMessage({ id: job.id, source: job.source, theme: job.theme });
100
+ }
101
+
102
+ /**
103
+ * Draws one Mermaid diagram. Answers {svg} or {error}; never rejects. `theme` is what a preview
104
+ * sends (see figureTheme); `timeout` bounds the layout in ms.
105
+ */
106
+ export function renderDiagram(source, theme = {}, { timeout = FIGURE_TIMEOUT_MS } = {}) {
107
+ source = String(source ?? "");
108
+ if (!source.trim()) return Promise.resolve({ error: "empty diagram" });
109
+ if (source.length > MERMAID_LIMIT) return Promise.resolve({ error: "diagram over 32 KiB" });
110
+ const drawn = figureTheme(theme, source);
111
+ const key = JSON.stringify([source, drawn]);
112
+ const hit = cache.get(key);
113
+ if (hit) {
114
+ cache.delete(key);
115
+ cache.set(key, hit);
116
+ return Promise.resolve(hit);
117
+ }
118
+ return new Promise((resolve) => {
119
+ queue.push({ id: ++nextId, key, source, theme: drawn, timeout, resolve });
120
+ pump();
121
+ });
122
+ }
123
+
124
+ /** Ends the Worker and forgets the cache; queued diagrams fail. For tests and shutdown. */
125
+ export function stopFigures() {
126
+ const pending = active ? [active, ...queue] : [...queue];
127
+ active = null;
128
+ queue.length = 0;
129
+ stopWorker();
130
+ cache.clear();
131
+ for (const job of pending) finish(job, { error: "diagram engine stopped" });
132
+ }
package/src/find.js ADDED
@@ -0,0 +1,55 @@
1
+ // The commands Find runs (find for names, grep for contents) and folder sizes run (du), which ui/Backend.qml spawns itself and reads as a stream; the backend only
2
+ // names it. Looked for the way src/highlight.js looks for jsmdcui: a bunmsh checkout beside this
3
+ // repository runs its entry with Bun, then a `bunmsh` on PATH is spawned as it is, and last the
4
+ // system's own `find`. Windows' find.exe searches text inside files and takes none of these
5
+ // arguments, so there it is never the fallback.
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+
9
+ const BUN = Bun.which("bun") || process.argv0;
10
+ const REPO = join(import.meta.dir, "..");
11
+
12
+ let found;
13
+ /** The argv prefix a scope and its expression follow, or null when nothing can run Find. */
14
+ export function findCommand() {
15
+ if (found !== undefined) return found;
16
+ found = null;
17
+ const beside = join(REPO, "..", "bunmsh", "index.js");
18
+ if (existsSync(beside)) return (found = [BUN, beside, "-cc", "builtin", "find"]);
19
+ const bunmsh = Bun.which("bunmsh");
20
+ if (bunmsh) return (found = [bunmsh, "-cc", "builtin", "find"]);
21
+ const system = process.platform === "win32" ? null : Bun.which("find");
22
+ if (system) return (found = [system]);
23
+ return found;
24
+ }
25
+
26
+ let grepFound;
27
+ /** The argv prefix a content search's options follow, or null when nothing can run grep. The
28
+ * system's grep needs --line-buffered to stream into a pipe; Find always passes it. */
29
+ export function grepCommand() {
30
+ if (grepFound !== undefined) return grepFound;
31
+ grepFound = null;
32
+ const beside = join(REPO, "..", "bunmsh", "index.js");
33
+ if (existsSync(beside)) return (grepFound = [BUN, beside, "-cc", "builtin", "grep"]);
34
+ const bunmsh = Bun.which("bunmsh");
35
+ if (bunmsh) return (grepFound = [bunmsh, "-cc", "builtin", "grep"]);
36
+ const system = process.platform === "win32" ? null : Bun.which("grep");
37
+ if (system) return (grepFound = [system]);
38
+ return grepFound;
39
+ }
40
+
41
+ let duFound;
42
+ /** The argv prefix a folder size's options follow (src/dirsize.js), found in Find's order: bunmsh
43
+ * beside this repository, a `bunmsh` on PATH, then the system's own du. Windows' du, where one is
44
+ * installed (Sysinternals), takes none of GNU's options, so there it is never the fallback. */
45
+ export function duCommand() {
46
+ if (duFound !== undefined) return duFound;
47
+ duFound = null;
48
+ const beside = join(REPO, "..", "bunmsh", "index.js");
49
+ if (existsSync(beside)) return (duFound = [BUN, beside, "-cc", "builtin", "du"]);
50
+ const bunmsh = Bun.which("bunmsh");
51
+ if (bunmsh) return (duFound = [bunmsh, "-cc", "builtin", "du"]);
52
+ const system = process.platform === "win32" ? null : Bun.which("du");
53
+ if (system) return (duFound = [system]);
54
+ return duFound;
55
+ }
package/src/flea.js ADDED
@@ -0,0 +1,39 @@
1
+ // flea-qml's `flea`: the one program ui/ runs for everything it asks of the outside, through
2
+ // ui/js/FleaBin.js as `bun src/flea.js ARGS`. Upstream's equivalent is the `flea` binary's
3
+ // src/main.rs, whose other modes (--open, --run, --favourites, ...) are not ported yet.
4
+ import { runBackend } from "./backend.js";
5
+ import { readState, updateState } from "./uistore.js";
6
+ import { render } from "./uistate.js";
7
+
8
+ const args = process.argv.slice(2);
9
+
10
+ // --ui-state prints the settled document; --ui-state PATCH merges one JSON object under the lock
11
+ // and prints the result. Every refusal is one "flea: " sentence on stderr, nothing on stdout, exit 2.
12
+ function uiState(rest) {
13
+ try {
14
+ if (rest.length > 1) throw new Error("--ui-state takes nothing, or one JSON object");
15
+ if (rest.length === 0) {
16
+ process.stdout.write(render(readState()));
17
+ return 0;
18
+ }
19
+ let patch;
20
+ try {
21
+ patch = JSON.parse(rest[0]);
22
+ } catch (error) {
23
+ throw new Error(`the ui.json patch is not JSON (${error.message})`);
24
+ }
25
+ process.stdout.write(render(updateState(patch)));
26
+ return 0;
27
+ } catch (error) {
28
+ process.stderr.write(`flea: ${error.message}\n`);
29
+ return 2;
30
+ }
31
+ }
32
+
33
+ const [mode, ...rest] = args;
34
+ if (mode === "--backend") runBackend();
35
+ else if (mode === "--ui-state") process.exit(uiState(rest));
36
+ else {
37
+ console.error(`flea-qml: not implemented yet: ${args.join(" ") || "(no arguments)"}`);
38
+ process.exit(1);
39
+ }
package/src/fsinfo.js ADDED
@@ -0,0 +1,39 @@
1
+ // The `fsinfo` line: the listed directory's filesystem name and free bytes
2
+ // (docs/protocol.md "fsinfo"). Upstream: src/backend/fsinfo.rs.
3
+ import { readFileSync, statSync, statfsSync } from "node:fs";
4
+
5
+ // Kernel dev_t, as glibc's major() and minor() split it.
6
+ const majorOf = (dev) => Number(((dev >> 8n) & 0xfffn) | ((dev >> 32n) & ~0xfffn));
7
+ const minorOf = (dev) => Number((dev & 0xffn) | ((dev >> 12n) & ~0xffn));
8
+
9
+ // The mount holding a device id, by its "major:minor" in /proc/self/mountinfo. Every system tries
10
+ // the same read; where there is no mount table it fails and the name stays empty, which the status
11
+ // bar draws as no filesystem name.
12
+ export function fsTypeOf(dev) {
13
+ let text = "";
14
+ try {
15
+ text = readFileSync("/proc/self/mountinfo", "utf8");
16
+ } catch {
17
+ return "";
18
+ }
19
+ const id = `${majorOf(dev)}:${minorOf(dev)}`;
20
+ for (const line of text.split("\n")) {
21
+ const fields = line.split(" ");
22
+ if (fields[2] !== id) continue;
23
+ const separator = fields.indexOf("-");
24
+ if (separator > 0) return fields[separator + 1] ?? "";
25
+ }
26
+ return "";
27
+ }
28
+
29
+ // statfs exists on Linux, macOS and Windows in Node; a runtime without it lands in the catch.
30
+ export function fsinfo(path) {
31
+ try {
32
+ const dev = statSync(path || ".", { bigint: true }).dev,
33
+ fs = statfsSync(path || ".");
34
+ return { t: "fsinfo", fs: fsTypeOf(dev), free: fs.bavail * fs.bsize, path, class: "", tagable: false };
35
+ } catch {
36
+ // An unreadable path answers an empty fs with a free of 0 rather than a wrong number.
37
+ return { t: "fsinfo", fs: "", free: 0, path, class: "", tagable: false };
38
+ }
39
+ }
@@ -0,0 +1,141 @@
1
+ // Syntax colours for `code` lines, from jsmdcui's `--cat --edit FILE`, whose truecolor ANSI output
2
+ // is read back into the protocol's roles: 0 the foreground, then red, green, yellow, blue, magenta
3
+ // and cyan (docs/protocol.md "code"). Upstream runs `bat` for the same lines.
4
+ // jsmdcui is looked for beside this repository first, then as `jmi` or `jsmdcui` on PATH, and its
5
+ // a sibling checkout runs with Bun while PATH commands keep their own launcher semantics.
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+
9
+ const BUN = Bun.which("bun") || process.argv0;
10
+ const REPO = join(import.meta.dir, "..");
11
+
12
+ let found;
13
+ /** The command argv prefix, or null when none can be found. Looked up once per process. */
14
+ export function jsmdcui() {
15
+ if (found !== undefined) return found;
16
+ found = null;
17
+ const beside = join(REPO, "..", "jsmdcui", "src", "index.js");
18
+ if (existsSync(beside)) return (found = [BUN, beside]);
19
+ for (const name of ["jmi", "jsmdcui"]) {
20
+ const command = Bun.which(name);
21
+ if (command) return (found = [command]);
22
+ }
23
+ return found;
24
+ }
25
+
26
+ // The six ANSI hues the roles name, as angles on the colour wheel.
27
+ const HUES = [[1, 0], [3, 60], [2, 120], [6, 180], [4, 240], [5, 300]];
28
+ const BASIC = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6 };
29
+
30
+ // The role nearest a 24-bit colour; greys and near-greys are the foreground.
31
+ function roleOfRgb(r, g, b) {
32
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
33
+ if (max === 0 || (max - min) / max < 0.25) return 0;
34
+ const d = max - min;
35
+ let hue = max === r ? ((g - b) / d) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
36
+ hue = (hue * 60 + 360) % 360;
37
+ let best = 0, distance = Infinity;
38
+ for (const [role, angle] of HUES) {
39
+ const off = Math.min(Math.abs(hue - angle), 360 - Math.abs(hue - angle));
40
+ if (off < distance) [best, distance] = [role, off];
41
+ }
42
+ return best;
43
+ }
44
+
45
+ // xterm's 256 colours: the cube and the greys, by their RGB, and the first sixteen by name.
46
+ function roleOf256(n) {
47
+ if (n < 16) return BASIC[n % 8] ?? 0;
48
+ if (n >= 232) return 0;
49
+ const i = n - 16, level = (v) => (v === 0 ? 0 : 55 + v * 40);
50
+ return roleOfRgb(level(Math.floor(i / 36)), level(Math.floor(i / 6) % 6), level(i % 6));
51
+ }
52
+
53
+ /** One line of ANSI text as a flat run list [role, text, role, text, ...]; [] when empty. */
54
+ export function runsOf(line) {
55
+ const runs = [];
56
+ let role = 0;
57
+ const push = (text) => {
58
+ if (!text) return;
59
+ if (runs.length && runs[runs.length - 2] === role) runs[runs.length - 1] += text;
60
+ else runs.push(role, text);
61
+ };
62
+ const pattern = /\x1b\[([0-9;]*)m/g;
63
+ let at = 0;
64
+ for (let m; (m = pattern.exec(line)); at = pattern.lastIndex) {
65
+ push(line.slice(at, m.index));
66
+ const codes = m[1] === "" ? [0] : m[1].split(";").map(Number);
67
+ for (let i = 0; i < codes.length; i++) {
68
+ const c = codes[i];
69
+ if (c === 0 || c === 39) role = 0;
70
+ else if (c >= 30 && c <= 37) role = BASIC[c - 30] ?? 0;
71
+ else if (c >= 90 && c <= 97) role = BASIC[c - 90] ?? 0;
72
+ else if (c === 38 && codes[i + 1] === 2) {
73
+ role = roleOfRgb(codes[i + 2], codes[i + 3], codes[i + 4]);
74
+ i += 4;
75
+ } else if (c === 38 && codes[i + 1] === 5) {
76
+ role = roleOf256(codes[i + 2]);
77
+ i += 2;
78
+ } else if (c === 48 && codes[i + 1] === 2) i += 4;
79
+ else if (c === 48 && codes[i + 1] === 5) i += 2;
80
+ }
81
+ }
82
+ push(line.slice(at));
83
+ return runs;
84
+ }
85
+
86
+ /**
87
+ * The coloured lines of `path` as run lists, or null when jsmdcui is missing, fails or is
88
+ * cancelled through `signal`. Every line of the file is returned; the caller takes its window.
89
+ */
90
+ export async function highlightFile(path, signal, filetype = null) {
91
+ const command = jsmdcui();
92
+ if (!command) return null;
93
+ // Each highlight is a whole Bun process; a document with many code blocks in view
94
+ // must not start them all at once, so at most HIGHLIGHT_LIMIT run and the rest wait.
95
+ // A request cancelled while waiting never starts one.
96
+ if (!(await acquire(signal))) return null;
97
+ try {
98
+ const args = [...command, "--cat", "--edit"];
99
+ // jsmdcui settings use one dash: --filetype would name a different setting.
100
+ if (filetype) args.push("-filetype", filetype);
101
+ args.push(path);
102
+ const child = Bun.spawn(args, { stdout: "pipe", stderr: "ignore", stdin: "ignore", signal });
103
+ const [text, code] = await Promise.all([new Response(child.stdout).text(), child.exited]);
104
+ if (code !== 0 || signal?.aborted) return null;
105
+ return text.split("\n").map(runsOf);
106
+ } catch {
107
+ return null;
108
+ } finally {
109
+ release();
110
+ }
111
+ }
112
+
113
+ /** Highlight processes allowed at once. */
114
+ export const HIGHLIGHT_LIMIT = 2;
115
+ let running = 0;
116
+ const waiting = [];
117
+ // Resolves true when a slot is taken, false when `signal` aborts first.
118
+ function acquire(signal) {
119
+ if (signal?.aborted) return Promise.resolve(false);
120
+ if (running < HIGHLIGHT_LIMIT) {
121
+ running++;
122
+ return Promise.resolve(true);
123
+ }
124
+ return new Promise((resolve) => {
125
+ const entry = { resolve };
126
+ const abort = () => {
127
+ const index = waiting.indexOf(entry);
128
+ if (index >= 0) waiting.splice(index, 1);
129
+ resolve(false);
130
+ };
131
+ entry.resolve = (taken) => { signal?.removeEventListener("abort", abort); resolve(taken); };
132
+ signal?.addEventListener("abort", abort, { once: true });
133
+ waiting.push(entry);
134
+ });
135
+ }
136
+ // A finished highlight hands its slot straight to the oldest waiting one.
137
+ function release() {
138
+ const next = waiting.shift();
139
+ if (next) next.resolve(true);
140
+ else running--;
141
+ }
package/src/html.js ADDED
@@ -0,0 +1,52 @@
1
+ // Rebuild a passive fragment. Never use HTMLRewriter's transformed source output.
2
+ import { open, constants } from 'node:fs/promises';
3
+ import { officeKind, officePreview } from './office.js';
4
+ export const HTML_TAGS = new Set('div h1 h2 h3 h4 h5 h6 p br b strong i em u s del pre code ul ol li blockquote table thead tbody tr th td hr sub sup'.split(' '));
5
+ const OMIT = new Set('script style iframe object embed template svg math noembed noframes head'.split(' '));
6
+ export const HTML_LIMIT = 1 << 20;
7
+ export function previewHtml(source) {
8
+ if (Buffer.byteLength(source) > HTML_LIMIT) throw Error('HTML preview exceeds size limit');
9
+ const result = []; let hidden = 0;
10
+ new HTMLRewriter().on('*', { element(el) {
11
+ const tag = el.tagName;
12
+ const omit = OMIT.has(tag) || el.namespaceURI !== 'http://www.w3.org/1999/xhtml';
13
+ if (omit && el.canHaveContent) { hidden++; el.onEndTag(() => hidden--); }
14
+ if (omit || hidden || !HTML_TAGS.has(tag)) return;
15
+ result.push('<' + tag + '>');
16
+ if (el.canHaveContent) el.onEndTag(() => result.push('</' + tag + '>'));
17
+ }}).onDocument({ text(chunk) {
18
+ // Text callbacks include literal markup inside textarea/xmp/plaintext. It
19
+ // must stay text on reparse. Entities are NOT decoded before reconstruction.
20
+ if (!hidden) result.push(chunk.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;'));
21
+ }}).transform(source);
22
+ return result.join('');
23
+ }
24
+ export async function html(request) {
25
+ const answer = {t:'html', slot:request.slot, token:request.token, state:'failed', html:'', total:0};
26
+ if (request.cancel) return null;
27
+ // A document or a deck is converted to the same fixed tags (src/office.js) and rebuilt like any
28
+ // other source, so nothing reaches the page that an HTML file could not put there.
29
+ const kind = officeKind(request.path);
30
+ if (kind === 'document' || kind === 'slides') {
31
+ try {
32
+ const preview = officePreview(request.path);
33
+ return {...answer,state:'ready',html:previewHtml(preview.html),total:preview.text.split('\n').filter(Boolean).length};
34
+ } catch { return answer; }
35
+ }
36
+ let file;
37
+ try {
38
+ file = await open(request.path, constants.O_RDONLY | constants.O_NONBLOCK);
39
+ const stat = await file.stat();
40
+ if (!stat.isFile() || stat.size > HTML_LIMIT) return answer;
41
+ const bytes = Buffer.alloc(HTML_LIMIT + 1); let count = 0;
42
+ while (count < bytes.length) {
43
+ const {bytesRead} = await file.read(bytes, count, bytes.length-count, null);
44
+ if (!bytesRead) break;
45
+ count += bytesRead;
46
+ }
47
+ if (count > HTML_LIMIT) return answer;
48
+ const source = bytes.subarray(0,count).toString('utf8');
49
+ return {...answer,state:'ready',html:previewHtml(source),total:source ? source.split('\n').length : 0};
50
+ } catch { return answer; }
51
+ finally { await file?.close(); }
52
+ }
package/src/kinds.js ADDED
@@ -0,0 +1,89 @@
1
+ // What a row is, from its name and its own stat alone: the emoji the row draws (`i`), its Kind
2
+ // description and the class kind ordering groups by. No MIME database or other system file is read,
3
+ // so every platform answers the same. The emoji set starts from bunmsh's fancy ls (src/fancy-ls.js),
4
+ // with 📁 for a folder so 📦 can mean a package and 🗜️ a compressed archive.
5
+
6
+ const S_IFMT = 0o170000,
7
+ S_IFLNK = 0o120000,
8
+ ANY_EXECUTE_BIT = 0o111;
9
+
10
+ // [group, emoji, extensions, Kind]. A Kind given as a function is built from the upper-case
11
+ // extension; a string is used as it is.
12
+ const TABLE = [
13
+ ["image", "🖼️", "png jpg jpeg gif webp svg bmp ico avif heic", (x) => `${x} image`],
14
+ ["audio", "🎵", "mp3 wav flac ogg m4a aac opus mid midi", (x) => `${x} audio`],
15
+ ["video", "🎬", "mp4 mkv webm mov avi m4v mpeg mpg", (x) => `${x} video`],
16
+ // A .tar.gz names its outer layer, gz, so it is compressed; a bare .tar only packs.
17
+ ["archive", "🗜️", "zip gz tgz xz txz bz2 tbz2 zst tzst 7z rar lz lz4 lzma z", (x) => `${x} archive`],
18
+ ["package", "📦", "tar deb rpm apk jar war whl appimage msi dmg pkg snap flatpak cpio", (x) => `${x} package`],
19
+ ["disc", "💿", "iso img qcow2 vhd vhdx vmdk", (x) => `${x} disk image`],
20
+ ["pdf", "📕", "pdf", "PDF document"],
21
+ ["document", "📘", "doc docx odt rtf", (x) => `${x} document`],
22
+ ["spreadsheet", "📗", "xls xlsx ods csv tsv", (x) => `${x} spreadsheet`],
23
+ ["presentation", "📙", "ppt pptx odp key", (x) => `${x} presentation`],
24
+ ["markdown", "📝", "md markdown", "Markdown document"],
25
+ ["text", "📄", "txt log", (x) => `${x} text`],
26
+ ["font", "🔤", "ttf otf woff woff2", (x) => `${x} font`],
27
+ ["database", "🐬", "sql db sqlite sqlite3 db3", (x) => `${x} database`],
28
+ ["config", "🔣", "json yaml yml toml ini xml conf cfg", (x) => `${x} data`],
29
+ ["shell", "💲", "sh bash zsh fish bat cmd ps1", (x) => `${x} script`],
30
+ ["binary", "💾", "bin dat o obj so a dll lib dylib class pyc wasm", (x) => `${x} binary`],
31
+ // Source code, one language per row.
32
+ ["code", "🟨", "js mjs cjs", "JavaScript"],
33
+ ["code", "🟦", "ts mts cts", "TypeScript"],
34
+ ["code", "🐍", "py", "Python script"],
35
+ ["code", "🔧", "c", "C source"],
36
+ ["code", "🧩", "h", "C header"],
37
+ ["code", "➕", "cpp cc cxx hpp hh hxx", "C++ source"],
38
+ ["code", "🔧", "cs", "C# source"],
39
+ ["code", "🦀", "rs", "Rust source"],
40
+ ["code", "🐹", "go", "Go source"],
41
+ ["code", "☕", "java", "Java source"],
42
+ ["code", "🟪", "kt kts", "Kotlin source"],
43
+ ["code", "💎", "rb", "Ruby script"],
44
+ ["code", "🐘", "php", "PHP script"],
45
+ ["code", "🌙", "lua", "Lua script"],
46
+ ["code", "🎯", "dart", "Dart source"],
47
+ ["code", "❄️", "nix", "Nix expression"],
48
+ ["code", "🌐", "html htm", "HTML document"],
49
+ ["code", "🎨", "css", "CSS stylesheet"],
50
+ ["code", "🐳", "dockerfile", "Dockerfile"],
51
+ ];
52
+ const BY_EXTENSION = new Map();
53
+ for (const [group, icon, extensions, kind] of TABLE)
54
+ for (const ext of extensions.split(" ")) BY_EXTENSION.set(ext, { group, icon, kind });
55
+ // Files known by their whole name rather than an extension, compared case-insensitively.
56
+ const BY_NAME = new Map([
57
+ ["dockerfile", BY_EXTENSION.get("dockerfile")],
58
+ ["containerfile", BY_EXTENSION.get("dockerfile")],
59
+ ]);
60
+
61
+ export const FOLDER = "📁",
62
+ LINK = "🔗",
63
+ BROKEN_LINK = "🚫",
64
+ PROGRAM = "⚙️",
65
+ OTHER = "📄";
66
+
67
+ export function extensionOf(name) {
68
+ const dot = name.lastIndexOf(".");
69
+ return dot > 0 && dot < name.length - 1 ? name.slice(dot + 1).toLowerCase() : "";
70
+ }
71
+
72
+ /**
73
+ * { icon, kind, group } for a row. `group` is the class kind ordering compares and kind grouping
74
+ * reads ("folder", "image", ...); `target` is a symlink's resolved stat, or null when it is broken.
75
+ */
76
+ export function describeKind(name, isDir, mode, target) {
77
+ if (isDir) return { icon: FOLDER, kind: "Folder", group: "folder" };
78
+ const ext = extensionOf(name),
79
+ upper = ext.toUpperCase(),
80
+ known = BY_NAME.get(name.toLowerCase()) ?? BY_EXTENSION.get(ext);
81
+ let icon, kind, group;
82
+ if (known) [icon, kind, group] = [known.icon, typeof known.kind === "function" ? known.kind(upper) : known.kind, known.group];
83
+ else if ((mode & S_IFMT) !== S_IFLNK && mode & ANY_EXECUTE_BIT) [icon, kind, group] = [PROGRAM, "Program", "program"];
84
+ // An extension nothing names may still be text, so it keeps the plain page rather than the disk.
85
+ else [icon, kind, group] = [OTHER, ext ? `${upper} file` : "File", "file"];
86
+ // A link draws as a link, whatever it names; a link to a folder still sorts with files by its own name.
87
+ if ((mode & S_IFMT) === S_IFLNK) icon = target === null ? BROKEN_LINK : LINK;
88
+ return { icon, kind, group };
89
+ }