bookmarks-v1.3 feat(add /bk launcher provider) (#165)

* feat: changed keybinds + new keybinds

Changes:

- ctrl+n -> ctrl+j
- ctrl+p -> ctrl+k

Additions:

- ctrl+h or left: return to the root level when in a folder
- ctrl+l or right: get into a folder

* feat: /bk Provider

- feat: /bk Provider
- chore: better preview imgs
- docs: updated README
- chore: updated Turkish translation strings
- chore: updated thumbnail

* feat: keybind changes + index tracking fix

- feat: Some more keybind changes
    - Added: ctrl + n for new bookmark
    - Changed: ctrl + f for search (changed from ctrl + s)
- fix: Added index tracking for bookmark entries
    - For keyboard-centric use, navigating to a folder by accident then
returning to root was annoying since no tracking meant that we'd always
land on the first entry within the folder or the root itself
- docs: updated README to reflected the changes and added a changelog
section

* fix translation merge

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
This commit is contained in:
mgu
2026-07-30 21:22:53 -04:00
committed by GitHub
co-authored by Lemmy
parent 8cd39525e2
commit dabe695a93
13 changed files with 572 additions and 74 deletions
+122 -42
View File
@@ -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.
| 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 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**
```
[ <entry>, <entry>]
```
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": [ <bookmark>, <bookmark>, ... ]
}
```
| 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:
@@ -101,21 +177,25 @@ 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. |
| `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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

+292
View File
@@ -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
+94 -19
View File
@@ -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
+33 -2
View File
@@ -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"]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 51 KiB

+12
View File
@@ -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"
+13 -5
View File
@@ -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"
}
},