mirror of
https://github.com/abue-ammar/tinycast.git
synced 2026-10-02 08:14:38 +08:00
Let a quicklink answer to an alias
Settings ▸ Quicklinks rows now carry an AliasField, keyed by the quicklink's entry ID, so a quicklink can be reached by a short alias like any application. AliasField takes the preference key and name directly, with an init(entry:) for the panes built on LauncherItemsSection. The field dims on a quicklink hidden from root search, whose entry the ranker never sees. Ranking, the launcher's alias chip, backup coverage and alias cleanup on delete were already kind-agnostic and needed no change. Closes #383
This commit is contained in:
@@ -105,7 +105,9 @@ struct SettingsFilterField: View {
|
||||
|
||||
/// Dressed like `ShortcutRecorder`; a persistent `TextField` — swapping views broke repeat focus.
|
||||
struct AliasField: View {
|
||||
let entry: AppEntry
|
||||
/// The owner's `preferenceKey`, taken raw so a row without an `AppEntry` can carry one too.
|
||||
let key: String
|
||||
let name: String
|
||||
@Environment(AliasStore.self) private var aliases
|
||||
@State private var draft = ""
|
||||
@FocusState private var focused: Bool
|
||||
@@ -134,13 +136,13 @@ struct AliasField: View {
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("Clear alias for \(entry.name)")
|
||||
.accessibilityLabel("Clear alias for \(name)")
|
||||
}
|
||||
}
|
||||
.onAppear { draft = aliases.alias(for: entry.preferenceKey) ?? "" }
|
||||
.onAppear { draft = aliases.alias(for: key) ?? "" }
|
||||
// A backup import replaces the table out from under an unfocused row.
|
||||
.onChange(of: aliases.revision) { _, _ in
|
||||
if !focused { draft = aliases.alias(for: entry.preferenceKey) ?? "" }
|
||||
if !focused { draft = aliases.alias(for: key) ?? "" }
|
||||
}
|
||||
.padding(.horizontal, Theme.Spacing.sm)
|
||||
.frame(width: Theme.Size.shortcutRecorder, height: 24)
|
||||
@@ -150,17 +152,17 @@ struct AliasField: View {
|
||||
focused ? Color.accentColor : Theme.Colors.cardStroke, lineWidth: 1)
|
||||
)
|
||||
.clipShape(shape)
|
||||
.accessibilityLabel("Alias for \(entry.name)")
|
||||
.accessibilityLabel("Alias for \(name)")
|
||||
}
|
||||
|
||||
/// The one commit path — ↵ or focus landing elsewhere; a blank draft removes the alias.
|
||||
private func commit() {
|
||||
aliases.setAlias(draft, for: entry.preferenceKey)
|
||||
draft = aliases.alias(for: entry.preferenceKey) ?? ""
|
||||
aliases.setAlias(draft, for: key)
|
||||
draft = aliases.alias(for: key) ?? ""
|
||||
}
|
||||
|
||||
private func revert() {
|
||||
draft = aliases.alias(for: entry.preferenceKey) ?? ""
|
||||
draft = aliases.alias(for: key) ?? ""
|
||||
focused = false
|
||||
}
|
||||
|
||||
@@ -169,3 +171,9 @@ struct AliasField: View {
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
extension AliasField {
|
||||
init(entry: AppEntry) {
|
||||
self.init(key: entry.preferenceKey, name: entry.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ struct QuicklinksSettingsView: View {
|
||||
}
|
||||
Button("Add Quicklink…") { core.quicklinkCoordinator.editQuicklink(nil) }
|
||||
} footer: {
|
||||
Text("Name it, paste a link, then give it a shortcut if you want one.")
|
||||
Text("Name it, paste a link, then add an alias or a shortcut if you want one.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
@@ -166,6 +166,10 @@ private struct QuicklinkSettingsRow: View {
|
||||
.help("Hidden from root search")
|
||||
}
|
||||
|
||||
// An alias only reaches the ranker through the root-search slice, so it dims with it.
|
||||
AliasField(key: quicklink.entryID, name: quicklink.name)
|
||||
.settingsEnabled(quicklink.showsInRootSearch)
|
||||
|
||||
ShortcutRecorder(action: .quicklink(id: quicklink.id))
|
||||
|
||||
Button(action: onEdit) {
|
||||
|
||||
@@ -151,7 +151,9 @@ visibility checkbox, not a per-invocation action, so the ⌘K menu stays out of
|
||||
on `LauncherItemsSection` puts an `AliasField` on each row, dressed like the `ShortcutRecorder`
|
||||
beside it; edits store as typed and trim when the field loses focus, and a blank means none. That
|
||||
list filters by **membership only**, keeping the index's name order — re-ranking it per keystroke
|
||||
would move the row being edited out from under its own field editor.
|
||||
would move the row being edited out from under its own field editor. A pane with a hand-written row
|
||||
hands `AliasField` the key itself: Settings ▸ Quicklinks passes `Quicklink.entryID`, and dims the
|
||||
field on a quicklink hidden from root search, whose entry the ranker never sees.
|
||||
|
||||
Aliases ride along in a settings backup (`launcherAliases`), and deleting what an alias points at —
|
||||
uninstalling an app, deleting a quicklink or custom command, uninstalling an extension — removes it
|
||||
|
||||
@@ -122,7 +122,9 @@ Tinycast's own dialog and leaves no partial state.
|
||||
|
||||
Quicklinks are their own `AppEntry.Kind`, their own `AppIndex` slice and their own launcher section,
|
||||
between System Settings and Snippets. Only the **name** is indexed; the destination is not searchable
|
||||
(a URL is a subsequence of almost any query). Per-quicklink "Show in root search" filters the slice;
|
||||
(a URL is a subsequence of almost any query) — beside the name, a quicklink answers to whatever
|
||||
[user alias](launcher.md#user-aliases) its Settings row carries, which is why a hidden one dims the
|
||||
field. Per-quicklink "Show in root search" filters the slice;
|
||||
the pane's "Show in launcher" takes the section and the four Quicklink commands out of the
|
||||
launcher together, leaving shortcuts and the pane itself working.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user