This commit is contained in:
zsviczian
2023-12-03 20:00:47 +01:00
parent 87b6335905
commit 52cc5d3aa7
23 changed files with 587 additions and 212 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 949 KiB

After

Width:  |  Height:  |  Size: 409 KiB

View File

@@ -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",

View File

@@ -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<ImgData> {
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,

View File

@@ -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<event.dataTransfer.files.length;i++) {
//@ts-ignore
const path = encodeURI(event.dataTransfer.files[i].path);
const {x,y} = this.currentPosition;
await insertImageToView(getEA(this), {x:x+i*300, y:y+i*300}, `file://${path}`);
if (event.dataTransfer.types.length >= 1 && ["image-url","image-import","embeddable"].contains(localFileDragAction)) {
for(let i=0;i<event.dataTransfer.files.length;i++) {
//@ts-ignore
const path = event.dataTransfer.files[i].path;
if(!path) return true; //excalidarw to continue processing
const link = getInternalLinkOrFileURLLink(path, this.plugin, event.dataTransfer.files[i].name, this.file);
const {x,y} = this.currentPosition;
const pos = {x:x+i*300, y:y+i*300};
if(link.isInternal) {
if(localFileDragAction === "embeddable") {
insertEmbeddableToView(getEA(this), pos, link.file);
} else {
insertImageToView(getEA(this), pos, link.file);
}
} else {
const extension = getURLImageExtension(link.url);
if(
localFileDragAction === "image-import" &&
(IMAGE_TYPES.contains(extension) || extension === "excalidraw")
) {
return true; //excalidarw to continue processing
}
if(localFileDragAction === "embeddable" || !IMAGE_TYPES.contains(extension)) {
insertEmbeddableToView(getEA(this), pos, null, link.url);
if(localFileDragAction !== "embeddable") {
new Notice("Not imported to Vault. Embedded with local URI");
}
} else {
insertImageToView(getEA(this), pos, link.url);
}
}
})();
};
return false;
}
if(event.dataTransfer.types.length >= 1 && localFileDragAction === "insert-link") {
if(event.dataTransfer.types.length >= 1 && localFileDragAction === "link") {
const ea = getEA(this) as ExcalidrawAutomate;
for(let i=0;i<event.dataTransfer.files.length;i++) {
//@ts-ignore
const path = event.dataTransfer.files[i].path;
const name = event.dataTransfer.files[i].name;
if(!path || !name) return true; //excalidarw to continue processing
const link = getInternalLinkOrFileURLLink(path, this.plugin, name, this.file);
const id = ea.addText(
this.currentPosition.x+i*40,
this.currentPosition.y+i*20,
`📂 ${name}`);
ea.getElement(id).link = `[${name}](file://${path})`;
link.isInternal ? link.link :`📂 ${name}`);
if(!link.isInternal) {
ea.getElement(id).link = link.link;
}
}
ea.addElementsToView();
return false;
@@ -3898,7 +3913,7 @@ export default class ExcalidrawView extends TextFileView {
let event = e?.detail?.nativeEvent;
if(this.handleLinkHookCall(element,element.link,event)) return;
if(this.openExternalLink(element.link, !isSHIFT(event) && !isCTRL(event) && !isMETA(event) && !isALT(event) ? element : undefined)) return;
if(this.openExternalLink(element.link, !isSHIFT(event) && !isWinCTRLorMacCMD(event) && !isWinMETAorMacCTRL(event) && !isWinALTorMacOPT(event) ? element : undefined)) return;
//if element is type text and element has multiple links, then submit the element text to linkClick to trigger link suggester
if(element.type === "text") {
@@ -3927,7 +3942,7 @@ export default class ExcalidrawView extends TextFileView {
if (
element &&
(this.plugin.settings.hoverPreviewWithoutCTRL ||
isCTRL(event))
isWinCTRLorMacCMD(event))
) {
this.lastMouseEvent = event;
this.lastMouseEvent.ctrlKey = !(DEVICE.isIOS || DEVICE.isMacOS) || this.lastMouseEvent.ctrlKey;

View File

@@ -400,6 +400,7 @@ const createImgElement = async (
fheight: imgOrDiv.getAttribute("h"),
style: [...Array.from(imgOrDiv.classList)],
}, onCanvas);
if(!newImg) return;
parent.empty();
if(!onCanvas) {
newImg.style.maxHeight = imgMaxHeigth;

View File

@@ -139,7 +139,8 @@ export const REG_BLOCK_REF_CLEAN = /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\\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";

View File

@@ -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();
})
);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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";

View File

@@ -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<TFile> {
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<TFile> {
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) {

View File

@@ -17,6 +17,23 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
`,
"2.0.5":`
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/kp1K7GRrE6E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
# 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":`
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

View File

@@ -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<string, ModifierKeySet>;
Win: Record<string, ModifierKeySet>;
},
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();
})
);
});
});
}
}

View File

@@ -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();
}

View File

@@ -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";

View File

@@ -277,6 +277,12 @@ FILENAME_HEAD: "Filename",
"the plugin will open it in a browser. " +
"When Obsidian files change, the matching <code>[[link]]</code> in your drawings will also change. " +
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
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: ",
};

View File

@@ -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;

View File

@@ -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<PanelProps, PanelState> {
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<PanelProps, PanelState> {
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<PanelProps, PanelState> {
key={"link-to-element"}
title={t("INSERT_LINK_TO_ELEMENT")}
action={(e:React.MouseEvent<HTMLButtonElement, 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}

View File

@@ -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<ModifierSetType, ModifierKeySet>,
Win: Record<ModifierSetType, ModifierKeySet>,
}
}
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

9
src/types.d.ts vendored
View File

@@ -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;
}
}

View File

@@ -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(

View File

@@ -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<ArrayBuffer>
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)})`
}

View File

@@ -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;
}

View File

@@ -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;
}