diff --git a/bookmarks/README.md b/bookmarks/README.md index 63c4b36..8e9e675 100644 --- a/bookmarks/README.md +++ b/bookmarks/README.md @@ -1,28 +1,55 @@ # Bookmarks -A bookmarks/shortcuts plugin for Noctalia. You can add folders and commands to a list of bookmarks. -Reorder bookmarks, categorize them with folders, set glyphs and labels, and simply left click to -execute. All of it is user defined, stored in a JSON file you can backup. +![](./thumbnail.webp) + +A user-managed bookmarks plugin for Noctalia. You can add bookmarks by selecting a glyph, a label, +and a shell command to execute. You can backup your bookmarks as well! ## Plugin -| Field | Value | -| ------- | ---------------------------------- | -| ID | `dunarand/bookmarks` | -| Entries | Bar widget: `bar`; panel: `panel`; | - -Bar widget `bar` is not required. Instead, you can assign a keybind to directly open the `panel`, -which is shown in the Usage section. +| Field | Value | +| --------------- | ----------------------------------------------------------------- | +| ID | `dunarand/bookmarks` | +| Entries | Bar widget: `bar`; panel: `panel`; Launcher provider: `provider`; | +| Launcher Prefix | `/bk` | ## Requirements - Noctalia v5.0.0 or higher - `nohup` (Optional): For "Run in background" wrapper toggle +## IPC & Keybinds + +1. Open the bookmarks panel: + + ```sh + noctalia msg panel-toggle dunarand/bookmarks:panel + ``` + +2. Open the bookmarks panel in search mode (immediately puts you into search mode) so that you can + use your bookmarks panel as a launcher: + + ```sh + noctalia msg panel-toggle dunarand/bookmarks:panel search + ``` + +You can interact with the bookmarks list via keybinds: + +| Keybind | Purpose | +| ------------------------- | ---------------------------------------------------------------- | +| `CTRL + J` or Down Arrow | Select the next (below) item | +| `CTRL + K` or Up Arrow | Select the previous (above) item | +| `CTRL + H` or Left Arrow | Return to the root level when in a folder | +| `CTRL + L` or Right Arrow | Get into a folder | +| `CTRL + F` | Search bookmarks | +| `CTRL + N` | New bookmark | +| `Enter` / `Return` | Execute the selected bookmark's command / Navigate into a folder | + ## Usage -You can add the widget to your bar or assign the following command to your compositor keybinds. For -example, in Hyprland v0.55+ +The plugin ships a bar widget, a panel, and a launcher provider. The bar widget just launches the +panel. The panel is able to be toggled via IPC. For example, in Hyprland v0.55 or higher, you can +assign it to a keybind as follows: ``` hl.bind( @@ -31,27 +58,14 @@ hl.bind( ) ``` -You can interact with the bookmarks list via keybinds: - -| Keybind | Purpose | -| ------------------------ | ---------------------------------------------------------------- | -| `CTRL + N` or Down Arrow | Select the next (below) item | -| `CTRL + P` or Up Arrow | Select the previous (above) item | -| `CTRL + S` | Search bookmarks | -| `Enter` / `Return` | Execute the selected bookmark's command / Navigate into a folder | - - Bookmarks and folders are listed in the main panel. ![](./assets/preview-1.png) -- "?" button shows tooltips: bookmark command and its description. - - ![](./assets/preview-2.png) - - You can create or edit bookmarks by assigning them a glyph, a label, a command, and an optional description. - ![](./assets/preview-3.png) + ![](./assets/preview-2.png) - "Run in background" toggle wraps the command you defined in the following way: @@ -70,24 +84,86 @@ You can interact with the bookmarks list via keybinds: - You can create folders and nest other bookmarks within folders. - ![](./assets/preview-4.png) - Folders cannot nest other folders. This is by design and it'll not change unless I find a - genuine usecase. You can edit folders by clicking on the "pen" icon next to its name. + genuine use case. You can edit folders by clicking on the "pen" icon next to its name. - You can press the "eye" icon to enter edit mode where you can edit, delete, and reorder bookmarks. + This is a setting that you can disable. - ![](./assets/preview-5.png) + ![](./assets/preview-3.png) -- Each bookmark can be edited anytime. +- You can also use your launcher to query your bookmarks with the `/bk` prefix. - ![](./assets/preview-6.png) + ![](./assets/preview-4.png) + - Queries are folder-aware, meaning if you nested a bookmark inside a folder, the folder name will + be displayed as well. + +## Bookmarks Data The saved bookmarks are written to `$NOCTALIA_STATE_HOME/plugins/data/dunarand/bookmarks/data.json`. By default, `$NOCTALIA_STATE_HOME` should point to `~/.local/state/noctalia`. You can point to a different location for saving and backing up your bookmarks. This setting is configurable via **plugin settings** under Settings -> Plugins. Only JSON format is accepted. +You can manage the bookmark data via external scripts. + +**Root** + +``` +[ , ] +``` + +The root is a JSON array of entries. Order in the array is display order (used directly by drag-and +drop reordering). + +**Entry** + +Two types of entries exist: `bookmark` and `folder`. + +### Bookmarks + +```JSON +{ + "type": "bookmark", + "glyph": "bookmark", + "label": "My Bookmark", + "cmd": "firefox", + "description": "Opens Firefox", + "runInBackground": false, + "runInTerminal": false +} +``` + +| Key | Type | Required / Default | Notes | +| ------------------------------------------------- | ------- | ------------------------------------------- | ----------------------------------------------- | +| `type` | string | `"bookmark"` | | +| `glyph` | string | falls back to `"bookmark"` if empty/missing | | +| `label` | string | required, enforced at save time | `""` fails validation | +| `cmd` | string | required for bookmarks | shell command to execute, enforced at save time | +| `description` | string | optional, defaults to `""` | shown in the info tooltip if enabled | +| `runInBackground` | boolean | optional, defaults to `false` | mutually exclusive with `runInTerminal` in | +| the UI but the schema itself doesn't enforce that | +| `runInTerminal` | boolean | optional, defaults to `false` | + +### Folders + +```JSON +{ + "type": "folder", + "glyph": "folder", + "label": "My Folder", + "items": [ , , ... ] +} +``` + +| Key | Type | Required/Default | Notes | +| ------------------------------------ | ------ | ----------------------------------------------------------------------- | ---------- | +| `type` | string | `"folder"` | +| `glyph` | string | falls back to `"folder"` if empty/missing | +| `label` | string | required | +| `items` | array | optional, treated as `{}` if missing, `folder.items=folder.items or {}` | contents — | +| bookmarks only, one level of nesting | + ## Settings The bar widget has the following settings: @@ -100,22 +176,26 @@ The bar widget has the following settings: The plugin itself has the following settings: -| Setting | Type | Default | Description | -| ------------------ | ------ | ------- | ------------------------------------------------------------------------------------- | -| `data_path` | `file` | | data.json file to store the saved bookmarks. Leave empty to use the default location. | -| `show_info_button` | `bool` | `true` | Shows the "?" tooltip button on the bookmark entries. | +| Setting | Type | Default | Description | +| -------------------- | -------- | ----------- | ------------------------------------------------------------------------------------- | +| `data_path` | `file` | | data.json file to store the saved bookmarks. Leave empty to use the default location. | +| `show_info_button` | `bool` | `true` | Shows the "?" tooltip button on the bookmark entries. | +| `enable_bk_provider` | `bool` | `true` | Enable bookmarks launcher provider | +| `bk_sort_by` | `select` | `"history"` | Provider's sorting strategy. Options: `"history"`, `"usage_count"` | -## IPC +## Changelog -1. Open the bookmarks panel: +### v1.3.0 - ```sh - noctalia msg panel-toggle dunarand/bookmarks:panel - ``` - -2. Open the bookmarks panel in search mode (immediately puts you into search mode) so that you can - use your bookmarks panel as a launcher: - - ```sh - noctalia msg panel-toggle dunarand/bookmarks:panel search - ``` +- Added `/bk` launcher provider +- Changed the following keybinds (check IPC & Keybinds section for what they do) + - CTRL + N changed to CTRL + J + - CTRL + P changed to CTRL + K + - CTRL + S changed to CTRL + F +- Added the following keybinds (check IPC & Keybinds section for what they do) + - CTRL + H or Left Arrow + - CTRL + L or Right Arrow + - CTRL + N +- Index tracking on root level for keyboard-centric usage + - Previously, when using a keyboard to navigate, going into a folder would reset the selected + entry's index to 1 causing fat fingers to be annoying diff --git a/bookmarks/assets/preview-1.png b/bookmarks/assets/preview-1.png index 637dda8..fc16af2 100644 Binary files a/bookmarks/assets/preview-1.png and b/bookmarks/assets/preview-1.png differ diff --git a/bookmarks/assets/preview-2.png b/bookmarks/assets/preview-2.png index 8d71934..9e96279 100644 Binary files a/bookmarks/assets/preview-2.png and b/bookmarks/assets/preview-2.png differ diff --git a/bookmarks/assets/preview-3.png b/bookmarks/assets/preview-3.png index 88f5cb6..5d834ec 100644 Binary files a/bookmarks/assets/preview-3.png and b/bookmarks/assets/preview-3.png differ diff --git a/bookmarks/assets/preview-4.png b/bookmarks/assets/preview-4.png index f646068..0e0f90c 100644 Binary files a/bookmarks/assets/preview-4.png and b/bookmarks/assets/preview-4.png differ diff --git a/bookmarks/assets/preview-5.png b/bookmarks/assets/preview-5.png deleted file mode 100644 index 9436722..0000000 Binary files a/bookmarks/assets/preview-5.png and /dev/null differ diff --git a/bookmarks/assets/preview-6.png b/bookmarks/assets/preview-6.png deleted file mode 100644 index b4647ef..0000000 Binary files a/bookmarks/assets/preview-6.png and /dev/null differ diff --git a/bookmarks/bk_provider.luau b/bookmarks/bk_provider.luau new file mode 100644 index 0000000..75027c5 --- /dev/null +++ b/bookmarks/bk_provider.luau @@ -0,0 +1,292 @@ +-- Launcher provider: "/bk" fuzzy-searches bookmark labels (root + one level +-- of folders, bookmarks only - same scope as the panel's own search) and +-- runs the selected one on activation. Reads the same data.json the panel +-- writes, resolved the same way (configured data_path, else the plugin's +-- data dir), so no state is shared beyond the file on disk. + +local function resolveDataPath() + local configured = noctalia.getConfig("data_path") + if type(configured) == "string" and noctalia.string.trim(configured) ~= "" then + return noctalia.expandPath(noctalia.string.trim(configured)), nil + end + + local dataDir, dataDirErr = noctalia.pluginDataDir() + if dataDir == nil then + return nil, dataDirErr + end + return dataDir .. "/data.json", nil +end + +local function entryType(entry) + return entry.type == "folder" and "folder" or "bookmark" +end + +-- Reads and decodes data.json fresh on every query. The panel is the only +-- writer and bookmark lists are small, so there's no need to cache across +-- queries here. +local function loadBookmarks() + local path, pathErr = resolveDataPath() + if path == nil then + noctalia.log("bookmarks-provider: could not resolve data path: " .. tostring(pathErr)) + return {} + end + if not noctalia.fileExists(path) then + return {} + end + local raw, readErr = noctalia.readFile(path) + if raw == nil then + noctalia.log("bookmarks-provider: failed to read data file: " .. tostring(readErr)) + return {} + end + local decoded, decodeErr = noctalia.json.decode(raw) + if type(decoded) ~= "table" then + noctalia.log("bookmarks-provider: data file is corrupt: " .. tostring(decodeErr)) + return {} + end + return decoded +end + +-- Flat list of { entry, path } across root and one level of folders, +-- bookmarks only (folders themselves aren't launchable results). +local function flatten(bookmarks) + local flat = {} + for _, entry in ipairs(bookmarks) do + if entryType(entry) == "folder" then + for _, subEntry in ipairs(entry.items or {}) do + if entryType(subEntry) ~= "folder" then + table.insert(flat, { entry = subEntry, path = entry.label or "" }) + end + end + else + table.insert(flat, { entry = entry, path = nil }) + end + end + return flat +end + +-- Bookmarks are indexed by a stable synthetic id (path-qualified label), +-- rather than by array position, since onActivate only receives the id +-- string and the list is re-read from disk on every query. +local function resultId(item) + return (item.path or "") .. "\30" .. (item.entry.label or "") +end + +local function buildResults(query) + local bookmarks = loadBookmarks() + local flat = flatten(bookmarks) + + local results = {} + for _, item in ipairs(flat) do + local score + if query == "" then + score = 0 + else + score = noctalia.fuzzyScore(query, item.entry.label or "") + end + if score ~= nil then + table.insert(results, { + id = resultId(item), + title = item.entry.label or "", + subtitle = item.path, + glyph = item.entry.glyph or "bookmark", + score = score, + }) + end + end + + table.sort(results, function(a, b) + return (a.score or 0) > (b.score or 0) + end) + return results +end + +local function providerEnabled() + return noctalia.getConfig("enable_bk_provider") == true +end + +-- "history" (most-recently-used first) or "usage_count" (most-frequently- +-- used first). Only affects ordering when the query is empty; a non-empty +-- query always ranks by fuzzy match score, same as before. +local function sortBy() + local value = noctalia.getConfig("bk_sort_by") + if value == "usage_count" then + return "usage_count" + end + return "history" +end + +local function statsPath() + local dataDir = noctalia.pluginDataDir() + if dataDir == nil then + return nil + end + return dataDir .. (sortBy() == "history" and "/bk_history.json" or "/bk_usage.json") +end + +-- Reads and decodes the current stats file (history array or usage-count +-- map, per sortBy()), falling back to an empty table when the file is +-- missing, unreadable, or corrupt. Shared by getSortScores/recordActivation +-- so the read/decode/default boilerplate only lives in one place. +local function readStatsData() + local path = statsPath() + if path == nil then + return {}, nil + end + local file = noctalia.readFile(path) + if file == nil then + return {}, path + end + local decoded = noctalia.json.decode(file) + if type(decoded) ~= "table" then + return {}, path + end + return decoded, path +end + +-- Scores used to order results when the query is empty, keyed by result id. +-- History ranks by recency (higher = more recently used); usage_count ranks +-- by frequency (higher = used more often). Reads/decodes the stats file +-- once for the whole batch rather than once per result. Ids missing from +-- the stats file fall back to 0 (unranked, sorted last). +local function getSortScores(ids) + local data = readStatsData() + local scores = {} + + if sortBy() == "history" then + for _, id in ipairs(ids) do + local i = table.find(data, id) + scores[id] = i ~= nil and (#data - i) or 0 + end + else + for _, id in ipairs(ids) do + scores[id] = data[id] or 0 + end + end + + return scores +end + +-- Records an activation for ranking purposes: pushes `id` to the front of +-- the history list, or increments its usage count, depending on `sort_by`. +local function recordActivation(id) + local data, path = readStatsData() + if path == nil then + return + end + + if sortBy() == "history" then + local i = table.find(data, id) + if i ~= nil then + table.remove(data, i) + end + table.insert(data, 1, id) + else + data[id] = (data[id] or 0) + 1 + end + + local encoded = noctalia.json.encode(data) + if encoded ~= nil then + noctalia.writeFile(path, encoded) + end +end + +function onQuery(query) + query = noctalia.string.trim(query) + + if not providerEnabled() then + launcher.setResults(query, {}) + return + end + + local results = buildResults(query) + + if query == "" then + local ids = {} + for _, result in ipairs(results) do + table.insert(ids, result.id) + end + local scores = getSortScores(ids) + for _, result in ipairs(results) do + result.score = scores[result.id] or 0 + end + table.sort(results, function(a, b) + return (a.score or 0) > (b.score or 0) + end) + end + + if #results == 0 then + launcher.setResults(query, { + { + id = "", + title = noctalia.tr("no_bookmarks"), + glyph = "bookmark-off", + }, + }) + return + end + launcher.setResults(query, results) +end + +function onActivate(id) + if id == "" or not providerEnabled() then + return + end + + local wantPath, wantLabel = id:match("^([^\30]*)\30(.*)$") + if wantLabel == nil then + return + end + if wantPath == "" then + wantPath = nil + end + + local bookmarks = loadBookmarks() + for _, item in ipairs(flatten(bookmarks)) do + if item.entry.label == wantLabel and (item.path or nil) == wantPath then + local entry = item.entry + local cmd = entry.cmd + if type(cmd) ~= "string" or cmd == "" then + noctalia.notifyError(noctalia.tr("title"), noctalia.tr("err_no_command")) + return + end + local label = entry.label or cmd + + recordActivation(id) + + if entry.runInTerminal == true then + noctalia.runInTerminal(cmd) + return + end + + local finalCmd = cmd + if entry.runInBackground == true then + finalCmd = "nohup " .. cmd .. " >/dev/null 2>&1 &" + end + + local ok = noctalia.runAsync(finalCmd, function(result) + if entry.runInBackground == true then + return + end + if result.exitCode ~= 0 then + local detail = noctalia.string.trim(result.stderr or "") + local msg = noctalia.tr( + "err_exit_code", + { label = label, code = tostring(result.exitCode) } + ) + if detail ~= "" then + msg = msg .. ": " .. detail + end + noctalia.notifyError(noctalia.tr("title"), msg) + end + end) + + if not ok then + noctalia.notifyError( + noctalia.tr("title"), + noctalia.tr("err_launch_failed", { label = label }) + ) + end + return + end + end +end diff --git a/bookmarks/panel.luau b/bookmarks/panel.luau index 9fddaef..01f448f 100644 --- a/bookmarks/panel.luau +++ b/bookmarks/panel.luau @@ -33,6 +33,37 @@ local focusSearchOnRender = false -- one-shot: grabs the search input on the nex local selectedIndex = nil -- 1-based position within the currently visible list, or nil +-- Last selected index per scope, keyed by currentFolder (0 for root, since +-- folder indices start at 1). Restores position when re-entering a folder +-- or coming back to root instead of resetting to 1. +local rememberedSelection = {} + +local function rememberSelection(folder) + rememberedSelection[folder or 0] = selectedIndex +end + +local function recallSelection(folder, rowCount) + if rowCount == nil or rowCount <= 0 then + return nil + end + return math.min(math.max(rememberedSelection[folder or 0] or 1, 1), rowCount) +end + +-- ui.scroll has no scroll-into-view API, so instead of scrolling to the +-- selection we only render a window of rows around it - the selection is +-- then always inside what's drawn. ROWS_PER_SCREEN is a rough fit for the +-- panel's height; doesn't need to be exact. +local ROWS_PER_SCREEN = 9 + +local function visibleWindow(count, selected) + if count <= ROWS_PER_SCREEN or selected == nil then + return 1, math.min(count, ROWS_PER_SCREEN) + end + local startIndex = + math.max(1, math.min(selected - ROWS_PER_SCREEN // 2, count - ROWS_PER_SCREEN + 1)) + return startIndex, startIndex + ROWS_PER_SCREEN - 1 +end + local draftKind = "bookmark" -- "bookmark" | "folder", set when the form opens local draftGlyph = "bookmark" local draftLabel = "" @@ -689,9 +720,10 @@ local function listView() }) ) else + local startIndex, endIndex = visibleWindow(#results, selectedIndex) local rows = {} - for rowIndex, result in ipairs(results) do - table.insert(rows, searchResultRow(result, rowIndex)) + for rowIndex = startIndex, endIndex do + table.insert(rows, searchResultRow(results[rowIndex], rowIndex)) end table.insert(children, ui.scroll({ flexGrow = 1, gap = 2 }, rows)) end @@ -762,15 +794,20 @@ local function listView() }) ) else - local rows = {} local count = #list - for index, entry in ipairs(list) do + local startIndex, endIndex = visibleWindow(count, selectedIndex) + local rows = {} + for index = startIndex, endIndex do if editMode then table.insert(rows, insertionGap(index, "before")) end - table.insert(rows, entryRow(entry, index)) + table.insert(rows, entryRow(list[index], index)) end - if editMode and count > 0 then + -- The trailing "after last row" gap only belongs at the very end of + -- the real list, not at the end of a windowed slice that's cut off + -- partway through - an "after" gap mid-list would duplicate the + -- "before" gap of the row right after the window. + if editMode and count > 0 and endIndex == count then table.insert(rows, insertionGap(count, "after")) end table.insert(children, ui.scroll({ flexGrow = 1, gap = 2 }, rows)) @@ -976,16 +1013,20 @@ function _onOpenFolder(index) if entry == nil or entryType(entry) ~= "folder" then return end + -- Remember where we were in root before diving into the folder. + rememberSelection(currentFolder) -- currentFolder is only ever set from root (folders are one level deep), -- so `index` here is always an index into the root `bookmarks` array. currentFolder = index - selectedIndex = 1 + selectedIndex = recallSelection(currentFolder, #(entry.items or {})) render() end function _onBackToRoot() + -- Remember where we were inside the folder before leaving it. + rememberSelection(currentFolder) currentFolder = nil - selectedIndex = 1 + selectedIndex = recallSelection(currentFolder, #bookmarks) render() end @@ -1043,7 +1084,11 @@ function _onDeleteFolder() noctalia.tr("title"), noctalia.tr("folder_deleted", { label = removed.label or "" }) ) + -- Folder selections are now stale (indices after the deleted one + -- shifted), so drop everything but root's. + rememberedSelection = { [0] = rememberedSelection[0] } currentFolder = nil + selectedIndex = recallSelection(currentFolder, #bookmarks) render() end @@ -1309,10 +1354,14 @@ end -- `noctalia msg panel-open dunarand/bookmarks:panel search`, or any other -- IPC caller) open the panel straight into a focused search box, turning -- the bookmark list into a fast fuzzy launcher without a second keypress. --- Root-only, same as ctrl+s: searching already isn't offered inside a +-- Root-only, same as ctrl+f: searching already isn't offered inside a -- folder, so context is ignored there. +-- +-- To open in search mode and close from a single keybind: +-- noctalia msg panel-toggle dunarand/bookmarks:panel search function onOpen(context) view = "list" + editingIndex = nil editingRoot = false editMode = false @@ -1320,6 +1369,7 @@ function onOpen(context) searchQuery = "" searchRev += 1 selectedIndex = 1 + rememberedSelection = {} loadBookmarks() noctalia.state.set("bookmarks_open", true) if context == "search" then @@ -1332,12 +1382,18 @@ function onClose() noctalia.state.set("bookmarks_open", false) end --- Keyboard navigation: ctrl+n/down and ctrl+p/up move the selection through --- the currently visible rows (search results or the active folder's list), --- looping past either end; Return activates whatever is selected; ctrl+s --- jumps focus to the search box (root view only, since that's the only --- place it's rendered). Only acts on key-down (pressed == true) so each --- chord fires once per press. +-- Keyboard navigation: +-- - ctrl+j/down and ctrl+k/up move the selection through the currently +-- visible rows (search results or the active folder's contents), +-- wrapping past either end. +-- - ctrl+l/right enters the selected folder, if any. +-- - ctrl+h/left returns from a folder to the root list. +-- - Return activates the selected row (opens a folder or runs a bookmark). +-- - ctrl+f focuses the search box (root view only, since folders don't +-- have one). +-- - ctrl+n opens the new-bookmark form. +-- root-only - same restriction as the "New Folder" button). +-- Only acts on key-down (pressed == true) so each chord fires once per press. function onKey(chord, pressed) if not pressed then return @@ -1346,7 +1402,7 @@ function onKey(chord, pressed) return end - if chord == "ctrl+s" then + if chord == "ctrl+f" then if currentFolder == nil then searchRev += 1 focusSearchOnRender = true @@ -1355,6 +1411,18 @@ function onKey(chord, pressed) return end + if chord == "ctrl+n" then + _onNewBookmark() + return + end + + if chord == "ctrl+h" or chord == "left" then + if currentFolder ~= nil then + _onBackToRoot() + end + return + end + local rows = visibleRows() local count = #rows if count == 0 then @@ -1362,21 +1430,28 @@ function onKey(chord, pressed) return end - if chord == "ctrl+n" or chord == "down" then + if chord == "ctrl+j" or chord == "down" then if selectedIndex == nil then selectedIndex = 1 else selectedIndex = selectedIndex % count + 1 end render() - elseif chord == "ctrl+p" or chord == "up" then + elseif chord == "ctrl+k" or chord == "up" then if selectedIndex == nil then selectedIndex = count else selectedIndex = (selectedIndex - 2) % count + 1 end render() - elseif chord == "Return" then + elseif chord == "ctrl+l" or chord == "right" then + local row = rows[selectedIndex] + if row ~= nil and row.kind == "entry" then + if entryType(row.entry) == "folder" then + _onOpenFolder(row.index) + end + end + elseif chord == "return" then local row = rows[selectedIndex] activateRow(row) end diff --git a/bookmarks/plugin.toml b/bookmarks/plugin.toml index da8d013..25de378 100644 --- a/bookmarks/plugin.toml +++ b/bookmarks/plugin.toml @@ -1,6 +1,6 @@ id = "dunarand/bookmarks" name = "Bookmarks" -version = "1.2.1" +version = "1.3.0" plugin_api = 13 author = "dunarand" license = "MIT" @@ -49,6 +49,37 @@ entry = "bar.luau" description_key = "settings.text.description" default = "Bookmarks" +[[setting]] +key = "enable_bk_provider" +type = "bool" +label_key = "settings.enable_bk_provider.label" +description_key = "settings.enable_bk_provider.description" +default = true + +[[setting]] +key = "bk_sort_by" +type = "select" +label_key = "settings.bk_sort_by.label" +description_key = "settings.bk_sort_by.description" +default = "history" +visible_when = { key = "enable_bk_provider", values = ["true"] } + + [[setting.options]] + value = "history" + label_key = "settings.bk_sort_by.options.history" + [[setting.options]] + value = "usage_count" + label_key = "settings.bk_sort_by.options.usage_count" + +[[launcher_provider]] +id = "provider" +entry = "bk_provider.luau" +prefix = "bk" +glyph = "bookmark" +include_in_global_search = false +debounce_ms = 100 + + [[panel]] id = "panel" entry = "panel.luau" @@ -57,4 +88,4 @@ height = 470 placement = "attached" position = "auto" open_near_click = true -capture_keys = ["ctrl+n", "ctrl+p", "up", "down", "Return", "ctrl+s"] +capture_keys = ["ctrl+h", "ctrl+j", "ctrl+k", "ctrl+l", "left", "down", "up", "right", "ctrl+n", "ctrl+f", "return"] diff --git a/bookmarks/thumbnail.webp b/bookmarks/thumbnail.webp index 3e666ae..5f7f62c 100644 Binary files a/bookmarks/thumbnail.webp and b/bookmarks/thumbnail.webp differ diff --git a/bookmarks/translations/en.json b/bookmarks/translations/en.json index 6246180..73de41c 100644 --- a/bookmarks/translations/en.json +++ b/bookmarks/translations/en.json @@ -40,10 +40,22 @@ "save_button": "Save", "search_placeholder": "Search bookmarks...", "settings": { + "bk_sort_by": { + "description": "How bookmarks are ordered in \"/bk\" results when there's no search text.", + "label": "/bk sort order", + "options": { + "history": "Recently used", + "usage_count": "Most used" + } + }, "data_path": { "description": "Where data.json is stored. Leave empty to use the default plugin data directory.", "label": "Data file" }, + "enable_bk_provider": { + "description": "Adds a \"/bk\" launcher command to fuzzy-search and run your bookmarks.", + "label": "Enable /bk launcher provider" + }, "glyph": { "description": "Bar widget's glyph", "label": "Glyph" diff --git a/bookmarks/translations/tr.json b/bookmarks/translations/tr.json index 2029bdf..87db9e4 100644 --- a/bookmarks/translations/tr.json +++ b/bookmarks/translations/tr.json @@ -25,7 +25,7 @@ "err_save_failed": "Kaydedilemedi: {error}", "folder_deleted": "\"{label}\" klasörü ve içeriği silindi", "folder_name_field": "Klasör ismi", - "glyph_field": "Sembol (ör. bookmark)", + "glyph_field": "Simge (ör. bookmark)", "hide_controls_tooltip": "Sıralama/düzenleme/silme kontrollerini gizle", "info_cmd_line": "Komut: {cmd}", "info_description_line": "Açıklama: {description}", @@ -40,24 +40,32 @@ "save_button": "Kaydet", "search_placeholder": "Yer imlerinde ara...", "settings": { + "bk_sort_by": { + "description": "\"/bk\" sonuçlarının arama yapılmadığı durumda yer imlerini nasıl sıralacağını belirler.", + "label": "/bk sıralama önceliği", + "options": { + "history": "Son kullanılanlar", + "usage_count": "En sık kullanılanlar" + } + }, "data_path": { "description": "data.json dosyasının saklandığı konum. Varsayılan eklenti veri konumunu kullanmak için boş bırakın.", "label": "Veri dosyası" }, "glyph": { - "description": "Bar aracının sembolü (ör. bookmark)", - "label": "Sembol" + "description": "Çubuk aracının simgesi (ör. bookmark)", + "label": "Simge" }, "show_info_button": { "description": "Yer imlerinde (klasörlerde değil) üzerine gelindiğinde açıklamalarını ve komutlarını görüntülemek için bir \"?\" düğmesi göster.", "label": "Bilgi butonunu göster" }, "text": { - "description": "Bar aracının etiketi. Barda sadece sembolün görünmesi için boş bırakın.", + "description": "Çubuk aracının etiketi. Çubukta sadece simgenin görünmesi için boş bırakın.", "label": "Yazı" }, "tooltip": { - "description": "Bar aracının isim etiketi", + "description": "Çubuk aracının isim etiketi", "label": "İsim etiketi" } },