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.
- package/LICENSE +22 -0
- package/README.md +511 -2
- package/THIRD-PARTY-NOTICES.md +29 -0
- package/demos/App.java +5 -0
- package/demos/Dockerfile +4 -0
- package/demos/Main.kt +3 -0
- package/demos/Program.cs +1 -0
- package/demos/README.md +12 -0
- package/demos/app.dart +3 -0
- package/demos/app.deb +0 -0
- package/demos/app.js +1 -0
- package/demos/backup.tar +0 -0
- package/demos/budget.xlsx +0 -0
- package/demos/build.sh +2 -0
- package/demos/bundle.zip +0 -0
- package/demos/clip.mp4 +0 -0
- package/demos/data.sqlite +0 -0
- package/demos/disk.iso +0 -0
- package/demos/engine.cpp +5 -0
- package/demos/firmware.bin +0 -0
- package/demos/flake.nix +4 -0
- package/demos/folder/inside.txt +1 -0
- package/demos/font.ttf +0 -0
- package/demos/index.html +5 -0
- package/demos/index.php +2 -0
- package/demos/init.lua +1 -0
- package/demos/letter.docx +0 -0
- package/demos/lib.rs +3 -0
- package/demos/main.c +6 -0
- package/demos/main.h +3 -0
- package/demos/math-and-diagrams.md +200 -0
- package/demos/notes.tar.gz +0 -0
- package/demos/notes.txt +3 -0
- package/demos/photo.png +0 -0
- package/demos/program +2 -0
- package/demos/report.pdf +32 -0
- package/demos/script.py +1 -0
- package/demos/server.go +7 -0
- package/demos/settings.json +4 -0
- package/demos/slides.pptx +0 -0
- package/demos/song.mp3 +0 -0
- package/demos/style.css +4 -0
- package/demos/tool.rb +1 -0
- package/demos/types.ts +2 -0
- package/docs/pele.md +23 -0
- package/docs/protocol.md +3420 -0
- package/docs/temml.md +63 -0
- package/index.js +107 -0
- package/package.json +34 -4
- package/src/archive.js +407 -0
- package/src/archiveops.js +728 -0
- package/src/backend.js +369 -0
- package/src/bulkrename.js +95 -0
- package/src/clip-host.js +76 -0
- package/src/clip.js +462 -0
- package/src/code.js +168 -0
- package/src/compare.js +74 -0
- package/src/create.js +128 -0
- package/src/dirsize.js +147 -0
- package/src/figure-svg.js +530 -0
- package/src/figure-thread.js +23 -0
- package/src/figure.js +132 -0
- package/src/find.js +55 -0
- package/src/flea.js +39 -0
- package/src/fsinfo.js +39 -0
- package/src/highlight.js +141 -0
- package/src/html.js +52 -0
- package/src/kinds.js +89 -0
- package/src/listing.js +130 -0
- package/src/markdown-html.js +317 -0
- package/src/mathml.js +186 -0
- package/src/menu.js +180 -0
- package/src/meta.js +135 -0
- package/src/office.js +810 -0
- package/src/order.js +100 -0
- package/src/peek.js +49 -0
- package/src/pele-wrapper.js +695 -0
- package/src/permissions.js +180 -0
- package/src/places.js +313 -0
- package/src/preview-language.js +10 -0
- package/src/terminal-server.js +95 -0
- package/src/terminal-shell.js +26 -0
- package/src/transfer.js +669 -0
- package/src/trash-record.js +41 -0
- package/src/trash.js +210 -0
- package/src/trashbrowse.js +327 -0
- package/src/uistate.js +393 -0
- package/src/uistore.js +168 -0
- package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
- package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
- package/src/vendor/LICENSES/MIT-katex.txt +21 -0
- package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
- package/src/vendor/LICENSES/MIT-pele.txt +40 -0
- package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
- package/src/vendor/LICENSES/MIT-temml.txt +21 -0
- package/src/vendor/pele.js +145 -0
- package/src/vendor/temml.mjs +14708 -0
- package/src/watch.js +98 -0
- package/src/wayland/client.js +109 -0
- package/src/wayland/protocol.js +55 -0
- package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
- package/src/wayland/protocols/wayland.xml +3248 -0
- package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
- package/src/wayland/resource-io.js +6 -0
- package/src/wayland/socket-discovery.js +26 -0
- package/src/wayland/transport-posix.js +120 -0
- package/src/wayland/transport-win32.js +11 -0
- package/src/wayland/transport.js +3 -0
- package/src/wayland/win32-resources.js +80 -0
- package/src/wayland/win32-wire.js +62 -0
- package/test/archive.test.js +80 -0
- package/test/bench-markdown-scroll.mjs +190 -0
- package/test/clip-host.test.js +113 -0
- package/test/code-preview.test.js +35 -0
- package/test/compare-ui-paths.test.js +24 -0
- package/test/compare.test.js +54 -0
- package/test/delete-trash.test.js +67 -0
- package/test/dirsize.test.js +138 -0
- package/test/file-ops.test.js +307 -0
- package/test/file-url.test.js +24 -0
- package/test/find-contents.test.js +161 -0
- package/test/find-input.test.js +66 -0
- package/test/fixtures/pele-parity.json +1006 -0
- package/test/highlight-launcher.test.js +35 -0
- package/test/html-preview.test.js +22 -0
- package/test/input.test.js +15 -0
- package/test/keymap.test.js +26 -0
- package/test/language-icons.test.js +36 -0
- package/test/launcher.test.js +51 -0
- package/test/manual-archive-here-ui.mjs +47 -0
- package/test/manual-code-scroll.mjs +28 -0
- package/test/manual-column-scroll.mjs +24 -0
- package/test/manual-compare-ui.mjs +20 -0
- package/test/manual-find-status.mjs +37 -0
- package/test/manual-folder-focus.mjs +63 -0
- package/test/manual-html-security.mjs +23 -0
- package/test/manual-html-ui.mjs +30 -0
- package/test/manual-syntax-preview.mjs +26 -0
- package/test/manual-terminal-lifecycle.mjs +42 -0
- package/test/manual-terminal-navigation.mjs +48 -0
- package/test/manual-terminal-ui.mjs +47 -0
- package/test/manual-trash-ui.mjs +86 -0
- package/test/markdown-diagrams.test.js +93 -0
- package/test/markdown-html.test.js +105 -0
- package/test/mathml.test.js +92 -0
- package/test/menu-transfer.test.js +57 -0
- package/test/office-preview.test.js +154 -0
- package/test/pele-parity.test.js +28 -0
- package/test/places.test.js +134 -0
- package/test/preview-keys.test.js +60 -0
- package/test/preview-language.test.js +22 -0
- package/test/status-filename.test.js +20 -0
- package/test/transfer-replace-merge.test.js +187 -0
- package/test/trash-address.test.js +23 -0
- package/test/trash-view.test.js +37 -0
- package/test/trashbrowse.test.js +135 -0
- package/ui/AboutFacts.qml +78 -0
- package/ui/ArchiveCards.qml +95 -0
- package/ui/ArchiveCompressDialog.qml +378 -0
- package/ui/ArchiveHereConfirm.qml +127 -0
- package/ui/ArchivePasswordDialog.qml +257 -0
- package/ui/Backend.qml +775 -0
- package/ui/BulkOldNames.qml +47 -0
- package/ui/BulkRenameDialog.qml +380 -0
- package/ui/CardFrame.qml +67 -0
- package/ui/CardScroll.qml +79 -0
- package/ui/CheckBox.qml +73 -0
- package/ui/ChromeBar.qml +360 -0
- package/ui/ChromeButton.qml +131 -0
- package/ui/ChromeModes.qml +43 -0
- package/ui/CodeLines.qml +256 -0
- package/ui/CodeRefusal.qml +52 -0
- package/ui/CollideConfirm.qml +147 -0
- package/ui/CollideHost.qml +167 -0
- package/ui/ColumnPane.qml +525 -0
- package/ui/ColumnRow.qml +380 -0
- package/ui/ColumnsArea.qml +594 -0
- package/ui/ColumnsLanding.qml +147 -0
- package/ui/CompareView.qml +321 -0
- package/ui/ContextMenu.qml +743 -0
- package/ui/ConvertDialog.qml +456 -0
- package/ui/CountedWorker.qml +7 -0
- package/ui/Crumb.qml +64 -0
- package/ui/CrumbDrop.qml +25 -0
- package/ui/CsvTable.qml +394 -0
- package/ui/CursorMark.qml +41 -0
- package/ui/DefaultClaim.qml +130 -0
- package/ui/DeviceLock.qml +47 -0
- package/ui/DeviceMounts.qml +540 -0
- package/ui/DialogButton.qml +150 -0
- package/ui/DialogField.qml +147 -0
- package/ui/DialogHeader.qml +31 -0
- package/ui/DialogScrim.qml +20 -0
- package/ui/DialogTitle.qml +37 -0
- package/ui/Divider.qml +9 -0
- package/ui/DropInto.qml +126 -0
- package/ui/DropTarget.qml +35 -0
- package/ui/DropboxMark.qml +60 -0
- package/ui/EmptyState.qml +150 -0
- package/ui/EscapeHint.qml +11 -0
- package/ui/FactsTable.qml +54 -0
- package/ui/FastScrollHandler.qml +476 -0
- package/ui/FatNamesConfirm.qml +157 -0
- package/ui/Favourites.qml +143 -0
- package/ui/FigureService.qml +120 -0
- package/ui/FileDrag.qml +267 -0
- package/ui/FilterStrip.qml +133 -0
- package/ui/FitMetrics.qml +22 -0
- package/ui/FlashDriveRows.qml +137 -0
- package/ui/FlashHost.qml +72 -0
- package/ui/FlashImageRow.qml +104 -0
- package/ui/FlashRunner.qml +119 -0
- package/ui/FlashStepRow.qml +172 -0
- package/ui/FlashStrip.qml +38 -0
- package/ui/FlashView.qml +397 -0
- package/ui/FleaMark.qml +88 -0
- package/ui/FlyoutFade.qml +23 -0
- package/ui/FontPane.qml +170 -0
- package/ui/Glass.qml +114 -0
- package/ui/GlassTagger.qml +59 -0
- package/ui/Glyph.qml +64 -0
- package/ui/GlyphSolid.qml +22 -0
- package/ui/GridArea.qml +664 -0
- package/ui/GridTile.qml +325 -0
- package/ui/GvfsBridge.qml +164 -0
- package/ui/Header.qml +433 -0
- package/ui/HoverTimers.qml +39 -0
- package/ui/HtmlClip.qml +30 -0
- package/ui/HtmlPane.qml +63 -0
- package/ui/HyprlandMark.qml +45 -0
- package/ui/Ipc.qml +1005 -0
- package/ui/IpcControls.qml +7 -0
- package/ui/IpcMenuState.qml +48 -0
- package/ui/IpcPreviewState.qml +185 -0
- package/ui/JumpPath.qml +51 -0
- package/ui/KeymapSheet.qml +561 -0
- package/ui/KeymapSheetResult.qml +89 -0
- package/ui/KindStack.qml +40 -0
- package/ui/LangMark.qml +51 -0
- package/ui/LangMarkLoader.qml +26 -0
- package/ui/List.qml +480 -0
- package/ui/LoadingState.qml +54 -0
- package/ui/LocalSend.qml +56 -0
- package/ui/LocalSendMark.qml +73 -0
- package/ui/MarkdownBlockView.qml +311 -0
- package/ui/MarkdownBlocks.qml +35 -0
- package/ui/MarkdownFigure.qml +254 -0
- package/ui/MarkdownImages.qml +72 -0
- package/ui/MarkdownList.qml +81 -0
- package/ui/MarkdownMathsText.qml +97 -0
- package/ui/MarkdownPane.qml +83 -0
- package/ui/MarkdownPictures.qml +41 -0
- package/ui/MarkdownQuote.qml +52 -0
- package/ui/MarkdownRemote.qml +82 -0
- package/ui/MarkdownSource.qml +155 -0
- package/ui/MarkdownTable.qml +222 -0
- package/ui/MarkdownTableScroll.qml +64 -0
- package/ui/MarkdownTableWheel.qml +126 -0
- package/ui/MarkdownText.qml +87 -0
- package/ui/MarkdownWorker.js +132 -0
- package/ui/MatchText.qml +99 -0
- package/ui/MediaSession.qml +38 -0
- package/ui/MediaSound.qml +26 -0
- package/ui/MediaStrip.qml +191 -0
- package/ui/MediaTrack.qml +36 -0
- package/ui/MediaVolume.qml +54 -0
- package/ui/MenuActionDialog.qml +396 -0
- package/ui/MenuEdgeFade.qml +24 -0
- package/ui/MenuHost.qml +227 -0
- package/ui/MenuRow.qml +351 -0
- package/ui/MountListing.qml +100 -0
- package/ui/NetworkDialog.qml +435 -0
- package/ui/NetworkForm.qml +426 -0
- package/ui/NetworkMounts.qml +913 -0
- package/ui/NetworkPlaces.qml +127 -0
- package/ui/OpenWithDialog.qml +613 -0
- package/ui/Opener.qml +95 -0
- package/ui/Pane.qml +1320 -0
- package/ui/PaneMenuActions.qml +527 -0
- package/ui/PanePath.qml +90 -0
- package/ui/PaneRail.qml +268 -0
- package/ui/PaneStates.qml +88 -0
- package/ui/PaneSwap.qml +339 -0
- package/ui/PaneWire.qml +601 -0
- package/ui/ParkedWait.qml +9 -0
- package/ui/PathEntry.qml +186 -0
- package/ui/PathJump.qml +400 -0
- package/ui/PdfViewer.qml +297 -0
- package/ui/PermissionsDialog.qml +658 -0
- package/ui/PermissionsHeadings.qml +31 -0
- package/ui/PhoneMounts.qml +67 -0
- package/ui/PickerAnswers.qml +75 -0
- package/ui/PickerBox.qml +26 -0
- package/ui/PickerChrome.qml +295 -0
- package/ui/PickerFilter.qml +97 -0
- package/ui/PickerGrid.qml +308 -0
- package/ui/PickerHeader.qml +28 -0
- package/ui/PickerLifecycle.qml +27 -0
- package/ui/PickerList.qml +218 -0
- package/ui/PickerListing.qml +149 -0
- package/ui/PickerPathBar.qml +127 -0
- package/ui/PickerPlaces.qml +189 -0
- package/ui/PickerRecent.qml +92 -0
- package/ui/PickerSave.qml +101 -0
- package/ui/PickerSelection.qml +155 -0
- package/ui/PickerWindow.qml +846 -0
- package/ui/PowerSectorsReader.qml +10 -0
- package/ui/PrebuildDriver.qml +146 -0
- package/ui/Preview.qml +1005 -0
- package/ui/PreviewAnimation.qml +24 -0
- package/ui/PreviewArchive.qml +91 -0
- package/ui/PreviewColumn.qml +553 -0
- package/ui/PreviewEdge.qml +63 -0
- package/ui/PreviewFacts.qml +30 -0
- package/ui/PreviewFactsLine.qml +34 -0
- package/ui/PreviewHtml.qml +233 -0
- package/ui/PreviewImage.qml +253 -0
- package/ui/PreviewImagePan.qml +13 -0
- package/ui/PreviewImageSharp.qml +32 -0
- package/ui/PreviewLines.qml +106 -0
- package/ui/PreviewMarkdown.qml +706 -0
- package/ui/PreviewMedia.qml +250 -0
- package/ui/PreviewPdf.qml +482 -0
- package/ui/PreviewScroll.qml +201 -0
- package/ui/PreviewSwap.qml +335 -0
- package/ui/PreviewText.qml +149 -0
- package/ui/ProtocolChip.qml +18 -0
- package/ui/QuickLookArchive.qml +41 -0
- package/ui/QuickLookBar.qml +103 -0
- package/ui/QuickLookPrepare.qml +355 -0
- package/ui/QuickLookSwap.qml +10 -0
- package/ui/QuickLookWarm.qml +65 -0
- package/ui/RailDrop.qml +155 -0
- package/ui/RailHeading.qml +19 -0
- package/ui/RailPlaces.qml +38 -0
- package/ui/RailTrashRow.qml +67 -0
- package/ui/RenameField.qml +353 -0
- package/ui/RendererRetry.qml +11 -0
- package/ui/ResizeHandle.qml +52 -0
- package/ui/Reveal.qml +54 -0
- package/ui/Row.qml +476 -0
- package/ui/RowDrag.qml +213 -0
- package/ui/RowDropTarget.qml +27 -0
- package/ui/RunCard.qml +286 -0
- package/ui/Scripts.qml +65 -0
- package/ui/SearchStrip.qml +232 -0
- package/ui/SelectionBand.qml +140 -0
- package/ui/SelectionPreview.qml +370 -0
- package/ui/SettingsFavourite.qml +122 -0
- package/ui/SettingsGroup.qml +96 -0
- package/ui/SettingsHint.qml +12 -0
- package/ui/SettingsMetrics.qml +29 -0
- package/ui/SettingsPane.qml +178 -0
- package/ui/SettingsPanel.qml +547 -0
- package/ui/SettingsRail.qml +128 -0
- package/ui/SettingsRow.qml +409 -0
- package/ui/SettingsRuler.qml +85 -0
- package/ui/SettingsSegment.qml +85 -0
- package/ui/ShareBrowser.qml +113 -0
- package/ui/ShareLink.qml +67 -0
- package/ui/ShelfPins.qml +101 -0
- package/ui/Sidebar.qml +728 -0
- package/ui/SidebarRow.qml +358 -0
- package/ui/Spinner.qml +60 -0
- package/ui/StableModel.qml +89 -0
- package/ui/StateMessage.qml +84 -0
- package/ui/StatusBar.qml +512 -0
- package/ui/StatusFileName.qml +27 -0
- package/ui/TabBar.qml +673 -0
- package/ui/TabDragGeometry.qml +60 -0
- package/ui/TagMenuMark.qml +52 -0
- package/ui/TagRailSection.qml +53 -0
- package/ui/TagSlot.qml +40 -0
- package/ui/TagState.qml +150 -0
- package/ui/TagSwatch.qml +28 -0
- package/ui/Taildrop.qml +86 -0
- package/ui/TailscaleMark.qml +50 -0
- package/ui/TerminalService.qml +69 -0
- package/ui/TerminalTab.qml +48 -0
- package/ui/TerminalTabs.qml +27 -0
- package/ui/Theme.qml +460 -0
- package/ui/ThemeColors.qml +43 -0
- package/ui/TransferCard.qml +239 -0
- package/ui/TrashConfirm.qml +129 -0
- package/ui/TrashDropHandler.qml +11 -0
- package/ui/TrashHost.qml +202 -0
- package/ui/TrashMonitor.qml +30 -0
- package/ui/TrashToolbar.qml +19 -0
- package/ui/TrashView.qml +492 -0
- package/ui/UnlockDialog.qml +450 -0
- package/ui/UnlockHost.qml +206 -0
- package/ui/UpdateCheck.qml +97 -0
- package/ui/UserDirs.qml +25 -0
- package/ui/ViewState.qml +565 -0
- package/ui/ViewportScrollBar.qml +195 -0
- package/ui/ViewportScrollBars.qml +25 -0
- package/ui/WebPreview.qml +33 -0
- package/ui/WebPreviewView.qml +26 -0
- package/ui/WindowBody.qml +829 -0
- package/ui/WindowDrag.qml +35 -0
- package/ui/boot/Commons/AudioNodes.qml +50 -0
- package/ui/boot/Commons/AudioNodesModel.js +96 -0
- package/ui/boot/Commons/Border.qml +243 -0
- package/ui/boot/Commons/BorderGeometry.js +373 -0
- package/ui/boot/Commons/Color.qml +258 -0
- package/ui/boot/Commons/I18n.qml +118 -0
- package/ui/boot/Commons/InputDevices.qml +112 -0
- package/ui/boot/Commons/InputMethodState.qml +38 -0
- package/ui/boot/Commons/IpcRegistry.qml +77 -0
- package/ui/boot/Commons/Numbers.js +19 -0
- package/ui/boot/Commons/Plural.js +91 -0
- package/ui/boot/Commons/ShellIpc.qml +12 -0
- package/ui/boot/Commons/Style.qml +561 -0
- package/ui/boot/Commons/Translations.qml +100 -0
- package/ui/boot/Commons/UntypedInput.qml +152 -0
- package/ui/boot/Commons/Util.qml +188 -0
- package/ui/boot/Commons/qmldir +13 -0
- package/ui/boot/LICENSE-omarchy +20 -0
- package/ui/boot/Ui/BackgroundMedia.qml +49 -0
- package/ui/boot/Ui/BarIconButton.qml +106 -0
- package/ui/boot/Ui/BarIconContent.qml +38 -0
- package/ui/boot/Ui/BarIndicator.qml +53 -0
- package/ui/boot/Ui/BarWidget.qml +50 -0
- package/ui/boot/Ui/BorderOverlay.qml +54 -0
- package/ui/boot/Ui/BorderSurface.qml +40 -0
- package/ui/boot/Ui/Button.qml +224 -0
- package/ui/boot/Ui/ButtonGroup.qml +131 -0
- package/ui/boot/Ui/ConfirmDialog.qml +134 -0
- package/ui/boot/Ui/CursorSurface.qml +42 -0
- package/ui/boot/Ui/Dropdown.qml +245 -0
- package/ui/boot/Ui/FilterInput.qml +26 -0
- package/ui/boot/Ui/IconContentBounds.qml +141 -0
- package/ui/boot/Ui/KeyboardPanel.qml +453 -0
- package/ui/boot/Ui/MultiSelect.qml +624 -0
- package/ui/boot/Ui/NumberField.qml +86 -0
- package/ui/boot/Ui/OpticalGlyph.qml +68 -0
- package/ui/boot/Ui/OverlayWindow.qml +102 -0
- package/ui/boot/Ui/Panel.qml +60 -0
- package/ui/boot/Ui/PanelActionButton.qml +105 -0
- package/ui/boot/Ui/PanelController.qml +16 -0
- package/ui/boot/Ui/PanelHero.qml +112 -0
- package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
- package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
- package/ui/boot/Ui/PanelSeparator.qml +19 -0
- package/ui/boot/Ui/PanelSlider.qml +156 -0
- package/ui/boot/Ui/PanelToolTip.qml +50 -0
- package/ui/boot/Ui/PluginBarApi.qml +89 -0
- package/ui/boot/Ui/PointerMoveGate.qml +54 -0
- package/ui/boot/Ui/PopupCard.qml +181 -0
- package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
- package/ui/boot/Ui/SearchableDropdown.qml +354 -0
- package/ui/boot/Ui/SizedImage.qml +125 -0
- package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
- package/ui/boot/Ui/TextField.qml +58 -0
- package/ui/boot/Ui/Toggle.qml +118 -0
- package/ui/boot/Ui/ToggleSwitch.qml +115 -0
- package/ui/boot/Ui/WidgetButton.qml +172 -0
- package/ui/boot/Ui/qmldir +41 -0
- package/ui/boot/fleatab.qml +50 -0
- package/ui/boot/picker.qml +39 -0
- package/ui/boot/shell.qml +131 -0
- package/ui/boot/tabtearoff.qml +51 -0
- package/ui/js/Anchor.js +379 -0
- package/ui/js/AnchorHold.js +162 -0
- package/ui/js/Archive.js +157 -0
- package/ui/js/ArchiveCard.js +44 -0
- package/ui/js/ArchiveOps.js +44 -0
- package/ui/js/BackReturn.js +118 -0
- package/ui/js/BulkRename.js +100 -0
- package/ui/js/Buttons.js +54 -0
- package/ui/js/ChromeKeys.js +116 -0
- package/ui/js/ClipMarks.js +73 -0
- package/ui/js/ClipSelection.js +67 -0
- package/ui/js/Clipboard.js +179 -0
- package/ui/js/Cloud.js +97 -0
- package/ui/js/CodeRuns.js +72 -0
- package/ui/js/Collide.js +212 -0
- package/ui/js/ColumnFit.js +28 -0
- package/ui/js/ColumnMenu.js +111 -0
- package/ui/js/Columns.js +298 -0
- package/ui/js/ColumnsCarry.js +103 -0
- package/ui/js/Compare.js +74 -0
- package/ui/js/Contrast.js +157 -0
- package/ui/js/ControlReader.js +42 -0
- package/ui/js/Convert.js +52 -0
- package/ui/js/CopyAs.js +52 -0
- package/ui/js/Crumbs.js +178 -0
- package/ui/js/Csv.js +231 -0
- package/ui/js/Density.js +44 -0
- package/ui/js/Devices.js +440 -0
- package/ui/js/DirSizes.js +180 -0
- package/ui/js/Drag.js +360 -0
- package/ui/js/DragOut.js +166 -0
- package/ui/js/DropDev.js +52 -0
- package/ui/js/Dropbox.js +41 -0
- package/ui/js/Eject.js +217 -0
- package/ui/js/Errors.js +188 -0
- package/ui/js/ExtThumbs.js +115 -0
- package/ui/js/Facts.js +402 -0
- package/ui/js/FigureDate.js +14 -0
- package/ui/js/Filter.js +285 -0
- package/ui/js/Find.js +46 -0
- package/ui/js/Flash.js +184 -0
- package/ui/js/FlashCard.js +79 -0
- package/ui/js/FlashEndings.js +125 -0
- package/ui/js/FlashPane.js +116 -0
- package/ui/js/FleaBin.js +12 -0
- package/ui/js/Focus.js +504 -0
- package/ui/js/FolderSorts.js +108 -0
- package/ui/js/FolderViews.js +44 -0
- package/ui/js/Format.js +314 -0
- package/ui/js/Fuzzy.js +141 -0
- package/ui/js/GlassTag.js +72 -0
- package/ui/js/Grid.js +33 -0
- package/ui/js/GridGeometry.js +90 -0
- package/ui/js/GridNames.js +153 -0
- package/ui/js/GvfsBridge.js +200 -0
- package/ui/js/HostClipboard.js +29 -0
- package/ui/js/Icons.js +218 -0
- package/ui/js/Input.js +50 -0
- package/ui/js/Jump.js +200 -0
- package/ui/js/KeyName.js +64 -0
- package/ui/js/Keymap.js +401 -0
- package/ui/js/Kinds.js +164 -0
- package/ui/js/LICENSES/ISC-lucide.txt +43 -0
- package/ui/js/LangMarkPaths.js +47 -0
- package/ui/js/LangMarks.js +120 -0
- package/ui/js/Lists.js +11 -0
- package/ui/js/LocalSend.js +39 -0
- package/ui/js/LockedMenu.js +63 -0
- package/ui/js/MakeDefault.js +186 -0
- package/ui/js/MarkBox.js +7 -0
- package/ui/js/Markdown.js +119 -0
- package/ui/js/MarkdownLists.js +86 -0
- package/ui/js/MarkdownMaths.js +58 -0
- package/ui/js/MarkdownPictures.js +32 -0
- package/ui/js/MarkdownPrepared.js +139 -0
- package/ui/js/MarkdownTableFit.js +34 -0
- package/ui/js/Marks.js +148 -0
- package/ui/js/Marquee.js +96 -0
- package/ui/js/Match.js +93 -0
- package/ui/js/Material.js +109 -0
- package/ui/js/MdBlocks.js +437 -0
- package/ui/js/MdBreak.js +71 -0
- package/ui/js/MdChunks.js +70 -0
- package/ui/js/MdContainer.js +137 -0
- package/ui/js/MdDocument.js +337 -0
- package/ui/js/MdEmph.js +97 -0
- package/ui/js/MdEntity.js +108 -0
- package/ui/js/MdEntityTable.js +223 -0
- package/ui/js/MdEscape.js +59 -0
- package/ui/js/MdFront.js +37 -0
- package/ui/js/MdHold.js +32 -0
- package/ui/js/MdHtml.js +307 -0
- package/ui/js/MdHtmlBlock.js +290 -0
- package/ui/js/MdHtmlHeading.js +53 -0
- package/ui/js/MdHtmlImage.js +179 -0
- package/ui/js/MdInline.js +154 -0
- package/ui/js/MdItems.js +212 -0
- package/ui/js/MdLeaf.js +309 -0
- package/ui/js/MdLink.js +196 -0
- package/ui/js/MdMath.js +67 -0
- package/ui/js/MdParagraphs.js +138 -0
- package/ui/js/MdRefs.js +232 -0
- package/ui/js/MdResolve.js +226 -0
- package/ui/js/MdRun.js +300 -0
- package/ui/js/MdUrl.js +226 -0
- package/ui/js/Menu.js +514 -0
- package/ui/js/MenuContext.js +85 -0
- package/ui/js/MenuFit.js +13 -0
- package/ui/js/MenuRefresh.js +74 -0
- package/ui/js/MenuWheel.js +35 -0
- package/ui/js/Messages.js +186 -0
- package/ui/js/Motion.js +16 -0
- package/ui/js/Mounts.js +320 -0
- package/ui/js/MouseNav.js +13 -0
- package/ui/js/Nav.js +329 -0
- package/ui/js/NetFs.js +26 -0
- package/ui/js/OpenWith.js +83 -0
- package/ui/js/Ops.js +585 -0
- package/ui/js/Palette.js +33 -0
- package/ui/js/PathBar.js +260 -0
- package/ui/js/Permissions.js +179 -0
- package/ui/js/Phones.js +112 -0
- package/ui/js/Picker.js +353 -0
- package/ui/js/PlaceMenu.js +96 -0
- package/ui/js/Places.js +265 -0
- package/ui/js/PreviewAsk.js +21 -0
- package/ui/js/PreviewKeys.js +155 -0
- package/ui/js/PreviewSettle.js +29 -0
- package/ui/js/PreviewSwap.js +101 -0
- package/ui/js/PropertyFacts.js +144 -0
- package/ui/js/Protocols.js +296 -0
- package/ui/js/QuickLookKinds.js +27 -0
- package/ui/js/QuickLookRect.js +24 -0
- package/ui/js/RailDrop.js +51 -0
- package/ui/js/RailKeys.js +87 -0
- package/ui/js/RailMenu.js +133 -0
- package/ui/js/Recent.js +135 -0
- package/ui/js/RecentMode.js +156 -0
- package/ui/js/Reload.js +60 -0
- package/ui/js/Renderer.js +9 -0
- package/ui/js/RunFile.js +143 -0
- package/ui/js/Scripts.js +28 -0
- package/ui/js/Scroll.js +304 -0
- package/ui/js/ScrollOff.js +69 -0
- package/ui/js/Search.js +478 -0
- package/ui/js/Selection.js +97 -0
- package/ui/js/Settings.js +476 -0
- package/ui/js/SettingsAbout.js +97 -0
- package/ui/js/SettingsShelf.js +52 -0
- package/ui/js/SheetKeys.js +42 -0
- package/ui/js/SheetQuery.js +281 -0
- package/ui/js/ShelfPile.js +27 -0
- package/ui/js/SlowClick.js +53 -0
- package/ui/js/SlowOp.js +6 -0
- package/ui/js/Sort.js +134 -0
- package/ui/js/Startup.js +63 -0
- package/ui/js/StateWords.js +17 -0
- package/ui/js/Status.js +238 -0
- package/ui/js/Swap.js +166 -0
- package/ui/js/TabMove.js +39 -0
- package/ui/js/Tabs.js +751 -0
- package/ui/js/Tags.js +134 -0
- package/ui/js/Taildrop.js +128 -0
- package/ui/js/Tap.js +180 -0
- package/ui/js/TextSize.js +107 -0
- package/ui/js/Thumbs.js +158 -0
- package/ui/js/Transfer.js +172 -0
- package/ui/js/Trash.js +84 -0
- package/ui/js/TrashDates.js +47 -0
- package/ui/js/TrashDrop.js +64 -0
- package/ui/js/UiState.js +438 -0
- package/ui/js/Unlock.js +152 -0
- package/ui/js/Update.js +130 -0
- package/ui/js/UserDirs.js +56 -0
- package/ui/qmldir +230 -0
package/ui/js/PathBar.js
ADDED
|
@@ -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
|
+
}
|
package/ui/js/Phones.js
ADDED
|
@@ -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
|
+
}
|