Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { TemplateAction, performAction } from "./actions";
import { loadLegacyTemplates } from "./legacyTemplates";
import { Logger } from "./logger";
import { PromiseGroup } from "./utils/promises";
import { PluginSettingsRegistry, DefaultNoteTemplateIdSetting, DefaultTodoTemplateIdSetting, DefaultTemplatesConfigSetting } from "./settings";
import { PluginSettingsRegistry, DefaultNoteTemplateIdSetting, DefaultTodoTemplateIdSetting, DefaultTemplatesConfigSetting, KeyboardShortcutsSetting } from "./settings";
import { LocaleGlobalSetting, DateFormatGlobalSetting, TimeFormatGlobalSetting, ProfileDirGlobalSetting } from "./settings/global";
import { DefaultTemplatesConfig } from "./settings/defaultTemplatesConfig";
import { CommandsPanel } from "./views/commandsPanel";
Expand Down Expand Up @@ -265,6 +265,48 @@ joplin.plugins.register({
}));


// Register keyboard-shortcut commands for individual templates (Issue #122)
// Users configure these in Settings > Templates > "Template keyboard shortcuts (JSON)"
const shortcutEntries = await KeyboardShortcutsSetting.get();
const shortcutMenuItems: { commandName: string; accelerator?: string }[] = [];

for (let i = 0; i < shortcutEntries.length; i++) {
const entry = shortcutEntries[i];
const commandName = `templateShortcut_${i + 1}`;

const actionEnum: TemplateAction =
entry.action === "newNote"
? TemplateAction.NewNote
: entry.action === "newTodo"
? TemplateAction.NewTodo
: TemplateAction.InsertText;

joplinCommands.add(joplin.commands.register({
name: commandName,
label: entry.label || `Template shortcut ${i + 1}`,
execute: async () => {
if (!entry.templateId) {
await joplin.views.dialogs.showMessageBox(
`Template shortcut ${i + 1}: no templateId configured.`);
return;
}
const template = await getTemplateFromId(entry.templateId);
if (!template) {
await joplin.views.dialogs.showMessageBox(
`Template shortcut ${i + 1}: template not found (ID: ${entry.templateId}).`);
return;
}
await performActionWithParsedTemplate(actionEnum, template);
}
}));

const menuItem: { commandName: string; accelerator?: string } = { commandName };
if (entry.accelerator && entry.accelerator.trim() !== "") {
menuItem.accelerator = entry.accelerator.trim();
}
shortcutMenuItems.push(menuItem);
}

// Create templates menu
await joplin.views.menus.create("templates", "Templates", [
{
Expand Down Expand Up @@ -304,6 +346,10 @@ joplin.plugins.register({
},
]
},
// Dynamically-added template shortcuts (Issue #122)
...(shortcutMenuItems.length > 0
? [{ label: "Template shortcuts", submenu: shortcutMenuItems }]
: []),
{
commandName: "showPluginDocumentation"
}
Expand Down
2 changes: 2 additions & 0 deletions src/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { DefaultNoteTemplateIdSetting } from "./defaultNoteTemplateId";
export { DefaultTemplatesConfigSetting } from "./defaultTemplatesConfig";
export { DefaultTodoTemplateIdSetting } from "./defaultTodoTemplateId";
export { TemplatesSourceSetting } from "./templatesSource";
export { KeyboardShortcutsSetting } from "./keyboardShortcuts";
export type { KeyboardShortcutEntry } from "./keyboardShortcuts";

// Export registry
export { PluginSettingsRegistry } from "./registry";
69 changes: 69 additions & 0 deletions src/settings/keyboardShortcuts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import joplin from "api";
import { SettingItemType } from "api/types";
import { PluginSetting } from "./base";

export interface KeyboardShortcutEntry {
/** The Joplin Note ID of the template to use */
templateId: string;
/** The action to perform: "newNote", "newTodo", or "insertText" */
action: "newNote" | "newTodo" | "insertText";
/** Human-readable label shown in the menu */
label: string;
/** Optional accelerator string, e.g. "Ctrl+Alt+1". Leave empty to have no shortcut key. */
accelerator: string;
}

export const KEYBOARD_SHORTCUT_SLOTS = 10;

/**
* Stores an array of keyboard shortcut entries serialised as a JSON string.
* Each entry maps a template (by ID) + action to an accelerator.
*
* Example value (displayed in Joplin settings as a multi-line text area):
* [
* { "templateId": "abc123", "action": "newNote", "label": "Daily note", "accelerator": "Ctrl+Alt+1" },
* { "templateId": "def456", "action": "insertText", "label": "Meeting notes", "accelerator": "Ctrl+Alt+2" }
* ]
*/
export const KeyboardShortcutsSetting: PluginSetting<KeyboardShortcutEntry[]> =
class {
static id = "templateKeyboardShortcuts";
static manifest = {
public: true,
type: SettingItemType.String,
value: "[]",
label: "Template keyboard shortcuts (JSON)",
description:
"Define custom keyboard shortcuts for templates. " +
"Provide a JSON array where each item has: " +
'"templateId" (note ID of the template), ' +
'"action" ("newNote", "newTodo", or "insertText"), ' +
'"label" (menu label), and ' +
'"accelerator" (e.g. "Ctrl+Alt+1" — leave empty for no shortcut). ' +
"You can add up to " +
KEYBOARD_SHORTCUT_SLOTS +
" entries. " +
"Restart Joplin after saving.",
section: "templatesPlugin",
};

static async get(): Promise<KeyboardShortcutEntry[]> {
const raw: string = await joplin.settings.value(
KeyboardShortcutsSetting.id,
);
try {
const parsed = JSON.parse(raw || "[]");
if (!Array.isArray(parsed)) return [];
return parsed.slice(0, KEYBOARD_SHORTCUT_SLOTS);
} catch {
return [];
}
}

static async set(newValue: KeyboardShortcutEntry[]): Promise<void> {
await joplin.settings.setValue(
KeyboardShortcutsSetting.id,
JSON.stringify(newValue),
);
}
};
2 changes: 2 additions & 0 deletions src/settings/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DefaultNoteTemplateIdSetting } from "./defaultNoteTemplateId";
import { DefaultTemplatesConfigSetting } from "./defaultTemplatesConfig";
import { DefaultTodoTemplateIdSetting } from "./defaultTodoTemplateId";
import { TemplatesSourceSetting } from "./templatesSource";
import { KeyboardShortcutsSetting } from "./keyboardShortcuts";

import { PluginSetting } from "./base";

Expand All @@ -16,6 +17,7 @@ export class PluginSettingsRegistry {
DefaultTemplatesConfigSetting,
DefaultTodoTemplateIdSetting,
TemplatesSourceSetting,
KeyboardShortcutsSetting,
];

public static async registerSettings(): Promise<void> {
Expand Down