From 5c949dc71ca5aa4fd837952fd8ebf1856c48cda9 Mon Sep 17 00:00:00 2001 From: zsviczian Date: Thu, 29 Aug 2024 09:57:53 +0000 Subject: [PATCH] hotekey override, hide spalsh, save active tool, remove comments in prod build --- package.json | 22 ++--- rollup.config.js | 8 +- src/ExcalidrawView.ts | 2 + src/dialogs/HotkeyEditor.ts | 162 +++++++++++++++++++++++++++++++++ src/lang/locale/en.ts | 11 +++ src/main.ts | 76 ++++++++++------ src/settings.ts | 43 ++++++++- src/utils/ModifierkeyHelper.ts | 23 +++++ 8 files changed, 302 insertions(+), 45 deletions(-) create mode 100644 src/dialogs/HotkeyEditor.ts diff --git a/package.json b/package.json index 3648b2b..3fb4753 100644 --- a/package.json +++ b/package.json @@ -24,23 +24,27 @@ "clsx": "^2.0.0", "colormaster": "^1.2.1", "gl-matrix": "^3.4.3", + "js-yaml": "^4.1.0", "lucide-react": "^0.263.1", "mathjax-full": "^3.2.2", "monkey-around": "^2.3.0", "nanoid": "^4.0.2", + "opentype.js": "^1.3.4", "polybooljs": "^1.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "roughjs": "^4.5.2", - "js-yaml": "^4.1.0", - "opentype.js": "^1.3.4", "woff2sfnt-sfnt2woff": "^1.0.0" }, "devDependencies": { - "dotenv": "^16.4.5", "@babel/core": "^7.22.9", "@babel/preset-env": "^7.22.10", "@babel/preset-react": "^7.22.5", + "@codemirror/commands": "^6.3.3", + "@codemirror/language": "^6.10.0", + "@codemirror/search": "^6.5.5", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.23.0", "@excalidraw/eslint-config": "^1.0.3", "@excalidraw/prettier-config": "^1.0.2", "@rollup/plugin-babel": "^6.0.3", @@ -50,14 +54,15 @@ "@rollup/plugin-typescript": "^11.1.6", "@types/chroma-js": "^2.4.0", "@types/js-beautify": "^1.14.0", + "@types/js-yaml": "^4.0.9", "@types/node": "^20.10.5", + "@types/opentype.js": "^1.3.8", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", - "@types/js-yaml": "^4.0.9", - "@types/opentype.js": "^1.3.8", "@zerollup/ts-transform-paths": "^1.7.18", "cross-env": "^7.0.3", "cssnano": "^6.0.2", + "dotenv": "^16.4.5", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "lz-string": "^1.5.0", @@ -70,12 +75,7 @@ "rollup-plugin-typescript2": "^0.34.1", "tslib": "^2.6.1", "ttypescript": "^1.5.15", - "typescript": "^5.2.2", - "@codemirror/commands": "^6.3.3", - "@codemirror/language": "^6.10.0", - "@codemirror/search": "^6.5.5", - "@codemirror/state": "^6.4.0", - "@codemirror/view": "^6.23.0" + "typescript": "^5.2.2" }, "resolutions": { "@typescript-eslint/typescript-estree": "5.3.0" diff --git a/rollup.config.js b/rollup.config.js index 3942ae5..7c089c9 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -100,7 +100,13 @@ const BUILD_CONFIG = { plugins: getRollupPlugins( {tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json"}, ...(isProd ? [ - terser({ toplevel: false, compress: { passes: 2 } }), + terser({ + toplevel: false, + compress: { passes: 2 }, + format: { + comments: false, // Remove all comments + }, + }), //!postprocess - the version available on npmjs does not work, need this update: // npm install brettz9/rollup-plugin-postprocess#update --save-dev // https://github.com/developit/rollup-plugin-postprocess/issues/10 diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index f1651e9..a809334 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -3350,6 +3350,7 @@ export default class ExcalidrawView extends TextFileView { currentStrokeOptions: st.currentStrokeOptions, frameRendering: st.frameRendering, objectsSnapModeEnabled: st.objectsSnapModeEnabled, + activeTool: st.activeTool, }, prevTextMode: this.prevTextMode, files, @@ -5254,6 +5255,7 @@ export default class ExcalidrawView extends TextFileView { } private renderWelcomeScreen () { + if(!this.plugin.settings.showSplashscreen) return null; const React = this.packages.react; const {WelcomeScreen} = this.packages.excalidrawLib; const filecount = this.app.vault.getFiles().filter(f=>this.plugin.isExcalidrawFile(f)).length; diff --git a/src/dialogs/HotkeyEditor.ts b/src/dialogs/HotkeyEditor.ts new file mode 100644 index 0000000..e2e2355 --- /dev/null +++ b/src/dialogs/HotkeyEditor.ts @@ -0,0 +1,162 @@ +import { BaseComponent, Setting, Modifier } from 'obsidian'; +import { DEVICE } from 'src/constants/constants'; +import { t } from 'src/lang/helpers'; +import { ExcalidrawSettings } from 'src/settings'; +import { modifierLabel } from 'src/utils/ModifierkeyHelper'; +import { fragWithHTML } from 'src/utils/Utils'; + +export class HotkeyEditor extends BaseComponent { + private settings: ExcalidrawSettings; + private containerEl: HTMLElement; + private capturing: boolean = false; + private activeModifiers: Modifier[] = []; + public isDirty: boolean = false; + private applySettingsUpdate: Function; + + // Store bound event handlers + private boundKeydownHandler: (event: KeyboardEvent) => void; + private boundKeyupHandler: (event: KeyboardEvent) => void; + + constructor(containerEl: HTMLElement, settings: ExcalidrawSettings, applySettingsUpdate: Function) { + super(); + this.containerEl = containerEl.createDiv(); + this.settings = settings; + this.applySettingsUpdate = applySettingsUpdate; + + // Bind the event handlers once in the constructor + this.boundKeydownHandler = this.onKeydown.bind(this); + this.boundKeyupHandler = this.onKeyup.bind(this); + } + + onload(): void { + this.render(); + } + + private render(): void { + // Clear previous content + this.containerEl.empty(); + + // Render current overrides + this.settings.modifierKeyOverrides.forEach((override, index) => { + const key = override.key.toUpperCase(); + new Setting(this.containerEl) + .setDesc(fragWithHTML(`Code: ${override.modifiers.join("+")} + ${key} | ` + + `Apple: ${modifierLabel(override.modifiers, "Mac")} + ${key} | ` + + `Windows: ${modifierLabel(override.modifiers, "Other")} + ${key}`)) + .addButton((button) => + button + .setButtonText(t("HOTKEY_BUTTON_REMOVE")) + .setCta() + .onClick(() => { + this.settings.modifierKeyOverrides.splice(index, 1); + this.isDirty = true; + this.applySettingsUpdate(); + this.render(); + }) + ); + }); + + // Render Add New Override or Capture Instruction + if (this.capturing) { + new Setting(this.containerEl) + .setName(t("HOTKEY_PRESS_COMBO_NANE")) + .setDesc(t("HOTKEY_PRESS_COMBO_DESC")) + .controlEl.style.cursor = 'pointer'; + } else { + new Setting(this.containerEl) + .addButton((button) => + button + .setButtonText(t("HOTKEY_BUTTON_ADD_OVERRIDE")) + .setCta() + .onClick(() => this.startCapture()) + ); + } + } + + private startCapture(): void { + this.capturing = true; + this.activeModifiers = []; + this.render(); + // Use the pre-bound handlers + window.addEventListener('keydown', this.boundKeydownHandler); + window.addEventListener('keyup', this.boundKeyupHandler); + } + + private onKeydown(event: KeyboardEvent): void { + event.preventDefault(); + event.stopPropagation(); + + const modifiers = this.getModifiersFromEvent(event); + + // If only modifiers are pressed, update activeModifiers and continue listening + if (['Control', 'Shift', 'Alt', 'Meta'].includes(event.key)) { + this.activeModifiers = modifiers; + return; + } + + const key = event.key.length === 1 ? event.key.toLowerCase() : event.key; + + // Check for duplicate overrides + const exists = this.settings.modifierKeyOverrides.some( + (override) => + override.key === key && + override.modifiers.length === modifiers.length && + override.modifiers.every((mod) => modifiers.includes(mod)) + ); + + if (!exists) { + this.settings.modifierKeyOverrides.push({ modifiers, key }); + this.isDirty = true; + this.applySettingsUpdate(); + } + + this.stopCapture(); + } + + private onKeyup(event: KeyboardEvent): void { + // If all modifier keys are released, stop capturing + if (!event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) { + this.stopCapture(); + } + } + + private stopCapture(): void { + this.capturing = false; + // Use the pre-bound handlers for removal + window.removeEventListener('keydown', this.boundKeydownHandler); + window.removeEventListener('keyup', this.boundKeyupHandler); + this.render(); + } + + public unload(): void { + // Ensure listeners are removed when the component is unloaded + this.stopCapture(); + } + + private getModifiersFromEvent(event: KeyboardEvent): Modifier[] { + const modifiers: Modifier[] = []; + + if (DEVICE.isMacOS && event.metaKey) { + modifiers.push('Mod'); + } else if (!DEVICE.isMacOS && event.ctrlKey) { + modifiers.push('Mod'); + } + + if (DEVICE.isMacOS && event.ctrlKey) { + modifiers.push('Ctrl'); + } + + if (!DEVICE.isMacOS && event.metaKey) { + modifiers.push('Meta'); + } + + if (event.shiftKey) { + modifiers.push('Shift'); + } + if (event.altKey) { + modifiers.push('Alt'); + } + + return modifiers; + } +} diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index b078e1d..1757e2f 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -37,6 +37,7 @@ export default { TRANSCLUDE: "Embed a drawing", TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing", TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode", + TOGGLE_SPLASHSCREEN: "Show splash screen in new drawings", FLIP_IMAGE: "Open the back-of-the-note of the selected excalidraw image", NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW", NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB", @@ -352,6 +353,10 @@ FILENAME_HEAD: "Filename", "
  • When disabled the PDF will show the markdown side of the document.
  • " + "See the other related setting for Markdown Reading Mode under 'Appearnace and Behavior' further above.
    " + "⚠️ Note, you must close the active excalidraw/markdown file and reopen for this change to take effect. ⚠️", + HOTKEY_OVERRIDE_HEAD: "Hotkey overrides", + HOTKEY_OVERRIDE_DESC: `Some of the Excalidraw hotkeys such as ${labelCTRL()}+Enter to edit text or ${labelCTRL()}+K to create an element link ` + + "conflict with Obsidian hotkey settings. The hotkey combinations you add below will override Obsidian's hotkey settings while useing Excalidraw, thus " + + `you can add ${labelCTRL()}+G if you want to default to Group Object in Excalidraw instead of opening Graph View.`, THEME_HEAD: "Theme and styling", ZOOM_HEAD: "Zoom", DEFAULT_PINCHZOOM_NAME: "Allow pinch zoom in pen mode", @@ -739,6 +744,12 @@ FILENAME_HEAD: "Filename", "the developer of Taskbone (as you can imagine, there is no such thing as 'free', providing this awesome OCR service costs some money to the developer of Taskbone), you can " + "purchase a paid API key from taskbone.com. In case you have purchased a key, simply overwrite this auto generated free-tier API-key with your paid key.", + //HotkeyEditor + HOTKEY_PRESS_COMBO_NANE: "Press your hotkey combination", + HOTKEY_PRESS_COMBO_DESC: "Please press the desired key combination", + HOTKEY_BUTTON_ADD_OVERRIDE: "Add New Override", + HOTKEY_BUTTON_REMOVE: "Remove", + //openDrawings.ts SELECT_FILE: "Select a file then press enter.", SELECT_COMMAND: "Select a command then press enter.", diff --git a/src/main.ts b/src/main.ts index b7bd87d..9bc1124 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2826,37 +2826,53 @@ export default class ExcalidrawPlugin extends Plugin { this.popScope = null; } if (newActiveviewEV) { - const scope = this.app.keymap.getRootScope(); - const handler_ctrlEnter = scope.register(["Mod"], "Enter", () => true); - scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list - const handler_ctrlK = scope.register(["Mod"], "k", () => true); - scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list - const handler_ctrlF = scope.register(["Mod"], "f", () => { - const view = this.app.workspace.getActiveViewOfType(ExcalidrawView); - if (view) { - search(view); - return true; - } - return false; - }); - scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list - const overridSaveShortcut = ( - this.forceSaveCommand && - this.forceSaveCommand.hotkeys[0].key === "s" && - this.forceSaveCommand.hotkeys[0].modifiers.includes("Ctrl") - ) - const saveHandler = overridSaveShortcut - ? scope.register(["Ctrl"], "s", () => this.forceSaveActiveView(false)) - : undefined; - if(saveHandler) { - scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list - } - this.popScope = () => { - scope.unregister(handler_ctrlEnter); - scope.unregister(handler_ctrlK); - scope.unregister(handler_ctrlF); - Boolean(saveHandler) && scope.unregister(saveHandler); + this.registerHotkeyOverrides(); + } + } + + public registerHotkeyOverrides() { + //this is repeated here because the same function is called when settings is closed after hotkeys have changed + if (this.popScope) { + this.popScope(); + this.popScope = null; + } + + if(!this.activeExcalidrawView) { + return; + } + + const scope = this.app.keymap.getRootScope(); + // Register overrides from settings + const overrideHandlers = this.settings.modifierKeyOverrides.map(override => { + return scope.register(override.modifiers, override.key, () => true); + }); + // Force handlers to the front of the list + overrideHandlers.forEach(() => scope.keys.unshift(scope.keys.pop())); + + const handler_ctrlF = scope.register(["Mod"], "f", () => { + const view = this.app.workspace.getActiveViewOfType(ExcalidrawView); + if (view) { + search(view); + return true; } + return false; + }); + scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list + const overridSaveShortcut = ( + this.forceSaveCommand && + this.forceSaveCommand.hotkeys[0].key === "s" && + this.forceSaveCommand.hotkeys[0].modifiers.includes("Ctrl") + ) + const saveHandler = overridSaveShortcut + ? scope.register(["Ctrl"], "s", () => this.forceSaveActiveView(false)) + : undefined; + if(saveHandler) { + scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list + } + this.popScope = () => { + overrideHandlers.forEach(handler => scope.unregister(handler)); + scope.unregister(handler_ctrlF); + Boolean(saveHandler) && scope.unregister(saveHandler); } } diff --git a/src/settings.ts b/src/settings.ts index 5513de7..dea7965 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -3,6 +3,7 @@ import { ButtonComponent, DropdownComponent, getIcon, + Modifier, normalizePath, PluginSettingTab, Setting, @@ -39,6 +40,7 @@ import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler"; import { setDebugging } from "./utils/DebugHelper"; import { Rank } from "./menu/ActionIcons"; import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags"; +import { HotkeyEditor } from "./dialogs/HotkeyEditor"; export interface ExcalidrawSettings { folder: string; @@ -204,6 +206,8 @@ export interface ExcalidrawSettings { longPressMobile: number; isDebugMode: boolean; rank: Rank; + modifierKeyOverrides: {modifiers: Modifier[], key: string}[]; + showSplashscreen: boolean; } declare const PLUGIN_VERSION:string; @@ -464,6 +468,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { longPressMobile: 500, isDebugMode: false, rank: "Bronze", + modifierKeyOverrides: [ + {modifiers: ["Mod"], key:"Enter"}, + {modifiers: ["Mod"], key:"k"}, + ], + showSplashscreen: true, }; export class ExcalidrawSettingTab extends PluginSettingTab { @@ -472,6 +481,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab { private requestReloadDrawings: boolean = false; private requestUpdatePinnedPens: boolean = false; private requestUpdateDynamicStyling: boolean = false; + private hotkeyEditor: HotkeyEditor; //private reloadMathJax: boolean = false; //private applyDebounceTimer: number = 0; @@ -498,21 +508,25 @@ export class ExcalidrawSettingTab extends PluginSettingTab { } this.plugin.saveSettings(); if (this.requestUpdatePinnedPens) { - app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> { + this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> { if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens() }) } if (this.requestUpdateDynamicStyling) { - app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> { + this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> { if (v.view instanceof ExcalidrawView) { setDynamicStyle(this.plugin.ea,v.view,v.view.previousBackgroundColor,this.plugin.settings.dynamicStyling); } }) } + this.hotkeyEditor.unload(); + if (this.hotkeyEditor.isDirty) { + this.plugin.registerHotkeyOverrides(); + } if (this.requestReloadDrawings) { const exs = - app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); + this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); for (const v of exs) { if (v.view instanceof ExcalidrawView) { await v.view.save(false); @@ -1095,6 +1109,29 @@ export class ExcalidrawSettingTab extends PluginSettingTab { ); addIframe(detailsEl, "H8Njp7ZXYag",999); + new Setting(detailsEl) + .setName(t("TOGGLE_SPLASHSCREEN")) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.showSplashscreen) + .onChange((value)=> { + this.plugin.settings.showSplashscreen = value; + this.applySettingsUpdate(); + }) + ) + + detailsEl = displayDetailsEl.createEl("details"); + detailsEl.createEl("summary", { + text: t("HOTKEY_OVERRIDE_HEAD"), + cls: "excalidraw-setting-h3", + }); + detailsEl.createEl("span", {}, (el) => { + el.innerHTML = t("HOTKEY_OVERRIDE_DESC"); + }); + + this.hotkeyEditor = new HotkeyEditor(detailsEl, this.plugin.settings, this.applySettingsUpdate); + this.hotkeyEditor.onload(); + detailsEl = displayDetailsEl.createEl("details"); detailsEl.createEl("summary", { text: t("THEME_HEAD"), diff --git a/src/utils/ModifierkeyHelper.ts b/src/utils/ModifierkeyHelper.ts index 92f6f98..017c290 100644 --- a/src/utils/ModifierkeyHelper.ts +++ b/src/utils/ModifierkeyHelper.ts @@ -1,3 +1,4 @@ +import { Modifier } from "obsidian"; import { DEVICE } from "src/constants/constants"; import { ExcalidrawSettings } from "src/settings"; export type ModifierKeys = {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean}; @@ -177,4 +178,26 @@ export const emulateKeysForLinkClick = (action: PaneTarget): ModifierKeys => { export const anyModifierKeysPressed = (e: ModifierKeys): boolean => { return e.shiftKey || e.ctrlKey || e.metaKey || e.altKey; +} + +export function modifierLabel(modifiers: Modifier[], platform?: "Mac" | "Other"): string { + const isMacPlatform = platform === "Mac" || + (platform === undefined && (DEVICE.isIOS || DEVICE.isMacOS)); + + return modifiers.map(modifier => { + switch (modifier) { + case "Mod": + return isMacPlatform ? "CMD" : "CTRL"; + case "Ctrl": + return "CTRL"; + case "Meta": + return isMacPlatform ? "CMD" : "WIN"; + case "Shift": + return "SHIFT"; + case "Alt": + return isMacPlatform ? "OPTION" : "ALT"; + default: + return modifier; + } + }).join("+"); } \ No newline at end of file