buninu-browser 0.2.4 → 0.4.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/LICENSES/jsmdcui.txt +22 -0
- package/README.md +102 -2
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +57 -0
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +78 -13
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/paint/frame.js +35 -3
- package/lib/paint/raw-frames.js +47 -0
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/compositor-channel.js +103 -0
- package/lib/renderer/compositor.js +25 -33
- package/lib/renderer/flags.js +1 -1
- package/lib/renderer/host.js +128 -92
- package/lib/renderer/main.js +38 -4
- package/lib/renderer/page-realm.js +32 -1
- package/lib/renderer/page-renderer.js +250 -21
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +54 -0
- package/test/fixtures/browser-raw-demo.html +36 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/raw-capture-benchmark.json +76 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/raw-capture.test.js +152 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
- package/test/window-resize.test.js +36 -0
- package/tools/bench-raw-capture.js +32 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Lists.js" as Lists
|
|
4
|
+
.import "Archive.js" as Archive
|
|
5
|
+
.import "ExtThumbs.js" as ExtThumbs
|
|
6
|
+
.import "Format.js" as Format
|
|
7
|
+
.import "Sort.js" as Sort
|
|
8
|
+
.import "StateWords.js" as StateWords
|
|
9
|
+
|
|
10
|
+
// Submenus carry an entry array, including an empty array while a provider is unavailable.
|
|
11
|
+
function hasSubmenu(entry) {
|
|
12
|
+
return entry !== undefined && entry !== null && entry.submenu !== undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// A separator or a disabled row is never the cursor, so a step skips over one and an end keeps it where it is.
|
|
16
|
+
function stepRow(rows, from, delta) {
|
|
17
|
+
for (var i = from + delta; i >= 0 && i < rows.length; i += delta)
|
|
18
|
+
if (rows[i].separator !== true && rows[i].disabled !== true) return i
|
|
19
|
+
return from
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Flip at the far edge first, then shift only when neither side fits.
|
|
23
|
+
function clamp(point, size, bounds) {
|
|
24
|
+
var start = point + size <= bounds ? point : point - size
|
|
25
|
+
return Math.max(0, Math.min(Math.max(0, bounds - size), start))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The flyout's tail row, which is the only way into the dialog; ui/PaneMenuActions.qml reads it.
|
|
29
|
+
var OPEN_WITH_OTHER = "__another__"
|
|
30
|
+
|
|
31
|
+
// The file menu's rename row over two or more selected rows, one whole template.
|
|
32
|
+
var RENAME_MANY = "Rename %1 items\u2026"
|
|
33
|
+
|
|
34
|
+
// One order for every menu: F file, B background, T Trash, P Places, R rail rows (never hideable, never in Settings > Menus).
|
|
35
|
+
var INVENTORY = [
|
|
36
|
+
["open", "Open", "folder-open", "FTPR", "open"],
|
|
37
|
+
// Show original reveals a symlink's target in its own folder.
|
|
38
|
+
["showOriginal", "Show original", "symlink", "F", "open", "showOriginal"],
|
|
39
|
+
// MenuAdditions rule 3: a Places or Favorites row opens this menu for its own path, so the rows
|
|
40
|
+
// it carries are the ones that take a path and not the clipboard, archive, send or destroy ones.
|
|
41
|
+
["openTab", "New tab", "plus", "P", "open"],
|
|
42
|
+
["openwith", "Open with", "app-window", "F", "open", "openWith"],
|
|
43
|
+
// The run card's own entrances: Run asks through flea --open like Enter does, so the card stays the one place anything executes.
|
|
44
|
+
["run", "Run", "play", "F", "open"],
|
|
45
|
+
["openAsText", "Open as text", "file-text", "F", "open"],
|
|
46
|
+
// Mount and Unmount are one toggle; a locked container offers Unlock alone (ui/UnlockDialog.qml).
|
|
47
|
+
["unlockVolume", "Unlock", "lock", "R", "open"],
|
|
48
|
+
["mountVolume", "Mount", "drive", "R", "open"],
|
|
49
|
+
["mountPhone", "Mount", "drive", "R", "open"],
|
|
50
|
+
["unmountVolume", "Unmount", "drive", "R", "rrelease"],
|
|
51
|
+
["unmountPhone", "Unmount", "drive", "R", "rrelease"],
|
|
52
|
+
["unmount", "Unmount", "drive", "R", "rrelease"],
|
|
53
|
+
["eject", "Eject", "eject", "R", "rrelease"],
|
|
54
|
+
// Flash image opens the flasher with this drive chosen: last, in a group of its own, no switch.
|
|
55
|
+
["flashImage", "Flash image", "file", "R", "rflash"],
|
|
56
|
+
["newFolder", "New folder", "folder-plus", "B", "open"],
|
|
57
|
+
["newFile", "New file", "file-plus", "B", "open"],
|
|
58
|
+
["cut", "Cut", "scissors", "F", "basic"],
|
|
59
|
+
["copy", "Copy", "copy", "F", "basic"],
|
|
60
|
+
["paste", "Paste", "clipboard", "FB", "basic"],
|
|
61
|
+
// MenuAdditions040: Paste as links, undoable, through the collision
|
|
62
|
+
// card; hidden, and holding only the three link rows.
|
|
63
|
+
["pasteAs", "Paste as", "symlink", "FB", "basic", "pasteAs"],
|
|
64
|
+
["duplicate", "Duplicate", "file-plus", "F", "basic"],
|
|
65
|
+
["rename", "Rename", "rename", "FR", "basic"],
|
|
66
|
+
// A saved place's own rows beside Rename; Edit address and Remove from Network are rail-only.
|
|
67
|
+
["editPlace", "Edit address", "sliders", "R", "basic"],
|
|
68
|
+
["remove", "Remove from Network", "minus", "R", "rremove"],
|
|
69
|
+
["selectAll", "Select all", "check", "B", "basic"],
|
|
70
|
+
// MenuAdditions040: Invert selection flips the marks over the rows the
|
|
71
|
+
// listing draws; hidden, present only while something is selected.
|
|
72
|
+
["invertSelection", "Invert selection", "contrast", "B", "basic", "invertSelection"],
|
|
73
|
+
["compress", "Compress", "archive", "F", "archive"],
|
|
74
|
+
["extract", "Extract", "archive-out", "F", "archive"],
|
|
75
|
+
["convert", "Convert", "sliders", "F", "archive"],
|
|
76
|
+
// Flash to drive opens the flasher with this image chosen, after Convert.
|
|
77
|
+
["flashDrive", "Flash to drive", "drive", "F", "archive"],
|
|
78
|
+
// Flea's own spiral drawn static: FleaMark paints in over two seconds, which a menu row never waits for.
|
|
79
|
+
["shelf", "Add to shelf", "shelf", "F", "share", "addToShelf"],
|
|
80
|
+
["taildrop", "Send with Taildrop", "tailscale", "F", "share"],
|
|
81
|
+
// MenuAdditions rule 1: between Taildrop and Dropbox, present only while a localsend binary is
|
|
82
|
+
// on PATH, and carrying its own reproduced mark rather than a cut glyph.
|
|
83
|
+
["localsend", "Send with LocalSend", "localsend", "F", "share"],
|
|
84
|
+
["dropbox", "Move to Dropbox", "dropbox", "F", "share"],
|
|
85
|
+
["sharelink", "Copy share link", "network", "F", "share"],
|
|
86
|
+
["trash", "Move to Trash", "trash", "F", "trash"],
|
|
87
|
+
["delete", "Delete permanently", "trash", "F", "trash", "deletePermanently"],
|
|
88
|
+
["openTerminal", "Open in terminal", "terminal", "FBP", "inspect"],
|
|
89
|
+
["moveto", "Move to", "folder-plus", "F", "inspect", "moveTo"],
|
|
90
|
+
["copyto", "Copy to", "copy", "F", "inspect", "copyTo"],
|
|
91
|
+
["properties", "Properties", "info", "F", "inspect"],
|
|
92
|
+
["permissions", "Permissions", "lock", "F", "inspect"],
|
|
93
|
+
// Both boards place Make executable in inspect, without a key, only on scripts missing every execute bit.
|
|
94
|
+
["makeExecutable", "Make executable", "play", "F", "inspect", "makeExecutable"],
|
|
95
|
+
// MenuAdditions040: Copy as replaces the file menu's hidden Copy path row; a place keeps the flat Copy path behind the same switch.
|
|
96
|
+
["copyAs", "Copy as", "file-text", "FP", "inspect", "copyAs"],
|
|
97
|
+
// MenuAdditions rule 2: after Copy as, one row per executable in ~/.config/flea/scripts, and
|
|
98
|
+
// absent rather than greyed when that directory is missing or holds none.
|
|
99
|
+
["runScript", "Run script", "terminal", "F", "inspect"],
|
|
100
|
+
// MenuAdditions040 specimen e: Compare sits before Add to Favorites, with no key, and is absent unless two regular files are marked.
|
|
101
|
+
["compare", "Compare", "columns", "F", "inspect"],
|
|
102
|
+
["addFavourite", "Add to Favorites", "star", "FBP", "inspect"],
|
|
103
|
+
["tags", "Tags", "tag", "F", "inspect"],
|
|
104
|
+
["removeFavourite", "Remove from Favorites", "minus", "PR", "inspect"],
|
|
105
|
+
["sort", "Sort by", "sort", "B", "view"],
|
|
106
|
+
["toggleHidden", "Show hidden files", "eye", "FB", "view"],
|
|
107
|
+
["toggleLangMarks", "Show code icons", "code", "FB", "view"],
|
|
108
|
+
["extThumbs", "Show thumbnails", "image", "B", "view"],
|
|
109
|
+
["settings", "Settings", "sliders", "B", "settings"],
|
|
110
|
+
["updateFlea", "Update Flea", "download", "B", "settings"],
|
|
111
|
+
["restoreAll", "Restore all", "undo", "T", "restore"],
|
|
112
|
+
["emptyTrash", "Empty Trash", "trash", "T", "empty"]
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
function listingEntries(p) { return buildEntries(p.hasRow ? "F" : "B", p) }
|
|
116
|
+
// The Places rail's own, behind its Extras toggle: off, the rail keeps the menu it has today.
|
|
117
|
+
function placeEntries(p) { return isHidden(p.hiddenActions, "placeMenu") ? [] : buildEntries("P", p) }
|
|
118
|
+
function backgroundEntries(p) { return buildEntries("B", p) }
|
|
119
|
+
function trashEntries(total, busy) { return buildEntries("T", { trashTotal: total, busy: busy }) }
|
|
120
|
+
|
|
121
|
+
function buildEntries(kind, p) {
|
|
122
|
+
var out = [], group = ""
|
|
123
|
+
for (var i = 0; i < INVENTORY.length; i++) {
|
|
124
|
+
var spec = INVENTORY[i]
|
|
125
|
+
// Rail rows are never user-hideable: no switch governs them, so the stored set is not read here.
|
|
126
|
+
if (spec[3].indexOf(kind) < 0) continue
|
|
127
|
+
// The background menu shows Open in terminal without reading the hidden set.
|
|
128
|
+
if (kind !== "R" && isHidden(p.hiddenActions, spec[0])) {
|
|
129
|
+
if (!(kind === "B" && spec[0] === "openTerminal")) continue
|
|
130
|
+
}
|
|
131
|
+
var entry = { id: spec[0], action: spec[5] || spec[0], label: spec[1], glyph: spec[2] }
|
|
132
|
+
if (!availableEntry(entry, p, kind)) continue
|
|
133
|
+
if (out.length && group !== spec[4]) out.push({ separator: true })
|
|
134
|
+
group = spec[4]
|
|
135
|
+
out.push(entry)
|
|
136
|
+
}
|
|
137
|
+
return out
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The rail menu: the rows above with kind R, built with the listing menu's separator rule.
|
|
141
|
+
function railEntries(entry) { return buildEntries("R", { entry: entry }) }
|
|
142
|
+
|
|
143
|
+
function availableEntry(e, p, kind) {
|
|
144
|
+
// Rail availability reads the rail entry alone; nothing here is disabled, red or hideable.
|
|
145
|
+
if (kind === "R") return availableRail(e, p.entry)
|
|
146
|
+
var count = p.selectionCount === undefined ? 1 : p.selectionCount
|
|
147
|
+
// Rule 3: the last row adds the favourite or removes it, and the duplicate case is absent rather
|
|
148
|
+
// than grey, which is what keeps issue 138's second row impossible from the rail as well.
|
|
149
|
+
if (kind === "P" && (e.id === "addFavourite" || e.id === "removeFavourite"))
|
|
150
|
+
return (e.id === "removeFavourite") === (p.placeFavourite === true)
|
|
151
|
+
if (e.action === "addFavourite" && kind === "F")
|
|
152
|
+
e.disabled = count !== 1 || ((Number(p.rowMode) || 0) & 0o170000) !== 0o040000
|
|
153
|
+
if (e.action === "paste") e.disabled = p.clipboardAvailable !== true && p.clipboardWatchFailed !== true
|
|
154
|
+
// MenuAdditions040: Show original is visible but only on a symlink.
|
|
155
|
+
if (e.action === "showOriginal" && p.rowIsSymlink !== true) return false
|
|
156
|
+
// Paste as keeps its disabled row without a chevron until files are known.
|
|
157
|
+
if (e.action === "pasteAs") {
|
|
158
|
+
// A filesystem that holds no links offers no link rows at all.
|
|
159
|
+
if (p.canLink === false) return false
|
|
160
|
+
e.disabled = p.clipboardAvailable !== true
|
|
161
|
+
if (p.clipboardAvailable === true)
|
|
162
|
+
e.submenu = pasteAsEntries()
|
|
163
|
+
}
|
|
164
|
+
// Every Copy as variant covers the whole selection, one path per line; no board draws a place menu with it, so a place keeps 0.3.7's flat Copy path.
|
|
165
|
+
if (e.action === "copyAs") {
|
|
166
|
+
if (kind === "P") { e.id = e.action = "copypath"; e.label = "Copy path" }
|
|
167
|
+
else e.submenu = copyAsEntries()
|
|
168
|
+
}
|
|
169
|
+
// MenuAdditions040: Invert selection flips over the rows the listing
|
|
170
|
+
// draws, so with nothing selected there is nothing to flip from.
|
|
171
|
+
if (e.action === "invertSelection" && count === 0) return false
|
|
172
|
+
// Run names a file to execute, so a directory or a wider selection is not offered it.
|
|
173
|
+
if ((e.action === "run" || e.action === "openAsText")
|
|
174
|
+
&& (count !== 1 || (Number(p.rowMode) & Format.S_IFMT) === Format.S_IFDIR)) return false
|
|
175
|
+
if (["duplicate", "properties"].indexOf(e.action) >= 0)
|
|
176
|
+
e.disabled = count !== 1
|
|
177
|
+
// Bulk rename: several selected rows open the card instead of refusing, and the row says how many.
|
|
178
|
+
if (e.action === "rename") {
|
|
179
|
+
e.disabled = count < 1
|
|
180
|
+
if (count > 1) e.label = RENAME_MANY.arg(Format.count(count))
|
|
181
|
+
}
|
|
182
|
+
if (e.action === "permissions") {
|
|
183
|
+
var permission = permissionsEntry(p.rowMode, count, p.selectionModes)
|
|
184
|
+
e.disabled = permission.disabled
|
|
185
|
+
if (permission.errored) { e.errored = true; if (permission.hint) e.hint = permission.hint }
|
|
186
|
+
}
|
|
187
|
+
// Only a cursor-row script without any execute bit qualifies; its two-byte probe runs once at menu open.
|
|
188
|
+
if (e.action === "makeExecutable" && !canMakeExecutable(p.rowMode, count, p.hasShebang, p.cursorIsTarget)) return false
|
|
189
|
+
// The held rows or menu snapshot supply every selected item's own kind.
|
|
190
|
+
if (e.action === "compare" && !canCompare(p.compareKinds)) return false
|
|
191
|
+
if (e.action === "runScript") {
|
|
192
|
+
if (!(p.scripts || []).length) return false
|
|
193
|
+
e.submenu = p.scripts.map(function (script) { return { id: script.id, label: script.label } })
|
|
194
|
+
}
|
|
195
|
+
if (e.action === "compress") {
|
|
196
|
+
if (!(p.archiveFormats || []).length) return false
|
|
197
|
+
e.submenu = Archive.formatEntries(p.archiveFormats).concat(Archive.passwordEntry(p.archiveFormats))
|
|
198
|
+
}
|
|
199
|
+
// Issue 133: a mount with no trash directory never offers the row, rather than offering one that
|
|
200
|
+
// fails; ui/js/Mounts.js trashable is the one reader of what the path says about that.
|
|
201
|
+
if (e.action === "trash" && p.canTrash === false) return false
|
|
202
|
+
// A read-only folder turns its write rows off; the listing's own w flag decides, never the mode.
|
|
203
|
+
if (p.dirWritable === false && ["newFolder", "newFile", "paste", "pasteAs", "duplicate",
|
|
204
|
+
"rename", "trash", "deletePermanently"].indexOf(e.action) >= 0) e.disabled = true
|
|
205
|
+
if (e.action === "tags" && !p.tagSubmenu) return false
|
|
206
|
+
if (e.action === "tags") e.submenu = p.tagSubmenu
|
|
207
|
+
if (e.action === "extract" && !Archive.extractEntry(e, p, count)) return false
|
|
208
|
+
if (e.action === "convert" && !(p.rowIsImage && p.canConvert && count === 1)) return false
|
|
209
|
+
// Flash to drive shows only when one disk image is selected, read off the name table.
|
|
210
|
+
if (e.action === "flashDrive" && (count !== 1 || !Archive.isDiskImage(p.rowName, p.flashZipMember))) return false
|
|
211
|
+
// OpenWith.html: the desktop's current default is first and carries the muted caption "default"
|
|
212
|
+
// in the hint slot, the registry order follows it, and the tail row sits under its own
|
|
213
|
+
// separator with the app-window glyph. The row self-hides when the registry names nothing.
|
|
214
|
+
if (e.action === "openWith") {
|
|
215
|
+
if (p.openWithLoaded === true && !(p.openWithApps || []).length) return false
|
|
216
|
+
var apps = p.openWithApps || []
|
|
217
|
+
var rows = apps.map(function (app) {
|
|
218
|
+
return { id: app.id, label: app.label, icon: app.icon || "",
|
|
219
|
+
hint: app.default === true ? "default" : "" }
|
|
220
|
+
})
|
|
221
|
+
if (rows.length) rows.push({ separator: true })
|
|
222
|
+
rows.push({ id: OPEN_WITH_OTHER, label: "Another application\u2026", glyph: "app-window" })
|
|
223
|
+
e.submenu = rows
|
|
224
|
+
e.disabled = count !== 1
|
|
225
|
+
}
|
|
226
|
+
if (e.action === "taildrop") {
|
|
227
|
+
if (!p.taildropInstalled) return false
|
|
228
|
+
e.mark = "tailscale"
|
|
229
|
+
delete e.glyph
|
|
230
|
+
e.submenu = p.taildropPeers || []
|
|
231
|
+
e.disabled = p.taildropRefreshing === true || !e.submenu.length
|
|
232
|
+
// The row says it cannot answer by red and one state word, not the reason: a sentence widened the menu past its frame mid-read.
|
|
233
|
+
if (e.disabled && p.taildropRefreshing !== true) { e.errored = true; e.hint = StateWords.taildrop(p) }
|
|
234
|
+
}
|
|
235
|
+
// Absent rather than greyed when nothing is installed, the Menu board's rule for a row whose
|
|
236
|
+
// whole destination is missing; the row is a plain send, so it has no submenu and no reason.
|
|
237
|
+
// Directive 71: the row is Taildrop's twin, so it opens the same flyout and reads the same way.
|
|
238
|
+
if (e.action === "localsend") {
|
|
239
|
+
if (!p.localSendInstalled) return false
|
|
240
|
+
e.mark = "localsend"
|
|
241
|
+
delete e.glyph
|
|
242
|
+
e.submenu = p.localSendPeers || []
|
|
243
|
+
e.disabled = p.localSendChecking === true || !e.submenu.length
|
|
244
|
+
if (e.disabled && p.localSendChecking !== true) { e.errored = true; e.hint = StateWords.localsend(p) }
|
|
245
|
+
}
|
|
246
|
+
if (e.action === "dropbox" || e.action === "sharelink") {
|
|
247
|
+
if (!p.dropboxInstalled || (e.action === "dropbox" ? p.rowInDropbox : !p.rowInDropbox)) return false
|
|
248
|
+
e.disabled = p.dropboxRefreshing === true || !p.dropboxPath
|
|
249
|
+
if (e.action === "dropbox") { e.mark = "dropbox"; delete e.glyph }
|
|
250
|
+
if (e.disabled && p.dropboxRefreshing !== true) { e.errored = true; e.hint = StateWords.dropbox(p) }
|
|
251
|
+
}
|
|
252
|
+
if (e.action === "sort") e.submenu = sortEntries(p.hasFolderSort === true)
|
|
253
|
+
// Present only while a check has found a newer build, whose version rides the hint slot beside the status square.
|
|
254
|
+
if (e.action === "updateFlea") {
|
|
255
|
+
if (!p.updateVersion) return false
|
|
256
|
+
e.hint = p.updateVersion
|
|
257
|
+
e.hintSquare = true
|
|
258
|
+
}
|
|
259
|
+
if (e.action === "toggleHidden") {
|
|
260
|
+
var hidden = hiddenRow(p.showHidden)
|
|
261
|
+
e.label = hidden.label
|
|
262
|
+
e.glyph = hidden.glyph
|
|
263
|
+
}
|
|
264
|
+
if (e.action === "toggleLangMarks") {
|
|
265
|
+
var code = codeIconsRow(p.langMarks)
|
|
266
|
+
e.label = code.label
|
|
267
|
+
e.glyph = code.glyph
|
|
268
|
+
}
|
|
269
|
+
// The class switch ships hidden; switched on, it is present only on network,
|
|
270
|
+
// phone or USB storage and reads Show while that class is off, Hide while on.
|
|
271
|
+
if (e.action === "extThumbs") return ExtThumbs.entry(e, p)
|
|
272
|
+
if (e.action === "restoreAll" || e.action === "emptyTrash") e.disabled = !(p.trashTotal > 0) || p.busy === true
|
|
273
|
+
if (["trash", "deletePermanently", "emptyTrash"].indexOf(e.action) >= 0) e.danger = true
|
|
274
|
+
return true
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Rail rows read the rail entry alone; Mount and Unmount never meet, and Open is the row's own click.
|
|
278
|
+
function availableRail(e, entry) {
|
|
279
|
+
if (!entry) return false
|
|
280
|
+
var device = entry.group === "device", network = entry.group === "network"
|
|
281
|
+
var volume = device && entry.kind === "volume", phone = device && entry.kind === "phone"
|
|
282
|
+
var share = network && entry.kind === "share"
|
|
283
|
+
var mounted = entry.mounted === true
|
|
284
|
+
switch (e.id) {
|
|
285
|
+
case "unlockVolume": return volume && entry.locked === true
|
|
286
|
+
case "mountVolume": return volume && !mounted && entry.locked !== true
|
|
287
|
+
case "mountPhone": return phone && !mounted
|
|
288
|
+
case "open": return (volume && mounted) || (phone && mounted) || (share && mounted)
|
|
289
|
+
case "unmountVolume": return volume && mounted
|
|
290
|
+
case "unmountPhone": return phone && mounted
|
|
291
|
+
case "unmount": return share && mounted
|
|
292
|
+
case "eject": return volume && mounted && (entry.removable === true || entry.loop === true)
|
|
293
|
+
// Flash image shows on a writable removable drive, mounted or not, never on an open crypt, whose mapper names no drive.
|
|
294
|
+
case "flashImage": return volume && entry.removable === true && entry.readOnly !== true && !/^\/dev\/(mapper\/|dm-)/.test(entry.device || "")
|
|
295
|
+
case "rename": return share && entry.editable !== false
|
|
296
|
+
case "editPlace": return share && entry.editable !== false
|
|
297
|
+
case "remove": return share && entry.editable !== false
|
|
298
|
+
case "removeFavourite": return entry.kind === "favourite"
|
|
299
|
+
default: return false
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// The mode describes the selected object itself, so a symlink never grants access to its unseen target.
|
|
304
|
+
// Issue 193, decided 2026-09-24: a row that cannot act reads red with one state word, as a provider does.
|
|
305
|
+
// MenuAdditions040: Permissions takes the whole selection, so one bad row
|
|
306
|
+
// among modes refuses the row; the dialog inspects each file in turn.
|
|
307
|
+
function permissionsEntry(mode, count, modes) {
|
|
308
|
+
var word
|
|
309
|
+
if (modes !== undefined && modes !== null && modes.length > 0) {
|
|
310
|
+
for (var i = 0; i < modes.length; i++) {
|
|
311
|
+
var kind = (Number(modes[i]) || 0) & Format.S_IFMT
|
|
312
|
+
if (!(kind === Format.S_IFREG || kind === 0o040000)) {
|
|
313
|
+
var bad = { label: "Permissions", action: "permissions", glyph: "lock", disabled: true, errored: true }
|
|
314
|
+
word = StateWords.permissions(modes[i])
|
|
315
|
+
if (word) bad.hint = word
|
|
316
|
+
return bad
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return { label: "Permissions", action: "permissions", glyph: "lock", disabled: false, errored: false }
|
|
320
|
+
}
|
|
321
|
+
var one = (Number(mode) || 0) & Format.S_IFMT
|
|
322
|
+
var allowed = count >= 1 && (one === Format.S_IFREG || one === 0o040000)
|
|
323
|
+
var entry = { label: "Permissions", action: "permissions", glyph: "lock", disabled: !allowed, errored: !allowed }
|
|
324
|
+
// No selection refuses the row for want of an item, not for a kind, so it names no kind; an unread mode names none either.
|
|
325
|
+
if (!allowed && count >= 1) {
|
|
326
|
+
word = StateWords.permissions(mode)
|
|
327
|
+
if (word) entry.hint = word
|
|
328
|
+
}
|
|
329
|
+
return entry
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Only a single cursor-row regular file with a shebang and no execute bit.
|
|
333
|
+
function canMakeExecutable(mode, count, hasShebang, cursorIsTarget) {
|
|
334
|
+
if (count !== 1 || hasShebang !== true || cursorIsTarget !== true) return false
|
|
335
|
+
var bits = Number(mode) || 0
|
|
336
|
+
if ((bits & Format.S_IFMT) !== Format.S_IFREG) return false
|
|
337
|
+
return (bits & Format.ANY_EXECUTE_BIT) === 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Exactly two marked regular files; kinds holds each item's own type bits, so links stay links.
|
|
341
|
+
// Sample input: [0o100000, 0o100000] is true; [0o100000, 0o040000], [0o100000] and a link left at 0o120000 are false.
|
|
342
|
+
function canCompare(kinds) {
|
|
343
|
+
if (!Lists.isList(kinds) || kinds.length !== 2) return false
|
|
344
|
+
return kinds.every(function (kind) { return ((Number(kind) || 0) & Format.S_IFMT) === Format.S_IFREG })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// The Copy as flyout: six leaves in board order, letters on keyHint.
|
|
348
|
+
function copyAsEntries() {
|
|
349
|
+
return [
|
|
350
|
+
{ id: "copyPath", label: "Path", glyph: "file-text", keyHint: "p" },
|
|
351
|
+
{ id: "copyName", label: "Name", glyph: "type", keyHint: "n" },
|
|
352
|
+
{ id: "copyStem", label: "Name without extension", glyph: "type", keyHint: "e" },
|
|
353
|
+
{ id: "copydirpath", label: "Folder path", glyph: "folder", keyHint: "f" },
|
|
354
|
+
{ id: "copyUri", label: "File URI", glyph: "globe", keyHint: "u" },
|
|
355
|
+
{ id: "copyQuoted", label: "Shell-quoted", glyph: "terminal", keyHint: "s" }
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// MenuAdditions040: the Paste as flyout. Link is relative from the
|
|
360
|
+
// destination, Absolute link stores the full path; l and h open and close a
|
|
361
|
+
// flyout, so the letters are L, a and H.
|
|
362
|
+
function pasteAsEntries() {
|
|
363
|
+
return [
|
|
364
|
+
{ id: "pasteLink", label: "Link", glyph: "symlink", keyHint: "L" },
|
|
365
|
+
{ id: "pasteAbsoluteLink", label: "Absolute link", glyph: "symlink", keyHint: "a" },
|
|
366
|
+
{ id: "pasteHardLink", label: "Hard link", glyph: "copy", keyHint: "H" }
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// A hidden row still opens its flyout, built from the action.
|
|
371
|
+
function flyoutEntries(action) {
|
|
372
|
+
if (action === "copyAs")
|
|
373
|
+
return copyAsEntries()
|
|
374
|
+
if (action === "pasteAs")
|
|
375
|
+
return pasteAsEntries()
|
|
376
|
+
return []
|
|
377
|
+
}
|
|
378
|
+
// The sentence an empty Paste as refuses with, the same one ui/Pane.qml shows.
|
|
379
|
+
var EMPTY_CLIPBOARD = "There is nothing to paste; y copies and x cuts."
|
|
380
|
+
// Sample input: submenuFor("pasteAs", [], false) answers refuse.
|
|
381
|
+
function submenuFor(action, entries, clipboardAvailable) {
|
|
382
|
+
if (flyoutEntries(action).length === 0)
|
|
383
|
+
return { kind: "none" }
|
|
384
|
+
for (var i = 0; i < entries.length; i++) {
|
|
385
|
+
if (entries[i].action === action) {
|
|
386
|
+
if (entries[i].disabled === true || !hasSubmenu(entries[i]))
|
|
387
|
+
return { kind: "refuse" }
|
|
388
|
+
return { kind: "row", index: i }
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (action === "pasteAs" && clipboardAvailable !== true)
|
|
392
|
+
return { kind: "refuse" }
|
|
393
|
+
return { kind: "lone" }
|
|
394
|
+
}
|
|
395
|
+
// Sample input: loneChoice("copyAs", "copyPath", false, false, true, "a", "a") fires "copyAs:copyPath".
|
|
396
|
+
function loneChoice(action, id, forRail, forHeader, hasRow, openedIdentity, selectionIdentity) {
|
|
397
|
+
var leaves = flyoutEntries(action)
|
|
398
|
+
var known = false
|
|
399
|
+
for (var i = 0; i < leaves.length; i++)
|
|
400
|
+
if (leaves[i].separator !== true && leaves[i].id === id) known = true
|
|
401
|
+
var moved = !forRail && !forHeader && hasRow && openedIdentity !== selectionIdentity
|
|
402
|
+
if (moved) return { kind: "moved" }
|
|
403
|
+
if (!known) return { kind: "unknown" }
|
|
404
|
+
return { kind: "fire", fired: action + ":" + id }
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
// The Sort by flyout, built from ui/js/Sort.js's own ORDERS so it can only ever offer an order the
|
|
409
|
+
// backend really produces; a fourth key would earn a refusal instead of a listing.
|
|
410
|
+
var SORT_LABELS = { name: "Name", size: "Size", mtime: "Modified", kind: "Kind" }
|
|
411
|
+
|
|
412
|
+
function sortEntries(hasOwn) {
|
|
413
|
+
var out = []
|
|
414
|
+
for (var i = 0; i < Sort.ORDERS.length; i++)
|
|
415
|
+
out.push({ id: Sort.ORDERS[i], label: SORT_LABELS[Sort.ORDERS[i]] })
|
|
416
|
+
// The id is the spelling ui/js/Sort.js's forget branch reads, so the two must agree.
|
|
417
|
+
if (hasOwn === true) { out.push({ separator: true }); out.push({ id: "__default__", label: "Use the default sort" }) }
|
|
418
|
+
return out
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// The one mark every row of an open flyout draws, keyed on the row that opened it: a Taildrop peer
|
|
422
|
+
// is a machine, a sort order is the row above it, and an archive format is a file about to exist.
|
|
423
|
+
function submenuGlyph(action) {
|
|
424
|
+
if (action === "taildrop")
|
|
425
|
+
return "server"
|
|
426
|
+
if (action === "runScript")
|
|
427
|
+
return "terminal"
|
|
428
|
+
if (action === "sort")
|
|
429
|
+
return "sort"
|
|
430
|
+
return "archive"
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// The visibility consumer SettingsMenus.html specifies, and the only reader of the stored hidden
|
|
434
|
+
// set: a switched-off action's row leaves, and a rule that has lost every row it divided leaves
|
|
435
|
+
// with it, so a menu never grows a doubled, leading or trailing separator. Order never changes,
|
|
436
|
+
// because the board offers visibility and deliberately not ordering.
|
|
437
|
+
function applyHidden(entries, hiddenActions) {
|
|
438
|
+
var kept = []
|
|
439
|
+
for (var i = 0; i < entries.length; i++) {
|
|
440
|
+
var entry = entries[i]
|
|
441
|
+
if (entry.separator === true) {
|
|
442
|
+
if (kept.length > 0 && kept[kept.length - 1].separator !== true)
|
|
443
|
+
kept.push(entry)
|
|
444
|
+
continue
|
|
445
|
+
}
|
|
446
|
+
if (!isHidden(hiddenActions, entry.id || entry.action))
|
|
447
|
+
kept.push(entry)
|
|
448
|
+
}
|
|
449
|
+
while (kept.length > 0 && kept[kept.length - 1].separator === true)
|
|
450
|
+
kept.pop()
|
|
451
|
+
return kept
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Open and the hidden toggle are never in the stored set: ui/js/Settings.js draws them locked, and
|
|
455
|
+
// this is the second half of that lock, so a hand-edited state file cannot empty the menu either.
|
|
456
|
+
function isHidden(hiddenActions, action) {
|
|
457
|
+
if (action === "open" || action === "toggleHidden")
|
|
458
|
+
return false
|
|
459
|
+
for (var i = 0; hiddenActions && i < hiddenActions.length; i++) {
|
|
460
|
+
if (hiddenActions[i] === action)
|
|
461
|
+
return true
|
|
462
|
+
}
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Hidden-files labels and glyphs flip with state; MenuRow draws no checkmark.
|
|
467
|
+
function hiddenRow(showHidden) {
|
|
468
|
+
return {
|
|
469
|
+
label: showHidden ? "Hide hidden files" : "Show hidden files",
|
|
470
|
+
action: "toggleHidden",
|
|
471
|
+
glyph: showHidden ? "eye-off" : "eye"
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Code icons use the hidden-files row's label flip and the existing code glyph.
|
|
476
|
+
function codeIconsRow(langMarks) {
|
|
477
|
+
return { label: langMarks !== false ? "Hide code icons" : "Show code icons", action: "toggleLangMarks", glyph: "code" }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ui/Header.qml's own rows, on a right click over the column titles. Name is not among them: it is
|
|
481
|
+
// the one column a file manager cannot do without (see ui/js/Columns.js), so it is never hidden and
|
|
482
|
+
// never offered. A hidden column reads "Show", a drawn one "Hide", the flip the state rows use.
|
|
483
|
+
function headerEntries(hiddenCols, showHidden) {
|
|
484
|
+
var out = []
|
|
485
|
+
var hidden = {}
|
|
486
|
+
for (var h = 0; h < hiddenCols.length; h++)
|
|
487
|
+
hidden[hiddenCols[h]] = true
|
|
488
|
+
var columns = [["mode", "Mode"], ["size", "Size"], ["date", "Modified"], ["kind", "Kind"]]
|
|
489
|
+
var glyphs = { mode: "lock", size: "drive", date: "download", kind: "type" }
|
|
490
|
+
for (var i = 0; i < columns.length; i++) {
|
|
491
|
+
var key = columns[i][0]
|
|
492
|
+
var shown = !hidden[key]
|
|
493
|
+
out.push({
|
|
494
|
+
label: shown ? "Hide " + columns[i][1] : "Show " + columns[i][1],
|
|
495
|
+
action: "col:" + key,
|
|
496
|
+
glyph: glyphs[key]
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
out.push({ separator: true })
|
|
500
|
+
out.push(hiddenRow(showHidden))
|
|
501
|
+
return out
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// The keyboard's own entrance to the row menu, under the cursor row the way the rail's opens under
|
|
505
|
+
// its own; setCursor first, because a wheel scroll in the grid can leave the cursor off screen.
|
|
506
|
+
// paddingX is ui/Theme.qml's rowPaddingX, passed in because a singleton has no name in a library.
|
|
507
|
+
function openAtCursor(root, menu, paddingX) {
|
|
508
|
+
root.setCursor(root.cursorIndex)
|
|
509
|
+
var row = root.visibleItemFor(root.cursorIndex)
|
|
510
|
+
if (row)
|
|
511
|
+
menu.openAt(row.mapToItem(null, paddingX, row.height))
|
|
512
|
+
return row !== null
|
|
513
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// The structural transitions use OutCubic, 180 ms to reveal and 140 ms to hide.
|
|
4
|
+
|
|
5
|
+
var durMs = {
|
|
6
|
+
// A reveal reads slower than a hide.
|
|
7
|
+
open: 180,
|
|
8
|
+
close: 140
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Open rises into place from this far below its resting position; close does not translate,
|
|
12
|
+
// opacity only (see the Preview/ShareBrowser/NetworkDialog verticalCenterOffset/y bindings).
|
|
13
|
+
var translateUpPx = 10
|
|
14
|
+
|
|
15
|
+
// How long the pointer rests at the left edge before the auto-hidden rail opens; the OEM bar holds its centre peek 120 ms (Bar.qml:834).
|
|
16
|
+
var dwellMs = 120
|