flea-qml 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (637) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +511 -2
  3. package/THIRD-PARTY-NOTICES.md +29 -0
  4. package/demos/App.java +5 -0
  5. package/demos/Dockerfile +4 -0
  6. package/demos/Main.kt +3 -0
  7. package/demos/Program.cs +1 -0
  8. package/demos/README.md +12 -0
  9. package/demos/app.dart +3 -0
  10. package/demos/app.deb +0 -0
  11. package/demos/app.js +1 -0
  12. package/demos/backup.tar +0 -0
  13. package/demos/budget.xlsx +0 -0
  14. package/demos/build.sh +2 -0
  15. package/demos/bundle.zip +0 -0
  16. package/demos/clip.mp4 +0 -0
  17. package/demos/data.sqlite +0 -0
  18. package/demos/disk.iso +0 -0
  19. package/demos/engine.cpp +5 -0
  20. package/demos/firmware.bin +0 -0
  21. package/demos/flake.nix +4 -0
  22. package/demos/folder/inside.txt +1 -0
  23. package/demos/font.ttf +0 -0
  24. package/demos/index.html +5 -0
  25. package/demos/index.php +2 -0
  26. package/demos/init.lua +1 -0
  27. package/demos/letter.docx +0 -0
  28. package/demos/lib.rs +3 -0
  29. package/demos/main.c +6 -0
  30. package/demos/main.h +3 -0
  31. package/demos/math-and-diagrams.md +200 -0
  32. package/demos/notes.tar.gz +0 -0
  33. package/demos/notes.txt +3 -0
  34. package/demos/photo.png +0 -0
  35. package/demos/program +2 -0
  36. package/demos/report.pdf +32 -0
  37. package/demos/script.py +1 -0
  38. package/demos/server.go +7 -0
  39. package/demos/settings.json +4 -0
  40. package/demos/slides.pptx +0 -0
  41. package/demos/song.mp3 +0 -0
  42. package/demos/style.css +4 -0
  43. package/demos/tool.rb +1 -0
  44. package/demos/types.ts +2 -0
  45. package/docs/pele.md +23 -0
  46. package/docs/protocol.md +3420 -0
  47. package/docs/temml.md +63 -0
  48. package/index.js +107 -0
  49. package/package.json +34 -4
  50. package/src/archive.js +407 -0
  51. package/src/archiveops.js +728 -0
  52. package/src/backend.js +369 -0
  53. package/src/bulkrename.js +95 -0
  54. package/src/clip-host.js +76 -0
  55. package/src/clip.js +462 -0
  56. package/src/code.js +168 -0
  57. package/src/compare.js +74 -0
  58. package/src/create.js +128 -0
  59. package/src/dirsize.js +147 -0
  60. package/src/figure-svg.js +530 -0
  61. package/src/figure-thread.js +23 -0
  62. package/src/figure.js +132 -0
  63. package/src/find.js +55 -0
  64. package/src/flea.js +39 -0
  65. package/src/fsinfo.js +39 -0
  66. package/src/highlight.js +141 -0
  67. package/src/html.js +52 -0
  68. package/src/kinds.js +89 -0
  69. package/src/listing.js +130 -0
  70. package/src/markdown-html.js +317 -0
  71. package/src/mathml.js +186 -0
  72. package/src/menu.js +180 -0
  73. package/src/meta.js +135 -0
  74. package/src/office.js +810 -0
  75. package/src/order.js +100 -0
  76. package/src/peek.js +49 -0
  77. package/src/pele-wrapper.js +695 -0
  78. package/src/permissions.js +180 -0
  79. package/src/places.js +313 -0
  80. package/src/preview-language.js +10 -0
  81. package/src/terminal-server.js +95 -0
  82. package/src/terminal-shell.js +26 -0
  83. package/src/transfer.js +669 -0
  84. package/src/trash-record.js +41 -0
  85. package/src/trash.js +210 -0
  86. package/src/trashbrowse.js +327 -0
  87. package/src/uistate.js +393 -0
  88. package/src/uistore.js +168 -0
  89. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  90. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  91. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  92. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  93. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  94. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  95. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  96. package/src/vendor/pele.js +145 -0
  97. package/src/vendor/temml.mjs +14708 -0
  98. package/src/watch.js +98 -0
  99. package/src/wayland/client.js +109 -0
  100. package/src/wayland/protocol.js +55 -0
  101. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  102. package/src/wayland/protocols/wayland.xml +3248 -0
  103. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  104. package/src/wayland/resource-io.js +6 -0
  105. package/src/wayland/socket-discovery.js +26 -0
  106. package/src/wayland/transport-posix.js +120 -0
  107. package/src/wayland/transport-win32.js +11 -0
  108. package/src/wayland/transport.js +3 -0
  109. package/src/wayland/win32-resources.js +80 -0
  110. package/src/wayland/win32-wire.js +62 -0
  111. package/test/archive.test.js +80 -0
  112. package/test/bench-markdown-scroll.mjs +190 -0
  113. package/test/clip-host.test.js +113 -0
  114. package/test/code-preview.test.js +35 -0
  115. package/test/compare-ui-paths.test.js +24 -0
  116. package/test/compare.test.js +54 -0
  117. package/test/delete-trash.test.js +67 -0
  118. package/test/dirsize.test.js +138 -0
  119. package/test/file-ops.test.js +307 -0
  120. package/test/file-url.test.js +24 -0
  121. package/test/find-contents.test.js +161 -0
  122. package/test/find-input.test.js +66 -0
  123. package/test/fixtures/pele-parity.json +1006 -0
  124. package/test/highlight-launcher.test.js +35 -0
  125. package/test/html-preview.test.js +22 -0
  126. package/test/input.test.js +15 -0
  127. package/test/keymap.test.js +26 -0
  128. package/test/language-icons.test.js +36 -0
  129. package/test/launcher.test.js +51 -0
  130. package/test/manual-archive-here-ui.mjs +47 -0
  131. package/test/manual-code-scroll.mjs +28 -0
  132. package/test/manual-column-scroll.mjs +24 -0
  133. package/test/manual-compare-ui.mjs +20 -0
  134. package/test/manual-find-status.mjs +37 -0
  135. package/test/manual-folder-focus.mjs +63 -0
  136. package/test/manual-html-security.mjs +23 -0
  137. package/test/manual-html-ui.mjs +30 -0
  138. package/test/manual-syntax-preview.mjs +26 -0
  139. package/test/manual-terminal-lifecycle.mjs +42 -0
  140. package/test/manual-terminal-navigation.mjs +48 -0
  141. package/test/manual-terminal-ui.mjs +47 -0
  142. package/test/manual-trash-ui.mjs +86 -0
  143. package/test/markdown-diagrams.test.js +93 -0
  144. package/test/markdown-html.test.js +105 -0
  145. package/test/mathml.test.js +92 -0
  146. package/test/menu-transfer.test.js +57 -0
  147. package/test/office-preview.test.js +154 -0
  148. package/test/pele-parity.test.js +28 -0
  149. package/test/places.test.js +134 -0
  150. package/test/preview-keys.test.js +60 -0
  151. package/test/preview-language.test.js +22 -0
  152. package/test/status-filename.test.js +20 -0
  153. package/test/transfer-replace-merge.test.js +187 -0
  154. package/test/trash-address.test.js +23 -0
  155. package/test/trash-view.test.js +37 -0
  156. package/test/trashbrowse.test.js +135 -0
  157. package/ui/AboutFacts.qml +78 -0
  158. package/ui/ArchiveCards.qml +95 -0
  159. package/ui/ArchiveCompressDialog.qml +378 -0
  160. package/ui/ArchiveHereConfirm.qml +127 -0
  161. package/ui/ArchivePasswordDialog.qml +257 -0
  162. package/ui/Backend.qml +775 -0
  163. package/ui/BulkOldNames.qml +47 -0
  164. package/ui/BulkRenameDialog.qml +380 -0
  165. package/ui/CardFrame.qml +67 -0
  166. package/ui/CardScroll.qml +79 -0
  167. package/ui/CheckBox.qml +73 -0
  168. package/ui/ChromeBar.qml +360 -0
  169. package/ui/ChromeButton.qml +131 -0
  170. package/ui/ChromeModes.qml +43 -0
  171. package/ui/CodeLines.qml +256 -0
  172. package/ui/CodeRefusal.qml +52 -0
  173. package/ui/CollideConfirm.qml +147 -0
  174. package/ui/CollideHost.qml +167 -0
  175. package/ui/ColumnPane.qml +525 -0
  176. package/ui/ColumnRow.qml +380 -0
  177. package/ui/ColumnsArea.qml +594 -0
  178. package/ui/ColumnsLanding.qml +147 -0
  179. package/ui/CompareView.qml +321 -0
  180. package/ui/ContextMenu.qml +743 -0
  181. package/ui/ConvertDialog.qml +456 -0
  182. package/ui/CountedWorker.qml +7 -0
  183. package/ui/Crumb.qml +64 -0
  184. package/ui/CrumbDrop.qml +25 -0
  185. package/ui/CsvTable.qml +394 -0
  186. package/ui/CursorMark.qml +41 -0
  187. package/ui/DefaultClaim.qml +130 -0
  188. package/ui/DeviceLock.qml +47 -0
  189. package/ui/DeviceMounts.qml +540 -0
  190. package/ui/DialogButton.qml +150 -0
  191. package/ui/DialogField.qml +147 -0
  192. package/ui/DialogHeader.qml +31 -0
  193. package/ui/DialogScrim.qml +20 -0
  194. package/ui/DialogTitle.qml +37 -0
  195. package/ui/Divider.qml +9 -0
  196. package/ui/DropInto.qml +126 -0
  197. package/ui/DropTarget.qml +35 -0
  198. package/ui/DropboxMark.qml +60 -0
  199. package/ui/EmptyState.qml +150 -0
  200. package/ui/EscapeHint.qml +11 -0
  201. package/ui/FactsTable.qml +54 -0
  202. package/ui/FastScrollHandler.qml +476 -0
  203. package/ui/FatNamesConfirm.qml +157 -0
  204. package/ui/Favourites.qml +143 -0
  205. package/ui/FigureService.qml +120 -0
  206. package/ui/FileDrag.qml +267 -0
  207. package/ui/FilterStrip.qml +133 -0
  208. package/ui/FitMetrics.qml +22 -0
  209. package/ui/FlashDriveRows.qml +137 -0
  210. package/ui/FlashHost.qml +72 -0
  211. package/ui/FlashImageRow.qml +104 -0
  212. package/ui/FlashRunner.qml +119 -0
  213. package/ui/FlashStepRow.qml +172 -0
  214. package/ui/FlashStrip.qml +38 -0
  215. package/ui/FlashView.qml +397 -0
  216. package/ui/FleaMark.qml +88 -0
  217. package/ui/FlyoutFade.qml +23 -0
  218. package/ui/FontPane.qml +170 -0
  219. package/ui/Glass.qml +114 -0
  220. package/ui/GlassTagger.qml +59 -0
  221. package/ui/Glyph.qml +64 -0
  222. package/ui/GlyphSolid.qml +22 -0
  223. package/ui/GridArea.qml +664 -0
  224. package/ui/GridTile.qml +325 -0
  225. package/ui/GvfsBridge.qml +164 -0
  226. package/ui/Header.qml +433 -0
  227. package/ui/HoverTimers.qml +39 -0
  228. package/ui/HtmlClip.qml +30 -0
  229. package/ui/HtmlPane.qml +63 -0
  230. package/ui/HyprlandMark.qml +45 -0
  231. package/ui/Ipc.qml +1005 -0
  232. package/ui/IpcControls.qml +7 -0
  233. package/ui/IpcMenuState.qml +48 -0
  234. package/ui/IpcPreviewState.qml +185 -0
  235. package/ui/JumpPath.qml +51 -0
  236. package/ui/KeymapSheet.qml +561 -0
  237. package/ui/KeymapSheetResult.qml +89 -0
  238. package/ui/KindStack.qml +40 -0
  239. package/ui/LangMark.qml +51 -0
  240. package/ui/LangMarkLoader.qml +26 -0
  241. package/ui/List.qml +480 -0
  242. package/ui/LoadingState.qml +54 -0
  243. package/ui/LocalSend.qml +56 -0
  244. package/ui/LocalSendMark.qml +73 -0
  245. package/ui/MarkdownBlockView.qml +311 -0
  246. package/ui/MarkdownBlocks.qml +35 -0
  247. package/ui/MarkdownFigure.qml +254 -0
  248. package/ui/MarkdownImages.qml +72 -0
  249. package/ui/MarkdownList.qml +81 -0
  250. package/ui/MarkdownMathsText.qml +97 -0
  251. package/ui/MarkdownPane.qml +83 -0
  252. package/ui/MarkdownPictures.qml +41 -0
  253. package/ui/MarkdownQuote.qml +52 -0
  254. package/ui/MarkdownRemote.qml +82 -0
  255. package/ui/MarkdownSource.qml +155 -0
  256. package/ui/MarkdownTable.qml +222 -0
  257. package/ui/MarkdownTableScroll.qml +64 -0
  258. package/ui/MarkdownTableWheel.qml +126 -0
  259. package/ui/MarkdownText.qml +87 -0
  260. package/ui/MarkdownWorker.js +132 -0
  261. package/ui/MatchText.qml +99 -0
  262. package/ui/MediaSession.qml +38 -0
  263. package/ui/MediaSound.qml +26 -0
  264. package/ui/MediaStrip.qml +191 -0
  265. package/ui/MediaTrack.qml +36 -0
  266. package/ui/MediaVolume.qml +54 -0
  267. package/ui/MenuActionDialog.qml +396 -0
  268. package/ui/MenuEdgeFade.qml +24 -0
  269. package/ui/MenuHost.qml +227 -0
  270. package/ui/MenuRow.qml +351 -0
  271. package/ui/MountListing.qml +100 -0
  272. package/ui/NetworkDialog.qml +435 -0
  273. package/ui/NetworkForm.qml +426 -0
  274. package/ui/NetworkMounts.qml +913 -0
  275. package/ui/NetworkPlaces.qml +127 -0
  276. package/ui/OpenWithDialog.qml +613 -0
  277. package/ui/Opener.qml +95 -0
  278. package/ui/Pane.qml +1320 -0
  279. package/ui/PaneMenuActions.qml +527 -0
  280. package/ui/PanePath.qml +90 -0
  281. package/ui/PaneRail.qml +268 -0
  282. package/ui/PaneStates.qml +88 -0
  283. package/ui/PaneSwap.qml +339 -0
  284. package/ui/PaneWire.qml +601 -0
  285. package/ui/ParkedWait.qml +9 -0
  286. package/ui/PathEntry.qml +186 -0
  287. package/ui/PathJump.qml +400 -0
  288. package/ui/PdfViewer.qml +297 -0
  289. package/ui/PermissionsDialog.qml +658 -0
  290. package/ui/PermissionsHeadings.qml +31 -0
  291. package/ui/PhoneMounts.qml +67 -0
  292. package/ui/PickerAnswers.qml +75 -0
  293. package/ui/PickerBox.qml +26 -0
  294. package/ui/PickerChrome.qml +295 -0
  295. package/ui/PickerFilter.qml +97 -0
  296. package/ui/PickerGrid.qml +308 -0
  297. package/ui/PickerHeader.qml +28 -0
  298. package/ui/PickerLifecycle.qml +27 -0
  299. package/ui/PickerList.qml +218 -0
  300. package/ui/PickerListing.qml +149 -0
  301. package/ui/PickerPathBar.qml +127 -0
  302. package/ui/PickerPlaces.qml +189 -0
  303. package/ui/PickerRecent.qml +92 -0
  304. package/ui/PickerSave.qml +101 -0
  305. package/ui/PickerSelection.qml +155 -0
  306. package/ui/PickerWindow.qml +846 -0
  307. package/ui/PowerSectorsReader.qml +10 -0
  308. package/ui/PrebuildDriver.qml +146 -0
  309. package/ui/Preview.qml +1005 -0
  310. package/ui/PreviewAnimation.qml +24 -0
  311. package/ui/PreviewArchive.qml +91 -0
  312. package/ui/PreviewColumn.qml +553 -0
  313. package/ui/PreviewEdge.qml +63 -0
  314. package/ui/PreviewFacts.qml +30 -0
  315. package/ui/PreviewFactsLine.qml +34 -0
  316. package/ui/PreviewHtml.qml +233 -0
  317. package/ui/PreviewImage.qml +253 -0
  318. package/ui/PreviewImagePan.qml +13 -0
  319. package/ui/PreviewImageSharp.qml +32 -0
  320. package/ui/PreviewLines.qml +106 -0
  321. package/ui/PreviewMarkdown.qml +706 -0
  322. package/ui/PreviewMedia.qml +250 -0
  323. package/ui/PreviewPdf.qml +482 -0
  324. package/ui/PreviewScroll.qml +201 -0
  325. package/ui/PreviewSwap.qml +335 -0
  326. package/ui/PreviewText.qml +149 -0
  327. package/ui/ProtocolChip.qml +18 -0
  328. package/ui/QuickLookArchive.qml +41 -0
  329. package/ui/QuickLookBar.qml +103 -0
  330. package/ui/QuickLookPrepare.qml +355 -0
  331. package/ui/QuickLookSwap.qml +10 -0
  332. package/ui/QuickLookWarm.qml +65 -0
  333. package/ui/RailDrop.qml +155 -0
  334. package/ui/RailHeading.qml +19 -0
  335. package/ui/RailPlaces.qml +38 -0
  336. package/ui/RailTrashRow.qml +67 -0
  337. package/ui/RenameField.qml +353 -0
  338. package/ui/RendererRetry.qml +11 -0
  339. package/ui/ResizeHandle.qml +52 -0
  340. package/ui/Reveal.qml +54 -0
  341. package/ui/Row.qml +476 -0
  342. package/ui/RowDrag.qml +213 -0
  343. package/ui/RowDropTarget.qml +27 -0
  344. package/ui/RunCard.qml +286 -0
  345. package/ui/Scripts.qml +65 -0
  346. package/ui/SearchStrip.qml +232 -0
  347. package/ui/SelectionBand.qml +140 -0
  348. package/ui/SelectionPreview.qml +370 -0
  349. package/ui/SettingsFavourite.qml +122 -0
  350. package/ui/SettingsGroup.qml +96 -0
  351. package/ui/SettingsHint.qml +12 -0
  352. package/ui/SettingsMetrics.qml +29 -0
  353. package/ui/SettingsPane.qml +178 -0
  354. package/ui/SettingsPanel.qml +547 -0
  355. package/ui/SettingsRail.qml +128 -0
  356. package/ui/SettingsRow.qml +409 -0
  357. package/ui/SettingsRuler.qml +85 -0
  358. package/ui/SettingsSegment.qml +85 -0
  359. package/ui/ShareBrowser.qml +113 -0
  360. package/ui/ShareLink.qml +67 -0
  361. package/ui/ShelfPins.qml +101 -0
  362. package/ui/Sidebar.qml +728 -0
  363. package/ui/SidebarRow.qml +358 -0
  364. package/ui/Spinner.qml +60 -0
  365. package/ui/StableModel.qml +89 -0
  366. package/ui/StateMessage.qml +84 -0
  367. package/ui/StatusBar.qml +512 -0
  368. package/ui/StatusFileName.qml +27 -0
  369. package/ui/TabBar.qml +673 -0
  370. package/ui/TabDragGeometry.qml +60 -0
  371. package/ui/TagMenuMark.qml +52 -0
  372. package/ui/TagRailSection.qml +53 -0
  373. package/ui/TagSlot.qml +40 -0
  374. package/ui/TagState.qml +150 -0
  375. package/ui/TagSwatch.qml +28 -0
  376. package/ui/Taildrop.qml +86 -0
  377. package/ui/TailscaleMark.qml +50 -0
  378. package/ui/TerminalService.qml +69 -0
  379. package/ui/TerminalTab.qml +48 -0
  380. package/ui/TerminalTabs.qml +27 -0
  381. package/ui/Theme.qml +460 -0
  382. package/ui/ThemeColors.qml +43 -0
  383. package/ui/TransferCard.qml +239 -0
  384. package/ui/TrashConfirm.qml +129 -0
  385. package/ui/TrashDropHandler.qml +11 -0
  386. package/ui/TrashHost.qml +202 -0
  387. package/ui/TrashMonitor.qml +30 -0
  388. package/ui/TrashToolbar.qml +19 -0
  389. package/ui/TrashView.qml +492 -0
  390. package/ui/UnlockDialog.qml +450 -0
  391. package/ui/UnlockHost.qml +206 -0
  392. package/ui/UpdateCheck.qml +97 -0
  393. package/ui/UserDirs.qml +25 -0
  394. package/ui/ViewState.qml +565 -0
  395. package/ui/ViewportScrollBar.qml +195 -0
  396. package/ui/ViewportScrollBars.qml +25 -0
  397. package/ui/WebPreview.qml +33 -0
  398. package/ui/WebPreviewView.qml +26 -0
  399. package/ui/WindowBody.qml +829 -0
  400. package/ui/WindowDrag.qml +35 -0
  401. package/ui/boot/Commons/AudioNodes.qml +50 -0
  402. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  403. package/ui/boot/Commons/Border.qml +243 -0
  404. package/ui/boot/Commons/BorderGeometry.js +373 -0
  405. package/ui/boot/Commons/Color.qml +258 -0
  406. package/ui/boot/Commons/I18n.qml +118 -0
  407. package/ui/boot/Commons/InputDevices.qml +112 -0
  408. package/ui/boot/Commons/InputMethodState.qml +38 -0
  409. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  410. package/ui/boot/Commons/Numbers.js +19 -0
  411. package/ui/boot/Commons/Plural.js +91 -0
  412. package/ui/boot/Commons/ShellIpc.qml +12 -0
  413. package/ui/boot/Commons/Style.qml +561 -0
  414. package/ui/boot/Commons/Translations.qml +100 -0
  415. package/ui/boot/Commons/UntypedInput.qml +152 -0
  416. package/ui/boot/Commons/Util.qml +188 -0
  417. package/ui/boot/Commons/qmldir +13 -0
  418. package/ui/boot/LICENSE-omarchy +20 -0
  419. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  420. package/ui/boot/Ui/BarIconButton.qml +106 -0
  421. package/ui/boot/Ui/BarIconContent.qml +38 -0
  422. package/ui/boot/Ui/BarIndicator.qml +53 -0
  423. package/ui/boot/Ui/BarWidget.qml +50 -0
  424. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  425. package/ui/boot/Ui/BorderSurface.qml +40 -0
  426. package/ui/boot/Ui/Button.qml +224 -0
  427. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  428. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  429. package/ui/boot/Ui/CursorSurface.qml +42 -0
  430. package/ui/boot/Ui/Dropdown.qml +245 -0
  431. package/ui/boot/Ui/FilterInput.qml +26 -0
  432. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  433. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  434. package/ui/boot/Ui/MultiSelect.qml +624 -0
  435. package/ui/boot/Ui/NumberField.qml +86 -0
  436. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  437. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  438. package/ui/boot/Ui/Panel.qml +60 -0
  439. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  440. package/ui/boot/Ui/PanelController.qml +16 -0
  441. package/ui/boot/Ui/PanelHero.qml +112 -0
  442. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  443. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  444. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  445. package/ui/boot/Ui/PanelSlider.qml +156 -0
  446. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  447. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  448. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  449. package/ui/boot/Ui/PopupCard.qml +181 -0
  450. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  451. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  452. package/ui/boot/Ui/SizedImage.qml +125 -0
  453. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  454. package/ui/boot/Ui/TextField.qml +58 -0
  455. package/ui/boot/Ui/Toggle.qml +118 -0
  456. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  457. package/ui/boot/Ui/WidgetButton.qml +172 -0
  458. package/ui/boot/Ui/qmldir +41 -0
  459. package/ui/boot/fleatab.qml +50 -0
  460. package/ui/boot/picker.qml +39 -0
  461. package/ui/boot/shell.qml +131 -0
  462. package/ui/boot/tabtearoff.qml +51 -0
  463. package/ui/js/Anchor.js +379 -0
  464. package/ui/js/AnchorHold.js +162 -0
  465. package/ui/js/Archive.js +157 -0
  466. package/ui/js/ArchiveCard.js +44 -0
  467. package/ui/js/ArchiveOps.js +44 -0
  468. package/ui/js/BackReturn.js +118 -0
  469. package/ui/js/BulkRename.js +100 -0
  470. package/ui/js/Buttons.js +54 -0
  471. package/ui/js/ChromeKeys.js +116 -0
  472. package/ui/js/ClipMarks.js +73 -0
  473. package/ui/js/ClipSelection.js +67 -0
  474. package/ui/js/Clipboard.js +179 -0
  475. package/ui/js/Cloud.js +97 -0
  476. package/ui/js/CodeRuns.js +72 -0
  477. package/ui/js/Collide.js +212 -0
  478. package/ui/js/ColumnFit.js +28 -0
  479. package/ui/js/ColumnMenu.js +111 -0
  480. package/ui/js/Columns.js +298 -0
  481. package/ui/js/ColumnsCarry.js +103 -0
  482. package/ui/js/Compare.js +74 -0
  483. package/ui/js/Contrast.js +157 -0
  484. package/ui/js/ControlReader.js +42 -0
  485. package/ui/js/Convert.js +52 -0
  486. package/ui/js/CopyAs.js +52 -0
  487. package/ui/js/Crumbs.js +178 -0
  488. package/ui/js/Csv.js +231 -0
  489. package/ui/js/Density.js +44 -0
  490. package/ui/js/Devices.js +440 -0
  491. package/ui/js/DirSizes.js +180 -0
  492. package/ui/js/Drag.js +360 -0
  493. package/ui/js/DragOut.js +166 -0
  494. package/ui/js/DropDev.js +52 -0
  495. package/ui/js/Dropbox.js +41 -0
  496. package/ui/js/Eject.js +217 -0
  497. package/ui/js/Errors.js +188 -0
  498. package/ui/js/ExtThumbs.js +115 -0
  499. package/ui/js/Facts.js +402 -0
  500. package/ui/js/FigureDate.js +14 -0
  501. package/ui/js/Filter.js +285 -0
  502. package/ui/js/Find.js +46 -0
  503. package/ui/js/Flash.js +184 -0
  504. package/ui/js/FlashCard.js +79 -0
  505. package/ui/js/FlashEndings.js +125 -0
  506. package/ui/js/FlashPane.js +116 -0
  507. package/ui/js/FleaBin.js +12 -0
  508. package/ui/js/Focus.js +504 -0
  509. package/ui/js/FolderSorts.js +108 -0
  510. package/ui/js/FolderViews.js +44 -0
  511. package/ui/js/Format.js +314 -0
  512. package/ui/js/Fuzzy.js +141 -0
  513. package/ui/js/GlassTag.js +72 -0
  514. package/ui/js/Grid.js +33 -0
  515. package/ui/js/GridGeometry.js +90 -0
  516. package/ui/js/GridNames.js +153 -0
  517. package/ui/js/GvfsBridge.js +200 -0
  518. package/ui/js/HostClipboard.js +29 -0
  519. package/ui/js/Icons.js +218 -0
  520. package/ui/js/Input.js +50 -0
  521. package/ui/js/Jump.js +200 -0
  522. package/ui/js/KeyName.js +64 -0
  523. package/ui/js/Keymap.js +401 -0
  524. package/ui/js/Kinds.js +164 -0
  525. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  526. package/ui/js/LangMarkPaths.js +47 -0
  527. package/ui/js/LangMarks.js +120 -0
  528. package/ui/js/Lists.js +11 -0
  529. package/ui/js/LocalSend.js +39 -0
  530. package/ui/js/LockedMenu.js +63 -0
  531. package/ui/js/MakeDefault.js +186 -0
  532. package/ui/js/MarkBox.js +7 -0
  533. package/ui/js/Markdown.js +119 -0
  534. package/ui/js/MarkdownLists.js +86 -0
  535. package/ui/js/MarkdownMaths.js +58 -0
  536. package/ui/js/MarkdownPictures.js +32 -0
  537. package/ui/js/MarkdownPrepared.js +139 -0
  538. package/ui/js/MarkdownTableFit.js +34 -0
  539. package/ui/js/Marks.js +148 -0
  540. package/ui/js/Marquee.js +96 -0
  541. package/ui/js/Match.js +93 -0
  542. package/ui/js/Material.js +109 -0
  543. package/ui/js/MdBlocks.js +437 -0
  544. package/ui/js/MdBreak.js +71 -0
  545. package/ui/js/MdChunks.js +70 -0
  546. package/ui/js/MdContainer.js +137 -0
  547. package/ui/js/MdDocument.js +337 -0
  548. package/ui/js/MdEmph.js +97 -0
  549. package/ui/js/MdEntity.js +108 -0
  550. package/ui/js/MdEntityTable.js +223 -0
  551. package/ui/js/MdEscape.js +59 -0
  552. package/ui/js/MdFront.js +37 -0
  553. package/ui/js/MdHold.js +32 -0
  554. package/ui/js/MdHtml.js +307 -0
  555. package/ui/js/MdHtmlBlock.js +290 -0
  556. package/ui/js/MdHtmlHeading.js +53 -0
  557. package/ui/js/MdHtmlImage.js +179 -0
  558. package/ui/js/MdInline.js +154 -0
  559. package/ui/js/MdItems.js +212 -0
  560. package/ui/js/MdLeaf.js +309 -0
  561. package/ui/js/MdLink.js +196 -0
  562. package/ui/js/MdMath.js +67 -0
  563. package/ui/js/MdParagraphs.js +138 -0
  564. package/ui/js/MdRefs.js +232 -0
  565. package/ui/js/MdResolve.js +226 -0
  566. package/ui/js/MdRun.js +300 -0
  567. package/ui/js/MdUrl.js +226 -0
  568. package/ui/js/Menu.js +514 -0
  569. package/ui/js/MenuContext.js +85 -0
  570. package/ui/js/MenuFit.js +13 -0
  571. package/ui/js/MenuRefresh.js +74 -0
  572. package/ui/js/MenuWheel.js +35 -0
  573. package/ui/js/Messages.js +186 -0
  574. package/ui/js/Motion.js +16 -0
  575. package/ui/js/Mounts.js +320 -0
  576. package/ui/js/MouseNav.js +13 -0
  577. package/ui/js/Nav.js +329 -0
  578. package/ui/js/NetFs.js +26 -0
  579. package/ui/js/OpenWith.js +83 -0
  580. package/ui/js/Ops.js +585 -0
  581. package/ui/js/Palette.js +33 -0
  582. package/ui/js/PathBar.js +260 -0
  583. package/ui/js/Permissions.js +179 -0
  584. package/ui/js/Phones.js +112 -0
  585. package/ui/js/Picker.js +353 -0
  586. package/ui/js/PlaceMenu.js +96 -0
  587. package/ui/js/Places.js +265 -0
  588. package/ui/js/PreviewAsk.js +21 -0
  589. package/ui/js/PreviewKeys.js +155 -0
  590. package/ui/js/PreviewSettle.js +29 -0
  591. package/ui/js/PreviewSwap.js +101 -0
  592. package/ui/js/PropertyFacts.js +144 -0
  593. package/ui/js/Protocols.js +296 -0
  594. package/ui/js/QuickLookKinds.js +27 -0
  595. package/ui/js/QuickLookRect.js +24 -0
  596. package/ui/js/RailDrop.js +51 -0
  597. package/ui/js/RailKeys.js +87 -0
  598. package/ui/js/RailMenu.js +133 -0
  599. package/ui/js/Recent.js +135 -0
  600. package/ui/js/RecentMode.js +156 -0
  601. package/ui/js/Reload.js +60 -0
  602. package/ui/js/Renderer.js +9 -0
  603. package/ui/js/RunFile.js +143 -0
  604. package/ui/js/Scripts.js +28 -0
  605. package/ui/js/Scroll.js +304 -0
  606. package/ui/js/ScrollOff.js +69 -0
  607. package/ui/js/Search.js +478 -0
  608. package/ui/js/Selection.js +97 -0
  609. package/ui/js/Settings.js +476 -0
  610. package/ui/js/SettingsAbout.js +97 -0
  611. package/ui/js/SettingsShelf.js +52 -0
  612. package/ui/js/SheetKeys.js +42 -0
  613. package/ui/js/SheetQuery.js +281 -0
  614. package/ui/js/ShelfPile.js +27 -0
  615. package/ui/js/SlowClick.js +53 -0
  616. package/ui/js/SlowOp.js +6 -0
  617. package/ui/js/Sort.js +134 -0
  618. package/ui/js/Startup.js +63 -0
  619. package/ui/js/StateWords.js +17 -0
  620. package/ui/js/Status.js +238 -0
  621. package/ui/js/Swap.js +166 -0
  622. package/ui/js/TabMove.js +39 -0
  623. package/ui/js/Tabs.js +751 -0
  624. package/ui/js/Tags.js +134 -0
  625. package/ui/js/Taildrop.js +128 -0
  626. package/ui/js/Tap.js +180 -0
  627. package/ui/js/TextSize.js +107 -0
  628. package/ui/js/Thumbs.js +158 -0
  629. package/ui/js/Transfer.js +172 -0
  630. package/ui/js/Trash.js +84 -0
  631. package/ui/js/TrashDates.js +47 -0
  632. package/ui/js/TrashDrop.js +64 -0
  633. package/ui/js/UiState.js +438 -0
  634. package/ui/js/Unlock.js +152 -0
  635. package/ui/js/Update.js +130 -0
  636. package/ui/js/UserDirs.js +56 -0
  637. package/ui/qmldir +230 -0
