Skip to content

feat: add customizable keyboard shortcuts via textpod.toml#54

Open
SandroPacella wants to merge 2 commits intofreetonik:mainfrom
SandroPacella:feat/customizable-shortcuts
Open

feat: add customizable keyboard shortcuts via textpod.toml#54
SandroPacella wants to merge 2 commits intofreetonik:mainfrom
SandroPacella:feat/customizable-shortcuts

Conversation

@SandroPacella
Copy link

Summary

Resolves #46.

  • Adds an optional textpod.toml config file that users place in the working directory alongside notes.md
  • Shortcuts are defined under a [shortcuts] section (e.g. save = "Cmd+Enter")
  • Supported modifiers: Ctrl, Cmd, Alt, Shift, combinable with any key name
  • If no config file is present, defaults to Ctrl+Enter — fully backwards-compatible
  • Invalid TOML logs a warning and falls back to defaults
  • Placeholder text in the textarea updates to reflect the configured shortcut

Implementation

Reuses the existing {{FAVICON}} template injection pattern: the backend reads textpod.toml on startup, serializes the shortcuts config to JSON, and injects it into the HTML via {{SHORTCUTS_CONFIG}} and {{SAVE_SHORTCUT_DISPLAY}} template variables. The frontend matchShortcut() function parses the binding string and matches it against KeyboardEvent properties.

Config example

[shortcuts]
save = "Cmd+Enter"

Test plan

  • No config file: default Ctrl+Enter saves notes, placeholder says "Ctrl+Enter to save."
  • With save = "Cmd+Enter": Cmd+Enter saves, Ctrl+Enter does not, placeholder says "Cmd+Enter to save."
  • Invalid TOML: error logged, app starts with defaults
  • Tested on Chrome and Firefox on macOS

Reads an optional textpod.toml config file from the working directory
on startup. Shortcuts are defined under a [shortcuts] section (e.g.
save = "Cmd+Enter") and injected into the frontend via template
variables, reusing the existing {{FAVICON}} replacement pattern.

Defaults to Ctrl+Enter when no config file is present, keeping full
backwards compatibility. Resolves freetonik#46.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds customizable keyboard shortcuts to Textpod by introducing an optional textpod.toml configuration file. The implementation addresses issue #46, where users reported that the hardcoded Ctrl+Enter shortcut conflicts with Firefox on Mac. The feature maintains full backward compatibility by using Ctrl+Enter as the default when no config file is present.

Changes:

  • Added TOML-based configuration system for keyboard shortcuts with graceful fallback to defaults
  • Implemented frontend shortcut matching logic that supports Ctrl, Cmd, Alt, and Shift modifiers
  • Updated UI placeholder text to dynamically display the configured save shortcut

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/main.rs Added Config and ShortcutsConfig structs, TOML parsing at startup, and template injection for shortcuts
src/index.html Added matchShortcut() function and updated keydown handler to use configurable shortcuts
README.md Added Configuration section documenting the textpod.toml format and supported modifiers
Cargo.toml Added toml and serde_json dependencies
Cargo.lock Updated dependency tree with new packages

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +133
function matchShortcut(event, binding) {
const parts = binding.split('+');
const key = parts.pop();
const mods = parts.map(m => m.toLowerCase());
return (
event.key === key &&
event.ctrlKey === mods.includes('ctrl') &&
event.metaKey === mods.includes('cmd') &&
event.altKey === mods.includes('alt') &&
event.shiftKey === mods.includes('shift')
);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The matchShortcut function has a case-sensitivity issue. The event.key property returns capitalized key names (e.g., "Enter", "Escape") but the function performs a strict equality comparison without normalizing the case. This means a configuration like save = "Cmd+enter" (lowercase) would fail to match. Consider normalizing both sides to lowercase for the key comparison, or document that key names must match the exact case returned by event.key (e.g., "Enter" not "enter").

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +84
#[derive(Deserialize, Default)]
struct Config {
#[serde(default)]
shortcuts: ShortcutsConfig,
}

#[derive(Deserialize, Serialize)]
struct ShortcutsConfig {
#[serde(default = "default_save_shortcut")]
save: String,
}

impl Default for ShortcutsConfig {
fn default() -> Self {
Self {
save: default_save_shortcut(),
}
}
}

fn default_save_shortcut() -> String {
"Ctrl+Enter".to_string()
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no validation of the shortcut configuration value. If a user provides an invalid shortcut string (e.g., empty string, malformed format, or invalid key names), the application will still accept it and inject it into the HTML, potentially causing the shortcut functionality to silently fail. Consider adding validation to ensure the shortcut string follows the expected format (modifiers+key) and providing a warning or error if it's invalid.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +129
let shortcuts_json =
serde_json::to_string(&config.shortcuts).expect("failed to serialize shortcuts config");
let html = INDEX_HTML
.replace(
"{{FAVICON}}",
format!("data:image/svg+xml;base64,{favicon}").as_str(),
)
.replace("{{SHORTCUTS_CONFIG}}", &shortcuts_json)
.replace("{{SAVE_SHORTCUT_DISPLAY}}", &config.shortcuts.save);
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The shortcut configuration is injected directly into the HTML without any sanitization or escaping. While the TOML parser provides some protection, a malicious or malformed textpod.toml file could potentially inject arbitrary JavaScript code into the page via the {{SAVE_SHORTCUT_DISPLAY}} and {{SHORTCUTS_CONFIG}} placeholders. Consider HTML-escaping the save shortcut display string and ensuring the JSON serialization is safe (which it should be with serde_json, but verify the dependencies are legitimate - see other comments).

Copilot uses AI. Check for mistakes.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we care.

@freetonik
Copy link
Owner

Huh, I've tried this new copilot code review feature. I did not focus-read the suggestions yet, but on a brief scan some of them at least sound relevant.

… default browser behavior when save shortcut is triggered

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@SandroPacella
Copy link
Author

SandroPacella commented Feb 26, 2026

Huh, I've tried this new copilot code review feature. I did not focus-read the suggestions yet, but on a brief scan some of them at least sound relevant.

I like these suggestions copilot made:

For e.preventDefault() —> I just pushed a commit. Without it the browser could intercept the shortcut before we ever see it.

On the case-sensitivity and validation points: both valid imo, and I want to address them. I’d rather not tack them onto this PR since they’d expand the scope pretty quickly. Would it make more sense to open follow-up issues so we can track properly, or do you want them handled here first?​​​​​​​​​​​​​​​​


An afterthought: I use StackEdit often and I hate when its key bindings override mine, so that's something I'll keep in mind here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Customizable shortcuts

3 participants