Compare commits

...

3 Commits

Author SHA1 Message Date
zsviczian
41b1a170f7 1.8.15-beta 2023-02-19 20:58:04 +01:00
zsviczian
e6d39eca75 upload modifiers table 2023-02-19 16:56:12 +01:00
zsviczian
8ca6a9fe96 1.8.14 2023-02-13 17:16:32 +01:00
28 changed files with 637 additions and 374 deletions

5
.gitignore vendored
View File

@@ -15,5 +15,6 @@ data.json
lib
#VSCode
.vscode
yarn.lock
.vscode
yarn.lock
.DS_Store

32
README-BUILD.md Normal file
View File

@@ -0,0 +1,32 @@
The project runs with `node 16.10.0`. Some packages will throw dependency errors if you try to compile with a higher node version.
After running `npm -i` you'll need to make two manual changes:
## postprocess
postprocess is used in rollup.config.js.
However, the version available on npmjs does not work, after installing packages you need this update:
`npm install brettz9/rollup-plugin-postprocess#update --save-dev``
More info here: https://github.com/developit/rollup-plugin-postprocess/issues/10
## colormaster
1.2.1 misses 3 plugin references after installing the package you need to update
`node_modules/colormaster/package.json` adding the following to the `exports:` section:
```typescript
,
"./plugins/luv": {
"import": "./plugins/luv.mjs",
"require": "./plugins/luv.js",
"default": "./plugins/luv.mjs"
},
"./plugins/uvw": {
"import": "./plugins/uvw.mjs",
"require": "./plugins/uvw.js",
"default": "./plugins/uvw.mjs"
},
"./plugins/ryb": {
"import": "./plugins/ryb.mjs",
"require": "./plugins/ryb.js",
"default": "./plugins/ryb.mjs"
}
```

View File

@@ -126,7 +126,10 @@ Plugin settings are grouped into the following sections:
- Excalidraw drawings do not display in Obsidian Publish. If you want to use Excalidraw in your published documents, you can configure in plugin settings, under `Embed & Export`, to automatically insert a PNG or SVG version of the drawing in your document when creating a new file. See `type of file to insert into document`
- Under `Export settings` you can also configure to automatically export a dark and light version of the image, in case your published site supports dark and light mode.
### Hyperlinks
### Hyperlinks and Drag & Drop support
![](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/images/excalidraw-modifiers.png)
#### Hyperlinks
- Supports hyperlinks e.g.
- `https://zsolt.blog`,
- `[Obsidian](https://obsidian.md)`, and
@@ -141,23 +144,11 @@ Plugin settings are grouped into the following sections:
in curly brackets right after the transclusion e.g. `![[myfile#^blockref]]{40}` will wrap text at 40 characters.
- For convenience you can also use the command palette to insert links into drawings
- <kbd>CTRL/CMD + hover</kbd> to bring up the Obsidian quick preview for the link. (On Mac it is <kbd>CTRL+CMD+hover</kbd>).
- <kbd>CTRL/CMD + CLICK</kbd> a text element to open it as a link.
- <kbd>CTRL/CMD + ALT + CLICK</kbd> to create the file (if it does not yet exist) and open it
- <kbd>CTRL/CMD + SHIFT + CLICK</kbd> to open the file in a new pane
- <kbd>CTRL/CMD + ALT + SHIFT + CLICK</kbd> to create the file (if it does not yet exist) and open it in a new pane
- Using the block reference you can also reference & transclude text that appears on drawings, in other documents
### LaTeX
Insert LaTeX formulas using the Command Palette action "Insert LaTeX formula".
You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd> on the formula.
### Drag & Drop support
- You can drag files from the Obsidian file explorer and they will become links to those files in Excalidraw.
- Dragging image files (PNG, SVG, JPG, ICO, GIF, WEBP, Excalidraw) from Obsidian's file explorer while pressing the
<kbd>CTRL</kbd> (<kbd>SHIFT</kbd> on Mac) button will embed the image into your drawing.
- If in addition to <kbd>CTRL</kbd> or <kbd>SHIFT</kbd> you also hold down <kbd>ALT</kbd>,
the image will be inserted at 100% of its size.
- Note: this is a very niche feature with a very particular behavior that I built primarily for myself
#### Drag & Drop support
- You can drag files from the Obsidian file explorer and they will become links to those files in Excalidraw. See table above for the varios modifier key combinations.
- Note: anchoring an image to 100% of its size is a very niche feature with a very particular behavior that I built primarily for myself
- (even more so than other features in Excalidraw Obsidian - also built primarily for myself 😉).
- This will reset your embedded image to 100% size every time you open the Excalidraw drawing,
or in case you have embedded an Excalidraw drawing on your canvas inserted using this function,
@@ -169,6 +160,11 @@ You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd>
construct Book-on-a-Page summaries from atomic drawings.
- You can drag and drop text from Markdown views onto Excalidraw.
- You can drag and drop web addresses from your browser and they will become links.
- You can drag and drop YouTube links and thumbnails and they will be YouTube links with thumbnails in Excalidraw
### LaTeX
Insert LaTeX formulas using the Command Palette action "Insert LaTeX formula".
You can edit formulas either in Markdown view, or by <kbd>CTRL/CMD + Click</kbd> on the formula.
### Image support
- On iOS and Android you can add images from your camera by pressing the add image button in Excalidraw.
@@ -215,7 +211,7 @@ For more details see this [video](https://youtu.be/yZQoJg2RCKI)
- `excalidraw-export-pngscale`: This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.
### Embed complete markdown files into your drawings
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>CTRL/CMD</kbd> while dropping the file onto the canvas.
- Drag from the desired file from the Obsidian file explorer and hold down <kbd>SHIFT</kbd> while dropping the file onto the canvas.
- Use the command palette action: `Insert markdown file from vault`
- Use custom woff, woff2 or TTF font to display the document, you can set the default font to use under Excalidraw Settings.
- You can set a custom css for rendering the snapshot image of your markdown document.
@@ -226,7 +222,7 @@ For more details see this [video](https://youtu.be/yZQoJg2RCKI)
- (for a demonstration watch this [video](https://youtu.be/K6qZkTz8GHs) and check out this
- [sample css](https://github.com/zsviczian/obsidian-excalidraw-plugin/discussions/281)).
- To help with styling you can observe the SVG snapshot of the markdown document created by Excalidraw.
- Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>) and
- Open Obsidian Developer Console (<kbd>CTRL+Shift+i</kbd>/<kbd>CMD+OPT+i</kbd>) and
- execute the following command: `ExcalidrawAutomate.mostRecentMarkdownSVG`
- You can control appearance of the embedded markdown file on a file by file
bases by adding the following front matter keys to your markdown document:
@@ -235,7 +231,7 @@ For more details see this [video](https://youtu.be/yZQoJg2RCKI)
- you can find css color names [here](https://www.w3schools.com/colors/colors_names.asp).
- `excalidraw-border-color: css-color-name|#HEXcolor|any-other-html-standard-format`
- `excalidraw-css: "css-filename|css snippet"`
- Switch to markdown view or use <kbd>CTRL/CMD+ALT/OPT</kbd> click on the image to edit properties of the embed:
- Switch to markdown view or use <kbd>WIN+CTRL</kbd>/<kbd>CMD+CTRL</kbd> click on the image to edit properties of the embed:
- `[[filename#^blockref|WIDTHxMAXHEIGHT]]`
### Custom Font, Custom Pen, OCR support, SVG import

14
TODO.md
View File

@@ -1,14 +0,0 @@
[x] do not embed font into SVG when embedding Excalidraw into other Excalidraw
[x] add ```html <SVG>...</SVG> ``` codeblock to excalidraw markdown
[x] read pre-saved `<SVG>` when generating image preview
[x] update code to adopt change files moving from AppState to App
- Add "files" to legacy excalidraw export
[x] PNG preview
[x] markdown embed SVG 190
[x] markdown embed PNG
[x] embed Excalidraw into other Excalidraw

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.8.3-beta",
"version": "1.8.15-beta",
"minAppVersion": "0.16.0",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.8.13",
"version": "1.8.14",
"minAppVersion": "1.0.0",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -19,16 +19,19 @@
"dependencies": {
"@types/lz-string": "^1.3.34",
"@zsviczian/excalidraw": "0.14.1-obsidian",
"chroma-js": "^2.4.2",
"clsx": "^1.2.1",
"colormaster": "^1.2.1",
"gl-matrix": "^3.4.3",
"lz-string": "^1.4.4",
"monkey-around": "^2.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "^5.0.1",
"roughjs": "^4.5.2",
"colormaster": "1.2.1",
"chroma-js": "^2.4.2",
"gl-matrix": "^3.4.3"
"html2canvas": "^1.4.1",
"@popperjs/core": "^2.11.6",
"nanoid": "^4.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.12",
@@ -36,22 +39,19 @@
"@babel/preset-react": "^7.18.6",
"@excalidraw/eslint-config": "^1.0.3",
"@excalidraw/prettier-config": "^1.0.2",
"@popperjs/core": "^2.11.6",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^24.0.0",
"@rollup/plugin-node-resolve": "^15.0.1",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-typescript": "^11.0.0",
"@types/js-beautify": "^1.13.3",
"@types/chroma-js": "^2.1.4",
"@types/js-beautify": "^1.13.3",
"@types/node": "^18.11.18",
"@types/react-dom": "^18.0.10",
"@zerollup/ts-transform-paths": "^1.7.18",
"cross-env": "^7.0.3",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"html2canvas": "^1.4.1",
"nanoid": "^4.0.0",
"obsidian": "^1.1.1",
"prettier": "^2.8.2",
"rollup": "^2.70.1",

View File

@@ -23,6 +23,7 @@ import { ExportSettings } from "./ExcalidrawView";
import { t } from "./lang/helpers";
import { tex2dataURL } from "./LaTeX";
import ExcalidrawPlugin from "./main";
import { getDataURLFromURL, getMimeType, getURLImageExtension } from "./utils/FileUtils";
import {
errorlog,
getDataURL,
@@ -40,6 +41,15 @@ import {
const THEME_FILTER = "invert(100%) hue-rotate(180deg) saturate(1.25)";
//An ugly workaround for the following situation.
//File A is a markdown file that has an embedded Excalidraw file B
//Later file A is embedded into file B as a Markdown embed
//Because MarkdownRenderer.renderMarkdown does not take a depth parameter as input
//EmbeddedFileLoader cannot track the recursion depth (as it can when Excalidraw drawings are embedded)
//For this reason, the markdown TFile is added to the Watchdog when rendering starts
//and getObsidianImage is aborted if the file is already in the Watchdog stack
const markdownRendererRecursionWatcthdog = new Set<TFile>();
export declare type MimeType =
| "image/svg+xml"
| "image/png"
@@ -224,9 +234,15 @@ export class EmbeddedFilesLoader {
if (!this.plugin || !inFile) {
return null;
}
const isHyperlink = inFile instanceof EmbeddedFile ? inFile.isHyperlink : false;
const hyperlink = inFile instanceof EmbeddedFile ? inFile.hyperlink : "";
const file: TFile = inFile instanceof EmbeddedFile ? inFile.file : inFile;
if(file && markdownRendererRecursionWatcthdog.has(file)) {
new Notice(`Loading of ${file.path}. Please check if there is an inifinite loop of one file embedded in the other.`);
return null;
}
const linkParts =
isHyperlink
? null
@@ -307,60 +323,19 @@ export class EmbeddedFilesLoader {
? await getExcalidrawSVG(this.isDark)
: null;
let mimeType: MimeType = "image/svg+xml";
const corelink = isHyperlink
? hyperlink.split("?")[0]
: "";
const extension = isHyperlink
? corelink.substring(corelink.lastIndexOf(".")+1)
? getURLImageExtension(hyperlink)
: file.extension;
if (!isExcalidrawFile) {
switch (extension) {
case "png":
mimeType = "image/png";
break;
case "jpeg":
mimeType = "image/jpeg";
break;
case "jpg":
mimeType = "image/jpeg";
break;
case "gif":
mimeType = "image/gif";
break;
case "webp":
mimeType = "image/webp";
break;
case "bmp":
mimeType = "image/bmp";
break;
case "ico":
mimeType = "image/x-icon"
break;
case "svg":
case "md":
mimeType = "image/svg+xml";
break;
default:
mimeType = "application/octet-stream";
}
}
let response:RequestUrlResponse;
if(isHyperlink && inFile instanceof EmbeddedFile) {
try {
response = await Promise.race([
(async () => new Promise<RequestUrlResponse>((resolve) => setTimeout(()=>resolve(null), URLFETCHTIMEOUT)))(),
requestUrl({url: inFile.hyperlink, method: "get", contentType: mimeType, throw: false })
])
} catch (e) {
errorlog({where: this.getObsidianImage, message: `URL did not load within timeout period of ${URLFETCHTIMEOUT}ms`, url: inFile.hyperlink});
}
mimeType = getMimeType(extension);
}
let dataURL =
isHyperlink
? (
inFile instanceof EmbeddedFile
? (response && response.status === 200 ? await getDataURL(response.arrayBuffer, mimeType) : inFile.hyperlink)
? await getDataURLFromURL(inFile.hyperlink, mimeType)
: null
)
: excalidrawSVG ??
@@ -371,7 +346,9 @@ export class EmbeddedFilesLoader {
: await getDataURL(ab, mimeType));
if(!isHyperlink && !dataURL) {
const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts);
markdownRendererRecursionWatcthdog.add(file);
const result = await this.convertMarkdownToSVG(this.plugin, file, linkParts, depth);
markdownRendererRecursionWatcthdog.delete(file);
dataURL = result.dataURL;
hasSVGwithBitmap = result.hasSVGwithBitmap;
}
@@ -473,6 +450,7 @@ export class EmbeddedFilesLoader {
plugin: ExcalidrawPlugin,
file: TFile,
linkParts: LinkParts,
depth: number,
): Promise<{dataURL: DataURL, hasSVGwithBitmap:boolean}> {
//1.
//get the markdown text
@@ -700,7 +678,7 @@ const getSVGData = async (app: App, file: TFile): Promise<DataURL> => {
return svgToBase64(svg) as DataURL;
};
const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
let id: FileId;
try {
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);

View File

@@ -38,7 +38,7 @@ import {
wrapTextAtCharLength,
} from "./utils/Utils";
import { getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils";
import { AppState, BinaryFileData, Point } from "@zsviczian/excalidraw/types/types";
import { AppState, BinaryFileData, DataURL, Point } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader";
import { tex2dataURL } from "./LaTeX";
//import Excalidraw from "@zsviczian/excalidraw";

View File

@@ -17,6 +17,7 @@ import {
FRONTMATTER_KEY_LINKBUTTON_OPACITY,
FRONTMATTER_KEY_ONLOAD_SCRIPT,
FRONTMATTER_KEY_AUTOEXPORT,
DEVICE,
} from "./Constants";
import { _measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
@@ -40,8 +41,8 @@ import {
ExcalidrawImageElement,
FileId,
} from "@zsviczian/excalidraw/types/element/types";
import { BinaryFiles, SceneData } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFile } from "./EmbeddedFileLoader";
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
@@ -528,9 +529,6 @@ export class ExcalidrawData {
if (parseRes.link) {
textEl.link = parseRes.link;
}
if(!parseRes.link && elementLink.done) {
textEl.link = null;
}
//this will set the rawText field of text elements imported from files before 1.3.14, and from other instances of Excalidraw
if (textEl && (!textEl.rawText || textEl.rawText === "")) {
textEl.rawText = text;
@@ -1096,6 +1094,54 @@ export class ExcalidrawData {
);
}
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId) {
const scene = this.scene as SceneDataWithFiles;
let fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
}
const filepath = (
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
).filepath;
const file = await this.app.vault.createBinary(
filepath,
getBinaryFileFromDataURL(dataURL),
);
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
filepath,
);
embeddedFile.setImage(
dataURL,
mimeType,
{ height: 0, width: 0 },
scene.appState?.theme === "dark",
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
);
this.setFile(key as FileId, embeddedFile);
return file;
}
/**
* deletes fileIds from Excalidraw data for files no longer in the scene
* @returns
@@ -1165,47 +1211,11 @@ export class ExcalidrawData {
for (const key of Object.keys(scene.files)) {
if (!(this.hasFile(key as FileId) || this.hasEquation(key as FileId))) {
dirty = true;
let fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
const mimeType = scene.files[key].mimeType;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
}
const filepath = (
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
).filepath;
const dataURL = scene.files[key].dataURL;
await this.app.vault.createBinary(
filepath,
getBinaryFileFromDataURL(dataURL),
await this.saveDataURLtoVault(
scene.files[key].dataURL,
scene.files[key].mimeType,
key as FileId
);
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
filepath,
);
embeddedFile.setImage(
dataURL,
mimeType,
{ height: 0, width: 0 },
scene.appState?.theme === "dark",
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
);
this.setFile(key as FileId, embeddedFile);
}
}
@@ -1349,7 +1359,7 @@ export class ExcalidrawData {
public getOpenMode(): { viewModeEnabled: boolean; zenModeEnabled: boolean } {
const fileCache = this.app.metadataCache.getFileCache(this.file);
let mode = this.plugin.settings.defaultMode === "view-mobile"
? (this.plugin.device.isPhone ? "view" : "normal")
? (DEVICE.isPhone ? "view" : "normal")
: this.plugin.settings.defaultMode;
if (
fileCache?.frontmatter &&

View File

@@ -24,7 +24,6 @@ import {
BinaryFileData,
ExcalidrawImperativeAPI,
LibraryItems,
PointerDownState,
} from "@zsviczian/excalidraw/types/types";
import {
VIEW_TYPE_EXCALIDRAW,
@@ -38,13 +37,13 @@ import {
TEXT_DISPLAY_PARSED_ICON_NAME,
FULLSCREEN_ICON_NAME,
IMAGE_TYPES,
CTRL_OR_CMD,
REG_LINKINDEX_INVALIDCHARS,
KEYCODE,
FRONTMATTER_KEY_EXPORT_PADDING,
FRONTMATTER_KEY_EXPORT_PNGSCALE,
FRONTMATTER_KEY_EXPORT_DARK,
FRONTMATTER_KEY_EXPORT_TRANSPARENT,
DEVICE,
} from "./Constants";
import ExcalidrawPlugin from "./main";
import { repositionElementsToCursor, ExcalidrawAutomate, getTextElementsMatchingQuery, cloneElement } from "./ExcalidrawAutomate";
@@ -58,8 +57,11 @@ import {
import {
checkAndCreateFolder,
download,
getDataURLFromURL,
getIMGFilename,
getMimeType,
getNewUniqueFilepath,
getURLImageExtension,
} from "./utils/FileUtils";
import {
checkExcalidrawVersion,
@@ -75,9 +77,7 @@ import {
getExportPadding,
getWithBackground,
hasExportTheme,
isVersionNewerThanOther,
scaleLoadedImage,
setDocLeftHandedMode,
svgToBase64,
viewportCoordsToSceneCoords,
updateFrontmatterInString,
@@ -85,7 +85,7 @@ import {
hyperlinkIsYouTubeLink,
getYouTubeThumbnailLink,
} from "./utils/Utils";
import { getNewOrAdjacentLeaf, getParentOfClass } from "./utils/ObsidianUtils";
import { getLeaf, getNewOrAdjacentLeaf, getParentOfClass } from "./utils/ObsidianUtils";
import { splitFolderAndFilename } from "./utils/FileUtils";
import { NewFileActions, Prompt } from "./dialogs/Prompt";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
@@ -94,6 +94,7 @@ import {
EmbeddedFile,
EmbeddedFilesLoader,
FileData,
generateIdFromFile,
} from "./EmbeddedFileLoader";
import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
import { ObsidianMenu } from "./menu/ObsidianMenu";
@@ -106,6 +107,7 @@ import { ICONS, saveIcon } from "./menu/ActionIcons";
//import {WelcomeScreen} from "@zsviczian/excalidraw";
import { ExportDialog } from "./dialogs/ExportDialog";
import { getEA } from "src";
import { externalDragModifierType, internalDragModifierType, isALT, isCTRL, isMETA, isSHIFT, linkClickModifierType, mdPropModifier, ModifierKeys } from "./utils/ModifierkeyHelper";
type SelectedElementWithLink = { id: string; text: string };
type SelectedImage = { id: string; fileId: FileId };
@@ -212,14 +214,12 @@ export default class ExcalidrawView extends TextFileView {
private onKeyUp: (e: KeyboardEvent) => void;
private onKeyDown:(e: KeyboardEvent) => void;
//store key state for view mode link resolution
private metaKeyDown: boolean = false;
private ctrlKeyDown: boolean = false;
private shiftKeyDown: boolean = false;
private altKeyDown: boolean = false;
private modifierKeyDown: ModifierKeys = {shiftKey:false, metaKey: false, ctrlKey: false, altKey: false}
public currentPosition: {x:number,y:number} = { x: 0, y: 0 };
//Obsidian 0.15.0
public ownerWindow: Window;
public ownerDocument: Document;
private draginfoDiv: HTMLDivElement;
public semaphores: {
popoutUnload: boolean; //the unloaded Excalidraw view was the last leaf in the popout window
@@ -845,12 +845,13 @@ export default class ExcalidrawView extends TextFileView {
selectedText: SelectedElementWithLink,
selectedImage: SelectedImage,
selectedElementWithLink: SelectedElementWithLink,
keys?: {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean}
keys?: ModifierKeys
) {
if(!selectedText) selectedText = {id:null, text: null};
if(!selectedImage) selectedImage = {id:null, fileId: null};
if(!selectedElementWithLink) selectedElementWithLink = {id:null, text:null};
if(!keys) keys = {shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey, metaKey: ev.metaKey, altKey: ev.altKey};
const linkClickType = linkClickModifierType(keys);
let file = null;
let subpath: string = null;
@@ -929,7 +930,7 @@ export default class ExcalidrawView extends TextFileView {
window.open(ef.hyperlink,"_blank");
return;
}
if (keys.altKey) {
if (linkClickType === "md-properties") {
if (
ef.file.extension === "md" &&
!this.plugin.isExcalidrawFile(ef.file)
@@ -968,20 +969,18 @@ export default class ExcalidrawView extends TextFileView {
if(this.handleLinkHookCall(el,linkText,ev)) return;
try {
if (keys.shiftKey && this.isFullscreen()) {
if (linkClickType !== "active-pane" && this.isFullscreen()) {
this.exitFullscreen();
}
if (!file) {
new NewFileActions(this.plugin, linkText, ev.shiftKey, !app.isMobile && ev.metaKey, this).open();
new NewFileActions(this.plugin, linkText, keys, this).open();
return;
}
const leaf =
(!app.isMobile && ((keys.metaKey && this.linksAlwaysOpenInANewPane) || keys.metaKey))
//@ts-ignore
? app.workspace.openPopoutLeaf()
: (keys.shiftKey || this.linksAlwaysOpenInANewPane)
? getNewOrAdjacentLeaf(this.plugin, this.leaf)
: this.leaf;
if(this.linksAlwaysOpenInANewPane) {
keys.ctrlKey = true;
keys.altKey = true;
}
const leaf = getLeaf(this.plugin,this.leaf,keys);
await leaf.openFile(file, subpath ? { active: false, eState: { subpath } } : undefined); //if file exists open file and jump to reference
//view.app.workspace.setActiveLeaf(leaf, true, true); //0.15.4 ExcaliBrain focus issue
} catch (e) {
@@ -1123,17 +1122,21 @@ export default class ExcalidrawView extends TextFileView {
self.addParentMoveObserver();
self.onKeyUp = (e: KeyboardEvent) => {
self.ctrlKeyDown = e[CTRL_OR_CMD];
self.shiftKeyDown = e.shiftKey;
self.altKeyDown = e.altKey;
self.metaKeyDown = e.metaKey;
self.modifierKeyDown = {
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey
}
};
self.onKeyDown = (e: KeyboardEvent) => {
this.ctrlKeyDown = e[CTRL_OR_CMD];
this.shiftKeyDown = e.shiftKey;
this.altKeyDown = e.altKey;
this.metaKeyDown = e.metaKey;
this.modifierKeyDown = {
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey
}
};
self.ownerWindow.addEventListener("keydown", self.onKeyDown, false);
@@ -1978,6 +1981,38 @@ export default class ExcalidrawView extends TextFileView {
ea.addElementsToView(true,true,true);
}
async addImageSaveToVault(link:string) {
const ea = getEA(this) as ExcalidrawAutomate;
const mimeType = getMimeType(getURLImageExtension(link));
const dataURL = await getDataURLFromURL(link,mimeType,3000);
const fileId = await generateIdFromFile((new TextEncoder()).encode(dataURL as string))
const file = await this.excalidrawData.saveDataURLtoVault(dataURL,mimeType,fileId);
await ea.addImage(0,0,file);
ea.addElementsToView(true,true,true);
}
async addTextWithIframely(text:string) {
const id = await this.addText(text);
const url = `http://iframely.server.crestify.com/iframely?url=${text}`;
try {
const data = JSON.parse(await request({ url }));
if (!data || data.error || !data.meta?.title) {
return;
}
const ea = getEA(this) as ExcalidrawAutomate;
const el = ea
.getViewElements()
.filter((el) => el.id === id);
if (el.length === 1) {
//@ts-ignore
el[0].text = el[0].originalText = el[0].rawText =
`[${data.meta.title}](${text})`;
ea.copyViewElementsToEAforEditing(el);
ea.addElementsToView(false, false, false);
}
} catch(e) {};
}
onPaneMenu(menu: Menu, source: string): void {
if(this.excalidrawAPI && this.getViewSelectedElements().some(el=>el.type==="text")) {
menu.addItem(item => {
@@ -2029,12 +2064,12 @@ export default class ExcalidrawView extends TextFileView {
if (!this.getScene || !this.file) {
return;
}
if (ev[CTRL_OR_CMD]) {
this.exportPNG(ev.shiftKey);
if (isCTRL(ev)) {
this.exportPNG(isSHIFT(ev));
return;
}
this.savePNG(undefined,ev.shiftKey);
new Notice(`PNG export is ready${ev.shiftKey?" with embedded scene":""}`);
this.savePNG(undefined,isSHIFT(ev));
new Notice(`PNG export is ready${isSHIFT(ev)?" with embedded scene":""}`);
})
.setSection("pane");
})
@@ -2047,12 +2082,12 @@ export default class ExcalidrawView extends TextFileView {
if (!this.getScene || !this.file) {
return;
}
if (ev[CTRL_OR_CMD]) {
this.exportSVG(ev.shiftKey);
if (isCTRL(ev)) {
this.exportSVG(isSHIFT(ev));
return;
}
this.saveSVG(undefined,ev.shiftKey);
new Notice(`SVG export is ready${ev.shiftKey?" with embedded scene":""}`);
this.saveSVG(undefined,isSHIFT(ev));
new Notice(`SVG export is ready${isSHIFT(ev)?" with embedded scene":""}`);
});
})
.addItem(item => {
@@ -2607,10 +2642,10 @@ export default class ExcalidrawView extends TextFileView {
selectedTextElement = getTextElementAtPointer(this.currentPosition, this);
if (selectedTextElement && selectedTextElement.id) {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: this.metaKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
ctrlKey: !(DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.ctrlKey,
metaKey: (DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.metaKey,
shiftKey: this.modifierKeyDown.shiftKey,
altKey: this.modifierKeyDown.altKey,
});
this.handleLinkClick(event);
selectedTextElement = null;
@@ -2619,10 +2654,10 @@ export default class ExcalidrawView extends TextFileView {
selectedImageElement = getImageElementAtPointer(this.currentPosition, this);
if (selectedImageElement && selectedImageElement.id) {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: this.metaKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
ctrlKey: !(DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.ctrlKey,
metaKey: (DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.metaKey,
shiftKey: this.modifierKeyDown.shiftKey,
altKey: this.modifierKeyDown.altKey,
});
this.handleLinkClick(event);
selectedImageElement = null;
@@ -2632,10 +2667,10 @@ export default class ExcalidrawView extends TextFileView {
selectedElementWithLink = getElementWithLinkAtPointer(this.currentPosition, this);
if (selectedElementWithLink && selectedElementWithLink.id) {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: this.metaKeyDown,
shiftKey: this.shiftKeyDown,
altKey: this.altKeyDown,
ctrlKey: !(DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.ctrlKey,
metaKey: (DEVICE.isIOS || DEVICE.isMacOS) || this.modifierKeyDown.metaKey,
shiftKey: this.modifierKeyDown.shiftKey,
altKey: this.modifierKeyDown.altKey,
});
this.handleLinkClick(event);
selectedElementWithLink = null;
@@ -2779,7 +2814,7 @@ export default class ExcalidrawView extends TextFileView {
this.exitFullscreen();
}
if (e[CTRL_OR_CMD] && !e.shiftKey && !e.altKey) {
if (isCTRL(e) && !isSHIFT(e) && !isALT(e)) {
showHoverPreview();
}
},
@@ -2787,10 +2822,10 @@ export default class ExcalidrawView extends TextFileView {
//onClick: (e: MouseEvent): any => {
//to onPointerDown so touch events also open links on the iPad (with a keyboard)
onPointerDown: (e: PointerEvent) => {
if (!(e[CTRL_OR_CMD]||e.metaKey)) {
if (!(isCTRL(e)||isMETA(e))) {
return;
}
if (!this.plugin.settings.allowCtrlClick && !e.metaKey) {
if (!this.plugin.settings.allowCtrlClick && !!isMETA(e)) {
return;
}
//added setTimeout when I changed onClick(e: MouseEvent) to onPointerDown() in 1.7.9.
@@ -2819,12 +2854,45 @@ export default class ExcalidrawView extends TextFileView {
onDragOver: (e: any) => {
const action = dropAction(e.dataTransfer);
if (action) {
if(!this.draginfoDiv) {
this.draginfoDiv = createDiv({cls:"excalidraw-draginfo"});
this.ownerDocument.body.appendChild(this.draginfoDiv);
}
let msg: string = "";
if((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"; break;
}
} else if(e.dataTransfer.types.includes("Files")) {
//drag from OS file manager
msg = "External file"
} 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"; break;
case "insert-link": msg = "Insert link"; break;
}
}
if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg;
const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*3}px`;
const left = `${e.clientX-this.draginfoDiv.clientWidth/2}px`;
if(this.draginfoDiv.style.top !== top) this.draginfoDiv.style.top = top;
if(this.draginfoDiv.style.left !== left) this.draginfoDiv.style.left = left;
e.dataTransfer.dropEffect = action;
e.preventDefault();
return false;
}
},
onDragLeave: () => {},
onDragLeave: () => {
if(this.draginfoDiv) {
this.ownerDocument.body.removeChild(this.draginfoDiv);
delete this.draginfoDiv;
}
},
},
React.createElement(
Excalidraw,
@@ -2865,7 +2933,7 @@ export default class ExcalidrawView extends TextFileView {
blockOnMouseButtonDown = true;
//ctrl click
if (this.ctrlKeyDown || this.metaKeyDown) {
if (isCTRL(this.modifierKeyDown) || isMETA(this.modifierKeyDown)) {
handleLinkClick();
return;
}
@@ -2881,7 +2949,7 @@ export default class ExcalidrawView extends TextFileView {
if (p.button === "up") {
blockOnMouseButtonDown = false;
}
if (this.ctrlKeyDown ||
if (isCTRL(this.modifierKeyDown) ||
(this.excalidrawAPI.getAppState().isViewModeEnabled &&
this.plugin.settings.hoverPreviewWithoutCTRL)) {
@@ -2977,6 +3045,10 @@ export default class ExcalidrawView extends TextFileView {
ownerDocument: this.ownerDocument,
ownerWindow: this.ownerWindow,
onDrop: (event: React.DragEvent<HTMLDivElement>): boolean => {
if(this.draginfoDiv) {
this.ownerDocument.body.removeChild(this.draginfoDiv);
delete this.draginfoDiv;
}
const api = this.excalidrawAPI;
if (!api) {
return false;
@@ -2986,8 +3058,10 @@ export default class ExcalidrawView extends TextFileView {
{ clientX: event.clientX, clientY: event.clientY },
st,
);
const draggable = (app as any).dragManager.draggable;
const internalDragAction = internalDragModifierType(event);
const externalDragAction = externalDragModifierType(event);
const onDropHook = (
type: "file" | "text" | "unknown",
files: TFile[],
@@ -3019,8 +3093,7 @@ export default class ExcalidrawView extends TextFileView {
}
};
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468
event[CTRL_OR_CMD] = event.shiftKey || event[CTRL_OR_CMD];
//Obsidian internal drag event
switch (draggable?.type) {
case "file":
if (!onDropHook("file", [draggable.file], null)) {
@@ -3030,7 +3103,7 @@ export default class ExcalidrawView extends TextFileView {
return false;
}
if (
event[CTRL_OR_CMD] &&
["image", "image-fullsize"].contains(internalDragAction) &&
(IMAGE_TYPES.contains(draggable.file.extension) ||
draggable.file.extension === "md")
) {
@@ -3043,12 +3116,13 @@ export default class ExcalidrawView extends TextFileView {
this.currentPosition.x,
this.currentPosition.y,
draggable.file,
!event.altKey,
!(internalDragAction==="image-fullsize"),
);
ea.addElementsToView(false, false, true);
})();
return false;
}
//internalDragAction === "link"
this.addText(
`[[${app.metadataCache.fileToLinktext(
draggable.file,
@@ -3061,7 +3135,7 @@ export default class ExcalidrawView extends TextFileView {
case "files":
if (!onDropHook("file", draggable.files, null)) {
(async () => {
if (event[CTRL_OR_CMD]) {
if (["image", "image-fullsize"].contains(internalDragAction)) {
const ea = this.plugin.ea;
ea.reset();
ea.setView(this);
@@ -3073,7 +3147,7 @@ export default class ExcalidrawView extends TextFileView {
this.currentPosition.x + counter*50,
this.currentPosition.y + counter*50,
f,
!event.altKey,
!(internalDragAction==="image-fullsize"),
);
counter++;
await ea.addElementsToView(false, false, true);
@@ -3081,6 +3155,7 @@ export default class ExcalidrawView extends TextFileView {
}
return;
}
//internalDragAction === "link"
for (const f of draggable.files) {
await this.addText(
`[[${app.metadataCache.fileToLinktext(
@@ -3096,63 +3171,100 @@ export default class ExcalidrawView extends TextFileView {
}
return false;
}
//externalDragAction
if (event.dataTransfer.types.includes("Files")) {
if (event.dataTransfer.types.includes("text/plain")) {
const text: string = event.dataTransfer.getData("text");
if (text && onDropHook("text", null, text)) {
return false;
}
if(text && event[CTRL_OR_CMD] && hyperlinkIsImage(text)) {
if(text && (externalDragAction === "image-url") && hyperlinkIsImage(text)) {
this.addImageWithURL(text);
return false;
}
if(text && (externalDragAction === "insert-link")) {
if (
this.plugin.settings.iframelyAllowed &&
text.match(/^https?:\/\/\S*$/)
) {
this.addTextWithIframely(text);
return false;
} else {
this.addText(text);
return false;
}
}
}
if(event.dataTransfer.types.includes("text/html")) {
const html = event.dataTransfer.getData("text/html");
const src = html.match(/src=["']([^"']*)["']/)
if(src && event[CTRL_OR_CMD] && hyperlinkIsImage(src[1])) {
if(src && (externalDragAction === "image-url") && hyperlinkIsImage(src[1])) {
this.addImageWithURL(src[1]);
return false;
}
if(src && (externalDragAction === "insert-link")) {
if (
this.plugin.settings.iframelyAllowed &&
src[1].match(/^https?:\/\/\S*$/)
) {
this.addTextWithIframely(src[1]);
return false;
} else {
this.addText(src[1]);
return false;
}
}
}
return true;
}
if (event.dataTransfer.types.includes("text/plain")) {
const text: string = event.dataTransfer.getData("text");
if (!text) {
if (event.dataTransfer.types.includes("text/plain") || event.dataTransfer.types.includes("text/uri-list") || event.dataTransfer.types.includes("text/html")) {
const html = event.dataTransfer.getData("text/html");
const src = html.match(/src=["']([^"']*)["']/);
const htmlText = src ? src[1] : "";
const textText = event.dataTransfer.getData("text");
const uriText = event.dataTransfer.getData("text/uri-list");
let text: string = src ? htmlText : textText;
if (!text || text === "") {
text = uriText
}
if (!text || text === "") {
return true;
}
if (!onDropHook("text", null, text)) {
if(text && event[CTRL_OR_CMD] && hyperlinkIsYouTubeLink(text)) {
if(text && (externalDragAction==="image-url") && hyperlinkIsYouTubeLink(text)) {
this.addYouTubeThumbnail(text);
return false;
}
if(uriText && (externalDragAction==="image-url") && hyperlinkIsYouTubeLink(uriText)) {
this.addYouTubeThumbnail(uriText);
return false;
}
if(text && (externalDragAction==="image-url") && hyperlinkIsImage(text)) {
this.addImageWithURL(text);
return false;
}
if(uriText && (externalDragAction==="image-url") && hyperlinkIsImage(uriText)) {
this.addImageWithURL(uriText);
return false;
}
if(text && (externalDragAction==="image-import") && hyperlinkIsImage(text)) {
this.addImageSaveToVault(text);
return false;
}
if(uriText && (externalDragAction==="image-import") && hyperlinkIsImage(uriText)) {
this.addImageSaveToVault(uriText);
return false;
}
if (
this.plugin.settings.iframelyAllowed &&
text.match(/^https?:\/\/\S*$/)
) {
(async () => {
const id = await this.addText(text);
const url = `http://iframely.server.crestify.com/iframely?url=${text}`;
const data = JSON.parse(await request({ url }));
if (!data || data.error || !data.meta?.title) {
return false;
}
const ea = this.plugin.ea;
ea.reset();
ea.setView(this);
const el = ea
.getViewElements()
.filter((el) => el.id === id);
if (el.length === 1) {
//@ts-ignore
el[0].text = el[0].originalText = el[0].rawText =
`[${data.meta.title}](${text})`;
ea.copyViewElementsToEAforEditing(el);
ea.addElementsToView(false, false, false);
}
return false;
})();
this.addTextWithIframely(text);
return false;
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/599
@@ -3329,14 +3441,19 @@ export default class ExcalidrawView extends TextFileView {
const event = e?.detail?.nativeEvent;
if(this.handleLinkHookCall(element,element.link,event)) return;
if(this.openExternalLink(element.link, !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey ? element : undefined)) return;
if(this.openExternalLink(element.link, !isSHIFT(event) && !isCTRL(event) && !isMETA(event) && !isALT(event) ? element : undefined)) return;
this.linkClick(
event,
null,
null,
{id: element.id, text: element.link},
{shiftKey: event.ctrlKey??event.shiftKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey, altKey: event.altKey}
{
shiftKey: event.shitKey,
ctrlKey: event.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
metaKey: event.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
altKey: event.altKey
}
);
return;
},
@@ -3347,10 +3464,11 @@ export default class ExcalidrawView extends TextFileView {
if (
element &&
(this.plugin.settings.hoverPreviewWithoutCTRL ||
event[CTRL_OR_CMD])
isCTRL(event))
) {
mouseEvent = event;
mouseEvent.ctrlKey = true;
mouseEvent.ctrlKey = !(DEVICE.isIOS || DEVICE.isMacOS) || mouseEvent.ctrlKey;
mouseEvent.metaKey = (DEVICE.isIOS || DEVICE.isMacOS) || mouseEvent.metaKey;
const link = element.link;
if (!link || link === "") {
return;
@@ -3387,7 +3505,7 @@ export default class ExcalidrawView extends TextFileView {
React.createElement(MainMenu.DefaultItems.ChangeCanvasBackground),
React.createElement(MainMenu.DefaultItems.ToggleTheme),
React.createElement(MainMenu.Separator),
!this.plugin.device.isPhone ? React.createElement(
!DEVICE.isPhone ? React.createElement(
MainMenu.Item,
{
icon: ICONS.trayMode,

View File

@@ -4,7 +4,7 @@ import {
TFile,
Vault,
} from "obsidian";
import { CTRL_OR_CMD, RERENDER_EVENT } from "./Constants";
import { RERENDER_EVENT } from "./Constants";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { createPNG, createSVG } from "./ExcalidrawAutomate";
import { ExportSettings } from "./ExcalidrawView";
@@ -21,6 +21,7 @@ import {
svgToBase64,
} from "./utils/Utils";
import { isObsidianThemeDark } from "./utils/ObsidianUtils";
import { isCTRL, isMETA, linkClickModifierType } from "./utils/ModifierkeyHelper";
interface imgElementAttributes {
file?: TFile;
@@ -215,11 +216,7 @@ const createImgElement = async (
if(!srcParts) return;
plugin.openDrawing(
vault.getAbstractFileByPath(srcParts[1]) as TFile,
ev[CTRL_OR_CMD]
? "new-pane"
: (ev.metaKey && !app.isMobile)
? "popout-window"
: "active-pane",
linkClickModifierType(ev),
true,
srcParts[2],
);
@@ -599,11 +596,7 @@ export const observer = new MutationObserver(async (m) => {
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD]
? "new-pane"
: (ev.metaKey && !app.isMobile)
? "popout-window"
: "active-pane",
linkClickModifierType(ev)
);
} //.ctrlKey||ev.metaKey);
});

View File

@@ -4,7 +4,7 @@ import {
TFile,
Vault,
} from "obsidian";
import { CTRL_OR_CMD, RERENDER_EVENT } from "./Constants";
import { RERENDER_EVENT } from "./Constants";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { createPNG, createSVG } from "./ExcalidrawAutomate";
import { ExportSettings } from "./ExcalidrawView";
@@ -21,6 +21,7 @@ import {
svgToBase64,
} from "./utils/Utils";
import { isObsidianThemeDark } from "./utils/ObsidianUtils";
import { isCTRL, isMETA, linkClickModifierType } from "./utils/ModifierkeyHelper";
interface imgElementAttributes {
file?: TFile;
@@ -212,11 +213,7 @@ const createImageDiv = async (
if(!srcParts) return;
plugin.openDrawing(
vault.getAbstractFileByPath(srcParts[1]) as TFile,
ev[CTRL_OR_CMD]
? "new-pane"
: (ev.metaKey && !app.isMobile)
? "popout-window"
: "active-pane",
linkClickModifierType(ev),
true,
srcParts[2],
);
@@ -571,11 +568,7 @@ export const observer_Legacy = new MutationObserver(async (m) => {
if (src) {
plugin.openDrawing(
vault.getAbstractFileByPath(src) as TFile,
ev[CTRL_OR_CMD]
? "new-pane"
: (ev.metaKey && !app.isMobile)
? "popout-window"
: "active-pane",
linkClickModifierType(ev)
);
} //.ctrlKey||ev.metaKey);
});

View File

@@ -4,7 +4,28 @@ export function JSON_parse(x: string): any {
return JSON.parse(x.replaceAll("&#91;", "["));
}
export const isDarwin = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
export const CTRL_OR_CMD = isDarwin ? "metaKey" : "ctrlKey";
export const DEVICE: {
isDesktop: boolean,
isPhone: boolean,
isTablet: boolean,
isMobile: boolean,
isLinux: boolean,
isMacOS: boolean,
isWindows: boolean,
isIOS: boolean,
isAndroid: boolean
} = {
isDesktop: !document.body.hasClass("is-tablet") && !document.body.hasClass("is-mobile"),
isPhone: document.body.hasClass("is-phone"),
isTablet: document.body.hasClass("is-tablet"),
isMobile: document.body.hasClass("is-mobile"), //running Obsidian Mobile, need to also check isTablet
isLinux: document.body.hasClass("mod-linux") && ! document.body.hasClass("is-android"),
isMacOS: document.body.hasClass("mod-macos") && ! document.body.hasClass("is-ios"),
isWindows: document.body.hasClass("mod-windows"),
isIOS: document.body.hasClass("is-ios"),
isAndroid: document.body.hasClass("is-android")
};
export const nanoid = customAlphabet(
"1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
8,

View File

@@ -1,6 +1,7 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { Modal, Setting, SliderComponent, TFile } from "obsidian";
import { getEA } from "src";
import { DEVICE } from "src/Constants";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
@@ -149,7 +150,7 @@ export class ExportDialog extends Modal {
})
)
if(this.plugin.device.isDesktop) {
if(DEVICE.isDesktop) {
const saveToMessage = () => this.saveToVault?"Save image to your Vault":"Export image outside your Vault";
const saveToSetting = new Setting(this.contentEl)
.setName(saveToMessage())
@@ -184,7 +185,7 @@ export class ExportDialog extends Modal {
this.view.exportExcalidraw();
this.close();
};
if(this.plugin.device.isDesktop) {
if(DEVICE.isDesktop) {
const bPNGClipboard = div.createEl("button", { text: "PNG to Clipboard", cls: "excalidraw-prompt-button" });
bPNGClipboard.onclick = () => {
this.view.exportPNGToClipboard();

View File

@@ -1,6 +1,7 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { isALT, scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper";
import { fileURLToPath } from "url";
import { IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../Constants";
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../Constants";
import ExcalidrawView from "../ExcalidrawView";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "../main";
@@ -25,11 +26,15 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
this.emptyStateText = t("NO_MATCH");
this.inputEl.onkeyup = (e) => {
//@ts-ignore
if (e.key === "Enter" && e.altKey && this.chooser.values) {
if (e.key === "Enter" && scaleToFullsizeModifier(e) && this.chooser.values) {
this.onChooseItem(
//@ts-ignore
this.chooser.values[this.chooser.selectedItem].item,
new KeyboardEvent("keypress",{altKey: true})
new KeyboardEvent("keypress",{
shiftKey: true,
metaKey: !(DEVICE.isIOS || DEVICE.isMacOS),
ctrlKey: (DEVICE.isIOS || DEVICE.isMacOS),
})
);
this.close();
}
@@ -51,12 +56,11 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
}
onChooseItem(item: TFile, event: KeyboardEvent): void {
const ea = this.plugin.ea;
ea.reset();
ea.setView(this.view);
const ea = this.plugin.ea.getAPI(this.view);
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
const scaleToFullsize = scaleToFullsizeModifier(event);
(async () => {
await ea.addImage(0, 0, item, !event.altKey);
await ea.addImage(0, 0, item, !scaleToFullsize);
ea.addElementsToView(true, false, true);
})();
}

View File

@@ -17,6 +17,10 @@ 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>
`,
"1.8.14":`
## Fixed
- text element link gets deleted when the drawing is reloaded
`,
"1.8.13": `
## Fixed
- When changing a text element in markdown mode, the change seem to have showed up when switching back to Excalidraw mode, but then lost these changes when loading the file the next time.

View File

@@ -12,8 +12,9 @@ import {
import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { sleep } from "../utils/Utils";
import { getNewOrAdjacentLeaf } from "../utils/ObsidianUtils";
import { getLeaf, getNewOrAdjacentLeaf } from "../utils/ObsidianUtils";
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
import { KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
export class Prompt extends Modal {
private promptEl: HTMLInputElement;
@@ -370,8 +371,7 @@ export class NewFileActions extends Modal {
constructor(
private plugin: ExcalidrawPlugin,
private path: string,
private newPane: boolean,
private newWindow: boolean,
private keys: KeyEvent,
private view: ExcalidrawView,
) {
super(plugin.app);
@@ -387,14 +387,8 @@ export class NewFileActions extends Modal {
if (!file) {
return;
}
const leaf = this.newWindow
//@ts-ignore
? app.workspace.openPopoutLeaf()
: this.newPane
? getNewOrAdjacentLeaf(this.plugin, this.view.leaf)
: this.view.leaf;
const leaf = getLeaf(this.plugin,this.view.leaf,this.keys)
leaf.openFile(file, {active:true});
//this.app.workspace.setActiveLeaf(leaf, true, true);
}
createForm(): void {

View File

@@ -1,8 +1,10 @@
import {
DEVICE,
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
FRONTMATTER_KEY_CUSTOM_PREFIX,
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
} from "src/Constants";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
// English
export default {
@@ -29,13 +31,16 @@ export default {
TRANSCLUDE: "Embed a drawing",
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
NEW_IN_NEW_PANE: "Create new drawing - IN A NEW PANE",
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE PANE",
NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW",
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
NEW_IN_POPOUT_WINDOW: "Create new drawing - IN A POPOUT WINDOW",
NEW_IN_NEW_PANE_EMBED:
"Create new drawing - IN A NEW PANE - and embed into active document",
"Create new drawing - IN AN ADJACENT WINDOW - and embed into active document",
NEW_IN_NEW_TAB_EMBED:
"Create new drawing - IN A NEW TAB - and embed into active document",
NEW_IN_ACTIVE_PANE_EMBED:
"Create new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document",
"Create new drawing - IN THE CURRENT ACTIVE WINDOW - and embed into active document",
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
EXPORT_SVG: "Save as SVG next to current file",
EXPORT_PNG: "Save as PNG next to current file",
@@ -44,7 +49,7 @@ export default {
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
INSERT_LINK_TO_ELEMENT:
"Copy markdown link for selected element to clipboard. CTRL/CMD+Click to copy group link. SHIFT+click to copy an area link.",
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
INSERT_LINK_TO_ELEMENT_GROUP:
"Copy 'group=' markdown link for selected element to clipboard.",
INSERT_LINK_TO_ELEMENT_AREA:
@@ -58,7 +63,7 @@ export default {
IMPORT_SVG: "Import an SVG file as Excalidraw strokes (limited SVG support, TEXT currently not supported)",
INSERT_MD: "Insert markdown file from vault",
INSERT_LATEX:
"Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})",
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
ENTER_LATEX: "Enter a valid LaTeX expression",
READ_RELEASE_NOTES: "Read latest release notes",
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
@@ -71,18 +76,14 @@ export default {
//ExcalidrawView.ts
INSTALL_SCRIPT_BUTTON: "Install or update Excalidraw Scripts",
OPEN_AS_MD: "Open as Markdown",
SAVE_AS_PNG: "Save as PNG into Vault (CTRL/CMD+CLICK to export; SHIFT to embed scene)",
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/CMD+CLICK to export; SHIFT to embed scene)",
SAVE_AS_PNG: `Save as PNG into Vault (${labelCTRL()}+CLICK to export; SHIFT to embed scene)`,
SAVE_AS_SVG: `Save as SVG into Vault (${labelCTRL()}+CLICK to export; SHIFT to embed scene)`,
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
LINK_BUTTON_CLICK_NO_TEXT:
"Select a an ImageElement, or select a TextElement that contains an internal or external link.\n" +
"SHIFT CLICK this button to open the link in a new pane.\n" +
"CTRL/CMD CLICK the Image or TextElement on the canvas has the same effect!",
"Select a an ImageElement, or select a TextElement that contains an internal or external link.\n",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
FILE_DOES_NOT_EXIST:
"File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
FORCE_SAVE:
"Save (will also update transclusions)",
RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)",
@@ -206,7 +207,7 @@ FILENAME_HEAD: "Filename",
DEFAULT_WHEELZOOM_NAME: "Mouse wheel to zoom by default",
DEFAULT_WHEELZOOM_DESC:
"<b>Toggle on: </b>Mouse wheel to zoom; CTRL + mouse wheel to scroll</br><b>Toggle off: </b>CTRL + mouse wheel to zoom; Mouse wheel to scroll",
`<b>Toggle on: </b>Mouse wheel to zoom; ${labelCTRL()} + mouse wheel to scroll</br><b>Toggle off: </b>${labelCTRL()} + mouse wheel to zoom; Mouse wheel to scroll`,
ZOOM_TO_FIT_NAME: "Zoom to fit on view resize",
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized" +
@@ -219,7 +220,7 @@ FILENAME_HEAD: "Filename",
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
LINKS_HEAD: "Links and transclusion",
LINKS_DESC:
"CTRL/CMD + CLICK on <code>[[Text Elements]]</code> to open them as links. " +
`${labelCTRL()}+CLICK on <code>[[Text Elements]]</code> to open them as links. ` +
"If the selected text has more than one <code>[[valid Obsidian links]]</code>, only the first will be opened. " +
"If the text starts as a valid web link (i.e. <code>https://</code> or <code>http://</code>), then " +
"the plugin will open it in a browser. " +
@@ -227,13 +228,13 @@ FILENAME_HEAD: "Filename",
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
ADJACENT_PANE_NAME: "Open in adjacent pane",
ADJACENT_PANE_DESC:
"When CTRL/CMD+SHIFT clicking a link in Excalidraw, by default the plugin will open the link in a new pane. " +
`When ${labelCTRL()}+${labelSHIFT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` +
"Turning this setting on, Excalidraw will first look for an existing adjacent pane, and try to open the link there. " +
"Excalidraw will look for the adjacent pane based on your focus/navigation history, i.e. the workpane that was active before you " +
"activated Excalidraw.",
MAINWORKSPACE_PANE_NAME: "Open in main workspace",
MAINWORKSPACE_PANE_DESC:
"When CTRL/CMD+SHIFT clicking a link in Excalidraw, by default the plugin will open the link in a new pane in the current active window. " +
`When ${labelCTRL()}+${labelSHIFT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane in the current active window. ` +
"Turning this setting on, Excalidraw will open the link in an existing or new pane in the main workspace. ",
LINK_BRACKETS_NAME: "Show <code>[[brackets]]</code> around links",
LINK_BRACKETS_DESC: `${
@@ -256,16 +257,16 @@ FILENAME_HEAD: "Filename",
TODO_DESC: "Icon to use for open TODO items",
DONE_NAME: "Completed TODO icon",
DONE_DESC: "Icon to use for completed TODO items",
HOVERPREVIEW_NAME: "Hover preview without CTRL/CMD key",
HOVERPREVIEW_NAME: `Hover preview without pressing the ${labelCTRL()} key`,
HOVERPREVIEW_DESC:
"<b>Toggle On</b>: In Exalidraw <u>view mode</u> the hover preview for [[wiki links]] will be shown immediately, without the need to hold the CTRL/CMD key. " +
`<b>Toggle On</b>: In Exalidraw <u>view mode</u> the hover preview for [[wiki links]] will be shown immediately, without the need to hold the ${labelCTRL()} key. ` +
"In Excalidraw <u>normal mode</u>, the preview will be shown immediately only when hovering the blue link icon in the top right of the element.<br> " +
"<b>Toggle Off</b>: Hover preview is shown only when you hold the CTRL/CMD key while hovering the link.",
`<b>Toggle Off</b>: Hover preview is shown only when you hold the ${labelCTRL()} key while hovering the link.`,
LINKOPACITY_NAME: "Opacity of link icon",
LINKOPACITY_DESC:
"Opacity of the link indicator icon in the top right corner of an element. 1 is opaque, 0 is transparent.",
LINK_CTRL_CLICK_NAME:
"CTRL/CMD + CLICK on text with [[links]] or [](links) to open them",
`${labelCTRL()}+CLICK on text with [[links]] or [](links) to open them`,
LINK_CTRL_CLICK_DESC:
"You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
@@ -291,7 +292,7 @@ FILENAME_HEAD: "Filename",
"Use the <code>http://iframely.server.crestify.com/iframely?url=</code> to get title of page when dropping a link into Excalidraw",
MD_HEAD: "Markdown-embed settings",
MD_HEAD_DESC:
"You can transclude formatted markdown documents into drawings as images CTRL(Shift on Mac) drop from the file explorer or using " +
`You can transclude formatted markdown documents into drawings as images ${labelSHIFT()} drop from the file explorer or using ` +
"the command palette action.",
MD_TRANSCLUDE_WIDTH_NAME: "Default width of a transcluded markdown document",
MD_TRANSCLUDE_WIDTH_DESC:
@@ -323,7 +324,7 @@ FILENAME_HEAD: "Filename",
MD_CSS_DESC:
"The filename of the CSS to apply to markdown embeds. Provide the filename with extension (e.g. 'md-embed.css'). The css file may also be a plain " +
"markdown file (e.g. 'md-embed-css.md'), just make sure the content is written using valid css syntax. " +
"If you need to look at the HTML code you are applying the CSS to, then open Obsidian Developer Console (CTRL+SHIFT+i) and type in the following command: " +
`If you need to look at the HTML code you are applying the CSS to, then open Obsidian Developer Console (${DEVICE.isIOS || DEVICE.isMacOS ? "CMD+OPT+i" : "CTRL+SHIFT+i"}) and type in the following command: ` +
'"ExcalidrawAutomate.mostRecentMarkdownSVG". This will display the most recent SVG generated by Excalidraw. ' +
"Setting the font-family in the css is has limitations. By default only your operating system's standard fonts are available (see README for details). " +
"You can add one custom font beyond that using the setting above. " +
@@ -457,7 +458,7 @@ FILENAME_HEAD: "Filename",
//openDrawings.ts
SELECT_FILE: "Select a file then press enter.",
SELECT_FILE_WITH_OPTION_TO_SCALE: "Select a file then press ENTER, or ALT+ENTER to insert at 100% scale.",
SELECT_FILE_WITH_OPTION_TO_SCALE: `Select a file then press ENTER, or ${labelSHIFT()}+${labelMETA()}+ENTER to insert at 100% scale.`,
NO_MATCH: "No file matches your query.",
SELECT_FILE_TO_LINK: "Select the file you want to insert the link for.",
SELECT_DRAWING: "Select the image or drawing you want to insert",

View File

@@ -74,8 +74,6 @@ export default {
"您也可以直接在画布中按住 CTRL/CMD 并点击图形或文本元素来打开链接。",
FILENAME_INVALID_CHARS:
'文件名不能含有以下符号: * " \\ < > : | ? #',
FILE_DOES_NOT_EXIST:
"文件不存在。按住 ALT或 ALT + SHIFT并点击链接来创建新文件。",
FORCE_SAVE:
"立刻保存该绘图(并更新嵌入了该绘图的面板)。\n详见插件设置中的定期保存选项",
RAW: "文本元素正以原文RAW模式显示链接。\n点击切换到预览PREVIEW模式",

View File

@@ -36,7 +36,6 @@ import {
JSON_parse,
nanoid,
DARK_BLANK_DRAWING,
CTRL_OR_CMD,
SCRIPT_INSTALL_CODEBLOCK,
SCRIPT_INSTALL_FOLDER,
VIRGIL_FONT,
@@ -107,6 +106,7 @@ import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
import { check } from "prettier";
import Taskbone from "./ocr/Taskbone";
import { hoverEvent_Legacy, initializeMarkdownPostProcessor_Legacy, markdownPostProcessor_Legacy, observer_Legacy } from "./MarkdownPostProcessor_Legacy";
import { isCTRL, PaneTarget } from "./utils/ModifierkeyHelper";
declare module "obsidian" {
@@ -171,17 +171,6 @@ export default class ExcalidrawPlugin extends Plugin {
private packageMap: WeakMap<Window,Packages> = new WeakMap<Window,Packages>();
public leafChangeTimeout: NodeJS.Timeout = null;
private forceSaveCommand:Command;
public device: {
isDesktop: boolean,
isPhone: boolean,
isTablet: boolean,
isMobile: boolean,
isLinux: boolean,
isMacOS: boolean,
isWindows: boolean,
isIOS: boolean,
isAndroid: boolean
};
constructor(app: App, manifest: PluginManifest) {
super(app, manifest);
@@ -211,18 +200,6 @@ export default class ExcalidrawPlugin extends Plugin {
}
async onload() {
this.device = {
isDesktop: !document.body.hasClass("is-tablet") && !document.body.hasClass("is-mobile"),
isPhone: document.body.hasClass("is-phone"),
isTablet: document.body.hasClass("is-tablet"),
isMobile: document.body.hasClass("is-mobile"), //running Obsidian Mobile, need to also check isTablet
isLinux: document.body.hasClass("mod-linux") && ! document.body.hasClass("is-android"),
isMacOS: document.body.hasClass("mod-macos") && ! document.body.hasClass("is-ios"),
isWindows: document.body.hasClass("mod-windows"),
isIOS: document.body.hasClass("is-ios"),
isAndroid: document.body.hasClass("is-android")
}
addIcon(ICON_NAME, EXCALIDRAW_ICON);
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
addIcon(PNG_ICON_NAME, PNG_ICON);
@@ -729,7 +706,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), async (e) => {
this.createAndOpenDrawing(
getDrawingFilename(this.settings),
e[CTRL_OR_CMD]?"new-pane":"active-pane",
isCTRL(e)?"new-pane":"active-pane",
); //.ctrlKey||e.metaKey);
});
@@ -877,6 +854,14 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "excalidraw-autocreate-newtab",
name: t("NEW_IN_NEW_TAB"),
callback: () => {
this.createAndOpenDrawing(getDrawingFilename(this.settings), "new-tab");
},
});
this.addCommand({
id: "excalidraw-autocreate-on-current",
name: t("NEW_IN_ACTIVE_PANE"),
@@ -897,7 +882,7 @@ export default class ExcalidrawPlugin extends Plugin {
});
const insertDrawingToDoc = async (
location: "active-pane"|"new-pane"|"popout-window"
location: PaneTarget
) => {
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!activeView) {
@@ -933,6 +918,18 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "excalidraw-autocreate-and-embed-new-tab",
name: t("NEW_IN_NEW_TAB_EMBED"),
checkCallback: (checking: boolean) => {
if (checking) {
return Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
}
insertDrawingToDoc("new-tab");
return true;
},
});
this.addCommand({
id: "excalidraw-autocreate-and-embed-on-current",
name: t("NEW_IN_ACTIVE_PANE_EMBED"),
@@ -2165,16 +2162,18 @@ export default class ExcalidrawPlugin extends Plugin {
public openDrawing(
drawingFile: TFile,
location: "active-pane"|"new-pane"|"popout-window",
location: PaneTarget,
active: boolean = false,
subpath?: string
) {
let leaf: WorkspaceLeaf;
if(location === "popout-window") {
//@ts-ignore
leaf = app.workspace.openPopoutLeaf();
}
else {
if(location === "new-tab") {
leaf = app.workspace.getLeaf('tab');
}
if(!leaf) {
leaf = this.app.workspace.getLeaf(false);
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
leaf = getNewOrAdjacentLeaf(this, leaf)
@@ -2186,12 +2185,7 @@ export default class ExcalidrawPlugin extends Plugin {
!subpath || subpath === ""
? {active}
: { active, eState: { subpath } }
);
/* leaf.setViewState({
type: VIEW_TYPE_EXCALIDRAW,
state: { file: drawingFile.path, eState: {subpath}},
});*/
)
}
public async getBlankDrawing(): Promise<string> {
@@ -2292,7 +2286,7 @@ export default class ExcalidrawPlugin extends Plugin {
public async createAndOpenDrawing(
filename: string,
location: "active-pane"|"new-pane"|"popout-window",
location: PaneTarget,
foldername?: string,
initData?: string,
): Promise<string> {

View File

@@ -3,8 +3,8 @@ import ExcalidrawView from "../ExcalidrawView";
type ButtonProps = {
title: string;
action: Function;
longpress?: Function;
action: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
longpress?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
icon: JSX.Element;
view: ExcalidrawView;
};

View File

@@ -3,7 +3,7 @@ import { Notice, TFile } from "obsidian";
import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
import { SCRIPT_INSTALL_FOLDER, CTRL_OR_CMD, nanoid, VIEW_TYPE_EXCALIDRAW } from "../Constants";
import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../Constants";
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
import { t } from "../lang/helpers";
@@ -12,6 +12,7 @@ import { ScriptIconMap } from "../Scripts";
import { getIMGFilename } from "../utils/FileUtils";
import { ScriptInstallPrompt } from "src/dialogs/ScriptInstallPrompt";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { isALT, isCTRL, isSHIFT, mdPropModifier } from "src/utils/ModifierkeyHelper";
declare const PLUGIN_VERSION:string;
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
@@ -377,7 +378,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, e[CTRL_OR_CMD]);
this.props.view.plugin.taskbone.getTextForView(this.props.view, isCTRL(e));
}}
icon={ICONS.ocr}
view={this.props.view}
@@ -385,12 +386,12 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
<ActionButton
key={"openLink"}
title={t("OPEN_LINK_CLICK")}
action={() => {
action={(e) => {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: false,
shiftKey: false,
altKey: false,
ctrlKey: e.ctrlKey || !(DEVICE.isIOS || DEVICE.isMacOS),
metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS),
shiftKey: e.shiftKey,
altKey: e.altKey,
});
this.props.view.handleLinkClick(event);
}}
@@ -403,9 +404,9 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
action={() => {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: false,
shiftKey: true,
altKey: true,
metaKey: true,
shiftKey: false,
altKey: false,
});
this.props.view.handleLinkClick(event);
}}
@@ -515,7 +516,11 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
<ActionButton
key={"latex"}
title={t("INSERT_LATEX")}
action={() => {
action={(e) => {
if(isALT(e)) {
this.props.view.openExternalLink("https://youtu.be/r08wk-58DPk");
return;
}
this.props.centerPointer();
insertLaTeXToView(this.props.view);
}}
@@ -539,8 +544,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)) {
this.props.view.openExternalLink("https://youtu.be/yZQoJg2RCKI");
return;
}
this.props.view.copyLinkToSelectedElementToClipboard(
e[CTRL_OR_CMD] ? "group=" : (e.shiftKey ? "area=" : "")
isCTRL(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
);
}}
icon={ICONS.copyElementLink}

View File

@@ -1,5 +1,9 @@
import { normalizePath, Notice, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { DataURL } from "@zsviczian/excalidraw/types/types";
import { normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { URLFETCHTIMEOUT } from "src/Constants";
import { MimeType } from "src/EmbeddedFileLoader";
import { ExcalidrawSettings } from "src/Settings";
import { errorlog, getDataURL } from "./Utils";
/**
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
@@ -145,3 +149,41 @@ export async function checkAndCreateFolder(folderpath: string) {
await vault.createFolder(folderpath);
}
export const getURLImageExtension = (url: string):string => {
const corelink = url.split("?")[0];
return corelink.substring(corelink.lastIndexOf(".")+1);
}
export const getMimeType = (extension: string):MimeType => {
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";
}
}
const getFileFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT):Promise<RequestUrlResponse> => {
try {
return await Promise.race([
(async () => new Promise<RequestUrlResponse>((resolve) => setTimeout(()=>resolve(null), timeout)))(),
requestUrl({url: url, method: "get", contentType: mimeType, throw: false })
])
} catch (e) {
errorlog({where: getFileFromURL, message: `URL did not load within timeout period of ${timeout}ms`, url: url});
return undefined;
}
}
export const getDataURLFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT):Promise<DataURL> => {
const response = await getFileFromURL(url, mimeType, timeout);
return response && response.status === 200
? await getDataURL(response.arrayBuffer, mimeType)
: url as DataURL;
}

View File

@@ -0,0 +1,47 @@
import { DEVICE, isDarwin } from "src/Constants";
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";
export type InternalDragAction = "link"|"image"|"image-fullsize";
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 isSHIFT = (e:KeyEvent) => e.shiftKey;
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 externalDragModifierType = (ev: KeyEvent):ExternalDragAction => {
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link";
if(!isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) return "insert-link";
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import";
if(!isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import";
return "image-url";
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468
export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => {
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";
}

View File

@@ -3,8 +3,10 @@ import {
App,
normalizePath, Notice, WorkspaceLeaf
} from "obsidian";
import { DEVICE } from "src/Constants";
import ExcalidrawPlugin from "../main";
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
import { isALT, isCTRL, isMETA, isSHIFT, KeyEvent, linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLElement | null => {
let parent = element.parentElement;
@@ -19,6 +21,56 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
};
export const getLeaf = (
plugin: ExcalidrawPlugin,
origo: WorkspaceLeaf,
ev: ModifierKeys
) => {
const newTab = ():WorkspaceLeaf => {
if(!plugin.settings.openInMainWorkspace) return app.workspace.getLeaf('tab');
const [leafLoc, mainLeavesIds] = getLeafLoc(origo);
if(leafLoc === 'main') return app.workspace.getLeaf('tab');
return getNewOrAdjacentLeaf(plugin,origo);
}
const newTabGroup = ():WorkspaceLeaf => getNewOrAdjacentLeaf(plugin,origo);
const newWindow = ():WorkspaceLeaf => app.workspace.openPopoutLeaf();
switch(linkClickModifierType(ev)) {
case "active-pane": return origo;
case "new-tab": return newTab();
case "new-pane": return newTabGroup();
case "popout-window": return newWindow();
default: return newTab();
}
}
const getLeafLoc = (leaf: WorkspaceLeaf): ["main" | "popout" | "left" | "right" | "hover",any] => {
//@ts-ignore
const leafId = leaf.id;
const layout = app.workspace.getLayout();
const getLeaves = (l:any)=> l.children
.filter((c:any)=>c.type!=="leaf")
.map((c:any)=>getLeaves(c))
.flat()
.concat(l.children.filter((c:any)=>c.type==="leaf").map((c:any)=>c.id))
const mainLeavesIds = getLeaves(layout.main);
return [
layout.main && mainLeavesIds.contains(leafId)
? "main"
: layout.floating && getLeaves(layout.floating).contains(leafId)
? "popout"
: layout.left && getLeaves(layout.left).contains(leafId)
? "left"
: layout.right && getLeaves(layout.right).contains(leafId)
? "right"
: "hover",
mainLeavesIds
];
}
/*
| Setting | Originating Leaf |
| | Main Workspace | Hover Editor | Popout Window |
@@ -28,32 +80,11 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
| !InMain && InAdjacent | 1.1 Reuse Leaf in Main Workspace | 3 Reuse Leaf in Current Hover Editor | 4 Reuse Leaf in Current Popout |
| !InMain && !InAdjacent | 1.2 New Leaf in Main Workspace | 2 New Leaf in Current Hover Editor | 2 New Leaf in Current Popout |
*/
export const getNewOrAdjacentLeaf = (
plugin: ExcalidrawPlugin,
leaf: WorkspaceLeaf
): WorkspaceLeaf => {
//@ts-ignore
const leafId = leaf.id;
const layout = app.workspace.getLayout();
const getLeaves = (l:any)=> l.children
.filter((c:any)=>c.type!=="leaf")
.map((c:any)=>getLeaves(c))
.flat()
.concat(l.children.filter((c:any)=>c.type==="leaf").map((c:any)=>c.id))
const mainLeavesIds = getLeaves(layout.main);
const leafLoc =
layout.main && mainLeavesIds.contains(leafId)
? "main"
: layout.floating && getLeaves(layout.floating).contains(leafId)
? "popout"
: layout.left && getLeaves(layout.left).contains(leafId)
? "left"
: layout.right && getLeaves(layout.right).contains(leafId)
? "right"
: "hover";
const [leafLoc, mainLeavesIds] = getLeafLoc(leaf);
const getMainLeaf = ():WorkspaceLeaf => {
let mainLeaf = app.workspace.getMostRecentLeaf();

View File

@@ -326,4 +326,14 @@ label.color-input-container > input {
.excalidraw-settings input {
min-width: 10em;
}
div.excalidraw-draginfo {
position: absolute;
z-index: 1000;
color: var(--text-normal);
padding: 3px;
background: var(--color-base-40);
display: block;
border-radius: 5px;
}