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/src/backend.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// flea-qml's backend: the requests ui/Backend.qml writes to `flea --backend`, one JSON object per
|
|
2
|
+
// line on stdin, answered one per line on stdout (docs/protocol.md). Upstream: src/backend/run.rs.
|
|
3
|
+
// So far: list, window, sort, fsinfo, peek, meta, code, the four clip requests,
|
|
4
|
+
// paths, menuaction (snapshot, activate, validate, close), collisions, mergecollisions, transfer, transfercancel, places and quit,
|
|
5
|
+
// mkdir, newfile, duplicate, bulkrename, permissions, permissionsBatch, formats and archive, and `changed` lines for watched folders. Any other request is noted on stderr and
|
|
6
|
+
// left unanswered, which ui/ already survives for an operation that never reports back.
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
8
|
+
import { fsinfo } from "./fsinfo.js";
|
|
9
|
+
import { code } from "./code.js";
|
|
10
|
+
import { compare } from "./compare.js";
|
|
11
|
+
import { html } from "./html.js";
|
|
12
|
+
import { markdownHtml } from "./markdown-html.js";
|
|
13
|
+
import { renderDiagram } from "./figure.js";
|
|
14
|
+
import { clipClear, clipGet, clipSet, clipWatch } from "./clip.js";
|
|
15
|
+
import { clipHostReply } from "./clip-host.js";
|
|
16
|
+
import { dirsize, dirsizeCancel, dirsizeInit, forgetSizes } from "./dirsize.js";
|
|
17
|
+
import { collisions, mergeCollisions, sources, transfer, transferCancel } from "./transfer.js";
|
|
18
|
+
import { menuaction, capturedPaths } from "./menu.js";
|
|
19
|
+
import {renameWithTrashRecord} from './trash-record.js';
|
|
20
|
+
import { trash } from "./trash.js";
|
|
21
|
+
import { duplicate, mkdir, newfile } from "./create.js";
|
|
22
|
+
import { bulkrename } from "./bulkrename.js";
|
|
23
|
+
import { permissions, permissionsBatch } from "./permissions.js";
|
|
24
|
+
import { archive, archiveCancel, formats } from "./archiveops.js";
|
|
25
|
+
import { trashbrowse, readTrash, watchTrashCount } from "./trashbrowse.js";
|
|
26
|
+
import { findCommand, grepCommand } from "./find.js";
|
|
27
|
+
import { places } from "./places.js";
|
|
28
|
+
import { listDone, listFailed, onChanged, watchColumn, watchList } from "./watch.js";
|
|
29
|
+
import { meta } from "./meta.js";
|
|
30
|
+
import { peek } from "./peek.js";
|
|
31
|
+
import { Listing, directoryFacts } from "./listing.js";
|
|
32
|
+
import { SORT_KEYS } from "./order.js";
|
|
33
|
+
import { lstatSync } from "node:fs";
|
|
34
|
+
import { basename, dirname, join, resolve, isAbsolute } from "node:path";
|
|
35
|
+
|
|
36
|
+
// FLEA_BACKEND_LOG=/some/file appends every request and answer there, for debugging.
|
|
37
|
+
const trace = process.env.FLEA_BACKEND_LOG ? (await import("node:fs")).appendFileSync.bind(null, process.env.FLEA_BACKEND_LOG) : null;
|
|
38
|
+
const send = (object) => {
|
|
39
|
+
// A changed folder's size, and every size above it, is no longer known (src/dirsize.js).
|
|
40
|
+
if (object.t === "changed") forgetSizes(object.path);
|
|
41
|
+
const line = `${JSON.stringify(object)}\n`;
|
|
42
|
+
trace?.(`< ${line}`);
|
|
43
|
+
process.stdout.write(line);
|
|
44
|
+
};
|
|
45
|
+
const error = (where, path, msg, extra = {}) => send({ t: "error", where, path, msg, ...extra });
|
|
46
|
+
|
|
47
|
+
let listing = null,
|
|
48
|
+
// The numbering a row index is read in; the first listing is 1 (docs/protocol.md "listing").
|
|
49
|
+
stamp = 0,
|
|
50
|
+
order = { by: "name", desc: false, foldersFirst: true, groupByKind: false, hiddenLast: false };
|
|
51
|
+
|
|
52
|
+
const orderFrom = (request, previous) => ({
|
|
53
|
+
by: request.by ?? previous.by,
|
|
54
|
+
desc: request.desc ?? previous.desc,
|
|
55
|
+
foldersFirst: request.foldersFirst ?? previous.foldersFirst,
|
|
56
|
+
groupByKind: request.groupByKind ?? previous.groupByKind,
|
|
57
|
+
hiddenLast: request.hiddenLast ?? previous.hiddenLast,
|
|
58
|
+
});
|
|
59
|
+
const listed = (read, sorted, extra = {}) =>
|
|
60
|
+
send({ t: "listed", n: listing.rows.length, read, sort: sorted, ...directoryFacts(listing.path), path: listing.path, ...extra });
|
|
61
|
+
const rows = (start, count, extra = {}) => send({ t: "rows", ...listing.window(start, count), listing: stamp, ...extra });
|
|
62
|
+
// A request's `gen` rides back on its listed and rows lines, so a Find answer lands on its own query.
|
|
63
|
+
const genOf = (request) => (Number.isSafeInteger(request.gen) && request.gen > 0 ? { gen: request.gen } : {});
|
|
64
|
+
|
|
65
|
+
function list(request) {
|
|
66
|
+
const path = request.path ?? "",
|
|
67
|
+
first = request.first ?? 0;
|
|
68
|
+
// A list with no ordering fields is name ascending, folders first, without kind groups.
|
|
69
|
+
const next = orderFrom(request, { by: "name", desc: false, foldersFirst: true, groupByKind: false, hiddenLast: false });
|
|
70
|
+
if (!SORT_KEYS.has(next.by)) return error("sort", next.by, `no order is named ${next.by}`);
|
|
71
|
+
let fresh;
|
|
72
|
+
const started = performance.now(),
|
|
73
|
+
armed = watchList(path);
|
|
74
|
+
try {
|
|
75
|
+
fresh = Listing.scan(path, request.hidden === true);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
listFailed(armed);
|
|
78
|
+
// A failed list leaves the previous listing in place; `mode` only when the path could be stat'd.
|
|
79
|
+
let mode;
|
|
80
|
+
try {
|
|
81
|
+
mode = lstatSync(path || ".").mode;
|
|
82
|
+
} catch {}
|
|
83
|
+
return error("scan", path, e.code === "EACCES" ? "permission denied" : e.message, mode === undefined ? {} : { mode });
|
|
84
|
+
}
|
|
85
|
+
const scanned = performance.now() - started;
|
|
86
|
+
const [read, sorted] = fresh.sort(next);
|
|
87
|
+
let changed;
|
|
88
|
+
if (request.wantChanged && listing && resolve(listing.path || ".") === resolve(path || ".")) {
|
|
89
|
+
const before = new Set(listing.rows.map((row) => row.name)),
|
|
90
|
+
after = new Set(fresh.rows.map((row) => row.name));
|
|
91
|
+
changed = 0;
|
|
92
|
+
for (const name of before) if (!after.has(name)) changed++;
|
|
93
|
+
for (const name of after) if (!before.has(name)) changed++;
|
|
94
|
+
}
|
|
95
|
+
listDone(armed);
|
|
96
|
+
listing = fresh;
|
|
97
|
+
order = next;
|
|
98
|
+
stamp++;
|
|
99
|
+
listed(scanned + read, sorted, { ...(changed === undefined ? {} : { changed }), ...genOf(request) });
|
|
100
|
+
// `select` moves the first window onto its row when that row lies past `first`.
|
|
101
|
+
let start = 0;
|
|
102
|
+
if (typeof request.select === "string" && resolve(dirname(request.select)) === resolve(path || ".")) {
|
|
103
|
+
const index = listing.indexOfName(basename(request.select));
|
|
104
|
+
if (index >= first) start = Math.max(0, Math.min(index - Math.floor(first / 2), listing.rows.length - first));
|
|
105
|
+
}
|
|
106
|
+
rows(start, first, genOf(request));
|
|
107
|
+
// flea-qml: an emptied Find query lands back on the folder, and its terminal line follows the rows.
|
|
108
|
+
if (request.searched && typeof request.searched === "object")
|
|
109
|
+
send({ ...request.searched, t: "searched", n: listing.rows.length, ...genOf(request) });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The longest matched text a row keeps; the client already cuts it, this bounds what it may send.
|
|
113
|
+
export const MATCH_TEXT_LIMIT = 400;
|
|
114
|
+
/** A content search's match for one row, checked and bounded, or null. */
|
|
115
|
+
export function matchOf(value) {
|
|
116
|
+
if (!value || typeof value !== "object") return null;
|
|
117
|
+
const l = Number(value.l);
|
|
118
|
+
if (!Number.isInteger(l) || l < 1 || typeof value.t !== "string") return null;
|
|
119
|
+
return { l, t: value.t.slice(0, MATCH_TEXT_LIMIT) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A listing of the paths the client names, in that order (docs/protocol.md "listpaths"). flea-qml
|
|
123
|
+
// adds `base`, for Find: the paths are relative to it, as a `find .` run there prints them, and the
|
|
124
|
+
// listing's own base is that folder. `append` adds to the listing the same base and gen built, so a
|
|
125
|
+
// stream of results costs one lstat per new path; `searched` closes a Find answer with its terminal
|
|
126
|
+
// line, sent after these rows so the two arrive in order.
|
|
127
|
+
function listpaths(request) {
|
|
128
|
+
const started = performance.now(),
|
|
129
|
+
base = typeof request.base === "string" && request.base ? request.base : "/",
|
|
130
|
+
first = request.first ?? 0,
|
|
131
|
+
gen = genOf(request);
|
|
132
|
+
const fresh = [];
|
|
133
|
+
// A content search sends, beside each path, the line it matched: {l: line number, t: text}.
|
|
134
|
+
const matches = Array.isArray(request.matches) ? request.matches : [];
|
|
135
|
+
const paths = Array.isArray(request.paths) ? request.paths : [];
|
|
136
|
+
for (let at = 0; at < paths.length; at++) {
|
|
137
|
+
const path = paths[at];
|
|
138
|
+
if (typeof path !== "string" || !path) continue;
|
|
139
|
+
let name;
|
|
140
|
+
if (request.base) name = path.replace(/^\.\//, "");
|
|
141
|
+
else if (path.startsWith("/") && path !== "/") name = path.slice(1);
|
|
142
|
+
if (!name || name === "." || name.split("/").includes("..")) continue;
|
|
143
|
+
let st;
|
|
144
|
+
try {
|
|
145
|
+
st = lstatSync(join(base, name));
|
|
146
|
+
} catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const row = { name, dir: st.isDirectory(), link: st.isSymbolicLink() };
|
|
150
|
+
const match = matchOf(matches[at]);
|
|
151
|
+
if (match) row.match = match;
|
|
152
|
+
fresh.push(row);
|
|
153
|
+
}
|
|
154
|
+
const appending = request.append === true && listing?.fromPaths && listing.path === base && listing.gen === request.gen;
|
|
155
|
+
if (appending) listing.rows.push(...fresh);
|
|
156
|
+
else {
|
|
157
|
+
listing = new Listing(base, fresh);
|
|
158
|
+
listing.fromPaths = true;
|
|
159
|
+
listing.gen = request.gen;
|
|
160
|
+
stamp++;
|
|
161
|
+
}
|
|
162
|
+
listed(performance.now() - started, 0, gen);
|
|
163
|
+
rows(0, first, gen);
|
|
164
|
+
if (request.searched && typeof request.searched === "object")
|
|
165
|
+
send({ ...request.searched, t: "searched", n: listing.rows.length, ...gen });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Trash is a collection of real paths, presented through the ordinary listing.
|
|
169
|
+
// Relative names retain each mount's identity; display metadata never becomes an IO path.
|
|
170
|
+
function listtrash(request) {
|
|
171
|
+
const started = performance.now();
|
|
172
|
+
const items = readTrash();
|
|
173
|
+
listing = new Listing("/", items.map(item => ({
|
|
174
|
+
name: process.platform === "win32" ? item.file.replaceAll("\\", "/") : item.file.slice(1), dir: item.directory, trash: item,
|
|
175
|
+
})));
|
|
176
|
+
listing.fromPaths = true;
|
|
177
|
+
listing.trash = true;
|
|
178
|
+
stamp++;
|
|
179
|
+
listed(performance.now() - started, 0);
|
|
180
|
+
rows(0, request.first ?? 100);
|
|
181
|
+
}
|
|
182
|
+
function trashRequest(request) {
|
|
183
|
+
if (request.menuId) {
|
|
184
|
+
const paths = capturedPaths(request.menuId);
|
|
185
|
+
const picked = paths?.map(path => listing?.trash ? listing.rows.find(row => row.trash && resolve(row.trash.file) === resolve(path))?.trash : null);
|
|
186
|
+
if (!picked?.length || picked.some(item => !item))
|
|
187
|
+
return send({t:"trashbrowse",op:request.op,id:request.id,ok:false,stale:true,error:"Selected items changed; reopen the menu."});
|
|
188
|
+
request = {...request, rows:undefined, all:false, uris:picked.map(item=>item.uri), identities:picked.map(item=>item.identity)};
|
|
189
|
+
}
|
|
190
|
+
if (Array.isArray(request.rows)) {
|
|
191
|
+
if (!listing?.trash || request.listing !== stamp)
|
|
192
|
+
return send({t:"trashbrowse",op:request.op,id:request.id,ok:false,stale:true,error:"The listing changed; select the Trash items again."});
|
|
193
|
+
const picked = [...new Set(request.rows)].map(i => Number.isInteger(i) ? listing.rows[i]?.trash : null);
|
|
194
|
+
if (!picked.length || picked.some(item => !item))
|
|
195
|
+
return send({t:"trashbrowse",op:request.op,id:request.id,ok:false,error:"No Trash items selected."});
|
|
196
|
+
request = {...request,uris:picked.map(item=>item.uri),identities:picked.map(item=>item.identity)};
|
|
197
|
+
}
|
|
198
|
+
trashbrowse(request, send);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function sort(request) {
|
|
202
|
+
const next = orderFrom(request, order);
|
|
203
|
+
if (!SORT_KEYS.has(next.by)) return error("sort", String(request.by), `no order is named ${request.by}`);
|
|
204
|
+
if (!listing) return;
|
|
205
|
+
const [read, sorted] = listing.sort(next);
|
|
206
|
+
order = next;
|
|
207
|
+
stamp++;
|
|
208
|
+
listed(read, sorted);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// The `code` job running for each slot.
|
|
212
|
+
const codeJobs = new Map();
|
|
213
|
+
|
|
214
|
+
const htmlJobs = new Map();
|
|
215
|
+
const markdownJobs = new Map();
|
|
216
|
+
let compareJob;
|
|
217
|
+
const handlers = {
|
|
218
|
+
compare: request => {
|
|
219
|
+
compareJob?.abort();
|
|
220
|
+
const job = compareJob = new AbortController();
|
|
221
|
+
compare(request, {signal:job.signal}).then(reply => {
|
|
222
|
+
if(compareJob !== job)return;
|
|
223
|
+
compareJob = null;
|
|
224
|
+
if(reply)send(reply);
|
|
225
|
+
}).catch(() => {
|
|
226
|
+
if(compareJob !== job)return;
|
|
227
|
+
compareJob = null;
|
|
228
|
+
send({t:'compare',token:request.token,state:'failed',patch:''});
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
html: request => {
|
|
232
|
+
const slot = request.slot ?? "";
|
|
233
|
+
const job = {};
|
|
234
|
+
htmlJobs.set(slot, job);
|
|
235
|
+
html(request).then(reply => {
|
|
236
|
+
if (htmlJobs.get(slot) !== job) return;
|
|
237
|
+
htmlJobs.delete(slot);
|
|
238
|
+
if (reply) send(reply);
|
|
239
|
+
}).catch(() => {
|
|
240
|
+
if (htmlJobs.get(slot) !== job) return;
|
|
241
|
+
htmlJobs.delete(slot);
|
|
242
|
+
send({t:"html",slot,token:request.token,state:"failed",html:"",total:0});
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
// One Mermaid diagram for the block view's figures (ui/FigureService.qml), drawn by src/figure.js;
|
|
246
|
+
// the answer names the request's id and carries either the SVG or the reason there is none.
|
|
247
|
+
figure: request => {
|
|
248
|
+
renderDiagram(request.source, request.theme ?? {}).then(answer =>
|
|
249
|
+
send({ t: "figure", id: request.id, svg: answer.svg ?? "", error: answer.svg === undefined ? answer.error ?? "render failed" : "" }));
|
|
250
|
+
},
|
|
251
|
+
// A Markdown file as one passive page for a sandboxed web view (src/markdown-html.js);
|
|
252
|
+
// a newer request on the same slot drops the older one's reply.
|
|
253
|
+
markdownhtml: request => {
|
|
254
|
+
const slot = request.slot ?? "";
|
|
255
|
+
const job = {};
|
|
256
|
+
markdownJobs.set(slot, job);
|
|
257
|
+
markdownHtml(request).then(reply => {
|
|
258
|
+
if (markdownJobs.get(slot) !== job) return;
|
|
259
|
+
markdownJobs.delete(slot);
|
|
260
|
+
send(reply);
|
|
261
|
+
}).catch(() => {
|
|
262
|
+
if (markdownJobs.get(slot) !== job) return;
|
|
263
|
+
markdownJobs.delete(slot);
|
|
264
|
+
send({t:"markdownhtml",slot,token:request.token,state:"failed",html:""});
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
list,
|
|
268
|
+
sort,
|
|
269
|
+
listpaths,
|
|
270
|
+
listtrash,
|
|
271
|
+
rename: request => {
|
|
272
|
+
try {
|
|
273
|
+
const source=request.path,name=request.to;
|
|
274
|
+
if(typeof source!=="string" || !isAbsolute(source) || typeof name!=="string" || !name || name==='.' || name==='..' || /[\\/\0]/.test(name))throw Error('Invalid file name');
|
|
275
|
+
if(request.menuId && !capturedPaths(request.menuId)?.some(path=>resolve(path)===resolve(source)))throw Error('Selected item changed; reopen Rename');
|
|
276
|
+
const target=join(dirname(source),name);
|
|
277
|
+
if(resolve(target)!==resolve(source))renameWithTrashRecord(source,target);
|
|
278
|
+
send({t:'renamed',ok:true,path:process.platform==='win32'?target.replaceAll('\\','/'):target});send({t:'changed',path:process.platform==='win32'?dirname(source).replaceAll('\\','/'):dirname(source)});
|
|
279
|
+
}catch(e){send({t:'renamed',ok:false,path:request.path});error('rename',request.path,e.message)}
|
|
280
|
+
},
|
|
281
|
+
window: (request) => listing && rows(request.start ?? 0, request.count ?? 0),
|
|
282
|
+
fsinfo: () => listing && send(fsinfo(listing.path)),
|
|
283
|
+
peek: (request) => {
|
|
284
|
+
if (request.watch === true && typeof request.path === "string") watchColumn(request.path, request.keep);
|
|
285
|
+
send(peek(request));
|
|
286
|
+
},
|
|
287
|
+
// Answered when ready, off the read loop; a newer request on the same slot ends the older job,
|
|
288
|
+
// whose reply is never sent.
|
|
289
|
+
code: (request) => {
|
|
290
|
+
const slot = request.slot ?? "";
|
|
291
|
+
codeJobs.get(slot)?.abort();
|
|
292
|
+
const job = new AbortController();
|
|
293
|
+
codeJobs.set(slot, job);
|
|
294
|
+
code(request, job.signal).then((line) => {
|
|
295
|
+
if (codeJobs.get(slot) !== job) return;
|
|
296
|
+
codeJobs.delete(slot);
|
|
297
|
+
if (line) send(line);
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
// A row the listing no longer holds is dropped in silence; the client asks again after its rows.
|
|
301
|
+
// A compressed tar's listing streams, so the line is sent when it is ready.
|
|
302
|
+
meta: (request) => {
|
|
303
|
+
meta(listing, request).then((line) => line && send(line)).catch((err) => send({ t: "error", where: "meta", msg: String(err?.message ?? err) }));
|
|
304
|
+
},
|
|
305
|
+
// Answered off the read loop: a slow clipboard owner holds up no other request.
|
|
306
|
+
clipSet: (request) => clipSet(request, send),
|
|
307
|
+
clipGet: (request) => clipGet(request, send),
|
|
308
|
+
clipClear: (request) => clipClear(request, send),
|
|
309
|
+
clipWatch: (request) => clipWatch(request, send),
|
|
310
|
+
clipHostReply,
|
|
311
|
+
// Rows read as paths, the way copy, cut and drag name a selection wider than the client's window.
|
|
312
|
+
paths: (request) => {
|
|
313
|
+
const named = sources(request, listing, stamp, "paths");
|
|
314
|
+
send(named.stale ?? { t: "paths", paths: named.paths });
|
|
315
|
+
},
|
|
316
|
+
menuaction: (request) => menuaction(request, send, listing, stamp),
|
|
317
|
+
trashbrowse: trashRequest,
|
|
318
|
+
trash: (request) => {
|
|
319
|
+
const named = sources(request, listing, stamp, "trash");
|
|
320
|
+
if (named.stale) return send(named.stale);
|
|
321
|
+
trash(request, send, named);
|
|
322
|
+
},
|
|
323
|
+
collisions: (request) => collisions(request, send, listing, stamp),
|
|
324
|
+
mergecollisions: (request) => mergeCollisions(request, send, listing, stamp),
|
|
325
|
+
dirsize,
|
|
326
|
+
dirsizecancel: dirsizeCancel,
|
|
327
|
+
transfer: (request) => transfer(request, send, listing, stamp),
|
|
328
|
+
// One id space for both: an archive job's id is never a transfer's.
|
|
329
|
+
transfercancel: (request) => archiveCancel(request.id) || transferCancel(request),
|
|
330
|
+
mkdir: (request) => mkdir(request, send),
|
|
331
|
+
newfile: (request) => newfile(request, send),
|
|
332
|
+
duplicate: (request) => duplicate(request, send),
|
|
333
|
+
bulkrename: (request) => bulkrename(request, send),
|
|
334
|
+
permissions: (request) => permissions(request, send),
|
|
335
|
+
permissionsBatch: (request) => permissionsBatch(request, send),
|
|
336
|
+
formats: (request) => formats(request, send),
|
|
337
|
+
archive: (request) => archive(request, send),
|
|
338
|
+
// The sidebar's folders and drives, found by looking; `watch` keeps looking while a rail shows.
|
|
339
|
+
places: (request) => places(request, send),
|
|
340
|
+
quit: () => process.exit(0),
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
export function runBackend() {
|
|
344
|
+
onChanged(send);
|
|
345
|
+
dirsizeInit(send, () => listing);
|
|
346
|
+
// The command the interface runs for Find, which it spawns and reads itself; null when none exists.
|
|
347
|
+
send({ t: "findcommand", argv: findCommand(), grep: grepCommand() });
|
|
348
|
+
// The sidebar's Trash count, sent when it changes; upstream's UI ran `gio monitor` for it.
|
|
349
|
+
let trashRevision = 0;
|
|
350
|
+
watchTrashCount((count) => send({ t: "trashcount", count, revision: ++trashRevision }));
|
|
351
|
+
// The sidebar's probed places, named once at start; a rail asks for them again while it shows.
|
|
352
|
+
places({}, send);
|
|
353
|
+
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
354
|
+
lines.on("line", (line) => {
|
|
355
|
+
if (!line.trim()) return;
|
|
356
|
+
trace?.(`> ${line}\n`);
|
|
357
|
+
let request;
|
|
358
|
+
try {
|
|
359
|
+
request = JSON.parse(line);
|
|
360
|
+
} catch {
|
|
361
|
+
return error("read", "", "a request line is not JSON");
|
|
362
|
+
}
|
|
363
|
+
const handler = handlers[request.c];
|
|
364
|
+
if (handler) handler(request);
|
|
365
|
+
else console.error(`flea-qml backend: not implemented yet: ${request.c}`);
|
|
366
|
+
});
|
|
367
|
+
// Stdin closing ends the backend as quit does, with exit code 0 either way.
|
|
368
|
+
lines.on("close", () => process.exit(0));
|
|
369
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// `bulkrename` (docs/protocol.md "bulkrename"). Upstream: src/backend/bulkrename.rs.
|
|
2
|
+
// The whole batch is checked before anything moves; swaps and cycles go through temporary names in
|
|
3
|
+
// the same folder. A failure partway puts back what had moved, newest first. Not yet: the undo
|
|
4
|
+
// journal, so a finished batch cannot be undone.
|
|
5
|
+
import { lstatSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, join, resolve, isAbsolute } from "node:path";
|
|
7
|
+
import { renameWithTrashRecord } from "./trash-record.js";
|
|
8
|
+
import { reason } from "./transfer.js";
|
|
9
|
+
|
|
10
|
+
const slash = (path) => (process.platform === "win32" ? path.replaceAll("\\", "/") : path);
|
|
11
|
+
const lstatOrNull = (path) => {
|
|
12
|
+
try {
|
|
13
|
+
return lstatSync(path);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** The refusal for a batch, or "" when every rename may run. */
|
|
20
|
+
export function checkBatch(paths, names) {
|
|
21
|
+
if (!Array.isArray(paths) || !Array.isArray(names) || paths.length === 0) return "nothing to rename";
|
|
22
|
+
if (paths.length !== names.length) return "the names do not match the items";
|
|
23
|
+
const seen = new Set(),
|
|
24
|
+
taken = new Set();
|
|
25
|
+
let folder = null;
|
|
26
|
+
for (let i = 0; i < paths.length; i++) {
|
|
27
|
+
const path = paths[i],
|
|
28
|
+
name = names[i];
|
|
29
|
+
if (typeof path !== "string" || !isAbsolute(path)) return "every item needs an absolute path";
|
|
30
|
+
const full = resolve(path);
|
|
31
|
+
if (seen.has(full)) return `${basename(path)} is named twice`;
|
|
32
|
+
seen.add(full);
|
|
33
|
+
if (folder === null) folder = dirname(full);
|
|
34
|
+
else if (dirname(full) !== folder) return "every item must be in the same folder";
|
|
35
|
+
if (!lstatOrNull(full)) return `${basename(path)} no longer exists`;
|
|
36
|
+
if (typeof name !== "string" || name === "" || name === "." || name === "..") return "a new name is empty or not allowed";
|
|
37
|
+
if (/[/\0\r\n]/.test(name) || (process.platform === "win32" && name.includes("\\"))) return `${name} is not an allowed name`;
|
|
38
|
+
if (Buffer.byteLength(name) > 255) return `${name.slice(0, 40)}… is too long`;
|
|
39
|
+
if (taken.has(name)) return `two items would both be named ${name}`;
|
|
40
|
+
taken.add(name);
|
|
41
|
+
}
|
|
42
|
+
// A destination already there is refused unless it is itself one of the batch, which moves away first.
|
|
43
|
+
for (let i = 0; i < paths.length; i++) {
|
|
44
|
+
const target = join(folder, names[i]);
|
|
45
|
+
if (target !== resolve(paths[i]) && !seen.has(target) && lstatOrNull(target)) return `${names[i]} already exists`;
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function bulkrename(request, send) {
|
|
51
|
+
const { paths, names } = request;
|
|
52
|
+
const problem = checkBatch(paths, names);
|
|
53
|
+
if (problem) return send({ t: "error", where: "bulkrename", path: "", msg: problem });
|
|
54
|
+
const folder = dirname(resolve(paths[0]));
|
|
55
|
+
const moves = [];
|
|
56
|
+
let unchanged = 0;
|
|
57
|
+
for (let i = 0; i < paths.length; i++) {
|
|
58
|
+
const from = resolve(paths[i]),
|
|
59
|
+
to = join(folder, names[i]);
|
|
60
|
+
if (from === to) unchanged++;
|
|
61
|
+
else moves.push({ from, to });
|
|
62
|
+
}
|
|
63
|
+
// Every source steps aside to a temporary name first, so a to b and b to a never meet.
|
|
64
|
+
const done = [];
|
|
65
|
+
const tag = `.flea-rename-${process.pid}-${Date.now().toString(36)}`;
|
|
66
|
+
try {
|
|
67
|
+
moves.forEach((move, i) => {
|
|
68
|
+
move.temp = join(folder, `${tag}-${i}`);
|
|
69
|
+
if (lstatOrNull(move.temp)) throw Object.assign(Error("a temporary name is taken"), { code: "EEXIST" });
|
|
70
|
+
renameWithTrashRecord(move.from, move.temp);
|
|
71
|
+
done.push({ from: move.from, to: move.temp });
|
|
72
|
+
});
|
|
73
|
+
for (const move of moves) {
|
|
74
|
+
if (lstatOrNull(move.to)) throw Object.assign(Error(`${basename(move.to)} appeared meanwhile`), { code: "EEXIST" });
|
|
75
|
+
renameWithTrashRecord(move.temp, move.to);
|
|
76
|
+
done.push({ from: move.temp, to: move.to });
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// Put back what moved, newest first; what cannot go back is named.
|
|
80
|
+
const stuck = [];
|
|
81
|
+
for (const step of done.reverse()) {
|
|
82
|
+
try {
|
|
83
|
+
renameWithTrashRecord(step.to, step.from);
|
|
84
|
+
} catch {
|
|
85
|
+
stuck.push(basename(step.to));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
send({ t: "changed", path: slash(folder) });
|
|
89
|
+
const why = e?.code === "EEXIST" ? e.message : reason(e);
|
|
90
|
+
return send({ t: "error", where: "bulkrename", path: slash(folder),
|
|
91
|
+
msg: stuck.length ? `${why}; ${stuck.join(", ")} could not be put back` : `${why}; nothing was renamed` });
|
|
92
|
+
}
|
|
93
|
+
send({ t: "bulkrenamed", ok: moves.length, unchanged });
|
|
94
|
+
send({ t: "changed", path: slash(folder) });
|
|
95
|
+
}
|
package/src/clip-host.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// No Wayland: ask the QML frontend to use Quickshell's clipboard, never spawn
|
|
2
|
+
// a second platform clipboard implementation in the file-manager backend.
|
|
3
|
+
let nextId = 0;
|
|
4
|
+
const pending = new Map();
|
|
5
|
+
const LIMIT = 8 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
export function clipHostReply(message) {
|
|
8
|
+
const entry = pending.get(message.id);
|
|
9
|
+
if (!entry) return;
|
|
10
|
+
pending.delete(message.id);
|
|
11
|
+
clearTimeout(entry.timer);
|
|
12
|
+
if (!message.ok) entry.reject(Error(String(message.error || 'Quickshell clipboard failed')));
|
|
13
|
+
else if (typeof message.text === 'string' && Buffer.byteLength(message.text) > LIMIT)
|
|
14
|
+
entry.reject(Error('Clipboard text exceeds limit'));
|
|
15
|
+
else entry.resolve(message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class HostClipboard {
|
|
19
|
+
constructor(send, { clipText, parseClipText, none }) {
|
|
20
|
+
Object.assign(this, { send, clipText, parseClipText, none });
|
|
21
|
+
this.hostText = true;
|
|
22
|
+
this.owned = null;
|
|
23
|
+
this.queue = Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
ordered(fn) {
|
|
26
|
+
const result = this.queue.then(fn);
|
|
27
|
+
this.queue = result.catch(() => {});
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
request(op, text) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const id = ++nextId;
|
|
33
|
+
const timer = setTimeout(() => {
|
|
34
|
+
pending.delete(id);
|
|
35
|
+
reject(Error('Quickshell clipboard reply timed out'));
|
|
36
|
+
}, 15000);
|
|
37
|
+
timer.unref?.();
|
|
38
|
+
pending.set(id, { resolve, reject, timer });
|
|
39
|
+
try { this.send({ t: 'clipHost', id, op, ...(text === undefined ? {} : { text }) }); }
|
|
40
|
+
catch (e) { clearTimeout(timer); pending.delete(id); reject(e); }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
set(_payloads, owned) {
|
|
44
|
+
return this.ordered(async () => {
|
|
45
|
+
const text = owned.op === 'text' ? owned.paths[0] : this.clipText(owned.op, owned.paths);
|
|
46
|
+
await this.request('write', text);
|
|
47
|
+
this.owned = { ...owned, text };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
snapshot() {
|
|
51
|
+
return this.ordered(async () => {
|
|
52
|
+
const answer = await this.request('read');
|
|
53
|
+
if (typeof answer.text !== 'string') throw Error('Invalid Quickshell clipboard reply');
|
|
54
|
+
const clip = this.parseClipText(answer.text);
|
|
55
|
+
if (!clip) return this.none;
|
|
56
|
+
return { clip: clip.op, paths: clip.paths, skipped: 0,
|
|
57
|
+
token: this.owned?.text === answer.text ? this.owned.token : '' };
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
clearIf(request) {
|
|
61
|
+
return this.ordered(async () => {
|
|
62
|
+
let text;
|
|
63
|
+
if (typeof request.token === 'string' && request.token) {
|
|
64
|
+
if (this.owned?.token !== request.token) return false;
|
|
65
|
+
text = this.owned.text;
|
|
66
|
+
} else if (Array.isArray(request.cut) && request.cut.length
|
|
67
|
+
&& request.cut.every(p => typeof p === 'string')) text = this.clipText('cut', request.cut);
|
|
68
|
+
else return false;
|
|
69
|
+
// Compare and clear in the frontend's ordered queue. Sources may already
|
|
70
|
+
// have moved; checking their existence here would leave a spent cut alive.
|
|
71
|
+
const answer = await this.request('clear', text);
|
|
72
|
+
if (answer.cleared) this.owned = null;
|
|
73
|
+
return answer.cleared === true;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|