@@ -0,0 +1,260 @@
1
+ .pragma library
2
+
3
+ .import "Format.js" as Format
4
+
5
+ // The sentence Tab answers with when nothing completes, filled by Format.fill.
6
+ var WORDS = {
7
+ manyStart: "%1 items start with that",
8
+ nothingStartsWith: "Nothing in %1 starts with that"
9
+ }
10
+
11
+ // What a typed path line means, and nothing about the field that carries it: ui/ChromeBar.qml owns
12
+ // the field and ui/shell.qml owns the navigation, the same split ui/js/Filter.js keeps with its
13
+ // strip. Every function here is pure, so tests/js/pathbar.js drives the whole of it with no window.
14
+ //
15
+ // The bar reads what the chrome already draws: a tilde is the home directory because that is how
16
+ // the path is written above the listing, and a bare name is relative to the directory the pane is
17
+ // on, because with the line selected on open that is what one word and Enter has to mean.
18
+
19
+ // The one place a path is made canonical. Interior "." and ".." are resolved here rather than sent
20
+ // to the backend, so the listing is opened on the path the bar will draw and the two never disagree;
21
+ // "" is not a path and is what every caller checks for.
22
+ function normalize(path) {
23
+ var text = String(path)
24
+ if (text.length === 0) {
25
+ return ""
26
+ }
27
+ var parts = text.split("/")
28
+ var out = []
29
+ for (var i = 0; i < parts.length; i++) {
30
+ var part = parts[i]
31
+ // An empty part is a doubled or a trailing slash, and neither names a directory of its own.
32
+ if (part.length === 0 || part === ".") {
33
+ continue
34
+ }
35
+ if (part === "..") {
36
+ // The root has no parent, which is where climbing stops, exactly as ui/js/Nav.js parent does.
37
+ out.pop()
38
+ continue
39
+ }
40
+ out.push(part)
41
+ }
42
+ return "/" + out.join("/")
43
+ }
44
+
45
+ // A file:// URI is what a paste from another application carries, and Flea's own --select takes one
46
+ // too. A URI that will not decode is left as it stands rather than dropped: a literal percent in a
47
+ // filename is legal, and refusing the whole line over one would be worse than a listing that fails.
48
+ //
49
+ // The authority between the third slash and the fourth is part of the URI and not part of the path.
50
+ // RFC 8089 gives it two local spellings, the empty one and "localhost", and both mean this machine;
51
+ // file://localhost/etc is /etc. Any other authority names a file on another host, which is not a
52
+ // path this bar can open, so it answers "" and ui/ChromeBar.qml says so rather than opening the
53
+ // authority as a directory of its own: stripping the scheme alone turned that line into a relative
54
+ // walk into <current>/localhost/etc, which is a directory nobody named.
55
+ var LOCAL_HOSTS = ["", "localhost"]
56
+
57
+ function unwrap(text) {
58
+ var body = String(text)
59
+ if (body.indexOf("file://") !== 0) {
60
+ return body
61
+ }
62
+ body = body.substring("file://".length)
63
+ // Everything up to the next slash is the authority; with none, the whole remainder is one.
64
+ var cut = body.indexOf("/")
65
+ var authority = cut < 0 ? body : body.substring(0, cut)
66
+ if (LOCAL_HOSTS.indexOf(authority.toLowerCase()) < 0) {
67
+ return ""
68
+ }
69
+ body = cut < 0 ? "/" : body.substring(cut)
70
+ try {
71
+ return decodeURIComponent(body)
72
+ } catch (e) {
73
+ return body
74
+ }
75
+ }
76
+
77
+ // Display/input alias only. The Trash collection keeps its existing filesystem paths.
78
+ function isTrashRoot(text) {
79
+ return /^trash:\/{1,3}$/i.test(String(text).trim())
80
+ }
81
+
82
+ function addressTarget(text, current, home) {
83
+ if (isTrashRoot(text)) return "trash:///"
84
+ return resolve(text, isTrashRoot(current) ? "/" : current, home)
85
+ }
86
+
87
+ function editLine(path) {
88
+ if (isTrashRoot(path)) return "trash:///"
89
+ return path === "/" ? "/" : path + "/"
90
+ }
91
+
92
+ // A typed smb:// (or sftp://, davs://, ...) address is a network location, not a name
93
+ // relative to the pane: resolving it against the directory would list one nobody named.
94
+ // The bar hands it back untouched, and the window routes it to the rail's own open.
95
+ function isNetworkUri(text) {
96
+ return /^(smb|sftp|ftp|ftps|dav|davs|nfs|afp):\/\//i.test(String(text).trim())
97
+ }
98
+
99
+ // The typed line as an absolute path. Answers "" for a line that names nothing, which is what the
100
+ // field checks before it navigates: an empty commit closes the bar and leaves the pane where it is.
101
+ function resolve(text, current, home) {
102
+ var line = String(text).trim()
103
+ if (isNetworkUri(line)) {
104
+ return line
105
+ }
106
+ var body = unwrap(line)
107
+ // Empty either because nothing was typed or because unwrap refused a URI on another host; both
108
+ // answer "" here, and refused() below is what tells the two apart for the sentence.
109
+ if (body.length === 0) {
110
+ return ""
111
+ }
112
+ // The tilde only expands against a home the shell actually published; with none, it is a name.
113
+ if (home.length > 0 && (body === "~" || body.indexOf("~/") === 0)) {
114
+ body = home + body.substring(1)
115
+ } else if (body.charAt(0) !== "/") {
116
+ // Relative to where the pane is, so "Downloads" and Enter is a directory down.
117
+ body = current + "/" + body
118
+ }
119
+ return normalize(body)
120
+ }
121
+
122
+ // The line cut at its last slash: head is what names a directory and leaf is the part being typed,
123
+ // which is the only part a completion may rewrite. A line with no slash at all is all leaf.
124
+ function split(text) {
125
+ var body = String(text)
126
+ var cut = body.lastIndexOf("/")
127
+ if (cut < 0) {
128
+ return { head: "", leaf: body }
129
+ }
130
+ return { head: body.substring(0, cut + 1), leaf: body.substring(cut + 1) }
131
+ }
132
+
133
+ // Which directory a completion has to read to answer the line. The head alone, because the leaf is
134
+ // half a name and would resolve to a directory that does not exist; an empty head is the pane's own.
135
+ function completionDir(text, current, home) {
136
+ var head = split(String(text).trim()).head
137
+ if (head.length === 0) {
138
+ return current
139
+ }
140
+ return resolve(head, current, home)
141
+ }
142
+
143
+ // A leaf that starts with a dot is asking for hidden entries whatever the listing is showing, so the
144
+ // peek behind Tab is told to include them rather than completing against rows it cannot see.
145
+ function wantsHidden(text, showHidden) {
146
+ return showHidden || split(String(text).trim()).leaf.indexOf(".") === 0
147
+ }
148
+
149
+ // What a completion request and its reply are matched on. The peek wire carries no request id, and
150
+ // it does not need one: two peeks of the same directory with the same hidden flag answer the same
151
+ // rows, so the pair is identity enough, and a reply that differs in either is another asker's.
152
+ // ui/ColumnsArea.qml peeks the pane's ancestors on the same signal, with the listing's own hidden
153
+ // flag, so without this a Tab on ".conf" could complete against rows that carried no dotfiles at all.
154
+ function requestKey(dir, hidden) {
155
+ return String(dir) + "\u0000" + (hidden ? "hidden" : "visible")
156
+ }
157
+
158
+ function commonPrefix(names) {
159
+ if (names.length === 0) {
160
+ return ""
161
+ }
162
+ var prefix = names[0]
163
+ for (var i = 1; i < names.length; i++) {
164
+ var j = 0
165
+ while (j < prefix.length && j < names[i].length && prefix.charAt(j) === names[i].charAt(j)) {
166
+ j++
167
+ }
168
+ prefix = prefix.substring(0, j)
169
+ }
170
+ return prefix
171
+ }
172
+
173
+ function startsWith(name, leaf, fold) {
174
+ var a = fold ? name.toLowerCase() : name
175
+ var b = fold ? leaf.toLowerCase() : leaf
176
+ return a.indexOf(b) === 0
177
+ }
178
+
179
+ // What Tab makes of the names one directory holds. text is the whole line, names are that
180
+ // directory's entries; the answer is the line as it should now read and how many entries stand
181
+ // behind it, which is what the field uses to decide whether a slash goes on the end.
182
+ //
183
+ // Case-sensitively first, and only case-insensitively when that matched nothing: the filesystem is
184
+ // case sensitive, so "Dev" and "dev" are two directories and a fold must never quietly pick one,
185
+ // but a lone "dev" typed at a directory holding only "Dev" is a typist and not an ambiguity.
186
+ function complete(text, names) {
187
+ var line = String(text)
188
+ var parts = split(line)
189
+ var matched = []
190
+ var i
191
+ for (i = 0; i < names.length; i++) {
192
+ if (startsWith(names[i], parts.leaf, false)) {
193
+ matched.push(names[i])
194
+ }
195
+ }
196
+ if (matched.length === 0) {
197
+ for (i = 0; i < names.length; i++) {
198
+ if (startsWith(names[i], parts.leaf, true)) {
199
+ matched.push(names[i])
200
+ }
201
+ }
202
+ }
203
+ if (matched.length === 0) {
204
+ return { text: line, matches: 0 }
205
+ }
206
+ var grown = commonPrefix(matched)
207
+ // The fold only runs when nothing matched exactly, so the matches can disagree in case inside
208
+ // the leaf and their common prefix be shorter than it: "re" against README and ReadMe.bak
209
+ // shares only "R", and returning that would delete a character the user typed.
210
+ if (grown.length < parts.leaf.length)
211
+ grown = parts.leaf
212
+ // One match is a whole name, so the line goes on with the separator already typed: Tab, Tab,
213
+ // Tab walks a tree. Several share a prefix, which is as far as the line can honestly go.
214
+ var tail = matched.length === 1 ? grown + "/" : grown
215
+ return { text: parts.head + tail, matches: matched.length }
216
+ }
217
+
218
+ // Sample input: shouldNavigate("/etc", "/etc", false) is false, the settled no-op.
219
+ // Whether a committed target lists again. The settled same-path no-op keeps the selection.
220
+ function shouldNavigate(target, current, failed) {
221
+ // An empty line closes the bar and navigates nowhere, the rule commitEdit already kept.
222
+ if (String(target).length === 0) {
223
+ return false
224
+ }
225
+ // The same path re-lists only when its listing failed, so an error retries and all else is a no-op.
226
+ if (String(target) === String(current) && !failed) {
227
+ return false
228
+ }
229
+ return true
230
+ }
231
+
232
+ // What a typed line that resolved to a path says when the picker's peek of it finds nothing, or a file the listing leaves out.
233
+ var MISSING_SENTENCE = "%1 does not exist or cannot be reached."
234
+ var FILTERED_SENTENCE = "%1 is hidden by the file filter or the hidden files setting."
235
+
236
+ // Sample input: kindOfPeek(false, 0) and (true, 16877) are "folder", (true, 0) "missing", (true, 33188) "file".
237
+ function kindOfPeek(readFailed, mode) {
238
+ if (!readFailed || (mode & Format.S_IFMT) === Format.S_IFDIR) return "folder"
239
+ return mode === 0 ? "missing" : "file"
240
+ }
241
+
242
+ // A line that named something yet resolved to nothing (a file:// URI on another host) owes a sentence; an empty line owes silence.
243
+ var REFUSED_SENTENCE = "That URI names a file on another host, not a path on this machine."
244
+
245
+ function refused(text) {
246
+ var line = String(text).trim()
247
+ return line.length > 0 && unwrap(line).length === 0
248
+ }
249
+
250
+ // The sentence Tab answers with when it changed nothing, written for the user and never an action
251
+ // name. An empty string means the line moved and the bar says nothing, which is the usual case.
252
+ function completionMessage(before, after, dir) {
253
+ if (after.matches === 0) {
254
+ return Format.fill(WORDS.nothingStartsWith, dir)
255
+ }
256
+ if (after.text === before) {
257
+ return Format.fill(WORDS.manyStart, Format.count(after.matches))
258
+ }
259
+ return ""
260
+ }
@@ -0,0 +1,179 @@
1
+ .pragma library
2
+
3
+ .import "Format.js" as Format
4
+
5
+ // Keeps the one-shot Make executable id clear of the dialog's own batch ids; both QML readers add their pending id to it.
6
+ var MAKE_EXEC_ID = 1000000
7
+
8
+ // Only the newest id on the asked path lands, so a late answer never arms a later file.
9
+ function landsShebang(path, id, asked, currentId) {
10
+ return path === asked && id === currentId
11
+ }
12
+
13
+ // Sample input: "644" or "0644"; invalid text remains in the input until corrected.
14
+ function parse(text) {
15
+ return /^(0?[0-7]{3})$/.test(String(text)) ? parseInt(text, 8) : -1
16
+ }
17
+ function octal(value) { return ("0000" + value.toString(8)).slice(-4) }
18
+ function toggle(text, bit) {
19
+ var value = parse(text)
20
+ return value < 0 ? text : octal(value ^ bit)
21
+ }
22
+
23
+ // The nine rwx bits of a mode, below the special bits.
24
+ var PERMISSION_BITS = 0x1ff
25
+
26
+ // Sample input: "4644" answers 420 (0o644), "0755" answers 493, "" answers -1; the nine permission bits of a mode that carries special bits too.
27
+ function shownBits(text) {
28
+ return /^[0-7]{3,4}$/.test(String(text)) ? parseInt(text, 8) & PERMISSION_BITS : -1
29
+ }
30
+
31
+ // Sample input: (["0644", "4644"], ["", "Read-only: setuid bit is present."]); one row per permission bit, in grid order.
32
+ // A file the card skips (a mode parse refuses, or a reason) neither sets nor mixes a bit; when every file is skipped the boxes show their own bits and changeable is false.
33
+ function summarize(modes, reasons) {
34
+ var values = []
35
+ var shown = []
36
+ for (var i = 0; i < modes.length; i++) {
37
+ var value = parse(modes[i])
38
+ if (value >= 0 && !(reasons && reasons[i]))
39
+ values.push(value)
40
+ var bits9 = shownBits(modes[i])
41
+ if (bits9 >= 0)
42
+ shown.push(bits9)
43
+ }
44
+ var changeable = values.length > 0
45
+ if (!changeable)
46
+ values = shown
47
+ var bits = []
48
+ var anyMixed = false
49
+ for (var b = 0; b < 9; b++) {
50
+ var mask = 1 << (8 - b)
51
+ var on = true
52
+ var off = true
53
+ for (var j = 0; j < values.length; j++) {
54
+ if (!(values[j] & mask))
55
+ on = false
56
+ else
57
+ off = false
58
+ }
59
+ var mixed = !on && !off
60
+ if (mixed)
61
+ anyMixed = true
62
+ bits.push({ mask: mask, on: values.length > 0 && on, mixed: mixed })
63
+ }
64
+ return { bits: bits, mixed: anyMixed, changeable: changeable }
65
+ }
66
+
67
+ function mixedNote() { return "Mixed boxes keep each item's setting unless you change them." }
68
+
69
+ // A mode with special bits is named in the backend's own words, never dropped on parse -1.
70
+ function specialReason(text) {
71
+ var digits = String(text || "")
72
+ if (!/^[1-7][0-7]{3}$/.test(digits))
73
+ return ""
74
+ var special = parseInt(digits.charAt(0), 8)
75
+ var label = (special & 4) !== 0 ? "setuid" : (special & 2) !== 0 ? "setgid" : "sticky"
76
+ return "Read-only: " + label + " bit is present."
77
+ }
78
+
79
+ function leafOf(path) {
80
+ var text = String(path || "")
81
+ var cut = text.lastIndexOf("/")
82
+ return cut < 0 ? text : text.substring(cut + 1)
83
+ }
84
+
85
+ // Modes land in arrival order in place; this answers true once per selection, on the last reply.
86
+ function noteMode(store, at, path, message) {
87
+ if (message.ok === true) {
88
+ store.modes[at] = message.mode
89
+ store.reasons[at] = message.reason || ""
90
+ } else {
91
+ // A refused inspect rides the reasons once, in the backend's own words.
92
+ var why = message.error || "Could not change permissions."
93
+ store.modes[at] = ""
94
+ store.reasons[at] = why
95
+ store.skipped.push({ path: path, why: why })
96
+ }
97
+ store.pending -= 1
98
+ return store.pending <= 0
99
+ }
100
+
101
+ // One note names every row the grid cannot change, reasoned and refused together.
102
+ function inspectNote(store, paths) {
103
+ var list = []
104
+ var reasons = (store && store.reasons) || []
105
+ for (var i = 0; i < paths.length; i++) {
106
+ var why = reasons[i] || ""
107
+ if (why.length > 0) list.push({ path: paths[i], why: why })
108
+ }
109
+ return list.length === 0 ? "" : skipNote(list)
110
+ }
111
+
112
+ // Most names a skip line lists before it counts the rest.
113
+ var SKIP_NAMES_SHOWN = 3
114
+
115
+ // Sample input: "Read-only: setgid bit is present." answers "setgid"; any other reason answers "".
116
+ function specialLabel(why) {
117
+ var found = /^Read-only: (setuid|setgid|sticky) bit is present\.$/.exec(String(why))
118
+ return found ? found[1] : ""
119
+ }
120
+
121
+ function skipKind(why) {
122
+ if (specialLabel(why) !== "")
123
+ return "special"
124
+ return why === "Read-only: you are not the owner." ? "owner" : "other"
125
+ }
126
+
127
+ // Sample input: "Gone" answers "Gone."; "Gone." answers "Gone."; a backend reason's own words are never changed, only closed.
128
+ function closed(text) {
129
+ return /\.$/.test(text) ? text : text + "."
130
+ }
131
+
132
+ // Whole sentences with "%1" for a value, filled by fill so each translates as one; a cause is a sentence of its own, never joined in.
133
+ var WORDS = {
134
+ titleMany: "Permissions for %1 items",
135
+ requestedMode: "Requested mode %1",
136
+ currentMode: "Current mode %1",
137
+ keepsSpecial: "%1 keeps its mode because its %2 bit is set.",
138
+ keepsOwner: "%1 keeps its mode because you do not own it.",
139
+ keepsOther: "%1 keeps its mode: %2",
140
+ manySpecial: "%1 items keep their modes because a special bit is set: %2.",
141
+ manyOwner: "%1 items keep their modes because you do not own them: %2.",
142
+ manyOther: "%1 items keep their modes because they cannot be changed: %2.",
143
+ namesAndMore: "%1 and %2 more",
144
+ keptOne: "%1 kept its mode.",
145
+ keptMany: "%1 items kept their modes."
146
+ }
147
+
148
+ // Sample input: one setuid file answers "special.txt keeps its mode because its setuid bit is set."; two answer "2 items keep their modes because a special bit is set: a, b.".
149
+ function skipNote(skipped) {
150
+ var list = skipped || []
151
+ if (list.length === 0)
152
+ return ""
153
+ var kinds = list.map(function (item) { return skipKind(item.why) })
154
+ var same = kinds.every(function (kind) { return kind === kinds[0] })
155
+ if (list.length === 1) {
156
+ var leaf = leafOf(list[0].path)
157
+ if (kinds[0] === "special")
158
+ return Format.fill(WORDS.keepsSpecial, leaf, specialLabel(list[0].why))
159
+ if (kinds[0] === "owner")
160
+ return Format.fill(WORDS.keepsOwner, leaf)
161
+ return closed(Format.fill(WORDS.keepsOther, leaf, list[0].why))
162
+ }
163
+ var names = []
164
+ for (var i = 0; i < list.length && i < SKIP_NAMES_SHOWN; i++)
165
+ names.push(leafOf(list[i].path))
166
+ var shown = list.length > SKIP_NAMES_SHOWN ? Format.fill(WORDS.namesAndMore, names.join(", "), list.length - SKIP_NAMES_SHOWN) : names.join(", ")
167
+ var sentence = same && kinds[0] === "special" ? WORDS.manySpecial
168
+ : same && kinds[0] === "owner" ? WORDS.manyOwner : WORDS.manyOther
169
+ return Format.fill(sentence, list.length, shown)
170
+ }
171
+
172
+ // Sample input: one file answers "special.txt kept its mode."; three answer "3 items kept their modes."; the reason is the card note's, said before Apply.
173
+ // The status bar's room beside the counts and the disk is about 320 px at 800 wide, so the line holds no count of what changed, which the card closing already said.
174
+ function multiResult(skipped) {
175
+ var list = skipped || []
176
+ if (list.length === 0)
177
+ return "Permissions changed."
178
+ return list.length === 1 ? Format.fill(WORDS.keptOne, leafOf(list[0].path)) : Format.fill(WORDS.keptMany, list.length)
179
+ }
@@ -0,0 +1,112 @@
1
+ .pragma library
2
+
3
+ // Sample input, "gio mount -li" under the C locale ui/MountListing.qml pins, captured live on this
4
+ // box with a Samsung phone on USB (2026-09-11), the udisks noise around it cut:
5
+ // Volume(0): SAMSUNG Android
6
+ // Type: GProxyVolume (GProxyVolumeMonitorMTP)
7
+ // activation_root=mtp://SAMSUNG_SAMSUNG_Android_RQGL705T0NR/
8
+ // can_mount=1
9
+ // Mount(0): SAMSUNG Android -> mtp://SAMSUNG_SAMSUNG_Android_RQGL705T0NR/
10
+ // And with an iPhone on USB (iOS 26.6.2, 2026-09-14), which answers on two monitors at once,
11
+ // then once its root is mounted, at column zero and outside the block that offered it:
12
+ // Volume(0): iPhone
13
+ // Type: GProxyVolume (GProxyVolumeMonitorGPhoto2)
14
+ // activation_root=gphoto2://Apple_Inc._iPhone_00008130001641411883401C/
15
+ // can_mount=1
16
+ // Volume(1): Documents on GM's iPhone
17
+ // Type: GProxyVolume (GProxyVolumeMonitorAfc)
18
+ // uuid=00008130-001641411883401C
19
+ // activation_root=afc://00008130-001641411883401C:3/
20
+ // can_mount=1
21
+ // Mount(2): GM's iPhone -> afc://00008130-001641411883401C/
22
+ // The root is that column-zero line; the share answers can_mount=0 with its own indented Mount when gvfs automounts it.
23
+ // Only a COLUMN-ZERO Volume() block can be a phone: a udisks volume prints indented under its own
24
+ // Drive() block, and the Type line is required anyway, so only the three gvfs monitors with no block
25
+ // device behind them qualify, MTP for Android, AFC for an iPhone's files and GPhoto2 for cameras.
26
+ // lsblk can never list these, which is why ui/DeviceMounts.qml's enumeration misses a plugged phone.
27
+ // The indented Mount() inside the block is what says an MTP or GPhoto2 volume is live; an AFC root
28
+ // mounts outside the block that offered it, so a column-zero Mount() naming the row's own uri says
29
+ // the same thing. Either way that line is ui/js/Mounts.js parseMounts's to skip as a NETWORK row.
30
+ function parsePhones(output) {
31
+ var blocks = []
32
+ var mounted = {}
33
+ var lines = String(output || "").split("\n")
34
+ var v = null
35
+ for (var i = 0; i < lines.length; i++) {
36
+ var line = lines[i]
37
+ var head = line.match(/^Volume\(\d+\):\s*(.+?)\s*$/)
38
+ if (head) {
39
+ v = { label: head[1], uri: "", uuid: "", mounted: false, inBlock: false, monitor: "", canMount: false }
40
+ blocks.push(v)
41
+ continue
42
+ }
43
+ // An AFC root mounts outside the block that offered it, so these are collected first.
44
+ var top = line.match(/^Mount\(\d+\):\s*.+?\s*->\s*(\S+)\s*$/)
45
+ if (top) mounted[top[1]] = true
46
+ // Any other column-zero line ends the block, the next Drive() or Mount() included.
47
+ if (!/^\s/.test(line)) { v = null; continue }
48
+ if (!v) continue
49
+ // Rule 1: the mark names the transport, MTP and AFC the phone and GPhoto2 the camera.
50
+ var monitor = line.match(/^\s+Type: GProxyVolume \(GProxyVolumeMonitor(MTP|GPhoto2|Afc)\)\s*$/)
51
+ if (monitor) v.monitor = monitor[1]
52
+ // The block's own uuid line, not the deeper one under ids: that carries the same digits.
53
+ var uuid = line.match(/^\s+uuid=(\S+)\s*$/)
54
+ if (uuid) v.uuid = uuid[1]
55
+ var root = line.match(/^\s+activation_root=(\S+)\s*$/)
56
+ if (root) v.uri = root[1]
57
+ if (/^\s+can_mount=1\s*$/.test(line)) v.canMount = true
58
+ if (/^\s+Mount\(\d+\):/.test(line)) v.inBlock = true
59
+ }
60
+ for (var a = 0; a < blocks.length; a++) {
61
+ if (blocks[a].monitor !== "Afc")
62
+ continue
63
+ // gvfs-afc advertises afc://<uuid>:3/, the documents share; the phone's files are at the root.
64
+ blocks[a].uri = blocks[a].uuid.length > 0 ? "afc://" + blocks[a].uuid + "/" : ""
65
+ }
66
+ var kept = []
67
+ var serials = []
68
+ var seen = {}
69
+ for (var b = 0; b < blocks.length; b++) {
70
+ var p = blocks[b]
71
+ // An Afc block's own Mount is that documents share, so only the root's column-zero line counts.
72
+ p.mounted = p.monitor === "Afc" ? mounted[p.uri] === true : (p.inBlock || mounted[p.uri] === true)
73
+ // An Afc volume's can_mount is that share's too, so its row stands on the root it names.
74
+ var live = p.monitor === "Afc" || p.canMount || p.mounted
75
+ // gvfs answers can_mount=0 for a volume it has already mounted, measured on this box's own USB
76
+ if (p.monitor.length === 0 || p.uri.length === 0 || !live)
77
+ continue
78
+ // Two AFC volumes can carry one uuid, and they rebuild to one root, which is one row.
79
+ if (seen[p.uri])
80
+ continue
81
+ seen[p.uri] = true
82
+ if (p.monitor === "Afc")
83
+ serials.push(p.uuid.replace(/-/g, "").toUpperCase())
84
+ kept.push(p)
85
+ }
86
+ // Folded against rows that survived, so a dropped AFC volume cannot take the phone off the rail.
87
+ var out = []
88
+ for (var c = 0; c < kept.length; c++) {
89
+ // One phone, one row: the serial the two monitors share is what says they are one device.
90
+ if (kept[c].monitor === "GPhoto2" && carriesOneOf(kept[c].uri, serials))
91
+ continue
92
+ out.push(entry(kept[c]))
93
+ }
94
+ return out
95
+ }
96
+
97
+ // The serial both monitors spell: AFC hyphenates it in its uuid, GPhoto2 buries it in the host name.
98
+ function carriesOneOf(uri, serials) {
99
+ var up = String(uri).toUpperCase()
100
+ for (var i = 0; i < serials.length; i++)
101
+ if (up.indexOf(serials[i]) >= 0)
102
+ return true
103
+ return false
104
+ }
105
+
106
+ // path is "" until "gio info" resolves the FUSE folder, and size is null because ui/SidebarRow.qml reads a device row's detail off "size !== null".
107
+ function entry(v) {
108
+ // AFC names its volume for the documents share it advertises, and the row is the phone, not that.
109
+ var label = v.monitor === "Afc" ? v.label.replace(/^Documents on /, "") : v.label
110
+ return { path: "", label: label, group: "device", kind: "phone", uri: v.uri, size: null,
111
+ mounted: v.mounted, glyph: v.monitor === "GPhoto2" ? "camera" : "smartphone" }
112
+ }