diff --git a/images/excalidraw-modifiers.png b/images/excalidraw-modifiers.png index 6a9adec..2b6ab66 100644 Binary files a/images/excalidraw-modifiers.png and b/images/excalidraw-modifiers.png differ diff --git a/manifest.json b/manifest.json index 5d3aa4b..4ba080a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "2.0.4", + "version": "2.0.5", "minAppVersion": "1.1.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index c7706e1..c73b785 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -396,6 +396,15 @@ export class EmbeddedFilesLoader { return {dataURL: dURL as DataURL, hasSVGwithBitmap}; }; + //this is a fix for backward compatibility - I messed up with generating the local link + private getLocalPath(path: string) { + const localPath = path.split("file://")[1] + if(localPath.startsWith("/")) { + return localPath.substring(1); + } + return localPath; + } + private async _getObsidianImage(inFile: TFile | EmbeddedFile, depth: number): Promise { if (!this.plugin || !inFile) { return null; @@ -442,7 +451,7 @@ export class EmbeddedFilesLoader { const ab = isHyperLink || isPDF ? null : isLocalLink - ? await readLocalFileBinary((inFile as EmbeddedFile).hyperlink.split("file://")[1]) + ? await readLocalFileBinary(this.getLocalPath((inFile as EmbeddedFile).hyperlink)) : await app.vault.readBinary(file); let dURL: DataURL = null; @@ -535,7 +544,7 @@ export class EmbeddedFilesLoader { //debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"}); const data = await this._getObsidianImage(embeddedFile, depth); if (data) { - const fileData = { + const fileData: FileData = { mimeType: data.mimeType, id: entry.value[0], dataURL: data.dataURL, diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index d3399ea..d96af03 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -73,6 +73,7 @@ import { download, getDataURLFromURL, getIMGFilename, + getInternalLinkOrFileURLLink, getMimeType, getNewUniqueFilepath, getURLImageExtension, @@ -119,7 +120,7 @@ import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAt import { ICONS, LogoWrapper, saveIcon } from "./menu/ActionIcons"; import { ExportDialog } from "./dialogs/ExportDialog"; import { getEA } from "src" -import { anyModifierKeysPressed, emulateKeysForLinkClick, externalDragModifierType, internalDragModifierType, isALT, isCTRL, isMETA, isSHIFT, linkClickModifierType, localFileDragModifierType, ModifierKeys } from "./utils/ModifierkeyHelper"; +import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifierType, internalDragModifierType, isWinALTorMacOPT, isWinCTRLorMacCMD, isWinMETAorMacCTRL, isSHIFT, linkClickModifierType, localFileDragModifierType, ModifierKeys, modifierKeyTooltipMessages } from "./utils/ModifierkeyHelper"; import { setDynamicStyle } from "./utils/DynamicStyling"; import { InsertPDFModal } from "./dialogs/InsertPDFModal"; import { CustomEmbeddable, renderWebView } from "./customEmbeddable"; @@ -3041,16 +3042,16 @@ export default class ExcalidrawView extends TextFileView { if (this.isFullscreen() && event.keyCode === KEYCODE.ESC) { this.exitFullscreen(); } - if (isCTRL(event) && !isSHIFT(event) && !isALT(event)) { + if (isWinCTRLorMacCMD(event) && !isSHIFT(event) && !isWinALTorMacOPT(event)) { this.showHoverPreview(); } }; private onPointerDown(e: PointerEvent) { - if (!(isCTRL(e)||isMETA(e))) { + if (!(isWinCTRLorMacCMD(e)||isWinMETAorMacCTRL(e))) { return; } - if (!this.plugin.settings.allowCtrlClick && !!isMETA(e)) { + if (!this.plugin.settings.allowCtrlClick && !!isWinMETAorMacCTRL(e)) { return; } //added setTimeout when I changed onClick(e: MouseEvent) to onPointerDown() in 1.7.9. @@ -3081,31 +3082,18 @@ export default class ExcalidrawView extends TextFileView { let msg: string = ""; if((this.app as any).dragManager.draggable) { //drag from Obsidian file manager - switch (internalDragModifierType(e)) { - case "image": msg = "Embed image";break; - case "image-fullsize": msg = "Embed image @100%"; break; - case "link": msg = `Insert link\n${DEVICE.isMacOS || DEVICE.isIOS - ? "try SHIFT and CTRL combinations for other drop actions" - : "try SHIFT, CTRL, ALT combinations for other drop actions"}`; break; - case "embeddable": msg = "Insert in interactive frame"; break; - } + msg = modifierKeyTooltipMessages().InternalDragAction[internalDragModifierType(e)]; } else if(e.dataTransfer.types.length === 1 && e.dataTransfer.types.includes("Files")) { //drag from OS file manager - switch (localFileDragModifierType(e)) { - case "image-import": msg = "Import image to Vault"; break; - case "image-uri": msg = `Insert image with local URI`; break; - case "insert-link": msg = "Insert link"; break; - } + msg = modifierKeyTooltipMessages().LocalFileDragAction[localFileDragModifierType(e)]; } else { //drag from Internet - switch (externalDragModifierType(e)) { - case "image-import": msg = "Import image to Vault"; break; - case "image-url": msg = `Insert image/thumbnail with URL\n${DEVICE.isMacOS || DEVICE.isIOS - ? "try SHIFT, OPT, CTRL combinations for other drop actions" - : "try SHIFT, CTRL, ALT combinations for other drop actions"}`; break; - case "insert-link": msg = "Insert link"; break; - case "embeddable": msg = "Insert in interactive frame"; break; - } + msg = modifierKeyTooltipMessages().WebBrowserDragAction[webbrowserDragModifierType(e)]; + } + if(!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) { + msg += DEVICE.isMacOS || DEVICE.isIOS + ? "\nTry SHIFT, OPT, CTRL combinations for other drop actions" + : "\nTry SHIFT, CTRL, ALT combinations for other drop actions"; } if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg; const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*8}px`; @@ -3147,7 +3135,7 @@ export default class ExcalidrawView extends TextFileView { this.blockOnMouseButtonDown = true; //ctrl click - if (isCTRL(this.modifierKeyDown) || isMETA(this.modifierKeyDown)) { + if (isWinCTRLorMacCMD(this.modifierKeyDown) || isWinMETAorMacCTRL(this.modifierKeyDown)) { this.identifyElementClicked(); return; } @@ -3163,7 +3151,7 @@ export default class ExcalidrawView extends TextFileView { if (p.button === "up") { this.blockOnMouseButtonDown = false; } - if (isCTRL(this.modifierKeyDown) || + if (isWinCTRLorMacCMD(this.modifierKeyDown) || (this.excalidrawAPI.getAppState().isViewModeEnabled && this.plugin.settings.hoverPreviewWithoutCTRL)) { @@ -3349,7 +3337,7 @@ export default class ExcalidrawView extends TextFileView { ); const draggable = (app as any).dragManager.draggable; const internalDragAction = internalDragModifierType(event); - const externalDragAction = externalDragModifierType(event); + const externalDragAction = webbrowserDragModifierType(event); const localFileDragAction = localFileDragModifierType(event); //Call Excalidraw Automate onDropHook @@ -3525,7 +3513,7 @@ export default class ExcalidrawView extends TextFileView { this.addImageWithURL(text); return false; } - if(text && (externalDragAction === "insert-link")) { + if(text && (externalDragAction === "link")) { if ( this.plugin.settings.iframelyAllowed && text.match(/^https?:\/\/\S*$/) @@ -3555,7 +3543,7 @@ export default class ExcalidrawView extends TextFileView { this.addImageWithURL(src[1]); return false; } - if(src && (externalDragAction === "insert-link")) { + if(src && (externalDragAction === "link")) { if ( this.plugin.settings.iframelyAllowed && src[1].match(/^https?:\/\/\S*$/) @@ -3577,30 +3565,57 @@ export default class ExcalidrawView extends TextFileView { return false; } } - - if(event.dataTransfer.types.length >= 1 && localFileDragAction === "image-uri") { - (async () => { - for(let i=0;i= 1 && ["image-url","image-import","embeddable"].contains(localFileDragAction)) { + for(let i=0;i= 1 && localFileDragAction === "insert-link") { + if(event.dataTransfer.types.length >= 1 && localFileDragAction === "link") { const ea = getEA(this) as ExcalidrawAutomate; for(let i=0;i?@^`{|}~\/\[\]\\\r\n]/g; // /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\]/g; // https://discord.com/channels/686053708261228577/989603365606531104/1000128926619816048 // /\+|\/|~|=|%|\(|\)|{|}|,|&|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:|\s/g; -export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico"]; +export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico", "jtif", "tif"]; +export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"]; export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"]; export const MAX_IMAGE_SIZE = 500; export const FRONTMATTER_KEY = "excalidraw-plugin"; diff --git a/src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts b/src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts index 1388720..6c82585 100644 --- a/src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts +++ b/src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts @@ -8,6 +8,7 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent { private contentEl: HTMLElement, private mdCustomData: EmbeddableMDCustomProps, private update?: Function, + private isMDFile: boolean = true, ) { if(!update) this.update = () => {}; } @@ -33,16 +34,17 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent { detailsDIV.style.display = this.mdCustomData.useObsidianDefaults ? "none" : "block"; const contentEl = detailsDIV - new Setting(contentEl) - .setName(t("ES_FILENAME_VISIBLE")) - .addToggle(toggle => - toggle - .setValue(this.mdCustomData.filenameVisible) - .onChange(value => { - this.mdCustomData.filenameVisible = value; - }) - ); - + if(this.isMDFile) { + new Setting(contentEl) + .setName(t("ES_FILENAME_VISIBLE")) + .addToggle(toggle => + toggle + .setValue(this.mdCustomData.filenameVisible) + .onChange(value => { + this.mdCustomData.filenameVisible = value; + }) + ); + } contentEl.createEl("h4",{text: t("ES_BACKGROUND_HEAD")}); let bgSetting: Setting; @@ -126,50 +128,52 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent { }) ); - contentEl.createEl("h4",{text: t("ES_BORDER_HEAD")}); - let borderSetting: Setting; + if(this.isMDFile) { + contentEl.createEl("h4",{text: t("ES_BORDER_HEAD")}); + let borderSetting: Setting; - new Setting(contentEl) - .setName(t("ES_BORDER_MATCH_ELEMENT")) - .addToggle(toggle => - toggle - .setValue(this.mdCustomData.borderMatchElement) - .onChange(value => { - this.mdCustomData.borderMatchElement = value; - if(value) { - borderSetting.settingEl.style.display = "none"; - } else { - borderSetting.settingEl.style.display = ""; - } - this.update(); - }) - ); - - borderSetting = new Setting(contentEl) - .setName(t("ES_BORDER_COLOR")) - .addColorPicker(colorPicker => - colorPicker - .setValue(this.mdCustomData.borderColor) - .onChange((value) => { - this.mdCustomData.borderColor = value; - this.update(); - }) - ); - - borderSetting.settingEl.style.display = this.mdCustomData.borderMatchElement ? "none" : ""; - - const borderOpacitySetting = new Setting(contentEl) - .setName(t("ES_BORDER_OPACITY")) - .setDesc(opacity(this.mdCustomData.borderOpacity)) - .addSlider(slider => - slider - .setLimits(0,100,5) - .setValue(this.mdCustomData.borderOpacity) - .onChange(value => { - this.mdCustomData.borderOpacity = value; - borderOpacitySetting.setDesc(opacity(value)); - this.update(); - }) + new Setting(contentEl) + .setName(t("ES_BORDER_MATCH_ELEMENT")) + .addToggle(toggle => + toggle + .setValue(this.mdCustomData.borderMatchElement) + .onChange(value => { + this.mdCustomData.borderMatchElement = value; + if(value) { + borderSetting.settingEl.style.display = "none"; + } else { + borderSetting.settingEl.style.display = ""; + } + this.update(); + }) + ); + + borderSetting = new Setting(contentEl) + .setName(t("ES_BORDER_COLOR")) + .addColorPicker(colorPicker => + colorPicker + .setValue(this.mdCustomData.borderColor) + .onChange((value) => { + this.mdCustomData.borderColor = value; + this.update(); + }) + ); + + borderSetting.settingEl.style.display = this.mdCustomData.borderMatchElement ? "none" : ""; + + const borderOpacitySetting = new Setting(contentEl) + .setName(t("ES_BORDER_OPACITY")) + .setDesc(opacity(this.mdCustomData.borderOpacity)) + .addSlider(slider => + slider + .setLimits(0,100,5) + .setValue(this.mdCustomData.borderOpacity) + .onChange(value => { + this.mdCustomData.borderOpacity = value; + borderOpacitySetting.setDesc(opacity(value)); + this.update(); + }) ); + } } } \ No newline at end of file diff --git a/src/dialogs/EmbeddableSettings.ts b/src/dialogs/EmbeddableSettings.ts index 8b8b560..96651ef 100644 --- a/src/dialogs/EmbeddableSettings.ts +++ b/src/dialogs/EmbeddableSettings.ts @@ -10,7 +10,7 @@ import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/Utils"; import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils"; import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent"; -import { isCTRL } from "src/utils/ModifierkeyHelper"; +import { isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types"; export type EmbeddableMDCustomProps = { @@ -32,6 +32,8 @@ export class EmbeddableSettings extends Modal { private isYouTube: boolean; private youtubeStart: string = null; private isMDFile: boolean; + private notExcalidrawIsInternal: boolean; + private isLocalURI: boolean; private mdCustomData: EmbeddableMDCustomProps; private onKeyDown: (ev: KeyboardEvent) => void; @@ -46,7 +48,9 @@ export class EmbeddableSettings extends Modal { this.ea.copyViewElementsToEAforEditing([this.element]); this.zoomValue = element.scale[0]; this.isYouTube = isYouTube(this.element.link); - this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file) + this.notExcalidrawIsInternal = this.file && !this.view.plugin.isExcalidrawFile(this.file) + this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file); + this.isLocalURI = this.element.link.startsWith("file://"); if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link); this.mdCustomData = element.customData?.mdProps ?? view.plugin.settings.embeddableMarkdownDefaults; @@ -126,9 +130,9 @@ export class EmbeddableSettings extends Modal { ) } - if(this.isMDFile) { + if(this.isMDFile || this.notExcalidrawIsInternal) { this.contentEl.createEl("h3",{text: t("ES_EMBEDDABLE_SETTINGS")}); - new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData).render(); + new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData, undefined, this.isMDFile).render(); } new Setting(this.contentEl) @@ -150,7 +154,7 @@ export class EmbeddableSettings extends Modal { const onKeyDown = (ev: KeyboardEvent) => { - if(isCTRL(ev) && ev.key === "Enter") { + if(isWinCTRLorMacCMD(ev) && ev.key === "Enter") { this.applySettings(); } } diff --git a/src/dialogs/InsertImageDialog.ts b/src/dialogs/InsertImageDialog.ts index e2b9a84..db305f8 100644 --- a/src/dialogs/InsertImageDialog.ts +++ b/src/dialogs/InsertImageDialog.ts @@ -1,6 +1,5 @@ import { App, FuzzySuggestModal, TFile } from "obsidian"; -import { isALT, scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper"; -import { fileURLToPath } from "url"; +import { scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper"; import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants/constants"; import ExcalidrawView from "../ExcalidrawView"; import { t } from "../lang/helpers"; diff --git a/src/dialogs/InsertLinkDialog.ts b/src/dialogs/InsertLinkDialog.ts index 514c753..c9a2df8 100644 --- a/src/dialogs/InsertLinkDialog.ts +++ b/src/dialogs/InsertLinkDialog.ts @@ -1,15 +1,17 @@ import { App, FuzzySuggestModal, TFile } from "obsidian"; import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants"; import { t } from "../lang/helpers"; +import ExcalidrawPlugin from "src/main"; +import { getLink } from "src/utils/FileUtils"; export class InsertLinkDialog extends FuzzySuggestModal { public app: App; private addText: Function; private drawingPath: string; - constructor(app: App) { - super(app); - this.app = app; + constructor(private plugin: ExcalidrawPlugin) { + super(plugin.app); + this.app = plugin.app; this.limit = 20; this.setInstructions([ { @@ -45,7 +47,8 @@ export class InsertLinkDialog extends FuzzySuggestModal { true, ); } - this.addText(`[[${filepath + (item.alias ? `|${item.alias}` : "")}]]`, filepath, item.alias); + const link = getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}); + this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias); } public start(drawingPath: string, addText: Function) { diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index b462da0..9f43ece 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -17,6 +17,23 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
`, +"2.0.5":` +
+ +
+ +# Fixed +- Scaled-resizing a sticky note (SHIFT+resize) caused Excalidraw to choke on slower devices +- Improved plugin performance focusing on minimizing Excalidraw's effect on Obsidian overall +- Images embedded with a URL often did not show up in image exports, hopefully, the issue will less frequently occur in the future. +- Local file URL now follows Obsidian standard - making it easier to navigate in Markdown view mode. + +# New +- In plugin settings, under "Startup Script", the button now opens the startup script if it already exists. +- Partial support for animated GIFs (will not show up in image exports, but can be added as interactive embeddables) +- Configurable modifier keys for link click action and drag&drop actions. +- Improved support for drag&drop from your local drive and embedding of files external to Excalidraw. +`, "2.0.4":`
diff --git a/src/dialogs/ModifierKeySettings.ts b/src/dialogs/ModifierKeySettings.ts new file mode 100644 index 0000000..f0fad7e --- /dev/null +++ b/src/dialogs/ModifierKeySettings.ts @@ -0,0 +1,97 @@ +import { Setting } from "obsidian"; +import { DEVICE } from "src/constants/constants"; +import { t } from "src/lang/helpers"; +import { ModifierKeySet, ModifierSetType, modifierKeyTooltipMessages } from "src/utils/ModifierkeyHelper"; + +type ModifierKeyCategories = Partial<{ + [modifierSetType in ModifierSetType]: string; +}>; + +const CATEGORIES: ModifierKeyCategories = { + WebBrowserDragAction: t("WEB_BROWSER_DRAG_ACTION"), + LocalFileDragAction: t("LOCAL_FILE_DRAG_ACTION"), + InternalDragAction: t("INTERNAL_DRAG_ACTION"), + LinkClickAction: t("PANE_TARGET"), +}; + +export class ModifierKeySettingsComponent { + private isMacOS: boolean; + + constructor( + private contentEl: HTMLElement, + private modifierKeyConfig: { + Mac: Record; + Win: Record; + }, + private update?: Function, + ) { + this.isMacOS = (DEVICE.isMacOS || DEVICE.isIOS); + } + + render() { + const platform = this.isMacOS ? "Mac" : "Win"; + const modifierKeysConfig = this.modifierKeyConfig[platform]; + + Object.entries(CATEGORIES).forEach(([modifierSetType, label]) => { + const detailsEl = this.contentEl.createEl("details"); + detailsEl.createEl("summary", { + text: label, + cls: "excalidraw-setting-h4", + }); + + const modifierKeys = modifierKeysConfig[modifierSetType]; + detailsEl.createDiv({ + //@ts-ignore + text: t("DEFAULT_ACTION_DESC") + modifierKeyTooltipMessages()[modifierSetType][modifierKeys.defaultAction], + cls: "setting-item-description" + }); + Object.entries(modifierKeys.rules).forEach(([action, rule]) => { + const setting = new Setting(detailsEl) + //@ts-ignore + .setName(modifierKeyTooltipMessages()[modifierSetType][rule.result]); + + setting.addToggle((toggle) => + toggle + .setValue(rule.shift) + .setTooltip("SHIFT") + .onChange((value) => { + rule.shift = value; + this.update(); + }) + ); + setting.addToggle((toggle) => { + toggle + .setValue(rule.ctrl_cmd) + .setTooltip(this.isMacOS ? "CMD" : "CTRL") + .onChange((value) => { + rule.ctrl_cmd = value; + this.update(); + }) + if(this.isMacOS && modifierSetType !== "LinkClickAction") { + toggle.setDisabled(true); + toggle.toggleEl.style.opacity = "0.5"; + } + }); + + setting.addToggle((toggle) => + toggle + .setValue(rule.alt_opt) + .setTooltip(this.isMacOS ? "OPT" : "ALT") + .onChange((value) => { + rule.alt_opt = value; + this.update(); + }) + ); + setting.addToggle((toggle) => + toggle + .setValue(rule.meta_ctrl) + .setTooltip(this.isMacOS ? "CTRL" : "META") + .onChange((value) => { + rule.meta_ctrl = value; + this.update(); + }) + ); + }); + }); + } +} diff --git a/src/dialogs/Prompt.ts b/src/dialogs/Prompt.ts index 5abd410..526f31f 100644 --- a/src/dialogs/Prompt.ts +++ b/src/dialogs/Prompt.ts @@ -14,7 +14,7 @@ import ExcalidrawPlugin from "../main"; import { escapeRegExp, sleep } from "../utils/Utils"; import { getLeaf } from "../utils/ObsidianUtils"; import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils"; -import { KeyEvent, isCTRL } from "src/utils/ModifierkeyHelper"; +import { KeyEvent, isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper"; import { t } from "src/lang/helpers"; import { ExcalidrawElement, getEA } from "src"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; @@ -342,11 +342,11 @@ export class GenericInputPrompt extends Modal { private cancelClickCallback = () => this.cancel(); private keyDownCallback = (evt: KeyboardEvent) => { - if ((evt.key === "Enter" && this.lines === 1) || (isCTRL(evt) && evt.key === "Enter")) { + if ((evt.key === "Enter" && this.lines === 1) || (isWinCTRLorMacCMD(evt) && evt.key === "Enter")) { evt.preventDefault(); this.submit(); } - if (this.displayEditorButtons && evt.key === "k" && isCTRL(evt)) { + if (this.displayEditorButtons && evt.key === "k" && isWinCTRLorMacCMD(evt)) { evt.preventDefault(); this.linkBtnClickCallback(); } diff --git a/src/dialogs/UniversalInsertFileModal.ts b/src/dialogs/UniversalInsertFileModal.ts index d36fc57..ccd4abc 100644 --- a/src/dialogs/UniversalInsertFileModal.ts +++ b/src/dialogs/UniversalInsertFileModal.ts @@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView"; import ExcalidrawPlugin from "../main"; import { Modal, Setting, TextComponent } from "obsidian"; import { FileSuggestionModal } from "./FolderSuggester"; -import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE } from "src/constants/constants"; +import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES } from "src/constants/constants"; import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils"; import { getEA } from "src"; import { InsertPDFModal } from "./InsertPDFModal"; @@ -80,6 +80,7 @@ export class UniversalInsertFileModal extends Modal { const ea = this.plugin.ea; const isMarkdown = file && file.extension === "md" && !ea.isExcalidrawFile(file); const isImage = file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)); + const isAnimatedImage = file && ANIMATED_IMAGE_TYPES.contains(file.extension); const isIFrame = file && !isImage; const isPDF = file && file.extension === "pdf"; const isExcalidraw = file && ea.isExcalidrawFile(file); @@ -116,7 +117,7 @@ export class UniversalInsertFileModal extends Modal { actionImage.buttonEl.style.display = "none"; } - if (isIFrame) { + if (isIFrame || isAnimatedImage) { actionIFrame.buttonEl.style.display = "block"; } else { actionIFrame.buttonEl.style.display = "none"; diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 8ec1a69..15fb4e8 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -277,6 +277,12 @@ FILENAME_HEAD: "Filename", "the plugin will open it in a browser. " + "When Obsidian files change, the matching [[link]] in your drawings will also change. " + "If you don't want text accidentally changing in your drawings use [[links|with aliases]].", + DRAG_MODIFIER_NAME: "Link Click and Drag&Drop Modifier Keys", + DRAG_MODIFIER_DESC: "Modifier key behavior when clicking links and dragging and dropping elements. " + + "Excalidraw will not validate your configuration... pay attention to avoid conflicting settings. " + + "These settings are different for Apple and non-Apple. If you use Obsidian on multiple platforms, you'll need to make the settings separately. "+ + "The toggles follow the order of " + + (DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Windows key)."), ADJACENT_PANE_NAME: "Reuse adjacent pane", ADJACENT_PANE_DESC: `When ${labelCTRL()}+${labelALT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` + @@ -650,5 +656,11 @@ FILENAME_HEAD: "Filename", PROMPT_BUTTON_INSERT_SPACE: "Insert space", PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file", PROMPT_BUTTON_UPPERCASE: "Uppercase", - + + //ModifierKeySettings + WEB_BROWSER_DRAG_ACTION: "Web Browser Drag Action", + LOCAL_FILE_DRAG_ACTION: "OS Local File Drag Action", + INTERNAL_DRAG_ACTION: "Obsidian Internal Drag Action", + PANE_TARGET: "Link click behavior", + DEFAULT_ACTION_DESC: "In case none of the combinations apply the default action for this group is: ", }; diff --git a/src/main.ts b/src/main.ts index 5273da4..3b617ac 100644 --- a/src/main.ts +++ b/src/main.ts @@ -80,6 +80,7 @@ import { getDrawingFilename, getEmbedFilename, getIMGFilename, + getLink, getNewUniqueFilepath, } from "./utils/FileUtils"; import { @@ -791,7 +792,7 @@ export default class ExcalidrawPlugin extends Plugin { private registerCommands() { this.openDialog = new OpenFileDialog(this.app, this); - this.insertLinkDialog = new InsertLinkDialog(this.app); + this.insertLinkDialog = new InsertLinkDialog(this); this.insertCommandDialog = new InsertCommandDialog(this.app); this.insertImageDialog = new InsertImageDialog(this); this.importSVGDialog = new ImportSVGDialog(this); @@ -1978,7 +1979,7 @@ export default class ExcalidrawPlugin extends Plugin { private popScope: Function = null; private registerEventListeners() { - const self = this; + const self: ExcalidrawPlugin = this; this.app.workspace.onLayoutReady(async () => { const onPasteHandler = ( evt: ClipboardEvent, @@ -2008,18 +2009,15 @@ export default class ExcalidrawPlugin extends Plugin { if(sourceFile && imageFile && imageFile instanceof TFile) { path = self.app.metadataCache.fileToLinktext(imageFile,sourceFile.path); } - //@ts-ignore - editor.insertText(self.getLink({path})); + editor.insertText(getLink(self, {path})); } return; } if (element.type === "text") { - //@ts-ignore editor.insertText(element.text); return; } if (element.link) { - //@ts-ignore editor.insertText(`${element.link}`); return; } @@ -2485,14 +2483,6 @@ export default class ExcalidrawPlugin extends Plugin { }) } - public getLink( - { embed = true, path, alias }: { embed?: boolean; path: string; alias?: string } - ):string { - return this.settings.embedWikiLink - ? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]` - : `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})` - } - public async embedDrawing(file: TFile) { const activeView = this.app.workspace.getActiveViewOfType(MarkdownView); if (activeView && activeView.file) { @@ -2506,7 +2496,7 @@ export default class ExcalidrawPlugin extends Plugin { //embed Excalidraw if (this.settings.embedType === "excalidraw") { editor.replaceSelection( - this.getLink({path: excalidrawRelativePath}), + getLink(this, {path: excalidrawRelativePath}), ); editor.focus(); return; diff --git a/src/menu/ToolsPanel.tsx b/src/menu/ToolsPanel.tsx index 34c7363..a331382 100644 --- a/src/menu/ToolsPanel.tsx +++ b/src/menu/ToolsPanel.tsx @@ -11,7 +11,7 @@ import { ReleaseNotes } from "../dialogs/ReleaseNotes"; import { ScriptIconMap } from "../Scripts"; import { ScriptInstallPrompt } from "src/dialogs/ScriptInstallPrompt"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types"; -import { isALT, isCTRL, isSHIFT } from "src/utils/ModifierkeyHelper"; +import { isWinALTorMacOPT, isWinCTRLorMacCMD, isSHIFT } from "src/utils/ModifierkeyHelper"; import { InsertPDFModal } from "src/dialogs/InsertPDFModal"; import { ExportDialog } from "src/dialogs/ExportDialog"; @@ -379,7 +379,7 @@ export class ToolsPanel extends React.Component { new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000); return; } - this.props.view.plugin.taskbone.getTextForView(this.props.view, isCTRL(e)); + this.props.view.plugin.taskbone.getTextForView(this.props.view, isWinCTRLorMacCMD(e)); }} icon={ICONS.ocr} view={this.props.view} @@ -504,7 +504,7 @@ export class ToolsPanel extends React.Component { key={"latex"} title={t("INSERT_LATEX")} action={(e) => { - if(isALT(e)) { + if(isWinALTorMacOPT(e)) { this.props.view.openExternalLink("https://youtu.be/r08wk-58DPk"); return; } @@ -531,12 +531,12 @@ export class ToolsPanel extends React.Component { key={"link-to-element"} title={t("INSERT_LINK_TO_ELEMENT")} action={(e:React.MouseEvent) => { - if(isALT(e)) { + if(isWinALTorMacOPT(e)) { this.props.view.openExternalLink("https://youtu.be/yZQoJg2RCKI"); return; } this.props.view.copyLinkToSelectedElementToClipboard( - isCTRL(e) ? "group=" : (isSHIFT(e) ? "area=" : "") + isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "") ); }} icon={ICONS.copyElementLink} diff --git a/src/settings.ts b/src/settings.ts index 2f72166..241a315 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -3,7 +3,6 @@ import { ButtonComponent, DropdownComponent, normalizePath, - Notice, PluginSettingTab, Setting, TextComponent, @@ -32,6 +31,8 @@ import { ConfirmationPrompt } from "./dialogs/Prompt"; import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings"; import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/EmbeddableMDFileCustomDataSettingsComponent"; import { startupScript } from "./constants/starutpscript"; +import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper"; +import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings"; export interface ExcalidrawSettings { folder: string; @@ -159,6 +160,10 @@ export interface ExcalidrawSettings { openAIAPIToken: string, openAIDefaultTextModel: string, openAIDefaultVisionModel: string, + modifierKeyConfig: { + Mac: Record, + Win: Record, + } } declare const PLUGIN_VERSION:string; @@ -305,6 +310,86 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { openAIAPIToken: "", openAIDefaultTextModel: "gpt-3.5-turbo-1106", openAIDefaultVisionModel: "gpt-4-vision-preview", + modifierKeyConfig: { + Mac: { + LocalFileDragAction:{ + defaultAction: "image-import", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" }, + { shift: true , ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "link" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" }, + { shift: false, ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "embeddable" }, + ], + }, + WebBrowserDragAction: { + defaultAction: "image-url", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" }, + { shift: true , ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "link" }, + { shift: false, ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "embeddable" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" }, + ], + }, + InternalDragAction: { + defaultAction: "link", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "link" }, + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: true , result: "embeddable" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: true , result: "image-fullsize" }, + ], + }, + LinkClickAction: { + defaultAction: "new-tab", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "active-pane" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "new-tab" }, + { shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "new-pane" }, + { shift: true , ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "popout-window" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: true , result: "md-properties" }, + ], + }, + }, + Win: { + LocalFileDragAction:{ + defaultAction: "image-import", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "link" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" }, + { shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" }, + ], + }, + WebBrowserDragAction: { + defaultAction: "image-url", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "link" }, + { shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" }, + ], + }, + InternalDragAction: { + defaultAction: "link", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "link" }, + { shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" }, + { shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image" }, + { shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "image-fullsize" }, + ], + }, + LinkClickAction: { + defaultAction: "new-tab", + rules: [ + { shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "active-pane" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "new-tab" }, + { shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "new-pane" }, + { shift: true , ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "popout-window" }, + { shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: true , result: "md-properties" }, + ], + }, + }, + } }; export class ExcalidrawSettingTab extends PluginSettingTab { @@ -979,6 +1064,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab { el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_LENGTH.toString()}`; }); + detailsEl = displayDetailsEl.createEl("details"); + detailsEl.createEl("summary", { + text: t("DRAG_MODIFIER_NAME"), + cls: "excalidraw-setting-h3", + }); + detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" }); + + new ModifierKeySettingsComponent( + detailsEl, + this.plugin.settings.modifierKeyConfig, + this.applySettingsUpdate, + ).render(); // ------------------------------------------------ // Links and Transclusions diff --git a/src/types.d.ts b/src/types.d.ts index 5944567..54b70e5 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -49,4 +49,13 @@ declare module "obsidian" { ctx?: any, ): EventRef; } + interface DataAdapter { + url: { + pathToFileURL(path: string): URL; + }, + basePath: string; + } + interface Editor { + insertText(data: string): void; + } } \ No newline at end of file diff --git a/src/utils/ExcalidrawViewUtils.ts b/src/utils/ExcalidrawViewUtils.ts index 96044c5..b4c804b 100644 --- a/src/utils/ExcalidrawViewUtils.ts +++ b/src/utils/ExcalidrawViewUtils.ts @@ -1,5 +1,5 @@ -import { MAX_IMAGE_SIZE, IMAGE_TYPES } from "src/constants/constants"; +import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES } from "src/constants/constants"; import { TFile } from "obsidian"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData"; @@ -34,7 +34,7 @@ export const insertEmbeddableToView = async ( ea.clear(); ea.style.strokeColor = "transparent"; ea.style.backgroundColor = "transparent"; - if(file && IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) { + if(file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) && !ANIMATED_IMAGE_TYPES.contains(file.extension)) { return await insertImageToView(ea, position, file); } else { const id = ea.addEmbeddable( diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts index f8fe8df..7cd104c 100644 --- a/src/utils/FileUtils.ts +++ b/src/utils/FileUtils.ts @@ -1,19 +1,22 @@ import { DataURL } from "@zsviczian/excalidraw/types/types"; import { loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian"; import { URLFETCHTIMEOUT } from "src/constants/constants"; -import { MimeType } from "src/EmbeddedFileLoader"; +import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader"; import { ExcalidrawSettings } from "src/settings"; import { errorlog, getDataURL } from "./Utils"; +import ExcalidrawPlugin from "src/main"; /** * Splits a full path including a folderpath and a filename into separate folderpath and filename components * @param filepath */ +type ImageExtension = keyof typeof IMAGE_MIME_TYPES; export function splitFolderAndFilename(filepath: string): { folderpath: string; filename: string; basename: string; + extension: string; } { const lastIndex = filepath.lastIndexOf("/"); const filename = lastIndex == -1 ? filepath : filepath.substring(lastIndex + 1); @@ -21,6 +24,7 @@ export function splitFolderAndFilename(filepath: string): { folderpath: normalizePath(filepath.substring(0, lastIndex)), filename, basename: filename.replace(/\.[^/.]+$/, ""), + extension: filename.substring(filename.lastIndexOf(".") + 1), }; } @@ -155,15 +159,10 @@ export const getURLImageExtension = (url: string):string => { } export const getMimeType = (extension: string):MimeType => { + if(IMAGE_MIME_TYPES.hasOwnProperty(extension)) { + return IMAGE_MIME_TYPES[extension as ImageExtension]; + }; switch (extension) { - case "png": return "image/png"; - case "jpeg": return "image/jpeg"; - case "jpg": return "image/jpeg"; - case "gif": return "image/gif"; - case "webp": return "image/webp"; - case "bmp": return "image/bmp"; - case "ico": return "image/x-icon"; - case "svg": return "image/svg+xml"; case "md": return "image/svg+xml"; default: return "application/octet-stream"; } @@ -326,4 +325,40 @@ export const readLocalFileBinary = async (filePath:string): Promise export const getPathWithoutExtension = (f:TFile): string => { if(!f) return null; return f.path.substring(0, f.path.lastIndexOf(".")); +} + +const VAULT_BASE_URL = app.vault.adapter.url.pathToFileURL(app.vault.adapter.basePath).toString(); +export const getInternalLinkOrFileURLLink = ( + path: string, plugin:ExcalidrawPlugin, alias?: string, sourceFile?: TFile + ):{link: string, isInternal: boolean, file?: TFile, url?: string} => { + const vault = plugin.app.vault; + const fileURLString = vault.adapter.url.pathToFileURL(path).toString(); + if (fileURLString.startsWith(VAULT_BASE_URL)) { + const internalPath = normalizePath(fileURLString.substring(VAULT_BASE_URL.length)); + const file = vault.getAbstractFileByPath(internalPath); + if(file && file instanceof TFile) { + const link = plugin.app.metadataCache.fileToLinktext( + file, + sourceFile?.path, + true, + ); + return {link: getLink(plugin, { embed: false, path: link, alias}), isInternal: true, file}; + }; + } + return {link: `[${alias??""}](${fileURLString})`, isInternal: false, url: fileURLString}; +} + +/** + * get markdown or wiki link + * @param plugin + * @param param1: { embed = true, path, alias } + * @returns + */ +export const getLink = ( + plugin: ExcalidrawPlugin, + { embed = true, path, alias }: { embed?: boolean; path: string; alias?: string } +):string => { + return plugin.settings.embedWikiLink + ? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]` + : `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})` } \ No newline at end of file diff --git a/src/utils/ModifierkeyHelper.ts b/src/utils/ModifierkeyHelper.ts index f07f24c..24ccd14 100644 --- a/src/utils/ModifierkeyHelper.ts +++ b/src/utils/ModifierkeyHelper.ts @@ -1,19 +1,88 @@ -import { DEVICE, isDarwin } from "src/constants/constants"; +import { DEVICE } from "src/constants/constants"; +import { ExcalidrawSettings } from "src/settings"; export type ModifierKeys = {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean}; export type KeyEvent = PointerEvent | MouseEvent | KeyboardEvent | React.DragEvent | React.PointerEvent | React.MouseEvent | ModifierKeys; export type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties"; -export type ExternalDragAction = "insert-link"|"image-url"|"image-import"|"embeddable"; -export type LocalFileDragAction = "insert-link"|"image-uri"|"image-import"; +export type WebBrowserDragAction = "link"|"image-url"|"image-import"|"embeddable"; +export type LocalFileDragAction = "link"|"image-url"|"image-import"|"embeddable"; export type InternalDragAction = "link"|"image"|"image-fullsize"|"embeddable"; +export type ModifierSetType = "WebBrowserDragAction" | "LocalFileDragAction" | "InternalDragAction" | "LinkClickAction"; + +type ModifierKey = { + shift: boolean; + ctrl_cmd: boolean; + alt_opt: boolean; + meta_ctrl: boolean; + result: WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget; +}; + +export type ModifierKeySet = { + defaultAction: WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget; + rules: ModifierKey[]; +}; + +export type ModifierKeyTooltipMessages = Partial<{ + [modifierSetType in ModifierSetType]: Partial<{ + [action in WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget]: string; + }>; +}>; + +export const modifierKeyTooltipMessages = ():ModifierKeyTooltipMessages => { + return { + WebBrowserDragAction: { + "image-import": "Import Image to Vault", + "image-url": `Insert Image or YouTube Thumbnail with URL`, + "link": "Insert Link", + "embeddable": "Insert Interactive-Frame", + // Add more messages for WebBrowserDragAction as needed + }, + LocalFileDragAction: { + "image-import": "Insert Image: import external or reuse existing if path in Vault", + "image-url": `Insert Image: with local URI or internal-link if from Vault`, + "link": "Insert Link: local URI or internal-link if from Vault", + "embeddable": "Insert Interactive-Frame: local URI or internal-link if from Vault", + }, + InternalDragAction: { + "image": "Insert Image", + "image-fullsize": "Insert Image @100%", + "link": `Insert Link`, + "embeddable": "Insert Interactive-Frame", + }, + LinkClickAction: { + "active-pane": "Open in current active window", + "new-pane": "Open in a new adjacent window", + "popout-window": "Open in a popout window", + "new-tab": "Open in a new tab", + "md-properties": "Show the Markdown image-properties dialog (only relevant if you have embedded a markdown document as an image)", + }, + } +}; + +const processModifiers = (ev: KeyEvent, modifierType: ModifierSetType): WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget => { + const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings; + const keySet = ((DEVICE.isMacOS || DEVICE.isIOS) ? settings.modifierKeyConfig.Mac : settings.modifierKeyConfig.Win)[modifierType]; + for (const rule of keySet.rules) { + const { shift, ctrl_cmd, alt_opt, meta_ctrl, result } = rule; + if ( + (isSHIFT(ev) === shift) && + (isWinCTRLorMacCMD(ev) === ctrl_cmd) && + (isWinALTorMacOPT(ev) === alt_opt) && + (isWinMETAorMacCTRL(ev) === meta_ctrl) + ) { + return result; + } + } + return keySet.defaultAction; +} export const labelCTRL = () => DEVICE.isIOS || DEVICE.isMacOS ? "CMD" : "CTRL"; export const labelALT = () => DEVICE.isIOS || DEVICE.isMacOS ? "OPT" : "ALT"; export const labelMETA = () => DEVICE.isIOS || DEVICE.isMacOS ? "CTRL" : (DEVICE.isWindows ? "WIN" : "META"); export const labelSHIFT = () => "SHIFT"; -export const isCTRL = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.metaKey : e.ctrlKey; -export const isALT = (e:KeyEvent) => e.altKey; -export const isMETA = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey; +export const isWinCTRLorMacCMD = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.metaKey : e.ctrlKey; +export const isWinALTorMacOPT = (e:KeyEvent) => e.altKey; +export const isWinMETAorMacCTRL = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey; export const isSHIFT = (e:KeyEvent) => e.shiftKey; export const setCTRL = (e:ModifierKeys, value: boolean): ModifierKeys => { @@ -39,50 +108,38 @@ export const setSHIFT = (e:ModifierKeys, value: boolean): ModifierKeys => { return e; } -export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && isMETA(ev); -export const scaleToFullsizeModifier = (ev: KeyEvent) => - ( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) || - (!isSHIFT(ev) && isCTRL(ev) && isALT(ev) && !isMETA(ev)); - -export const linkClickModifierType = (ev: KeyEvent):PaneTarget => { - if(isCTRL(ev) && !isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "active-pane"; - if(isCTRL(ev) && !isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-tab"; - if(isCTRL(ev) && isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-pane"; - if(DEVICE.isDesktop && isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev) ) return "popout-window"; - if(isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "new-tab"; - if(mdPropModifier(ev)) return "md-properties"; - return "active-pane"; +export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isWinCTRLorMacCMD(ev) && !isWinALTorMacOPT(ev) && isWinMETAorMacCTRL(ev); +export const scaleToFullsizeModifier = (ev: KeyEvent) => { + const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings; + const keySet = ((DEVICE.isMacOS || DEVICE.isIOS) ? settings.modifierKeyConfig.Mac : settings.modifierKeyConfig.Win )["InternalDragAction"]; + const rule = keySet.rules.find(r => r.result === "image-fullsize"); + if(!rule) return false; + const { shift, ctrl_cmd, alt_opt, meta_ctrl, result } = rule; + return ( + (isSHIFT(ev) === shift) && + (isWinCTRLorMacCMD(ev) === ctrl_cmd) && + (isWinALTorMacOPT(ev) === alt_opt) && + (isWinMETAorMacCTRL(ev) === meta_ctrl) + ); } -export const externalDragModifierType = (ev: KeyEvent):ExternalDragAction => { - if(DEVICE.isWindows && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "embeddable"; - if(DEVICE.isMacOS && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "embeddable"; - if(DEVICE.isWindows && !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link"; - if(DEVICE.isMacOS && isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "insert-link"; - if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import"; - if(DEVICE.isWindows && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import"; - return "image-url"; +export const linkClickModifierType = (ev: KeyEvent):PaneTarget => { + const action = processModifiers(ev, "LinkClickAction") as PaneTarget; + if(!DEVICE.isDesktop && action === "popout-window") return "active-pane"; + return action; +} + +export const webbrowserDragModifierType = (ev: KeyEvent):WebBrowserDragAction => { + return processModifiers(ev, "WebBrowserDragAction") as WebBrowserDragAction; } export const localFileDragModifierType = (ev: KeyEvent):LocalFileDragAction => { - if(DEVICE.isWindows && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-uri"; - if(DEVICE.isMacOS && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-uri"; - if(DEVICE.isWindows && !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link"; - if(DEVICE.isMacOS && isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "insert-link"; - if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import"; - if(DEVICE.isWindows && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import"; - return "image-import"; + return processModifiers(ev, "LocalFileDragAction") as LocalFileDragAction; } - //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468 export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => { - if( !(DEVICE.isIOS || DEVICE.isMacOS) && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "embeddable"; - if( (DEVICE.isIOS || DEVICE.isMacOS) && !isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) return "embeddable"; - if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image"; - if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image"; - if(scaleToFullsizeModifier(ev)) return "image-fullsize"; - return "link"; + return processModifiers(ev, "InternalDragAction") as InternalDragAction; } export const emulateCTRLClickForLinks = (e:KeyEvent) => { @@ -97,27 +154,23 @@ export const emulateCTRLClickForLinks = (e:KeyEvent) => { export const emulateKeysForLinkClick = (action: PaneTarget): ModifierKeys => { const ev = {shiftKey: false, ctrlKey: false, metaKey: false, altKey: false}; if(!action) return ev; - switch(action) { - case "active-pane": - setCTRL(ev, true); - setSHIFT(ev, true); - break; - case "new-pane": - setCTRL(ev, true); - setALT(ev, true); - break; - case "popout-window": - setCTRL(ev, true); - setALT(ev, true); - setSHIFT(ev, true); - break; - case "new-tab": - setCTRL(ev, true); - break; - case "md-properties": - setCTRL(ev, true); - setMETA(ev, true); - break; + const platform = DEVICE.isMacOS || DEVICE.isIOS ? "Mac" : "Win"; + const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings; + const modifierKeyConfig = settings.modifierKeyConfig; + + const config = modifierKeyConfig[platform]?.LinkClickAction; + + if (config) { + const rule = config.rules.find(rule => rule.result === action); + if (rule) { + setCTRL(ev, rule.ctrl_cmd); + setALT(ev, rule.alt_opt); + setMETA(ev, rule.meta_ctrl); + setSHIFT(ev, rule.shift); + } else { + const defaultAction = config.defaultAction as PaneTarget; + return emulateKeysForLinkClick(defaultAction); + } } return ev; } diff --git a/styles.css b/styles.css index 0395d1d..d31928a 100644 --- a/styles.css +++ b/styles.css @@ -504,3 +504,31 @@ hr.excalidraw-setting-hr { .excalidraw .canvas-node .ex-md-font-code { --font-text: "Cascadia"; } + +.excalidraw__embeddable-container .workspace-leaf, +.excalidraw__embeddable-container .workspace-leaf .view-content { + ::-webkit-scrollbar, + ::-webkit-scrollbar-horizontal { + display: none; + } + background-color: transparent !important; +} + +.excalidraw__embeddable-container .workspace-leaf-content .view-content { + padding-left: 2px; + padding-right: 2px; + padding-top: 0px; + padding-bottom: 0px; +} + +.excalidraw__embeddable-container .workspace-leaf .view-content { + display: flex; + align-items: center; + justify-content: center; +} + +.excalidraw__embeddable-container .workspace-leaf-content .image-container, +.excalidraw__embeddable-container .workspace-leaf-content .audio-container, +.excalidraw__embeddable-container .workspace-leaf-content .video-container { + display: flex; +} \ No newline at end of file