feat: filter channel category tagging
This commit is contained in:
@@ -85,11 +85,11 @@ The full-description view scrolls with ↓ / ↑, Ctrl-J / Ctrl-K, or PageDown /
|
||||
|
||||
### Text input
|
||||
|
||||
These shortcuts work in search, category/channel-filter, and new-tag fields:
|
||||
These shortcuts work in search, category/channel/tag-filter, and new-tag fields:
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| Enter | Submit the search, browse the selected category/channel, or stage the new tag |
|
||||
| Enter | Submit the search, browse the selected category/channel, leave tag-filter mode, or stage the new tag |
|
||||
| Esc | Leave insert mode or abandon the new-tag name |
|
||||
| Backspace | Delete one character |
|
||||
| Option/Alt-Backspace or Ctrl-W | Delete the previous whitespace-delimited word |
|
||||
@@ -115,9 +115,12 @@ Press **`U` (Shift-U)** on a video to browse its creator directly, including cha
|
||||
|
||||
### Channel tags (`s`, or `S` for bulk editing)
|
||||
|
||||
- `j` / `k` or arrow keys navigate; Space toggles a tag.
|
||||
- `n` prompts for a new tag name. Enter stages it checked and returns to the list.
|
||||
- Enter **in the main tag list** saves the staged memberships. Esc cancels without writing.
|
||||
- **Normal:** `j` / `k` or arrow keys navigate; Space toggles a tag; `i` edits the filter.
|
||||
- **Insert:** type a case-insensitive substring to filter live; arrow keys or Ctrl-J / Ctrl-K navigate matches. Enter or Esc returns to normal mode without saving or clearing the filter.
|
||||
- Filtering only hides rows: checked and unchecked staged memberships outside the filter are retained. With no matches, navigation and toggling do nothing. Reopening the dialog clears its filter.
|
||||
- `n` prompts for a new tag name. Enter stages it checked, clears the filter, and selects the new tag; Esc abandons the name.
|
||||
- Enter **in the normal-mode tag list** saves all staged memberships, including hidden tags. Esc cancels without writing. These controls apply to both single-channel and bulk tagging.
|
||||
- Filter actions are configurable as `tags.edit`, `tags.input.next`, and `tags.input.previous`; text editing uses the shared `input.*` actions.
|
||||
|
||||
Bulk editing applies the exact checked tag set to **every saved channel in the current category**, not only channels with loaded videos. Tags shared by every target start checked; mixed memberships start unchecked. Review the checked set before saving: unchecked tags are removed from all targets. Other channels, saved registrations, empty categories, pins, and comments are preserved.
|
||||
|
||||
|
||||
+18
-4
@@ -17,6 +17,7 @@ pub enum Context {
|
||||
SelectorNormal,
|
||||
SelectorInput,
|
||||
TagList,
|
||||
TagFilter,
|
||||
TagInput,
|
||||
Help,
|
||||
}
|
||||
@@ -29,6 +30,7 @@ const ALL: &[Context] = &[
|
||||
Context::SelectorNormal,
|
||||
Context::SelectorInput,
|
||||
Context::TagList,
|
||||
Context::TagFilter,
|
||||
Context::TagInput,
|
||||
Context::Help,
|
||||
];
|
||||
@@ -36,6 +38,7 @@ const VIEW: &[Context] = &[Context::Normal, Context::Info, Context::Description]
|
||||
const TEXT: &[Context] = &[
|
||||
Context::SearchInput,
|
||||
Context::SelectorInput,
|
||||
Context::TagFilter,
|
||||
Context::TagInput,
|
||||
];
|
||||
const NON_TEXT: &[Context] = &[
|
||||
@@ -78,7 +81,7 @@ actions! {
|
||||
Interrupt, "app.interrupt", "Exit immediately", ALL, ["ctrl+c"];
|
||||
Quit, "app.quit", "Quit the application", &[Context::Normal, Context::Info, Context::Description, Context::Help], ["q"];
|
||||
Help, "help.toggle", "Open or close keyboard help", NON_TEXT, ["?"];
|
||||
InputSubmit, "input.submit", "Submit search, selector filter or new tag", TEXT, ["enter"];
|
||||
InputSubmit, "input.submit", "Submit search, browse selector, leave tag filter or stage new tag", TEXT, ["enter"];
|
||||
InputCancel, "input.cancel", "Leave text input without submitting", TEXT, ["escape"];
|
||||
DeleteCharacter, "input.delete.character", "Delete the previous character", TEXT, ["backspace"];
|
||||
DeleteWord, "input.delete.word", "Delete the previous word", TEXT, ["alt+backspace", "ctrl+w"];
|
||||
@@ -112,6 +115,9 @@ actions! {
|
||||
SelectorClose, "selector.close", "Close the category or channel picker", &[Context::SelectorNormal], ["escape"];
|
||||
TagNext, "tags.select.next", "Select the next category tag", &[Context::TagList], ["j", "down"];
|
||||
TagPrevious, "tags.select.previous", "Select the previous category tag", &[Context::TagList], ["k", "up"];
|
||||
TagEdit, "tags.edit", "Edit the category tag filter", &[Context::TagList], ["i"];
|
||||
TagInputNext, "tags.input.next", "Select the next tag while editing the filter", &[Context::TagFilter], ["down", "ctrl+j"];
|
||||
TagInputPrevious, "tags.input.previous", "Select the previous tag while editing the filter", &[Context::TagFilter], ["up", "ctrl+k"];
|
||||
TagToggle, "tags.toggle", "Toggle the selected category tag", &[Context::TagList], ["space"];
|
||||
TagNew, "tags.new", "Enter a new category tag name", &[Context::TagList], ["n"];
|
||||
TagSave, "tags.save", "Save category tag changes", &[Context::TagList], ["enter"];
|
||||
@@ -262,7 +268,7 @@ impl Key {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Bindings {
|
||||
labels: Vec<Vec<String>>,
|
||||
contexts: [Vec<(Key, Action)>; 9],
|
||||
contexts: [Vec<(Key, Action)>; ALL.len()],
|
||||
}
|
||||
|
||||
impl Default for Bindings {
|
||||
@@ -378,7 +384,7 @@ impl Bindings {
|
||||
}
|
||||
|
||||
fn build(keys: Vec<Vec<Key>>) -> io::Result<Self> {
|
||||
let mut contexts: [Vec<(Key, Action)>; 9] = std::array::from_fn(|_| Vec::new());
|
||||
let mut contexts: [Vec<(Key, Action)>; ALL.len()] = std::array::from_fn(|_| Vec::new());
|
||||
for definition in DEFINITIONS {
|
||||
for &context in definition.contexts {
|
||||
let entries = &mut contexts[context as usize];
|
||||
@@ -483,6 +489,12 @@ impl Bindings {
|
||||
"CHANNEL TAGS",
|
||||
&[
|
||||
(&[TagNext, TagPrevious], "Move between tags"),
|
||||
(&[TagEdit], "Enter filter INSERT mode"),
|
||||
(&[TagInputNext, TagInputPrevious], "Move while filtering"),
|
||||
(
|
||||
&[InputSubmit, InputCancel],
|
||||
"Leave tag filter without saving",
|
||||
),
|
||||
(&[TagToggle], "Toggle a tag"),
|
||||
(&[TagNew], "Enter a new tag name"),
|
||||
(&[TagSave], "Save tags"),
|
||||
@@ -565,7 +577,9 @@ impl Bindings {
|
||||
}
|
||||
if matches!(
|
||||
actions,
|
||||
[DescriptionNext, DescriptionPrevious] | [SelectorInputNext, SelectorInputPrevious]
|
||||
[DescriptionNext, DescriptionPrevious]
|
||||
| [SelectorInputNext, SelectorInputPrevious]
|
||||
| [TagInputNext, TagInputPrevious]
|
||||
) && matches(actions[0], &["↓", "Ctrl+j"])
|
||||
&& matches(actions[1], &["↑", "Ctrl+k"])
|
||||
{
|
||||
|
||||
+353
-78
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user