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/office.js ADDED
@@ -0,0 +1,810 @@
1
+ // Office documents for the preview, read by this process with no converter: a DOCX, ODT, PPTX or
2
+ // ODP becomes a passive HTML fragment for src/html.js (headings, emphasis, lists and tables), and
3
+ // an XLSX or ODS becomes the CSV of its first sheet for the table view (ui/CsvTable.qml).
4
+ // A container whose content cannot be read answers the names of its zip members instead, the
5
+ // listing an archive preview shows. Upstream: src/backend/officepdf.rs and officesheets.rs, which
6
+ // run LibreOffice; nothing here lays a page out, so fonts, images and page breaks are not drawn.
7
+ import { statSync } from "node:fs";
8
+ import { openZip } from "./archive.js";
9
+
10
+ // A container over this is not opened, and one XML member over MEMBER_LIMIT is not inflated.
11
+ export const OFFICE_LIMIT = 64 << 20;
12
+ const MEMBER_LIMIT = 32 << 20;
13
+ // src/html.js refuses a fragment over 1 MiB and src/code.js shows the first 1 MiB of text, so the
14
+ // conversion stops before either and says so.
15
+ const OUTPUT_LIMIT = 900_000;
16
+ const NAME_CAP = 64;
17
+ const TRUNCATED = "[Preview truncated]";
18
+
19
+ const KINDS = {
20
+ docx: "document", docm: "document", odt: "document",
21
+ pptx: "slides", pptm: "slides", odp: "slides",
22
+ xlsx: "sheet", xlsm: "sheet", ods: "sheet",
23
+ };
24
+
25
+ /** "document", "slides" or "sheet" for a name this file reads, "" for any other. */
26
+ export function officeKind(name) {
27
+ const text = String(name ?? ""),
28
+ dot = text.lastIndexOf(".");
29
+ return dot < 0 ? "" : (KINDS[text.slice(dot + 1).toLowerCase()] ?? "");
30
+ }
31
+
32
+ // ---- XML ----
33
+
34
+ const ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
35
+ const decode = (text) =>
36
+ text.includes("&")
37
+ ? text.replace(/&(#[xX][0-9a-fA-F]+|#\d+|\w+);/g, (match, name) => {
38
+ if (name[0] !== "#") return ENTITIES[name] ?? match;
39
+ const code = /^#[xX]/.test(name) ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
40
+ return code > 0 && code <= 0x10ffff && !(code >= 0xd800 && code <= 0xdfff) ? String.fromCodePoint(code) : "�";
41
+ })
42
+ : text;
43
+ // Control characters have no glyph and are not valid in the fragment; tab and newline are kept.
44
+ const clean = (text) => text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
45
+ const escape = (text) => clean(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
46
+
47
+ const TOKEN =
48
+ /<!--[\s\S]*?-->|<\?[\s\S]*?\?>|<!\[CDATA\[([\s\S]*?)\]\]>|<(\/?)([A-Za-z_][\w:.\-]*)((?:"[^"]*"|'[^']*'|[^<>"'])*?)(\/?)>|([^<]+)/g;
49
+
50
+ // Walks one XML member: open(name, attributes, selfClosing), close(name) and text(decoded) in
51
+ // document order. A self-closing element reports open and then close. open or text returning
52
+ // false ends the walk. A DOCTYPE is refused, so no entity a document declares is ever expanded.
53
+ function walk(source, { open, close, text }) {
54
+ if (/<!DOCTYPE/i.test(source.slice(0, 4096))) throw new Error("XML with a DOCTYPE is not read");
55
+ TOKEN.lastIndex = 0;
56
+ for (let match; (match = TOKEN.exec(source)); ) {
57
+ if (match[3] !== undefined) {
58
+ if (match[2]) close?.(match[3]);
59
+ else {
60
+ if (open?.(match[3], match[4], match[5] === "/") === false) return;
61
+ if (match[5] === "/") close?.(match[3]);
62
+ }
63
+ } else if (match[1] !== undefined) {
64
+ if (text?.(match[1]) === false) return;
65
+ } else if (match[6] !== undefined) {
66
+ if (text?.(decode(match[6])) === false) return;
67
+ }
68
+ }
69
+ }
70
+
71
+ const attributePatterns = new Map();
72
+ function attribute(attributes, name) {
73
+ if (!attributes) return undefined;
74
+ let pattern = attributePatterns.get(name);
75
+ if (!pattern) {
76
+ pattern = new RegExp(`(?:^|\\s)${name.replace(/[.:\-]/g, "\\$&")}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`);
77
+ attributePatterns.set(name, pattern);
78
+ }
79
+ const match = pattern.exec(attributes);
80
+ return match ? decode(match[1] ?? match[2]) : undefined;
81
+ }
82
+
83
+ function member(zip, name) {
84
+ const bytes = zip.read(name, MEMBER_LIMIT);
85
+ if (!bytes) throw new Error(`missing ${name}`);
86
+ return bytes.toString("utf8");
87
+ }
88
+ const optional = (zip, name) => (zip.sizes.has(name) ? member(zip, name) : "");
89
+
90
+ // Relationship targets by id; `base` is the directory the part that owns them sits in.
91
+ function relationships(zip, name, base) {
92
+ const targets = new Map();
93
+ const source = optional(zip, name);
94
+ if (source)
95
+ walk(source, {
96
+ open(tag, attributes) {
97
+ if (tag !== "Relationship") return;
98
+ const id = attribute(attributes, "Id"),
99
+ target = attribute(attributes, "Target");
100
+ if (id && target) targets.set(id, target.startsWith("/") ? target.slice(1) : base + target);
101
+ },
102
+ });
103
+ return targets;
104
+ }
105
+
106
+ // ---- the fragment ----
107
+
108
+ // Blocks land in the innermost open table cell, or in the body. Consecutive list items share one
109
+ // list, and a cell separates its paragraphs with line breaks, so the fragment's tags stay balanced
110
+ // however a document nests them. `full` turns true at the output limit and the walk ends there.
111
+ class Fragment {
112
+ constructor() {
113
+ this.body = { parts: [], list: "" };
114
+ this.containers = [this.body];
115
+ this.tables = [];
116
+ this.size = 0;
117
+ this.full = false;
118
+ }
119
+ get top() {
120
+ return this.containers.at(-1);
121
+ }
122
+ #push(container, html) {
123
+ container.parts.push(html);
124
+ if (container === this.body) {
125
+ this.size += html.length;
126
+ if (this.size > OUTPUT_LIMIT) this.full = true;
127
+ }
128
+ }
129
+ #endList(container) {
130
+ if (container.list) this.#push(container, `</${container.list}>`);
131
+ container.list = "";
132
+ }
133
+ /** A paragraph: `tag` wraps it in the body, `list` ("ul" or "ol") makes it an item instead. */
134
+ block(tag, inline, list = "") {
135
+ if (!inline.trim()) return;
136
+ const container = this.top;
137
+ if (container !== this.body) {
138
+ // Inside a table cell every paragraph is a line.
139
+ this.#push(container, (container.parts.length ? "<br>" : "") + inline);
140
+ return;
141
+ }
142
+ if (list) {
143
+ if (container.list !== list) {
144
+ this.#endList(container);
145
+ this.#push(container, `<${list}>`);
146
+ container.list = list;
147
+ }
148
+ this.#push(container, `<li>${inline}</li>`);
149
+ return;
150
+ }
151
+ this.#endList(container);
152
+ this.#push(container, `<${tag}>${inline}</${tag}>`);
153
+ }
154
+ tableOpen() {
155
+ this.tables.push({ rows: [], row: null });
156
+ }
157
+ rowOpen() {
158
+ const table = this.tables.at(-1);
159
+ if (table) table.row = [];
160
+ }
161
+ cellOpen() {
162
+ if (this.tables.at(-1)?.row) this.containers.push({ parts: [], list: "", cell: true });
163
+ }
164
+ cellClose() {
165
+ const table = this.tables.at(-1);
166
+ if (!table?.row || !this.top.cell) return;
167
+ table.row.push(this.containers.pop().parts.join(""));
168
+ }
169
+ rowClose() {
170
+ const table = this.tables.at(-1);
171
+ if (!table?.row) return;
172
+ if (table.row.length) table.rows.push(table.row);
173
+ table.row = null;
174
+ }
175
+ tableClose() {
176
+ // A table that ends inside an unfinished cell or row takes them with it.
177
+ while (this.top.cell && this.tables.at(-1)?.row) this.cellClose();
178
+ this.rowClose();
179
+ const table = this.tables.pop();
180
+ if (!table || !table.rows.some((row) => row.some((cell) => cell.trim()))) return;
181
+ const html = `<table>${table.rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("")}</table>`;
182
+ const container = this.top;
183
+ if (container === this.body) this.#endList(container);
184
+ this.#push(container, html);
185
+ }
186
+ html() {
187
+ // Whatever the walk left open is closed here, so a truncated document still balances.
188
+ while (this.tables.length) this.tableClose();
189
+ this.#endList(this.body);
190
+ if (this.full) this.body.parts.push(`<p>${TRUNCATED}</p>`);
191
+ return this.body.parts.join("");
192
+ }
193
+ }
194
+
195
+ const STYLE_TAGS = [["b", "b"], ["i", "i"], ["u", "u"], ["s", "s"], ["sup", "sup"], ["sub", "sub"]];
196
+ function styled(text, style) {
197
+ let html = escape(text);
198
+ if (!html) return "";
199
+ if (style) for (const [key, tag] of STYLE_TAGS) if (style[key]) html = `<${tag}>${html}</${tag}>`;
200
+ return html;
201
+ }
202
+ const TAB = "&nbsp;&nbsp;&nbsp;&nbsp;";
203
+ // An OOXML toggle is on when present, unless its value says otherwise.
204
+ const on = (attributes, name = "w:val") => !["0", "false", "none", "off"].includes(attribute(attributes, name) ?? "1");
205
+
206
+ // ---- DOCX ----
207
+
208
+ // Heading level by paragraph style id, from the style's own name ("heading 2", "Title").
209
+ function docxHeadings(zip) {
210
+ const levels = new Map();
211
+ const source = optional(zip, "word/styles.xml");
212
+ if (!source) return levels;
213
+ let id = "";
214
+ walk(source, {
215
+ open(tag, attributes) {
216
+ if (tag === "w:style") id = attribute(attributes, "w:styleId") ?? "";
217
+ else if (tag === "w:name" && id) {
218
+ const name = (attribute(attributes, "w:val") ?? "").toLowerCase();
219
+ const heading = /^heading ([1-6])$/.exec(name);
220
+ if (heading) levels.set(id, Number(heading[1]));
221
+ else if (name === "title") levels.set(id, 1);
222
+ else if (name === "subtitle") levels.set(id, 2);
223
+ }
224
+ },
225
+ close(tag) {
226
+ if (tag === "w:style") id = "";
227
+ },
228
+ });
229
+ return levels;
230
+ }
231
+
232
+ // Whether a numbering id counts (true) or bullets (false), from its first level's format.
233
+ function docxNumbering(zip) {
234
+ const ordered = new Map();
235
+ const source = optional(zip, "word/numbering.xml");
236
+ if (!source) return ordered;
237
+ const abstract = new Map();
238
+ let abstractId = "", level = "", numId = "";
239
+ walk(source, {
240
+ open(tag, attributes) {
241
+ if (tag === "w:abstractNum") abstractId = attribute(attributes, "w:abstractNumId") ?? "";
242
+ else if (tag === "w:lvl") level = attribute(attributes, "w:ilvl") ?? "";
243
+ else if (tag === "w:numFmt" && abstractId && level === "0")
244
+ abstract.set(abstractId, !["bullet", "none"].includes(attribute(attributes, "w:val") ?? ""));
245
+ else if (tag === "w:num") numId = attribute(attributes, "w:numId") ?? "";
246
+ else if (tag === "w:abstractNumId" && numId) ordered.set(numId, abstract.get(attribute(attributes, "w:val") ?? "") ?? false);
247
+ },
248
+ close(tag) {
249
+ if (tag === "w:abstractNum") abstractId = "";
250
+ else if (tag === "w:num") numId = "";
251
+ },
252
+ });
253
+ return ordered;
254
+ }
255
+
256
+ // Deleted text, a field's instruction and the fallback copy of a drawing are not the document's text.
257
+ const DOCX_SKIP = new Set(["w:del", "w:delText", "w:instrText", "mc:Fallback", "w:moveFrom"]);
258
+
259
+ function docxHtml(zip) {
260
+ const source = member(zip, "word/document.xml");
261
+ const headings = docxHeadings(zip),
262
+ numbering = docxNumbering(zip),
263
+ out = new Fragment(),
264
+ paragraphs = [];
265
+ let skip = 0, inParagraphProperties = false, inRunProperties = false, inText = false, run = null;
266
+ walk(source, {
267
+ open(tag, attributes) {
268
+ if (skip || DOCX_SKIP.has(tag)) {
269
+ skip++;
270
+ return;
271
+ }
272
+ const paragraph = paragraphs.at(-1);
273
+ switch (tag) {
274
+ case "w:p": paragraphs.push({ tag: "p", list: "", parts: [] }); break;
275
+ case "w:pPr": inParagraphProperties = true; break;
276
+ case "w:pStyle": {
277
+ const id = attribute(attributes, "w:val") ?? "";
278
+ const level = headings.get(id) ?? Number(/^heading\s*([1-6])$/i.exec(id)?.[1] ?? 0);
279
+ if (paragraph && level) paragraph.tag = `h${level}`;
280
+ break;
281
+ }
282
+ case "w:numId": {
283
+ const id = attribute(attributes, "w:val") ?? "0";
284
+ if (paragraph && inParagraphProperties && id !== "0") paragraph.list = numbering.get(id) ? "ol" : "ul";
285
+ break;
286
+ }
287
+ case "w:r": run = {}; break;
288
+ case "w:rPr": inRunProperties = !inParagraphProperties && run !== null; break;
289
+ case "w:b": if (inRunProperties) run.b = on(attributes); break;
290
+ case "w:i": if (inRunProperties) run.i = on(attributes); break;
291
+ case "w:u": if (inRunProperties) run.u = on(attributes); break;
292
+ case "w:strike":
293
+ case "w:dstrike": if (inRunProperties) run.s = on(attributes); break;
294
+ case "w:vertAlign": {
295
+ const value = attribute(attributes, "w:val");
296
+ if (inRunProperties) { run.sup = value === "superscript"; run.sub = value === "subscript"; }
297
+ break;
298
+ }
299
+ case "w:t": inText = true; break;
300
+ case "w:tab": if (paragraph && run && !inParagraphProperties) paragraph.parts.push(TAB); break;
301
+ case "w:br":
302
+ case "w:cr": if (paragraph && run) paragraph.parts.push("<br>"); break;
303
+ case "w:noBreakHyphen": if (paragraph && run) paragraph.parts.push("-"); break;
304
+ case "w:tbl": out.tableOpen(); break;
305
+ case "w:tr": out.rowOpen(); break;
306
+ case "w:tc": out.cellOpen(); break;
307
+ }
308
+ },
309
+ close(tag) {
310
+ if (skip) {
311
+ skip--;
312
+ return;
313
+ }
314
+ switch (tag) {
315
+ case "w:p": {
316
+ const paragraph = paragraphs.pop();
317
+ if (paragraph) out.block(paragraph.tag, paragraph.parts.join(""), paragraph.tag === "p" ? paragraph.list : "");
318
+ break;
319
+ }
320
+ case "w:pPr": inParagraphProperties = false; break;
321
+ case "w:rPr": inRunProperties = false; break;
322
+ case "w:r": run = null; break;
323
+ case "w:t": inText = false; break;
324
+ case "w:tc": out.cellClose(); break;
325
+ case "w:tr": out.rowClose(); break;
326
+ case "w:tbl": out.tableClose(); break;
327
+ }
328
+ return undefined;
329
+ },
330
+ text(value) {
331
+ if (skip || !inText) return undefined;
332
+ paragraphs.at(-1)?.parts.push(styled(value, run));
333
+ return !out.full;
334
+ },
335
+ });
336
+ return out.html();
337
+ }
338
+
339
+ // ---- PPTX ----
340
+
341
+ // Slide members in presentation order, by the presentation's own list; by number when it has none.
342
+ function pptxSlides(zip) {
343
+ const targets = relationships(zip, "ppt/_rels/presentation.xml.rels", "ppt/");
344
+ const slides = [];
345
+ const source = optional(zip, "ppt/presentation.xml");
346
+ if (source)
347
+ walk(source, {
348
+ open(tag, attributes) {
349
+ const target = tag === "p:sldId" ? targets.get(attribute(attributes, "r:id") ?? "") : undefined;
350
+ if (target && zip.sizes.has(target)) slides.push(target);
351
+ },
352
+ });
353
+ if (slides.length) return slides;
354
+ const number = (name) => Number(/(\d+)\.xml$/.exec(name)[1]);
355
+ return zip.names.filter((name) => /^ppt\/slides\/slide\d+\.xml$/.test(name)).sort((a, b) => number(a) - number(b));
356
+ }
357
+
358
+ function pptxHtml(zip) {
359
+ const slides = pptxSlides(zip);
360
+ if (!slides.length) throw new Error("no slides");
361
+ const out = new Fragment();
362
+ slides.every((name, index) => {
363
+ out.block("h2", escape(`Slide ${index + 1}`));
364
+ let paragraph = null, run = null, inText = false, title = false;
365
+ walk(member(zip, name), {
366
+ open(tag, attributes) {
367
+ switch (tag) {
368
+ case "p:sp": title = false; break;
369
+ case "p:ph": title = ["title", "ctrTitle"].includes(attribute(attributes, "type") ?? ""); break;
370
+ case "a:p": paragraph = []; break;
371
+ case "a:r":
372
+ case "a:fld": run = {}; break;
373
+ case "a:rPr":
374
+ if (!run) break;
375
+ run.b = attribute(attributes, "b") === "1";
376
+ run.i = attribute(attributes, "i") === "1";
377
+ run.u = !["none", undefined].includes(attribute(attributes, "u"));
378
+ run.s = !["noStrike", undefined].includes(attribute(attributes, "strike"));
379
+ break;
380
+ case "a:t": inText = true; break;
381
+ case "a:br": paragraph?.push("<br>"); break;
382
+ case "a:tbl": out.tableOpen(); break;
383
+ case "a:tr": out.rowOpen(); break;
384
+ case "a:tc": out.cellOpen(); break;
385
+ }
386
+ },
387
+ close(tag) {
388
+ switch (tag) {
389
+ case "a:p":
390
+ if (paragraph) out.block(title ? "h3" : "p", paragraph.join(""));
391
+ paragraph = null;
392
+ break;
393
+ case "a:r":
394
+ case "a:fld": run = null; break;
395
+ case "a:t": inText = false; break;
396
+ case "a:tc": out.cellClose(); break;
397
+ case "a:tr": out.rowClose(); break;
398
+ case "a:tbl": out.tableClose(); break;
399
+ }
400
+ },
401
+ text(value) {
402
+ if (inText && paragraph) paragraph.push(styled(value, run));
403
+ return !out.full;
404
+ },
405
+ });
406
+ return !out.full;
407
+ });
408
+ return out.html();
409
+ }
410
+
411
+ // ---- ODT and ODP ----
412
+
413
+ // Change tracking, comments, footnotes, speaker notes and form controls are not the body text.
414
+ const ODF_SKIP = new Set(["text:tracked-changes", "office:annotation", "text:note", "presentation:notes", "office:forms", "draw:plugin"]);
415
+
416
+ function odfHtml(zip, slides) {
417
+ const source = member(zip, "content.xml");
418
+ const styles = new Map(),
419
+ out = new Fragment(),
420
+ paragraphs = [],
421
+ spans = [];
422
+ let skip = 0, styleName = "", lists = 0, slide = 0, body = false;
423
+ const current = () => {
424
+ const merged = {};
425
+ for (const name of spans) Object.assign(merged, styles.get(name));
426
+ return merged;
427
+ };
428
+ walk(source, {
429
+ open(tag, attributes) {
430
+ if (skip || ODF_SKIP.has(tag)) {
431
+ skip++;
432
+ return undefined;
433
+ }
434
+ const paragraph = paragraphs.at(-1);
435
+ switch (tag) {
436
+ case "style:style": styleName = attribute(attributes, "style:name") ?? ""; break;
437
+ case "style:text-properties": {
438
+ if (!styleName) break;
439
+ const position = attribute(attributes, "style:text-position") ?? "";
440
+ styles.set(styleName, {
441
+ b: attribute(attributes, "fo:font-weight") === "bold",
442
+ i: attribute(attributes, "fo:font-style") === "italic",
443
+ u: !["none", undefined].includes(attribute(attributes, "style:text-underline-style")),
444
+ s: !["none", undefined].includes(attribute(attributes, "style:text-line-through-style")),
445
+ sup: position.startsWith("super"),
446
+ sub: position.startsWith("sub"),
447
+ });
448
+ break;
449
+ }
450
+ case "office:body": body = true; break;
451
+ case "draw:page": if (slides) out.block("h2", escape(`Slide ${++slide}`)); break;
452
+ case "text:h": {
453
+ const level = Math.min(6, Math.max(1, Number(attribute(attributes, "text:outline-level") ?? 1) || 1));
454
+ if (body) paragraphs.push({ tag: `h${level}`, parts: [] });
455
+ break;
456
+ }
457
+ case "text:p": if (body) paragraphs.push({ tag: "p", parts: [] }); break;
458
+ case "text:list": lists++; break;
459
+ case "text:span": spans.push(attribute(attributes, "text:style-name") ?? ""); break;
460
+ case "text:s": paragraph?.parts.push("&nbsp;".repeat(Math.min(64, Math.max(1, Number(attribute(attributes, "text:c") ?? 1) || 1)))); break;
461
+ case "text:tab": paragraph?.parts.push(TAB); break;
462
+ case "text:line-break": paragraph?.parts.push("<br>"); break;
463
+ case "table:table": if (body) out.tableOpen(); break;
464
+ case "table:table-row": if (body) out.rowOpen(); break;
465
+ case "table:table-cell":
466
+ case "table:covered-table-cell": if (body) out.cellOpen(); break;
467
+ }
468
+ return undefined;
469
+ },
470
+ close(tag) {
471
+ if (skip) {
472
+ skip--;
473
+ return;
474
+ }
475
+ switch (tag) {
476
+ case "style:style": styleName = ""; break;
477
+ case "text:h":
478
+ case "text:p": {
479
+ const paragraph = paragraphs.pop();
480
+ if (paragraph) out.block(paragraph.tag, paragraph.parts.join(""), lists > 0 && paragraph.tag === "p" ? "ul" : "");
481
+ break;
482
+ }
483
+ case "text:list": lists = Math.max(0, lists - 1); break;
484
+ case "text:span": spans.pop(); break;
485
+ case "table:table-cell":
486
+ case "table:covered-table-cell": if (body) out.cellClose(); break;
487
+ case "table:table-row": if (body) out.rowClose(); break;
488
+ case "table:table": if (body) out.tableClose(); break;
489
+ }
490
+ },
491
+ text(value) {
492
+ if (skip) return undefined;
493
+ paragraphs.at(-1)?.parts.push(styled(value, current()));
494
+ return !out.full;
495
+ },
496
+ });
497
+ return out.html();
498
+ }
499
+
500
+ // ---- sheets ----
501
+
502
+ const field = (value) => {
503
+ const text = clean(String(value)).replace(/\r\n?/g, "\n");
504
+ return /[",\n]|^\s|\s$/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
505
+ };
506
+
507
+ // Rows as CSV lines, without the empty cells that trail a row or the empty rows that trail a sheet.
508
+ class Csv {
509
+ constructor() {
510
+ this.lines = [];
511
+ this.size = 0;
512
+ this.blank = 0;
513
+ this.full = false;
514
+ }
515
+ row(cells) {
516
+ let end = cells.length;
517
+ while (end > 0 && (cells[end - 1] === undefined || cells[end - 1] === "")) end--;
518
+ if (end === 0) {
519
+ this.blank++;
520
+ return;
521
+ }
522
+ // Empty rows between two filled ones keep their place; the cap also bounds a gap of millions.
523
+ for (; this.blank > 0 && !this.full; this.blank--) this.#push("");
524
+ this.blank = 0;
525
+ const line = [];
526
+ for (let index = 0; index < end; index++) line.push(field(cells[index] ?? ""));
527
+ this.#push(line.join(","));
528
+ }
529
+ #push(line) {
530
+ if (this.full) return;
531
+ this.lines.push(line);
532
+ this.size += line.length + 1;
533
+ if (this.size > OUTPUT_LIMIT) this.full = true;
534
+ }
535
+ text(note) {
536
+ const lines = this.lines.slice();
537
+ if (this.full) lines.push(field(TRUNCATED));
538
+ if (note) lines.push(field(note));
539
+ return lines.length ? lines.join("\n") + "\n" : "";
540
+ }
541
+ }
542
+ const sheetNote = (names) => (names.length > 1 ? `[Sheet 1 of ${names.length}: ${names[0]}. The other sheets are not shown.]` : "");
543
+
544
+ // Column index of a cell reference: "A1" is 0, "AB7" is 27.
545
+ function columnOf(reference) {
546
+ let column = 0;
547
+ for (let index = 0; index < reference.length; index++) {
548
+ const code = reference.charCodeAt(index) & ~0x20;
549
+ if (code < 65 || code > 90) break;
550
+ column = column * 26 + code - 64;
551
+ }
552
+ return column - 1;
553
+ }
554
+ const COLUMN_CAP = 16384;
555
+ // One filled row a sheet repeats is written at most this many times.
556
+ const ROW_REPEAT_CAP = 1000;
557
+
558
+ // Whether a number format shows a date or a time: the built-in ids, or a custom code with date letters.
559
+ const BUILTIN_DATES = new Set([14, 15, 16, 17, 18, 19, 20, 21, 22, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 45, 46, 47, 50, 51, 52, 53, 54, 55, 56, 57, 58]);
560
+ const isDateCode = (code) => /[ymdhs]/i.test(code.replace(/"[^"]*"|\[[^\]]*\]|\\./g, "")) && !/^general$/i.test(code);
561
+
562
+ // A date serial as ISO text: a whole number is a date, a fraction below one a time, and otherwise both.
563
+ function serialDate(serial, date1904) {
564
+ if (!Number.isFinite(serial) || serial < 0 || serial > 2958465) return String(serial);
565
+ const date = new Date(Math.round((serial - (date1904 ? 24107 : 25569)) * 86400000)).toISOString();
566
+ if (Number.isInteger(serial)) return date.slice(0, 10);
567
+ return serial < 1 ? date.slice(11, 19) : `${date.slice(0, 10)} ${date.slice(11, 19)}`;
568
+ }
569
+
570
+ function xlsxCsv(zip) {
571
+ const targets = relationships(zip, "xl/_rels/workbook.xml.rels", "xl/");
572
+ const names = [];
573
+ let first = "", date1904 = false;
574
+ walk(member(zip, "xl/workbook.xml"), {
575
+ open(tag, attributes) {
576
+ if (tag === "workbookPr") date1904 = ["1", "true"].includes(attribute(attributes, "date1904") ?? "");
577
+ if (tag !== "sheet") return;
578
+ names.push(attribute(attributes, "name") ?? "");
579
+ if (!first) first = targets.get(attribute(attributes, "r:id") ?? "") ?? "";
580
+ },
581
+ });
582
+ if (!first || !zip.sizes.has(first)) first = zip.names.find((name) => /^xl\/worksheets\/[^/]+\.xml$/.test(name)) ?? "";
583
+ if (!first) throw new Error("no worksheet");
584
+
585
+ // Shared strings: every <t> of an item but its phonetic guide.
586
+ const shared = [];
587
+ const strings = optional(zip, "xl/sharedStrings.xml");
588
+ if (strings) {
589
+ let item = null, inText = false, phonetic = 0;
590
+ walk(strings, {
591
+ open(tag) {
592
+ if (tag === "si") item = [];
593
+ else if (tag === "rPh") phonetic++;
594
+ else if (tag === "t") inText = true;
595
+ },
596
+ close(tag) {
597
+ if (tag === "si") { shared.push(item ? item.join("") : ""); item = null; }
598
+ else if (tag === "rPh") phonetic--;
599
+ else if (tag === "t") inText = false;
600
+ },
601
+ text(value) {
602
+ if (inText && item && !phonetic) item.push(value);
603
+ },
604
+ });
605
+ }
606
+
607
+ // Which cell formats are dates, by the order cellXfs lists them in.
608
+ const dateStyles = [];
609
+ const stylesheet = optional(zip, "xl/styles.xml");
610
+ if (stylesheet) {
611
+ const custom = new Map();
612
+ let inCellFormats = false;
613
+ walk(stylesheet, {
614
+ open(tag, attributes) {
615
+ if (tag === "numFmt") custom.set(Number(attribute(attributes, "numFmtId")), attribute(attributes, "formatCode") ?? "");
616
+ else if (tag === "cellXfs") inCellFormats = true;
617
+ else if (tag === "xf" && inCellFormats) {
618
+ const id = Number(attribute(attributes, "numFmtId") ?? 0);
619
+ dateStyles.push(custom.has(id) ? isDateCode(custom.get(id)) : BUILTIN_DATES.has(id));
620
+ }
621
+ },
622
+ close(tag) {
623
+ if (tag === "cellXfs") inCellFormats = false;
624
+ },
625
+ });
626
+ }
627
+
628
+ const csv = new Csv();
629
+ let cells = null, rowNumber = 0, column = -1, type = "", style = 0, value = null, capture = "";
630
+ walk(member(zip, first), {
631
+ open(tag, attributes) {
632
+ if (tag === "row") {
633
+ const number = Number(attribute(attributes, "r") ?? rowNumber + 1) || rowNumber + 1;
634
+ // Rows the sheet leaves out are empty rows.
635
+ for (let gap = Math.min(number - rowNumber - 1, COLUMN_CAP); gap > 0; gap--) csv.row([]);
636
+ rowNumber = number;
637
+ cells = [];
638
+ } else if (tag === "c" && cells) {
639
+ const reference = attribute(attributes, "r");
640
+ column = reference ? columnOf(reference) : column + 1;
641
+ type = attribute(attributes, "t") ?? "";
642
+ style = Number(attribute(attributes, "s") ?? 0);
643
+ value = null;
644
+ } else if (cells && (tag === "v" || (tag === "t" && type === "inlineStr"))) capture = tag;
645
+ },
646
+ close(tag) {
647
+ if (tag === capture) capture = "";
648
+ else if (tag === "c" && cells) {
649
+ if (value === null || column < 0 || column >= COLUMN_CAP) return;
650
+ let text = value;
651
+ if (type === "s") text = shared[Number(value)] ?? "";
652
+ else if (type === "b") text = value === "1" ? "TRUE" : "FALSE";
653
+ else if (type === "" || type === "n") {
654
+ const number = Number(value);
655
+ if (value.trim() !== "" && Number.isFinite(number)) text = dateStyles[style] ? serialDate(number, date1904) : String(number);
656
+ }
657
+ cells[column] = text;
658
+ } else if (tag === "row" && cells) {
659
+ csv.row(cells);
660
+ cells = null;
661
+ column = -1;
662
+ }
663
+ },
664
+ text(text) {
665
+ if (capture) value = (value ?? "") + text;
666
+ return !csv.full;
667
+ },
668
+ });
669
+ return csv.text(sheetNote(names));
670
+ }
671
+
672
+ function odsCsv(zip) {
673
+ const names = [];
674
+ const csv = new Csv();
675
+ let depth = 0, sheet = 0, cells = null, rowRepeat = 1, cell = null, skip = 0;
676
+ walk(member(zip, "content.xml"), {
677
+ open(tag, attributes) {
678
+ if (skip || ODF_SKIP.has(tag)) {
679
+ skip++;
680
+ return;
681
+ }
682
+ if (tag === "table:table") {
683
+ // A table inside a cell belongs to that cell, not to the list of sheets.
684
+ if (depth++ === 0) names.push(attribute(attributes, "table:name") ?? "");
685
+ if (depth === 1) sheet = names.length;
686
+ } else if (sheet !== 1 || depth !== 1) return;
687
+ else if (tag === "table:table-row") {
688
+ cells = [];
689
+ rowRepeat = Math.max(1, Number(attribute(attributes, "table:number-rows-repeated") ?? 1) || 1);
690
+ } else if ((tag === "table:table-cell" || tag === "table:covered-table-cell") && cells)
691
+ cell = { repeat: Math.max(1, Number(attribute(attributes, "table:number-columns-repeated") ?? 1) || 1), lines: [], line: null };
692
+ else if (tag === "text:p" && cell) cell.line = [];
693
+ else if (tag === "text:s" && cell?.line) cell.line.push(" ".repeat(Math.min(64, Math.max(1, Number(attribute(attributes, "text:c") ?? 1) || 1))));
694
+ else if (tag === "text:tab" && cell?.line) cell.line.push("\t");
695
+ else if (tag === "text:line-break" && cell?.line) cell.line.push("\n");
696
+ },
697
+ close(tag) {
698
+ if (skip) {
699
+ skip--;
700
+ return;
701
+ }
702
+ if (tag === "table:table") {
703
+ depth--;
704
+ return;
705
+ }
706
+ if (sheet !== 1 || depth !== 1) return;
707
+ if (tag === "text:p" && cell?.line) {
708
+ cell.lines.push(cell.line.join(""));
709
+ cell.line = null;
710
+ } else if ((tag === "table:table-cell" || tag === "table:covered-table-cell") && cell && cells) {
711
+ const text = cell.lines.join("\n");
712
+ // A repeated empty cell is the padding to the sheet's edge; only a filled one is copied.
713
+ if (text === "") cells.length = Math.min(COLUMN_CAP, cells.length + cell.repeat);
714
+ else for (let copy = Math.min(cell.repeat, COLUMN_CAP - cells.length); copy > 0; copy--) cells.push(text);
715
+ cell = null;
716
+ } else if (tag === "table:table-row" && cells) {
717
+ const filled = cells.some((value) => value !== undefined && value !== "");
718
+ // Empty rows repeated to the sheet's end only count as blanks; a filled row repeats as written.
719
+ for (let copy = Math.min(rowRepeat, filled ? ROW_REPEAT_CAP : COLUMN_CAP); copy > 0 && !csv.full; copy--) csv.row(cells);
720
+ cells = null;
721
+ }
722
+ },
723
+ text(value) {
724
+ if (!skip && cell?.line) cell.line.push(value);
725
+ return !csv.full;
726
+ },
727
+ });
728
+ if (!names.length) throw new Error("no sheet");
729
+ return csv.text(sheetNote(names));
730
+ }
731
+
732
+ // ---- the preview ----
733
+
734
+ // The fragment's own text, for the source view and the line count.
735
+ const htmlText = (html) =>
736
+ decode(
737
+ html
738
+ .replace(/<\/(p|h[1-6]|li|tr|table)>|<br>/g, "\n")
739
+ .replace(/<\/td>/g, "\t")
740
+ .replace(/<[^>]*>/g, "")
741
+ .replace(/&nbsp;/g, " "),
742
+ ).replace(/\t+\n/g, "\n").replace(/\n{3,}/g, "\n\n");
743
+
744
+ // What an archive preview lists: the first names and the count of the rest.
745
+ function memberNames(zip) {
746
+ const names = zip.names.slice(0, NAME_CAP);
747
+ return { names, more: zip.names.length - names.length };
748
+ }
749
+ function listingHtml(zip) {
750
+ const { names, more } = memberNames(zip);
751
+ return `<p>No readable text in this document. It contains:</p><ul>${names.map((name) => `<li>${escape(name)}</li>`).join("")}</ul>${more > 0 ? `<p>+ ${more} more</p>` : ""}`;
752
+ }
753
+ function listingCsv(zip) {
754
+ const { names, more } = memberNames(zip);
755
+ const lines = ["Member,Bytes", ...names.map((name) => `${field(name)},${zip.sizes.get(name)}`)];
756
+ if (more > 0) lines.push(field(`+ ${more} more`));
757
+ return lines.join("\n") + "\n";
758
+ }
759
+
760
+ function convert(path, kind) {
761
+ const zip = openZip(path);
762
+ try {
763
+ const lower = path.toLowerCase();
764
+ const open = lower.endsWith(".odt") || lower.endsWith(".odp") || lower.endsWith(".ods");
765
+ if (kind === "sheet") {
766
+ let csv = "";
767
+ try {
768
+ csv = open ? odsCsv(zip) : xlsxCsv(zip);
769
+ } catch {}
770
+ const listed = csv === "";
771
+ if (listed) csv = listingCsv(zip);
772
+ return { kind, csv, text: csv, listed };
773
+ }
774
+ let html = "";
775
+ try {
776
+ html = open ? odfHtml(zip, kind === "slides") : kind === "slides" ? pptxHtml(zip) : docxHtml(zip);
777
+ } catch {}
778
+ // A deck of empty slides is its headings alone, which says nothing either.
779
+ const listed = html.replace(/<h2>Slide \d+<\/h2>/g, "") === "";
780
+ if (listed) html = listingHtml(zip);
781
+ return { kind, html, text: htmlText(html), listed };
782
+ } finally {
783
+ zip.close();
784
+ }
785
+ }
786
+
787
+ // The column, its facts and Quick Look ask about the same file within moments of each other.
788
+ const CACHE_SIZE = 4;
789
+ const cache = new Map();
790
+
791
+ /** The preview of the office file at `path`: { kind, html?, csv?, text, listed }. `html` is a
792
+ * fragment of the tags src/html.js keeps, `csv` the first sheet, `text` either one as plain lines,
793
+ * and `listed` is true when the content could not be read and the zip's member names stand in.
794
+ * Throws for a name this file does not read, a file over OFFICE_LIMIT, and a file that is not a zip
795
+ * (a legacy binary document or an encrypted one). */
796
+ export function officePreview(path) {
797
+ const kind = officeKind(path);
798
+ if (!kind) throw new Error("not an office document");
799
+ const st = statSync(path);
800
+ if (!st.isFile()) throw new Error("not a file");
801
+ if (st.size > OFFICE_LIMIT) throw new Error("office document too large");
802
+ const key = `${path}\0${st.size}\0${st.mtimeMs}`;
803
+ let preview = cache.get(key);
804
+ if (!preview) {
805
+ preview = convert(path, kind);
806
+ cache.set(key, preview);
807
+ if (cache.size > CACHE_SIZE) cache.delete(cache.keys().next().value);
808
+ }
809
+ return preview;
810
+ }