mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
1.8.15-beta
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -15,5 +15,6 @@ data.json
|
||||
lib
|
||||
|
||||
#VSCode
|
||||
.vscode
|
||||
yarn.lock
|
||||
.vscode
|
||||
yarn.lock
|
||||
.DS_Store
|
||||
|
||||
32
README-BUILD.md
Normal file
32
README-BUILD.md
Normal 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"
|
||||
}
|
||||
```
|
||||
34
README.md
34
README.md
@@ -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
|
||||

|
||||
|
||||
#### 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
14
TODO.md
@@ -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.
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 222 KiB |
@@ -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",
|
||||
|
||||
14
package.json
14
package.json
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -1093,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
|
||||
@@ -1162,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1346,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 &&
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,28 @@ export function JSON_parse(x: string): any {
|
||||
return JSON.parse(x.replaceAll("[", "["));
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
})();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)模式",
|
||||
|
||||
66
src/main.ts
66
src/main.ts
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
47
src/utils/ModifierkeyHelper.ts
Normal file
47
src/utils/ModifierkeyHelper.ts
Normal 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";
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
10
styles.css
10
styles.css
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user