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,115 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Thumbs.js" as Thumbs
|
|
4
|
+
.import "Format.js" as Format
|
|
5
|
+
|
|
6
|
+
// The ExtThumbs board's storage-class gate: network shares and phones are off, USB
|
|
7
|
+
// drives are on, and anything else is local and always gets thumbnails. A class is
|
|
8
|
+
// src/backend/extclass.rs's own word, carried beside the fsinfo line once per
|
|
9
|
+
// directory change, so nothing here classifies and there is no second classifier.
|
|
10
|
+
|
|
11
|
+
// Whole sentences with "%1" for a value, filled by fill so each translates as one; one per state.
|
|
12
|
+
var WORDS = {
|
|
13
|
+
hide: "Hide %1 thumbnails",
|
|
14
|
+
show: "Show %1 thumbnails",
|
|
15
|
+
isOn: "%1 thumbnails are on",
|
|
16
|
+
isOff: "%1 thumbnails are off"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Sample input: "network" answers "thumbNetwork", "" answers "".
|
|
20
|
+
function keyForClass(storageClass) {
|
|
21
|
+
if (storageClass === "network") return "thumbNetwork"
|
|
22
|
+
if (storageClass === "phone") return "thumbPhone"
|
|
23
|
+
if (storageClass === "usb") return "thumbUsb"
|
|
24
|
+
return ""
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The menu row is present only on network, phone or USB storage, never locally.
|
|
28
|
+
function present(storageClass) {
|
|
29
|
+
return keyForClass(storageClass) !== ""
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Sample input: ("usb", {}) answers true, ("network", {}) answers false.
|
|
33
|
+
function classOn(storageClass, preview) {
|
|
34
|
+
var key = keyForClass(storageClass)
|
|
35
|
+
if (key === "") return true
|
|
36
|
+
if (key === "thumbUsb") return !preview || preview.thumbUsb !== false
|
|
37
|
+
return !!preview && preview[key] === true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function classOff(storageClass, preview) {
|
|
41
|
+
return present(storageClass) && !classOn(storageClass, preview)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Off is cache-only, not blank: a visible row still asks, and the backend answers from
|
|
45
|
+
// the shared cache with one stat and never starts a decoder. Thumbnails already made
|
|
46
|
+
// still show, because the cache answers them. Sample input: ("network", {}) answers true.
|
|
47
|
+
function cacheOnly(storageClass, preview) {
|
|
48
|
+
if (!classOff(storageClass, preview)) return false
|
|
49
|
+
return !preview || preview.thumbnails !== "off"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The thumbed line's record: a cache-only miss on a generating class returns to unasked, any other reply is remembered.
|
|
53
|
+
function thumbReply(state, row, file, cap, known, storageClass, preview) {
|
|
54
|
+
if (file === "" && state.file[row] === Thumbs.CACHE_ASKED && (!known || !cacheOnly(storageClass, preview)))
|
|
55
|
+
return { state: Thumbs.miss(state, row, true, cap), missed: true }
|
|
56
|
+
return { state: Thumbs.remember(state, row, file, cap), missed: false }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// In Columns the preview is manual on a class that is off: one condition, two surfaces.
|
|
60
|
+
function manualHold(storageClass, preview) {
|
|
61
|
+
return cacheOnly(storageClass, preview)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The menu row's own entrance: absent off the three classes, otherwise labelled for
|
|
65
|
+
// the class and its state. Sample input: ({storageClass: "usb"}) answers "Hide USB thumbnails".
|
|
66
|
+
function entry(e, p) {
|
|
67
|
+
if (!present(p.storageClass)) return false
|
|
68
|
+
e.label = label(p.storageClass, classOn(p.storageClass, p.thumbPreview))
|
|
69
|
+
return true
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Sample input: ("network", false) answers "Show network thumbnails".
|
|
73
|
+
function label(storageClass, on) {
|
|
74
|
+
var noun = storageClass === "usb" ? "USB" : storageClass
|
|
75
|
+
return Format.fill(on ? WORDS.hide : WORDS.show, noun)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The status bar's own verdict for the toggle, in the board's words.
|
|
79
|
+
// Sample input: ("network", true) answers "Network thumbnails are on".
|
|
80
|
+
function statusLine(storageClass, on) {
|
|
81
|
+
var noun = storageClass === "usb" ? "USB"
|
|
82
|
+
: storageClass.charAt(0).toUpperCase() + storageClass.slice(1)
|
|
83
|
+
return Format.fill(on ? WORDS.isOn : WORDS.isOff, noun)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// A class switched on decodes again: only its cache-only marks leave the map, so a
|
|
87
|
+
// real refusal stays answered and is never decoded twice. Sample input:
|
|
88
|
+
// {file: {3: "", 4: "/c/x.png", 5: "cache-missed"}, order: [3, 4, 5]} forgets 5 alone.
|
|
89
|
+
function forgetMisses(state) {
|
|
90
|
+
var kept = []
|
|
91
|
+
for (var i = 0; i < state.order.length; i++) {
|
|
92
|
+
var at = state.order[i]
|
|
93
|
+
if (state.file[at] === Thumbs.CACHE_MISS || state.file[at] === Thumbs.CACHE_ASKED) delete state.file[at]
|
|
94
|
+
else kept.push(at)
|
|
95
|
+
}
|
|
96
|
+
state.order = kept
|
|
97
|
+
return { file: state.file, order: state.order }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Sample input: ("network", {}) answers "only|media"; a zoom step or another class keeps it, so refreshes nothing.
|
|
101
|
+
function verdict(storageClass, preview) {
|
|
102
|
+
var mode = preview && preview.thumbnails ? preview.thumbnails : "media"
|
|
103
|
+
return (cacheOnly(storageClass, preview) ? "only" : "full") + "|" + mode
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// FileView reads the whole file, so a row over the gate is refused, not truncated, the
|
|
107
|
+
// same rule ui/PreviewText.qml already keeps. Text previews on network and phone storage
|
|
108
|
+
// read at most the first 256 KiB. Sample input: ("phone") answers 262144.
|
|
109
|
+
var TEXT_PREVIEW_MAX = 1048576
|
|
110
|
+
var REMOTE_TEXT_PREVIEW_MAX = 262144
|
|
111
|
+
|
|
112
|
+
function textLimit(storageClass) {
|
|
113
|
+
if (storageClass === "network" || storageClass === "phone") return REMOTE_TEXT_PREVIEW_MAX
|
|
114
|
+
return TEXT_PREVIEW_MAX
|
|
115
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
.import "Input.js" as Input
|
|
3
|
+
|
|
4
|
+
.import "Format.js" as Format
|
|
5
|
+
.import "Match.js" as Match
|
|
6
|
+
.import "Thumbs.js" as Thumbs
|
|
7
|
+
|
|
8
|
+
// The strip's two sentences and the no-match tile's heading, each filled by Format.fill.
|
|
9
|
+
var WORDS = {
|
|
10
|
+
noMatchOne: "No match among 1 item",
|
|
11
|
+
noMatchMany: "No match among %1 items",
|
|
12
|
+
shown: "%1 of %2 shown",
|
|
13
|
+
shownOfFolder: "%1 of %2 shown · of %3 in this folder",
|
|
14
|
+
nothingMatched: "Nothing matched %1"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// The filter narrows the listing already on screen: no walk, no round trip, and every row it keeps
|
|
18
|
+
// is one the backend has already sent. ui/js/Search.js is its bigger sibling, which walks the
|
|
19
|
+
// subtree and replaces the listing; the design canvas draws the two as different things and this
|
|
20
|
+
// file never reaches ui/Backend.qml at all.
|
|
21
|
+
//
|
|
22
|
+
// Two index spaces meet here. A "listing row" is what the backend numbers, what a selection holds
|
|
23
|
+
// and what every write operation sends; a "view position" is where a row is drawn. With no filter
|
|
24
|
+
// up the two are the same number, and at() and viewOf() below are the only places that convert.
|
|
25
|
+
|
|
26
|
+
// The listing rows a query leaves standing, in the order the backend sent them, or null when there
|
|
27
|
+
// is no filter at all. Only the rows the pane holds can be tested, so the answer is a subsequence
|
|
28
|
+
// of the held window; scope() below is what says so when the window is not the whole listing.
|
|
29
|
+
// It is a subsequence and never a re-ranking, which is what keeps directories ahead of files.
|
|
30
|
+
function shown(rows, held, query) {
|
|
31
|
+
if (query.length === 0) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
var out = []
|
|
35
|
+
for (var i = 0; i < rows.length; i++) {
|
|
36
|
+
// The same run ui/js/Match.js paints in the name, so a row can never match without showing why.
|
|
37
|
+
if (Match.run(rows[i].n, query).start >= 0) {
|
|
38
|
+
out.push(held + i)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function at(list, view) {
|
|
45
|
+
if (list === null) {
|
|
46
|
+
return view
|
|
47
|
+
}
|
|
48
|
+
// A delegate can outlive the view position it was built for by a frame when the match list
|
|
49
|
+
// shrinks, so a position past the end answers -1: a listing row nothing holds and nothing draws.
|
|
50
|
+
return view < list.length ? list[view] : -1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// -1 for a row the filter hides, which is what the callers below clamp against.
|
|
54
|
+
function viewOf(list, row) {
|
|
55
|
+
return list === null ? row : list.indexOf(row)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Issue 92, nixfred: a query matching nothing leaves the cursor on a row the filter no longer draws.
|
|
59
|
+
function cursorShown(pane) { return viewOf(pane.shown, pane.cursorIndex) >= 0 }
|
|
60
|
+
|
|
61
|
+
// The strip's own sentence, SearchFilter rules 1 and 2: what the filter kept, out of the rows it
|
|
62
|
+
// could test, and the directory those rows are a window on. The scope is the half that must not be
|
|
63
|
+
// dropped, because zero matches among 350 loaded rows is not zero matches in a 104,812-file
|
|
64
|
+
// directory; the tail goes when the window is the whole listing, which is nothing left to say.
|
|
65
|
+
function summary(list, loaded, total) {
|
|
66
|
+
if (list === null) {
|
|
67
|
+
return ""
|
|
68
|
+
}
|
|
69
|
+
if (total > loaded) {
|
|
70
|
+
return Format.fill(WORDS.shownOfFolder, Format.count(list.length), Format.count(loaded), Format.count(total))
|
|
71
|
+
}
|
|
72
|
+
return Format.fill(WORDS.shown, Format.count(list.length), Format.count(loaded))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// States' no-match tile: the pane names the query back and says how many rows it tested, because
|
|
76
|
+
// the query is up in a strip the reader may have looked away from.
|
|
77
|
+
function noMatch(total) {
|
|
78
|
+
return total === 1 ? WORDS.noMatchOne : Format.fill(WORDS.noMatchMany, Format.count(total))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The rows drawn between two ends, which is not the range between them: a plain index range would
|
|
82
|
+
// sweep up every row the filter hid in the gap, and nothing on screen would say it had.
|
|
83
|
+
function between(list, a, b) {
|
|
84
|
+
var lo = Math.min(a, b)
|
|
85
|
+
var hi = Math.max(a, b)
|
|
86
|
+
var out = []
|
|
87
|
+
for (var i = 0; i < list.length; i++) {
|
|
88
|
+
if (list[i] >= lo && list[i] <= hi) {
|
|
89
|
+
out.push(list[i])
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// A request cut back to the rows still drawn. A filtered viewport covers a set and not a run, so
|
|
96
|
+
// the range-shaped planners hand back rows the filter hides, and asking for those would fetch work
|
|
97
|
+
// nothing draws.
|
|
98
|
+
function keep(asked, list) {
|
|
99
|
+
if (list === null) {
|
|
100
|
+
return asked
|
|
101
|
+
}
|
|
102
|
+
var drawn = {}
|
|
103
|
+
for (var i = 0; i < list.length; i++) {
|
|
104
|
+
drawn[list[i]] = true
|
|
105
|
+
}
|
|
106
|
+
var out = []
|
|
107
|
+
for (var j = 0; j < asked.length; j++) {
|
|
108
|
+
if (drawn[asked[j]] === true) {
|
|
109
|
+
out.push(asked[j])
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return out
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// A pending thumbnail can be hidden inside the planner's span as well as outside its viewport.
|
|
116
|
+
function cut(work, list, state) {
|
|
117
|
+
var drop = work.drop.slice()
|
|
118
|
+
if (list !== null) {
|
|
119
|
+
var drawn = {}
|
|
120
|
+
for (var i = 0; i < list.length; i++) drawn[list[i]] = true
|
|
121
|
+
var dropping = {}
|
|
122
|
+
for (var j = 0; j < drop.length; j++) dropping[drop[j]] = true
|
|
123
|
+
for (var key in state.file) {
|
|
124
|
+
var row = Number(key)
|
|
125
|
+
if (Thumbs.pending(state.file[key]) && drawn[row] !== true && dropping[row] !== true)
|
|
126
|
+
drop.push(row)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { ask: keep(work.ask, list), drop: drop }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The listing rows a view range covers, for the two planners that take a first and a last.
|
|
133
|
+
function span(list, first, last) {
|
|
134
|
+
if (list === null) {
|
|
135
|
+
return { first: first, last: last }
|
|
136
|
+
}
|
|
137
|
+
if (list.length === 0) {
|
|
138
|
+
return { first: 0, last: -1 }
|
|
139
|
+
}
|
|
140
|
+
var lo = Math.max(0, Math.min(list.length - 1, first))
|
|
141
|
+
var hi = Math.max(0, Math.min(list.length - 1, last))
|
|
142
|
+
return { first: list[lo], last: list[hi] }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// The transitions, taking ui/Pane.qml's root the way ui/js/Search.js and ui/js/Sort.js do: the pane
|
|
146
|
+
// holds the state, this holds what the state does.
|
|
147
|
+
|
|
148
|
+
// "/" opens the query line. Unlike the search's, nothing is committed to start a walk: the rows are
|
|
149
|
+
// already here, so the listing narrows on the keystroke itself.
|
|
150
|
+
function start(pane) {
|
|
151
|
+
pane.filterTyping = true
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function typed(pane, character) {
|
|
155
|
+
apply(pane, pane.filterQuery + character)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function backspace(pane) {
|
|
159
|
+
apply(pane, pane.filterQuery.substring(0, pane.filterQuery.length - 1))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Enter hands the keyboard back to the list with the filter standing, which is what makes y, x, d
|
|
163
|
+
// and r work over the narrowed rows; an empty query has nothing to stand, so it closes instead.
|
|
164
|
+
function commit(pane) {
|
|
165
|
+
if (pane.filterQuery.length === 0) {
|
|
166
|
+
close(pane)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
pane.filterTyping = false
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Esc, and every fresh listing. The selection is deliberately not cleared: prune() below has already
|
|
173
|
+
// cut it to rows the filter was showing, so what is left is exactly what was on screen.
|
|
174
|
+
function close(pane) {
|
|
175
|
+
pane.filterQuery = ""
|
|
176
|
+
pane.filterTyping = false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The new query is applied here rather than by writing the property alone, because the selection has
|
|
180
|
+
// to lose the rows that just dropped out before anything can act on rows nobody can see. The next
|
|
181
|
+
// match list is computed rather than read back off the pane's binding, so the order is not a guess.
|
|
182
|
+
function apply(pane, query) {
|
|
183
|
+
var next = shown(pane.rows, pane.held, query)
|
|
184
|
+
prune(pane, next)
|
|
185
|
+
if (query !== pane.filterQuery) pane.filterKeyAt = Date.now()
|
|
186
|
+
pane.filterQuery = query
|
|
187
|
+
if (query.length > 0) pane.filterNarrowedAt = Date.now()
|
|
188
|
+
// A cursor the filter just hid takes the first row still standing, so it is never off screen.
|
|
189
|
+
if (next !== null && next.length > 0 && viewOf(next, pane.cursorIndex) < 0) {
|
|
190
|
+
pane.cursorIndex = next[0]
|
|
191
|
+
pane.cursorSeq += 1
|
|
192
|
+
pane.showRow(0)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// A selection you cannot see is one you can act on by accident, and trash, cut and copy all take the
|
|
197
|
+
// selection with no confirmation at all: what the filter hides, it takes out of the selection too.
|
|
198
|
+
function prune(pane, list) {
|
|
199
|
+
if (list === null) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
var drawn = {}
|
|
203
|
+
for (var i = 0; i < list.length; i++) {
|
|
204
|
+
drawn[list[i]] = true
|
|
205
|
+
}
|
|
206
|
+
var was = pane.selectedIndices()
|
|
207
|
+
var dropped = 0
|
|
208
|
+
for (var j = 0; j < was.length; j++) {
|
|
209
|
+
if (drawn[was[j]] !== true) {
|
|
210
|
+
pane.selection.toggle(was[j])
|
|
211
|
+
dropped += 1
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (dropped > 0) {
|
|
215
|
+
pane.selectionVersion += 1
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Cursor is a listing row but moves through drawn rows, so both steps convert.
|
|
220
|
+
function setCursor(pane, index, context) {
|
|
221
|
+
setCursorView(pane, viewOf(pane.shown, index), context)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function setCursorView(pane, view, context) {
|
|
225
|
+
if (pane.shownTotal === 0) {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
var to = Math.max(0, Math.min(pane.shownTotal - 1, view))
|
|
229
|
+
pane.cursorIndex = at(pane.shown, to)
|
|
230
|
+
// Every cursor setter bumps cursorSeq, so a plain move ends a Shift gesture.
|
|
231
|
+
pane.cursorSeq += 1
|
|
232
|
+
pane.showRow(to, context)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function moveCursor(pane, delta) {
|
|
236
|
+
setCursorView(pane, viewOf(pane.shown, pane.cursorIndex) + delta)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// The wheel moved the viewport and the cursor follows it. Both ends are view positions, because
|
|
240
|
+
// under a filter the listing rows they name are not a run and a numeric clamp would be meaningless.
|
|
241
|
+
function clampCursor(pane, first, last) {
|
|
242
|
+
var was = viewOf(pane.shown, pane.cursorIndex)
|
|
243
|
+
var to = Math.max(first, Math.min(last, was))
|
|
244
|
+
if (to !== was) {
|
|
245
|
+
var held = pulledFrom(pane)
|
|
246
|
+
var from = held >= 0 ? held : pane.cursorIndex
|
|
247
|
+
pane.cursorIndex = at(pane.shown, to)
|
|
248
|
+
pane.cursorSeq = (pane.cursorSeq || 0) + 1
|
|
249
|
+
pane.cursorPulled = { from: from, to: pane.cursorIndex, seq: pane.cursorSeq, version: pane.selectionVersion }
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// The row the cursor held before the wheel pulled it, while no move, mark or new listing came since (issue 286); else -1.
|
|
254
|
+
function pulledFrom(pane) {
|
|
255
|
+
var pull = pane.cursorPulled
|
|
256
|
+
var current = pull && pull.seq === (pane.cursorSeq || 0) && pull.to === pane.cursorIndex
|
|
257
|
+
&& pull.version === pane.selectionVersion
|
|
258
|
+
return current ? pull.from : -1
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// The query line's own keys while it has the caret, the shape ui/js/Search.js typeKey uses for the
|
|
262
|
+
// search's. Enter commits, escape abandons, backspace shortens, every printable character narrows.
|
|
263
|
+
function typeKey(event, pane) {
|
|
264
|
+
// Ctrl+V and Shift+Insert type the clipboard's first line as one narrowing (issue 287).
|
|
265
|
+
if (Input.isPasteKey(event))
|
|
266
|
+
return Input.pasteInto(pane, function (text) { typed(pane, text) })
|
|
267
|
+
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
268
|
+
commit(pane)
|
|
269
|
+
return true
|
|
270
|
+
}
|
|
271
|
+
if (event.key === Qt.Key_Escape) {
|
|
272
|
+
close(pane)
|
|
273
|
+
return true
|
|
274
|
+
}
|
|
275
|
+
if (event.key === Qt.Key_Backspace) {
|
|
276
|
+
backspace(pane)
|
|
277
|
+
return true
|
|
278
|
+
}
|
|
279
|
+
if (Input.isPrintable(event.text)) {
|
|
280
|
+
typed(pane, event.text)
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
return true
|
|
284
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
.import "UserDirs.js" as UserDirs
|
|
3
|
+
|
|
4
|
+
// Owned here so Backend.qml loads no sort closure at startup.
|
|
5
|
+
var ORDERS = ["name", "size", "mtime", "kind"]
|
|
6
|
+
|
|
7
|
+
// Written only on a user sort in that folder, read once per listing, oldest first.
|
|
8
|
+
|
|
9
|
+
// The cap ui.json holds, oldest first; setting past it drops the folders sorted longest ago.
|
|
10
|
+
var MAX = 500
|
|
11
|
+
|
|
12
|
+
// Sample input: XDG_DOWNLOAD_DIR="$HOME/Inbox"; absent PICTURES and VIDEOS use their conventional names.
|
|
13
|
+
function newestPaths(body, home) {
|
|
14
|
+
var roles = ["DOWNLOAD", "PICTURES", "VIDEOS"]
|
|
15
|
+
var names = ["Downloads", "Pictures", "Videos"]
|
|
16
|
+
var paths = []
|
|
17
|
+
if (!home) return paths
|
|
18
|
+
var configured = {}
|
|
19
|
+
var entries = UserDirs.readRoles(body, home)
|
|
20
|
+
for (var e = 0; e < entries.length; e++)
|
|
21
|
+
configured[entries[e].role] = entries[e].path
|
|
22
|
+
for (var i = 0; i < roles.length; i++) {
|
|
23
|
+
var path = configured[roles[i]] === undefined ? home + "/" + names[i] : configured[roles[i]]
|
|
24
|
+
if (path && path !== home && paths.indexOf(path) < 0)
|
|
25
|
+
paths.push(path)
|
|
26
|
+
}
|
|
27
|
+
return paths
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function defaultOrder(path, newest) {
|
|
31
|
+
var folder = String(path || "").replace(/\/+$/, "")
|
|
32
|
+
return newest && newest.indexOf(folder) >= 0 ? { key: "mtime", reverse: true } : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Settings stores Modified as "date" while ORDERS and the backend spell it "mtime".
|
|
36
|
+
function liveKey(key) {
|
|
37
|
+
return key === "date" ? "mtime" : key
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Sample input: {"/home/gm/Work": {"key": "size", "reverse": true}}.
|
|
41
|
+
function get(sorts, path) {
|
|
42
|
+
var entry = sorts ? sorts[path] : null
|
|
43
|
+
if (!entry || ORDERS.indexOf(liveKey(entry.key)) < 0)
|
|
44
|
+
return null
|
|
45
|
+
return { key: liveKey(entry.key), reverse: entry.reverse === true }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function has(sorts, path) {
|
|
49
|
+
return get(sorts, path) !== null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function count(sorts) {
|
|
53
|
+
return sorts ? Object.keys(sorts).length : 0
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// A re-sort moves its folder to the most recent end; past the cap the oldest entries go.
|
|
57
|
+
function set(sorts, path, key, reverse) {
|
|
58
|
+
var next = {}
|
|
59
|
+
if (sorts) {
|
|
60
|
+
for (var k in sorts) {
|
|
61
|
+
if (k !== path)
|
|
62
|
+
next[k] = sorts[k]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
next[path] = { key: key, reverse: reverse === true }
|
|
66
|
+
var keys = Object.keys(next)
|
|
67
|
+
while (keys.length > MAX)
|
|
68
|
+
delete next[keys.shift()]
|
|
69
|
+
return next
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function forget(sorts, path) {
|
|
73
|
+
var next = {}
|
|
74
|
+
if (sorts) {
|
|
75
|
+
for (var k in sorts) {
|
|
76
|
+
if (k !== path)
|
|
77
|
+
next[k] = sorts[k]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return next
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A user sort writes its folder only while remembering is on.
|
|
84
|
+
function shouldRemember(remember, path) {
|
|
85
|
+
return remember !== false && !!path
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Columns pass keep: a saved order wins over XDG defaults; remembering off uses the global order, except kind, unsupported by peeks.
|
|
89
|
+
function peekOrder(sorts, path, remember, keep, newest, fallback) {
|
|
90
|
+
if (!Array.isArray(keep)) return {}
|
|
91
|
+
var own = remember !== false ? get(sorts, path) || defaultOrder(path, newest)
|
|
92
|
+
: fallback ? orderFor(null, path, fallback, false) : null
|
|
93
|
+
if (remember === false && own && own.key === "name" && !own.reverse) return {}
|
|
94
|
+
return own && own.key !== "kind" ? { by: own.key, desc: own.reverse } : {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// An unknown stored key reads as the default, so a newer Flea never wedges this one.
|
|
98
|
+
function orderFor(sorts, path, fallback, remember, newest) {
|
|
99
|
+
if (remember !== false) {
|
|
100
|
+
var own = get(sorts, path) || defaultOrder(path, newest)
|
|
101
|
+
if (own)
|
|
102
|
+
return own
|
|
103
|
+
}
|
|
104
|
+
fallback = fallback || {}
|
|
105
|
+
var key = liveKey(fallback.key)
|
|
106
|
+
return { key: ORDERS.indexOf(key) >= 0 ? key : "name",
|
|
107
|
+
reverse: fallback.reverse === true }
|
|
108
|
+
}
|