Compare commits

..

22 Commits

Author SHA1 Message Date
Zsolt Viczian
5ff8caa04f 1.6.32 2022-05-23 21:42:16 +02:00
Zsolt Viczian
9051fe2c01 1.6.31 2022-05-21 15:54:43 +02:00
Zsolt Viczian
018e42b34b Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-05-18 21:46:18 +02:00
Zsolt Viczian
b29e79f2b4 1.6.30 2022-05-18 21:45:23 +02:00
zsviczian
1ce0051d4e Update directory-info.json 2022-05-16 13:40:20 +02:00
zsviczian
c44a4a91b3 Update Change shape of selected elements.md 2022-05-16 13:39:07 +02:00
zsviczian
2c5a4c01d2 Update index-new.md 2022-05-16 12:49:28 +02:00
zsviczian
668c4d62e9 Update directory-info.json 2022-05-16 12:48:06 +02:00
zsviczian
0ce4f5f16b Update Change shape of selected elements.md 2022-05-16 12:45:31 +02:00
zsviczian
56d0966d4e Add files via upload 2022-05-16 12:43:42 +02:00
Zsolt Viczian
e000c6bf39 1.6.29 2022-05-15 19:18:20 +02:00
Zsolt Viczian
feb5b576cb Worksapces 2022-05-14 21:53:16 +02:00
Zsolt Viczian
2bd1f68276 fixed workspaces 2022-05-14 21:52:55 +02:00
Zsolt Viczian
22d1a9b90a sync 2022-05-14 20:46:50 +02:00
Zsolt Viczian
00a6a3dcaf fixed null width, sync in progress 2022-05-13 22:20:27 +02:00
Zsolt Viczian
82061cd126 sync wip 2022-05-11 22:03:20 +02:00
Zsolt Viczian
4bdd095ff0 version.json 2022-05-08 10:38:05 +02:00
Zsolt Viczian
f40ad62291 1.6.28 2022-05-08 10:34:56 +02:00
Zsolt Viczian
de4fc602ca dataview onDrop improvement, getNewLeaf in openDrawing 2022-05-08 00:05:39 +02:00
Zsolt Viczian
f5faec8ac9 fix text element de-select on autosave 2022-05-04 18:52:41 +02:00
Zsolt Viczian
8834762004 1.6.27 2022-05-01 20:30:56 +02:00
Zsolt Viczian
48477c7ee9 onload-script, isExcalidrawView 2022-04-30 20:29:14 +02:00
27 changed files with 624 additions and 146 deletions

View File

@@ -1,10 +1,13 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg)
The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.
The script allows you to change the shape and fill style of selected Rectangles, Diamonds, Ellipses, Lines, Arrows and Freedraw.
```javascript
*/
const fillStylesDispaly=["Dots (⚠ VERY SLOW performance on large objects!)","Zigzag","Zigzag-line", "Dashed", "Hachure", "Cross-hatch", "Solid"];
const fillStyles=["dots","zigzag","zigzag-line", "dashed", "hachure", "cross-hatch", "solid"];
const fillShapes=["ellipse","rectangle","diamond", "freedraw", "line"];
const boxShapesDispaly=["○ ellipse","□ rectangle","◇ diamond"];
const boxShapes=["ellipse","rectangle","diamond"];
const lineShapesDispaly=["- line","⭢ arrow"];
@@ -14,18 +17,27 @@ let editedElements = [];
let elements = ea.getViewSelectedElements().filter(el=>boxShapes.contains(el.type));
if (elements.length>0) {
newShape = await utils.suggester(boxShapesDispaly, boxShapes, "Change shape of 'box' type elements in selection");
newShape = await utils.suggester(boxShapesDispaly, boxShapes, "Change shape of 'box' type elements in selection, press ESC to skip");
if(newShape) {
editedElements = elements;
elements.forEach(el=>el.type = newShape);
}
}
elements = ea.getViewSelectedElements().filter(el=>fillShapes.contains(el.type));
if (elements.length>0) {
newFillStyle = await utils.suggester(fillStylesDispaly, fillStyles, "Change the fill style of elements in selection, press ESC to skip");
if(newFillStyle) {
editedElements = editedElements.concat(elements.filter(e=>!editedElements.some(el=>el.id===e.id)));
elements.forEach(el=>el.fillStyle = newFillStyle);
}
}
elements = ea.getViewSelectedElements().filter(el=>lineShapes.contains(el.type));
if (elements.length>0) {
newShape = await utils.suggester(lineShapesDispaly, lineShapes, "Change shape of 'line' type elements in selection");
newShape = await utils.suggester(lineShapesDispaly, lineShapes, "Change shape of 'line' type elements in selection, press ESC to skip");
if(newShape) {
editedElements = editedElements.concat(elements);
editedElements = editedElements.concat(elements.filter(e=>!editedElements.some(el=>el.id===e.id)));
elements.forEach((el)=>{
el.type = newShape;
if(newShape === "arrow") {
@@ -37,4 +49,4 @@ if (elements.length>0) {
ea.copyViewElementsToEAforEditing(editedElements);
ea.addElementsToView(false,false);
ea.addElementsToView(false,false);

File diff suppressed because one or more lines are too long

View File

@@ -112,7 +112,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Change%20shape%20of%20selected%20elements.md
```
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape of selected Rectangles, Diamonds and Ellipses.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Change%20shape%20of%20selected%20elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">The script allows you to change the shape and fill style of selected Rectangles, Diamonds, Ellipses, Lines, Arrows and Freedraw.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-change-shape.jpg'></td></tr></table>
## Connect elements
```excalidraw-script-install

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "obsidian-excalidraw-plugin",
"version": "1.6.26",
"version": "1.6.29",
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
"main": "lib/index.js",
"types": "lib/index.d.ts",
@@ -17,7 +17,7 @@
"author": "",
"license": "MIT",
"dependencies": {
"@zsviczian/excalidraw": "0.11.0-obsidian-16",
"@zsviczian/excalidraw": "0.11.0-obsidian-20",
"monkey-around": "^2.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",

View File

@@ -60,6 +60,8 @@ export class EmbeddedFile {
public mimeType: MimeType = "application/octet-stream";
public size: Size = { height: 0, width: 0 };
public linkParts: LinkParts;
private hostPath: string;
public attemptCounter: number = 0;
/*public isHyperlink: boolean = false;*/
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string) {
@@ -77,6 +79,7 @@ export class EmbeddedFile {
this.imgInverted = this.img = "";
this.mtime = 0;
this.linkParts = getLinkParts(imgPath);
this.hostPath = hostPath;
if (!this.linkParts.path) {
new Notice(`Excalidraw Error\nIncorrect embedded filename: ${imgPath}`);
return;
@@ -92,16 +95,25 @@ export class EmbeddedFile {
hostPath,
);
if (!this.file) {
new Notice(
`Excalidraw Warning: could not find image file: ${imgPath}`,
5000,
);
if(this.attemptCounter++ === 0) {
new Notice(
`Excalidraw Warning: could not find image file: ${imgPath}`,
5000,
);
}
}
}
private fileChanged(): boolean {
if (!this.file) {
return false;
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
this.linkParts.path,
this.hostPath,
); // maybe the file has synchronized in the mean time
if(!this.file) {
this.attemptCounter++;
return false;
}
}
return this.mtime != this.file.stat.mtime;
}
@@ -135,7 +147,14 @@ export class EmbeddedFile {
public isLoaded(isDark: boolean): boolean {
if (!this.file) {
return true;
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
this.linkParts.path,
this.hostPath,
); // maybe the file has synchronized in the mean time
if(!this.file) {
this.attemptCounter++;
return true;
}
}
if (this.fileChanged()) {
return false;

View File

@@ -376,6 +376,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
exportSettings?: ExportSettings,
loader?: EmbeddedFilesLoader,
theme?: string,
padding?: number,
): Promise<SVGSVGElement> {
if (!theme) {
theme = this.plugin.settings.previewMatchObsidianTheme
@@ -409,7 +410,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
this.canvas.viewBackgroundColor,
this.getElements(),
this.plugin,
0
0,
padding
);
};
@@ -1327,22 +1329,42 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* Register instance of EA to use for hooks with TargetView
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
* Using this event you can set a different instance of Excalidraw Automate for hooks
* @returns true if successful
*/
registerThisAsViewEA() {
registerThisAsViewEA():boolean {
//@ts-ignore
if (!this.targetView || !this.targetView?._loaded) {
errorMessage("targetView not set", "addElementsToView()");
return false;
}
this.targetView.hookServer = this;
this.targetView.setHookServer(this);
return true;
}
/**
* Sets the targetView EA to window.ExcalidrawAutomate
* @returns true if successful
*/
deregisterThisAsViewEA():boolean {
//@ts-ignore
if (!this.targetView || !this.targetView?._loaded) {
errorMessage("targetView not set", "addElementsToView()");
return false;
}
this.targetView.setHookServer(this);
return true;
}
/**
* If set, this callback is triggered when the user closes an Excalidraw view.
*/
onViewUnloadHook: (view: ExcalidrawView) => void = null;
/**
* If set, this callback is triggered, when the user changes the view mode.
* You can use this callback in case you want to do something additional when the user switches to view mode and back.
*/
onViewModeChangeHook: (isViewModeEnabled:boolean) => void = null;
onViewModeChangeHook: (isViewModeEnabled:boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void = null;
/**
* If set, this callback is triggered, when the user hovers a link in the scene.
@@ -1353,10 +1375,12 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
onLinkHoverHook: (
element: NonDeletedExcalidrawElement,
linkText: string,
view: ExcalidrawView,
ea: ExcalidrawAutomate
) => boolean = null;
/**
* If set, this callback is triggered, when the user click a link in the scene.
* If set, this callback is triggered, when the user clicks a link in the scene.
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
* This callback must return a boolean value.
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
@@ -1364,7 +1388,9 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
onLinkClickHook:(
element: ExcalidrawElement,
linkText: string,
event: MouseEvent
event: MouseEvent,
view: ExcalidrawView,
ea: ExcalidrawAutomate
) => boolean = null;
/**
@@ -1554,6 +1580,15 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
return manifest.version >= requiredVersion;
};
/**
* Check if view is instance of ExcalidrawView
* @param view
* @returns
*/
isExcalidrawView(view: any): boolean {
return view instanceof ExcalidrawView;
}
/**
* sets selection in view
* @param elements

View File

@@ -15,6 +15,7 @@ import {
fileid,
REG_BLOCK_REF_CLEAN,
FRONTMATTER_KEY_LINKBUTTON_OPACITY,
FRONTMATTER_KEY_ONLOAD_SCRIPT,
} from "./Constants";
import { _measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
@@ -211,26 +212,28 @@ const wrap = (text: string, lineLen: number) =>
lineLen ? wrapText(text, lineLen, false, 0) : text;
export class ExcalidrawData {
private textElements: Map<
public textElements: Map<
string,
{ raw: string; parsed: string; wrapAt: number | null }
> = null;
private elementLinks: Map<string, string> = null;
public elementLinks: Map<string, string> = null;
public scene: any = null;
public deletedElements: ExcalidrawElement[] = [];
private file: TFile = null;
private app: App;
private showLinkBrackets: boolean;
private linkPrefix: string;
private urlPrefix: string;
private textMode: TextMode = TextMode.raw;
private plugin: ExcalidrawPlugin;
public loaded: boolean = false;
private files: Map<FileId, EmbeddedFile> = null; //fileId, path
private equations: Map<FileId, { latex: string; isLoaded: boolean }> = null; //fileId, path
private compatibilityMode: boolean = false;
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
constructor(
private plugin: ExcalidrawPlugin,
) {
this.app = plugin.app;
this.files = new Map<FileId, EmbeddedFile>();
this.equations = new Map<FileId, { latex: string; isLoaded: boolean }>();
@@ -373,12 +376,13 @@ export class ExcalidrawData {
public async loadData(
data: string,
file: TFile,
textMode: TextMode,
textMode: TextMode
): Promise<boolean> {
if (!file) {
return false;
}
this.loaded = false;
this.selectedElementIds = {};
this.textElements = new Map<
string,
{ raw: string; parsed: string; wrapAt: number }
@@ -430,17 +434,10 @@ export class ExcalidrawData {
}
return sceneJSONandPOS;
};
//try {
sceneJSONandPOS = loadJSON();
/*} catch (e) {
if(await this.app.vault.adapter.exists(getBakPath(file))) {
data = await this.app.vault.adapter.read(getBakPath(file))
sceneJSONandPOS = loadJSON();
new Notice(t("LOAD_FROM_BACKUP"), 4000);
} else {
throw e;
}*/
this.deletedElements = this.scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted);
this.scene.elements = this.scene.elements.filter((el:ExcalidrawElement)=>!el.isDeleted);
if (!this.scene.files) {
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
@@ -551,6 +548,7 @@ export class ExcalidrawData {
return false;
}
this.loaded = false;
this.selectedElementIds = {};
this.compatibilityMode = true;
this.file = file;
this.textElements = new Map<
@@ -637,23 +635,23 @@ export class ExcalidrawData {
id: string,
wrapResult: boolean = true,
): Promise<string> {
const t = this.textElements.get(id);
if (!t) {
const text = this.textElements.get(id);
if (!text) {
return null;
}
if (this.textMode === TextMode.parsed) {
if (!t.parsed) {
if (!text.parsed) {
this.textElements.set(id, {
raw: t.raw,
parsed: (await this.parse(t.raw)).parsed,
wrapAt: t.wrapAt,
raw: text.raw,
parsed: (await this.parse(text.raw)).parsed,
wrapAt: text.wrapAt,
});
}
//console.log("parsed",this.textElements.get(id).parsed);
return wrapResult ? wrap(t.parsed, t.wrapAt) : t.parsed;
return wrapResult ? wrap(text.parsed, text.wrapAt) : text.parsed;
}
//console.log("raw",this.textElements.get(id).raw);
return t.raw;
return text.raw;
}
private findNewElementLinksInScene(): boolean {
@@ -691,9 +689,10 @@ export class ExcalidrawData {
* check for textElements in Scene missing from textElements Map
* @returns {boolean} - true if there were changes
*/
private findNewTextElementsInScene(): boolean {
private findNewTextElementsInScene(selectedElementIds: {[key: string]: boolean} = {}): boolean {
//console.log("Excalidraw.Data.findNewTextElementsInScene()");
//get scene text elements
this.selectedElementIds = selectedElementIds;
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
let jsonString = JSON.stringify(this.scene);
@@ -708,14 +707,18 @@ export class ExcalidrawData {
if (te.id.length > 8) {
dirty = true;
id = nanoid();
if(this.selectedElementIds[te.id]) { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
delete this.selectedElementIds[te.id];
this.selectedElementIds[id] = true;
}
jsonString = jsonString.replaceAll(te.id, id); //brute force approach to replace all occurances (e.g. links, groups,etc.)
if (this.textElements.has(te.id)) {
//element was created with onBeforeTextSubmit
const t = this.textElements.get(te.id);
const text = this.textElements.get(te.id);
this.textElements.set(id, {
raw: t.raw,
parsed: t.parsed,
wrapAt: t.wrapAt,
raw: text.raw,
parsed: text.parsed,
wrapAt: text.wrapAt,
});
this.textElements.delete(te.id); //delete the old ID from the Map
}
@@ -953,7 +956,7 @@ export class ExcalidrawData {
* @returns markdown string
*/
disableCompression: boolean = false;
generateMD(): string {
generateMD(deletedElements: ExcalidrawElement[] = []): string {
let outString = "# Text Elements\n";
for (const key of this.textElements.keys()) {
outString += `${this.textElements.get(key).raw} ^${key}\n\n`;
@@ -979,7 +982,14 @@ export class ExcalidrawData {
}
outString += this.equations.size > 0 || this.files.size > 0 ? "\n" : "";
const sceneJSONstring = JSON.stringify(this.scene, null, "\t");
const sceneJSONstring = JSON.stringify({
type: this.scene.type,
version: this.scene.version,
source: this.scene.source,
elements: this.scene.elements.concat(deletedElements),
appState: this.scene.appState,
files: this.scene.files
}, null, "\t");
return (
outString +
getMarkdownDrawingSection(
@@ -1130,7 +1140,7 @@ export class ExcalidrawData {
return dirty;
}
public async syncElements(newScene: any): Promise<boolean> {
public async syncElements(newScene: any, selectedElementIds?: {[key: string]: boolean}): Promise<boolean> {
this.scene = newScene;
let result = false;
if (!this.compatibilityMode) {
@@ -1145,7 +1155,7 @@ export class ExcalidrawData {
this.setShowLinkBrackets() ||
this.findNewElementLinksInScene();
await this.updateTextElementsFromScene();
return result || this.findNewTextElementsInScene();
return result || this.findNewTextElementsInScene(selectedElementIds);
}
public async updateScene(newScene: any) {
@@ -1267,6 +1277,17 @@ export class ExcalidrawData {
return opacity;
}
public getOnLoadScript(): string {
const fileCache = this.app.metadataCache.getFileCache(this.file);
if (
fileCache?.frontmatter &&
fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT] != null
) {
return fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT];
}
return null;
}
private setLinkPrefix(): boolean {
const linkPrefix = this.linkPrefix;
const fileCache = this.app.metadataCache.getFileCache(this.file);
@@ -1335,6 +1356,10 @@ export class ExcalidrawData {
});
}
public getFiles(): EmbeddedFile[] {
return Object.values(this.files);
}
public getFile(fileId: FileId): EmbeddedFile {
return this.files.get(fileId);
}

View File

@@ -39,6 +39,7 @@ import {
REG_LINKINDEX_INVALIDCHARS,
KEYCODE,
LOCAL_PROTOCOL,
nanoid,
} from "./Constants";
import ExcalidrawPlugin from "./main";
import { repositionElementsToCursor, ExcalidrawAutomate } from "./ExcalidrawAutomate";
@@ -67,6 +68,7 @@ import {
getSVGPadding,
getWithBackground,
hasExportTheme,
log,
scaleLoadedImage,
svgToBase64,
viewportCoordsToSceneCoords,
@@ -180,7 +182,8 @@ export default class ExcalidrawView extends TextFileView {
public toolsPanelRef: React.MutableRefObject<any> = null;
private parentMoveObserver: MutationObserver;
public linksAlwaysOpenInANewPane: boolean = false; //override the need for SHIFT+CTRL+click
public hookServer: ExcalidrawAutomate;
private hookServer: ExcalidrawAutomate;
public lastSaveTimestamp: number = 0; //used to validate if incoming file should sync with open file
public semaphores: {
//The role of justLoaded is to capture the Excalidraw.onChange event that fires right after the canvas was loaded for the first time to
@@ -246,6 +249,16 @@ export default class ExcalidrawView extends TextFileView {
this.hookServer = plugin.ea;
}
setHookServer(ea:ExcalidrawAutomate) {
if(ea) {
this.hookServer = ea;
} else {
this.hookServer = this.plugin.ea;
}
}
getHookServer = () => this.hookServer ?? this.plugin.ea;
preventAutozoom() {
this.semaphores.preventAutozoom = true;
setTimeout(() => (this.semaphores.preventAutozoom = false), 2000);
@@ -396,6 +409,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
private preventReloadResetTimer: NodeJS.Timeout = null;
async save(preventReload: boolean = true, forcesave: boolean = false) {
//debug({where:"save", preventReload, forcesave, semaphores:this.semaphores});
if (this.semaphores.saving) {
@@ -424,19 +438,36 @@ export default class ExcalidrawView extends TextFileView {
if (this.compatibilityMode) {
await this.excalidrawData.syncElements(scene);
} else if (
await this.excalidrawData.syncElements(scene)
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
//&& !this.semaphores.autosaving
) {
await this.loadDrawing(false);
await this.loadDrawing(
false,
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
);
}
if (allowSave) {
//reload() is triggered indirectly when saving by the modifyEventHandler in main.ts
//prevent reload is set here to override reload when not wanted: typically when the user is editing
//and we do not want to interrupt the flow by reloading the drawing into the canvas.
if(this.preventReloadResetTimer) {
clearTimeout(this.preventReloadResetTimer);
this.preventReloadResetTimer = null;
}
this.semaphores.preventReload = preventReload;
await super.save();
this.lastSaveTimestamp = this.file.stat.mtime;
this.clearDirty();
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629
//there were odd cases when preventReload semaphore did not get cleared and consequently a synchronized image
//did not update the open drawing
if(preventReload) {
const self = this;
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
}
}
if (!this.semaphores.autosaving) {
@@ -475,6 +506,9 @@ export default class ExcalidrawView extends TextFileView {
if (!this.excalidrawData.loaded) {
return this.data;
}
//include deleted elements in save in case saving in markdown mode
//deleted elements are only used if sync modifies files while Excalidraw is open
//otherwise deleted elements are discarded when loading the scene
const scene = this.getScene();
if (!this.compatibilityMode) {
let trimLocation = this.data.search(/(^%%\n)?# Text Elements\n/m);
@@ -490,7 +524,7 @@ export default class ExcalidrawView extends TextFileView {
.replace(
/excalidraw-plugin:\s.*\n/,
`${FRONTMATTER_KEY}: ${
this.textMode == TextMode.raw ? "raw\n" : "parsed\n"
this.textMode === TextMode.raw ? "raw\n" : "parsed\n"
}`,
);
@@ -504,7 +538,9 @@ export default class ExcalidrawView extends TextFileView {
this.excalidrawData.disableCompression =
this.isEditedAsMarkdownInOtherView();
}
const reuslt = header + this.excalidrawData.generateMD();
const reuslt = header + this.excalidrawData.generateMD(
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted) //will be concatenated to scene.elements
);
this.excalidrawData.disableCompression = false;
return reuslt;
}
@@ -617,6 +653,13 @@ export default class ExcalidrawView extends TextFileView {
}
async handleLinkClick(view: ExcalidrawView, ev: MouseEvent) {
const tooltip = document.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
document.body.removeChild(tooltip);
}
const selectedText = this.getSelectedTextElement();
const selectedImage = selectedText?.id
? null
@@ -643,15 +686,21 @@ export default class ExcalidrawView extends TextFileView {
}
linkText = linkText.replaceAll("\n", ""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
if(this.hookServer?.onLinkClickHook) {
if(this.getHookServer().onLinkClickHook) {
const id = selectedText.id??selectedElementWithLink.id;
const el = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === id)[0];
try {
if(!this.hookServer.onLinkClickHook(el,linkText,ev)) {
if(!this.getHookServer().onLinkClickHook(
el,
linkText,
ev,
this,
this.getHookServer()
)) {
return;
}
} catch (e) {
errorlog({where: "ExcalidrawView.handleLinkClick selectedText.id!==null", fn: this.hookServer.onLinkClickHook, error: e});
errorlog({where: "ExcalidrawView.handleLinkClick selectedText.id!==null", fn: this.getHookServer().onLinkClickHook, error: e});
}
}
@@ -719,7 +768,7 @@ export default class ExcalidrawView extends TextFileView {
latex: formula,
isLoaded: false,
});
await this.save(true);
await this.save(false);
await updateEquation(
formula,
selectedImage.fileId,
@@ -731,7 +780,7 @@ export default class ExcalidrawView extends TextFileView {
});
return;
}
await this.save(true); //in case pasted images haven't been saved yet
await this.save(false); //in case pasted images haven't been saved yet
if (this.excalidrawData.hasFile(selectedImage.fileId)) {
if (ev.altKey) {
const ef = this.excalidrawData.getFile(selectedImage.fileId);
@@ -751,7 +800,7 @@ export default class ExcalidrawView extends TextFileView {
return;
}
ef.resetImage(this.file.path, link);
await this.save(true);
await this.save(false);
await this.loadSceneFiles();
this.setDirty();
});
@@ -768,15 +817,21 @@ export default class ExcalidrawView extends TextFileView {
return;
}
if(this.hookServer?.onLinkClickHook) {
if(this.getHookServer().onLinkClickHook) {
const id = selectedImage.id??selectedText.id??selectedElementWithLink.id;
const el = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === id)[0];
try {
if(!this.hookServer.onLinkClickHook(el,linkText,ev)) {
if(!this.getHookServer().onLinkClickHook(
el,
linkText,
ev,
this,
this.getHookServer()
)) {
return;
}
} catch (e) {
errorlog({where: "ExcalidrawView.handleLinkClick selectedText.id===null", fn: this.hookServer.onLinkClickHook, error: e});
errorlog({where: "ExcalidrawView.handleLinkClick selectedText.id===null", fn: this.getHookServer().onLinkClickHook, error: e});
}
}
@@ -864,28 +919,32 @@ export default class ExcalidrawView extends TextFileView {
}
const self = this;
this.app.workspace.onLayoutReady(() => {
self.addSlidingPanesListner();
this.app.workspace.onLayoutReady(async () => {
self.contentEl.addClass("excalidraw-view");
//https://github.com/zsviczian/excalibrain/issues/28
await self.addSlidingPanesListner(); //awaiting this because when using workspaces, onLayoutReady comes too early
self.addParentMoveObserver();
});
this.setupAutosaveTimer();
this.contentEl.addClass("excalidraw-view");
}
//this is to solve sliding panes bug
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/9
private slidingPanesListner: any;
private addSlidingPanesListner() {
private async addSlidingPanesListner() {
const self = this;
this.slidingPanesListner = () => {
if (self.refresh) {
self.refresh();
}
};
(
this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt
).containerEl.addEventListener("scroll", this.slidingPanesListner);
let rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
while(!rootSplit) {
await sleep(50);
rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
}
rootSplit.containerEl.addEventListener("scroll", this.slidingPanesListner);
}
private removeSlidingPanesListner() {
@@ -997,14 +1056,19 @@ export default class ExcalidrawView extends TextFileView {
warningUnknowSeriousError();
return;
}
const editing = api.getAppState().editingElement !== null;
const st = api.getAppState();
const editing = st.editingElement !== null;
//this will reset positioning of the cursor in case due to the popup keyboard,
//or the command palette, or some other unexpected reason the onResize would not fire...
this.refresh();
if (
this.isLoaded &&
this.semaphores.dirty &&
this.semaphores.dirty == this.file?.path &&
this.plugin.settings.autosave &&
!this.semaphores.forceSaving &&
!editing
!editing &&
st.draggingElement === null //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
) {
this.autosaveTimer = null;
this.semaphores.autosaving = true;
@@ -1041,14 +1105,21 @@ export default class ExcalidrawView extends TextFileView {
//save current drawing when user closes workspace leaf
async onunload() {
this.removeParentMoveObserver();
this.removeSlidingPanesListner();
if(this.getHookServer().onViewUnloadHook) {
try {
this.getHookServer().onViewUnloadHook(this);
} catch(e) {
errorlog({where: "ExcalidrawView.onunload", fn: this.getHookServer().onViewUnloadHook, error: e});
}
}
const tooltip = document.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
document.body.removeChild(tooltip);
}
this.removeParentMoveObserver();
this.removeSlidingPanesListner();
if (this.autosaveTimer) {
clearInterval(this.autosaveTimer);
this.autosaveTimer = null;
@@ -1193,6 +1264,7 @@ export default class ExcalidrawView extends TextFileView {
if (clear) {
this.clear();
}
this.lastSaveTimestamp = this.file.stat.mtime;
data = this.data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
this.app.workspace.onLayoutReady(async () => {
this.compatibilityMode = this.file.extension === "excalidraw";
@@ -1234,6 +1306,19 @@ export default class ExcalidrawView extends TextFileView {
}
}
await this.loadDrawing(true);
const script = this.excalidrawData.getOnLoadScript();
if(script) {
const self = this;
const scriptname = this.file.basename+ "-onlaod-script";
const runScript = () => {
if(!self.excalidrawAPI) { //need to wait for Excalidraw to initialize
setTimeout(runScript,200);
return;
}
self.plugin.scriptEngine.executeScript(self,script,scriptname);
}
runScript();
}
this.isLoaded = true;
});
}
@@ -1259,6 +1344,21 @@ export default class ExcalidrawView extends TextFileView {
this.activeLoader = null;
if (this.nextLoader) {
runLoader(this.nextLoader);
} else {
//in case one or more files have not loaded retry later hoping that sync has delivered the file in the mean time.
this.excalidrawData.getFiles().some(ef=>{
if(ef && !ef.file && ef.attemptCounter<30) {
const self = this;
const currentFile = this.file.path;
setTimeout(async ()=>{
if(self && self.excalidrawAPI && currentFile === self.file.path) {
self.loadSceneFiles();
}
},2000)
return true;
}
return false;
})
}
},0
);
@@ -1270,12 +1370,149 @@ export default class ExcalidrawView extends TextFileView {
}
}
public async synchronizeWithData(inData: ExcalidrawData) {
//check if saving, wait until not
let counter = 0;
while(this.semaphores.saving && counter++<30) {
await sleep(100);
}
if(counter>=30) {
errorlog({
where:"ExcalidrawView.synchronizeWithData",
message:`Aborting sync with received file (${this.file.path}) because semaphores.saving remained true for ower 3 seconds`,
"fn": this.synchronizeWithData
});
return;
}
this.semaphores.saving = true;
let reloadFiles = false;
try {
const deletedIds = inData.deletedElements.map(el=>el.id);
const sceneElements = this.excalidrawAPI.getSceneElements()
//remove deleted elements
.filter((el: ExcalidrawElement)=>!deletedIds.contains(el.id));
const sceneElementIds = sceneElements.map((el:ExcalidrawElement)=>el.id);
const manageMapChanges = (incomingElement: ExcalidrawElement ) => {
switch(incomingElement.type) {
case "text":
this.excalidrawData.textElements.set(
incomingElement.id,
inData.textElements.get(incomingElement.id)
);
break;
case "image":
if(inData.getFile(incomingElement.fileId)) {
this.excalidrawData.setFile(
incomingElement.fileId,
inData.getFile(incomingElement.fileId)
);
reloadFiles = true;
} else if (inData.getEquation(incomingElement.fileId)) {
this.excalidrawData.setEquation(
incomingElement.fileId,
inData.getEquation(incomingElement.fileId)
)
reloadFiles = true;
}
break;
}
if(inData.elementLinks.has(incomingElement.id)) {
this.excalidrawData.elementLinks.set(
incomingElement.id,
inData.elementLinks.get(incomingElement.id)
)
}
}
//update items with higher version number then in scene
inData.scene.elements.forEach((
incomingElement:ExcalidrawElement,
idx: number,
inElements: ExcalidrawElement[]
)=>{
const sceneElement:ExcalidrawElement = sceneElements.filter(
(element:ExcalidrawElement)=>element.id === incomingElement.id
)[0];
if(
sceneElement &&
(sceneElement.version < incomingElement.version ||
//in case of competing versions of the truth, the incoming version will be honored
(sceneElement.version === incomingElement.version &&
JSON.stringify(sceneElement) !== JSON.stringify(incomingElement))
)
) {
manageMapChanges(incomingElement);
//place into correct element layer sequence
const currentLayer = sceneElementIds.indexOf(incomingElement.id);
//remove current element from scene
const elToMove = sceneElements.splice(currentLayer,1);
if(idx === 0) {
sceneElements.splice(0,0,incomingElement);
if(currentLayer!== 0) {
sceneElementIds.splice(currentLayer,1);
sceneElementIds.splice(0,0,incomingElement.id);
}
} else {
const prevId = inElements[idx-1].id;
const parentLayer = sceneElementIds.indexOf(prevId);
sceneElements.splice(parentLayer+1,0,incomingElement);
if(parentLayer!==currentLayer-1) {
sceneElementIds.splice(currentLayer,1)
sceneElementIds.splice(parentLayer+1,0,incomingElement.id);
}
}
return;
} else if(!sceneElement) {
manageMapChanges(incomingElement);
if(idx === 0) {
sceneElements.splice(0,0,incomingElement);
sceneElementIds.splice(0,0,incomingElement.id);
} else {
const prevId = inElements[idx-1].id;
const parentLayer = sceneElementIds.indexOf(prevId);
sceneElements.splice(parentLayer+1,0,incomingElement);
sceneElementIds.splice(parentLayer+1,0,incomingElement.id);
}
} else if(sceneElement && incomingElement.type === "image") { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/632
if(inData.getFile(incomingElement.fileId)) {
this.excalidrawData.setFile(
incomingElement.fileId,
inData.getFile(incomingElement.fileId)
);
reloadFiles = true;
}
}
})
this.previousSceneVersion = getSceneVersion(sceneElements);
//changing files could result in a race condition for sync. If at the end of sync there are differences
//set dirty will trigger an autosave
if(getSceneVersion(inData.scene.elements) !== this.previousSceneVersion) {
this.setDirty();
}
this.excalidrawAPI.updateScene({elements: sceneElements});
if(reloadFiles) this.loadSceneFiles();
} catch(e) {
errorlog({
where:"ExcalidrawView.synchronizeWithData",
message:`Error during sync with received file (${this.file.path})`,
"fn": this.synchronizeWithData,
error: e
});
}
this.semaphores.saving = false;
}
initialContainerSizeUpdate = false;
/**
*
* @param justloaded - a flag to trigger zoom to fit after the drawing has been loaded
*/
private async loadDrawing(justloaded: boolean) {
private async loadDrawing(justloaded: boolean, deletedElements?: ExcalidrawElement[]) {
const excalidrawData = this.excalidrawData.scene;
this.semaphores.justLoaded = justloaded;
this.initialContainerSizeUpdate = justloaded;
@@ -1299,9 +1536,12 @@ export default class ExcalidrawView extends TextFileView {
this.updateScene(
{
elements: excalidrawData.elements,
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
appState: {
...excalidrawData.appState,
...this.excalidrawData.selectedElementIds !== {} //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
? this.excalidrawData.selectedElementIds
: {},
zenModeEnabled,
viewModeEnabled,
linkOpacity: this.excalidrawData.getLinkOpacity(),
@@ -2103,13 +2343,18 @@ export default class ExcalidrawView extends TextFileView {
}
}
if(this.hookServer?.onLinkHoverHook) {
if(this.getHookServer().onLinkHoverHook) {
try {
if(!this.hookServer.onLinkHoverHook(element,linktext)) {
if(!this.getHookServer().onLinkHoverHook(
element,
linktext,
this,
this.getHookServer()
)) {
return;
}
} catch (e) {
errorlog({where: "ExcalidrawView.showHoverPreview", fn: this.hookServer.onLinkHoverHook, error: e});
errorlog({where: "ExcalidrawView.showHoverPreview", fn: this.getHookServer().onLinkHoverHook, error: e});
}
}
@@ -2272,6 +2517,7 @@ export default class ExcalidrawView extends TextFileView {
showHoverPreview();
}
},
libraryReturnUrl: "app://obsidian.md",
autoFocus: true,
onChange: (et: ExcalidrawElement[], st: AppState) => {
viewModeEnabled = st.viewModeEnabled;
@@ -2352,11 +2598,11 @@ export default class ExcalidrawView extends TextFileView {
files: TFile[],
text: string,
): boolean => {
if (this.hookServer.onDropHook) {
if (this.getHookServer().onDropHook) {
try {
return this.hookServer.onDropHook({
return this.getHookServer().onDropHook({
//@ts-ignore
ea: this.hookServer, //the ExcalidrawAutomate object
ea: this.getHookServer(), //the ExcalidrawAutomate object
event, //React.DragEvent<HTMLDivElement>
draggable, //Obsidian draggable object
type, //"file"|"text"
@@ -2493,6 +2739,16 @@ export default class ExcalidrawView extends TextFileView {
if(html) {
const path = html.match(/href="app:\/\/obsidian\.md\/(.*?)"/);
if(path.length === 2) {
const link = decodeURIComponent(path[1]).split("#");
const f = app.vault.getAbstractFileByPath(link[0]);
if(f && f instanceof TFile) {
const path = app.metadataCache.fileToLinktext(f,this.file.path);
this.addText(`[[${
path +
(link.length>1 ? "#" + link[1] + "|" + path : "")
}]]`);
return;
}
this.addText(`[[${decodeURIComponent(path[1])}]]`);
return false;
}
@@ -2611,14 +2867,26 @@ export default class ExcalidrawView extends TextFileView {
if (!link || link === "") {
return;
}
const tooltip = document.body.querySelector(
"body>div.excalidraw-tooltip,div.excalidraw-tooltip--visible",
);
if (tooltip) {
document.body.removeChild(tooltip);
}
const event = e?.detail?.nativeEvent;
if(this.hookServer?.onLinkClickHook) {
if(this.getHookServer().onLinkClickHook) {
try {
if(!this.hookServer.onLinkClickHook(element,element.link,event)) {
if(!this.getHookServer().onLinkClickHook(
element,
element.link,
event,
this,
this.getHookServer()
)) {
return;
}
} catch (e) {
errorlog({where: "ExcalidrawView.onLinkOpen", fn: this.hookServer.onLinkClickHook, error: e});
errorlog({where: "ExcalidrawView.onLinkOpen", fn: this.getHookServer().onLinkClickHook, error: e});
}
}
if (link.startsWith(LOCAL_PROTOCOL) || link.startsWith("[[")) {
@@ -2719,11 +2987,11 @@ export default class ExcalidrawView extends TextFileView {
this.toolsPanelRef?.current?.setExcalidrawViewMode(
isViewModeEnabled,
);
if(this.hookServer?.onViewModeChangeHook) {
if(this.getHookServer().onViewModeChangeHook) {
try {
this.hookServer.onViewModeChangeHook(isViewModeEnabled);
this.getHookServer().onViewModeChangeHook(isViewModeEnabled,this,this.getHookServer());
} catch(e) {
errorlog({where: "ExcalidrawView.onViewModeChange", fn: this.hookServer.onViewModeChangeHook, error: e});
errorlog({where: "ExcalidrawView.onViewModeChange", fn: this.getHookServer().onViewModeChangeHook, error: e});
}
}

View File

@@ -3,10 +3,9 @@ import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { FileData, MimeType } from "./EmbeddedFileLoader";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { getImageSize, log, sleep, svgToBase64 } from "./utils/Utils";
import { errorlog, getImageSize, log, sleep, svgToBase64 } from "./utils/Utils";
import { fileid } from "./Constants";
import html2canvas from "html2canvas";
import { count } from "console";
import { Notice } from "obsidian";
declare let window: any;
@@ -46,11 +45,14 @@ export async function tex2dataURL(
//if network is slow, or not available, or mathjax has not yet fully loaded
let counter = 0;
while (!plugin.mathjax && !plugin.mathjaxLoaderFinished && counter < 10) {
log({ where: "tex2dataURL", counter });
await sleep(100);
counter++;
}
if(!plugin.mathjaxLoaderFinished) {
errorlog({where: "text2dataURL", fn: tex2dataURL, message:"mathjaxLoader not ready, using fallback. Try reloading Obsidian or restarting the Excalidraw plugin"});
}
//it is not clear why this works, but it seems that after loading the plugin sometimes only the third attempt is successful.
try {
return await mathjaxSVG(tex, plugin);
@@ -79,7 +81,7 @@ export async function tex2dataURL(
}
}
async function mathjaxSVG(
export async function mathjaxSVG(
tex: string,
plugin: ExcalidrawPlugin,
): Promise<{

View File

@@ -36,7 +36,7 @@ let metadataCache: MetadataCache;
const getDefaultWidth = (plugin: ExcalidrawPlugin): string => {
const width = parseInt(plugin.settings.width);
if (isNaN(width) || width === 0) {
if (isNaN(width) || width === 0 || width === null) {
return "400";
}
return plugin.settings.width;
@@ -341,8 +341,8 @@ const tmpObsidianWYSIWYG = async (
const basename = splitFolderAndFilename(attr.fname).basename;
const setAttr = () => {
const hasWidth = internalEmbedDiv.getAttribute("width") !== "";
const hasHeight = internalEmbedDiv.getAttribute("height") !== "";
const hasWidth = internalEmbedDiv.getAttribute("width") && (internalEmbedDiv.getAttribute("width") !== "");
const hasHeight = internalEmbedDiv.getAttribute("height") && (internalEmbedDiv.getAttribute("height") !== "");
if (hasWidth) {
attr.fwidth = internalEmbedDiv.getAttribute("width");
}

View File

@@ -170,7 +170,12 @@ export class ScriptEngine {
}
const view = this.plugin.app.workspace.activeLeaf.view;
if (view instanceof ExcalidrawView) {
this.executeScript(view, f);
(async()=>{
const script = await this.plugin.app.vault.read(f);
if(script) {
this.executeScript(view, script, scriptName);
}
})()
return true;
}
return false;
@@ -206,18 +211,13 @@ export class ScriptEngine {
delete app.commands.commands[commandId];
}
async executeScript(view: ExcalidrawView, f: TFile) {
if (!view || !f) {
async executeScript(view: ExcalidrawView, script: string, title: string) {
if (!view || !script || !title) {
return;
}
this.plugin.ea.reset();
this.plugin.ea.setView(view);
const script = await this.plugin.app.vault.read(f);
if (!script) {
return;
}
this.plugin.ea.activeScript = this.getScriptName(f);
this.plugin.ea.activeScript = title;
//https://stackoverflow.com/questions/45381204/get-asyncfunction-constructor-in-typescript changed tsconfig to es2017
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction

View File

@@ -30,6 +30,7 @@ export const FRONTMATTER_KEY_EXPORT_PNGSCALE = "excalidraw-export-pngscale";
export const FRONTMATTER_KEY_CUSTOM_PREFIX = "excalidraw-link-prefix";
export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix";
export const FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS = "excalidraw-link-brackets";
export const FRONTMATTER_KEY_ONLOAD_SCRIPT = "excalidraw-onload-script";
export const FRONTMATTER_KEY_LINKBUTTON_OPACITY = "excalidraw-linkbutton-opacity";
export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
export const FRONTMATTER_KEY_FONT = "excalidraw-font";

View File

@@ -17,6 +17,47 @@ I develop this plugin as a hobby, spending most of my free time doing this. If y
<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.6.32": `
## Fixed
- Filenames of embedded images and markdown documents did not get updated if the drawing was open in a work-pane while you changed the filename of the embedded file (image or markdown document) [632](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/632).
- When you created a new text element and immediately dragged it, sometimes autosave interrupted the drag action and Excalidraw dropped the element you were dragging [630](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630)
- In some edge cases when you had the drawing open on your desktop and you also opened the same image on your tablet, Sync seemed to work in the background but the changes did not appear on the desktop until you closed and opened the drawing again. [629](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629)
- LaTeX support: Excalidraw must download a javascript library from one of the hosting sites for MathJax tex2svg. It seems that some people do not have access to the URL recommended in the first place by [MathJax](https://docs.mathjax.org/en/latest/web/start.html). If LaTeX formulas do not render correctly in Excalidraw, try changing the source server under Compatibility Settings in Excalidraw Plugin Settings. [628](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628)`,
"1.6.31": `
Minor update:
## Fixes
- Color picker hotkeys were not working. They are working again [627](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/627)
- I updated MathJax (LaTeX) to the newest (3.2.1) release.`,
"1.6.30": `
## Fixed
- The load stencil library button stopped working after 1.6.29 due to an error in the core Excalidraw package. It is now fixed. [#625](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/625).
- On iPad (probably other Obsidian mobile devices as well) after opening the command palette the positioning of the pointer was off. From now on, the pointer is automatically re-calibrated every 5 seconds.
- I improved shared-vault collaboration sync. If the open file has not been saved for the last 5 minutes (i.e. you are not working on the drawing actively), and a newer remote version of the file is received via sync, then the remote file will simply overwrite the local file (i.e. the behavior of Excalidraw Obsidian prior to implementing Shared (Multiplayer) Vault Synchronization support in 1.6.29). This solution will support active collaboration when parties participating are actively editing the drawing, but also caters to the scenario when you open a drawing on one device (e.g. your desktop) and once you are finished editing you do not close the drawing, but simply put your PC to sleep... then later you edit the same drawing on your tablet. When you turn your desktop PC on the next time, the changes you've made on your tablet will be synchronized by Obsidian sync. In this case the changes from your tablet should be honored. If you have not edited the open drawing for more then 5 minutes (like in this scenario) there is no value in running the file comparison between the local version and the received one. This approach reduces the probability of running into sync conflicts.`,
"1.6.29": `
## New
- I implemented sync support inspired by the new [Obsidian Multiplayer Sync](https://youtu.be/ZyCPhbd51eo) feature (available in insider build v0.14.10).
- To manage expectations, this is not real-time collaboration like on Excalidraw.com. Synchronization is delayed by the frequency of the autosave timer (every 10 secs) and the speed of Obsidian sync. Also if a file has conflicting versions, Obsidian sync may delay the delivery of the changed file.
- Even if you are not using multiplayer Obsidian Vaults, you may benefit from the improved synchronization, for example when using the freedraw tool on your tablet or phone, and in parallel editing the same drawing (e.g. typing text) on your desktop. I frequently do this in a mind-mapping scenario.
- If the same Excalidraw sketch is open on multiple devices then Excalidraw will try to merge changes into the open drawing, thus parallel modifications on different devices are possible. If the same element is edited by multiple parties at the same time, then the foreign (received) version will be honored and the local changes lost.
## Fixed:
- Default embed width setting stopped working. [#622](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/622)
- The link tooltip gets stuck on screen after Excalidraw closes [#621](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/621)
- Layout error when using the Workspaces core plugin. [#28](https://github.com/zsviczian/excalibrain/issues/28)`,
"1.6.28": `
## New
- When dropping a link from a DataView query into Excalidraw the link will honor your "New link format" preferences in Obsidian. It will add the "shortest path when possible", if that is your setting. If the link includes a block or section reference, then the link will automatically include an alias, such that only the filename is displayed (shortest path possible allowing) [#610](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/610)
- If Excalidraw is in a Hover Editor and you open a link in another pane by CTRL+SHIFT+Click then the new page will open in the main workspace, and not in a split pane in the hover editor.
## Fixed
- New text elements get de-selected after auto-save [#609](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609)
- Update opacity of bound text when the opacity of the container is updated [#5142](https://github.com/excalidraw/excalidraw/pull/5142)
- ExcalidrawAutomate: openFileInNewOrAdjacentLeaf() function. This also caused an error when clicking a link in Excalidraw in a hover window, when there were no leaves in the main workspace view.`,
"1.6.27": `
## New Features
- While these new features are benefitial for all Excalidraw Automation projects, the current changes are mainly in support of the [ExcaliBrain](https://youtu.be/O2s-h5VKCas) integration. See detailed [Release Notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.6.27) on GitHub.
`,
"1.6.26": `
## Fixed
- Dragging multiple files onto the canvas will now correctly [#589](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/589)

View File

@@ -36,7 +36,7 @@ export class ReleaseNotes extends Modal {
? Object.keys(RELEASE_NOTES)
.filter((key) => key > prevRelease)
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
.slice(0, 6)
.slice(0, 10)
.join("\n\n---\n")
: FIRST_RUN;
await MarkdownRenderer.renderMarkdown(

View File

@@ -532,6 +532,12 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
"Valid values are between 0 and 1, where 0 means the button is transparent.",
after: ": 0.5",
},
{
field: "onload-script",
code: null,
desc: "The value of this field will be executed as javascript code using the Script Engine environment. Use this to initiate custom actions or logic when loading your drawing.",
after: ': "new Notice(`Hello World!\\n\\nFile: ${ea.targetView.file.basename}`);"',
},
{
field: "font",
code: null,

View File

@@ -1,9 +1,13 @@
import "obsidian";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import ExcalidrawView from "./ExcalidrawView";
//import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
export {ExcalidrawAutomateInterface} from "./types";
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
export type { Point } from "@zsviczian/excalidraw/types/types";
export const getEA = (view?:ExcalidrawView): ExcalidrawAutomate => {
return window.ExcalidrawAutomate.getAPI(view);
export const getEA = (view?:any): any => {
try {
return window.ExcalidrawAutomate.getAPI(view);
} catch(e) {
console.log({message: "Excalidraw not available", fn: getEA});
return null;
}
}

View File

@@ -346,6 +346,10 @@ export default {
"By enabling this feature drawings you create with the ribbon icon, the command palette actions, " +
"and the file explorer are going to be all legacy *.excalidraw files. This setting will also turn off the reminder message " +
"when you open a legacy file for editing.",
MATHJAX_NAME: "MathJax (LaTeX) javascript library host",
MATHJAX_DESC: "If you are using LaTeX equiations in Excalidraw then the plugin needs to load a javascript library for that. " +
"Some users are unable to access certain host servers. If you are experiencing issues try changing the host here. You may need to "+
"restart Obsidian after closing settings, for this change to take effect.",
EXPERIMENTAL_HEAD: "Experimental features",
EXPERIMENTAL_DESC:
"Some of these setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",

View File

@@ -42,10 +42,11 @@ import {
VIRGIL_FONT,
VIRGIL_DATAURL,
} from "./Constants";
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
import {
changeThemeOfExcalidrawMD,
getMarkdownDrawingSection,
ExcalidrawData
} from "./ExcalidrawData";
import {
ExcalidrawSettings,
@@ -82,7 +83,7 @@ import {
setLeftHandedMode,
sleep,
} from "./utils/Utils";
import { getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils";
//import { OneOffs } from "./OneOffs";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { ScriptEngine } from "./Scripts";
@@ -94,7 +95,6 @@ import {
} from "./MarkdownPostProcessor";
import { FieldSuggester } from "./dialogs/FieldSuggester";
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
import { debug } from "./utils/Utils";
declare module "obsidian" {
interface App {
@@ -256,12 +256,17 @@ export default class ExcalidrawPlugin extends Plugin {
});
}
private loadMathJax() {
public loadMathJax() {
const self = this;
this.app.workspace.onLayoutReady(async () => {
//loading Obsidian MathJax as fallback
await loadMathJax();
try {
if(self.mathjaxDiv) {
document.body.removeChild(self.mathjaxDiv);
self.mathjax = null;
self.mathjaxLoaderFinished = false;
}
self.mathjaxDiv = document.body.createDiv();
self.mathjaxDiv.title = "Excalidraw MathJax Support";
self.mathjaxDiv.style.display = "none";
@@ -299,7 +304,7 @@ export default class ExcalidrawPlugin extends Plugin {
self.mathjaxLoaderFinished = true;
});
};
script.src = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js";
script.src = self.settings.mathjaxSourceURL; // "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js";
//script.src = MATHJAX_DATAURL;
doc.head.appendChild(script);
} catch {
@@ -1414,7 +1419,7 @@ export default class ExcalidrawPlugin extends Plugin {
const modifyEventHandler = async (file: TFile) => {
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
leaves.forEach(async (leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
if (
excalidrawView.file &&
@@ -1425,8 +1430,24 @@ export default class ExcalidrawPlugin extends Plugin {
file.path.lastIndexOf(".excalidraw"),
)}.md` === excalidrawView.file.path))
) {
//debug({where:"ExcalidrawPlugin.modifyEventHandler",file:file.name,reloadfile:excalidrawView.file,before:"reload(true)"});
excalidrawView.reload(true, excalidrawView.file);
if(excalidrawView.semaphores.preventReload) {
excalidrawView.semaphores.preventReload = false;
return;
}
//if the user hasn't touched the file for 5 minutes, don't synchronize, reload.
//this is to avoid complex sync scenarios of multiple remote changes outside an active collaboration session
if(excalidrawView.lastSaveTimestamp + 300000 < Date.now()) {
excalidrawView.reload(true, excalidrawView.file);
return;
}
if(file.extension==="md") {
const inData = new ExcalidrawData(self);
const data = await app.vault.read(file);
await inData.loadData(data,file,getTextMode(data));
excalidrawView.synchronizeWithData(inData);
} else {
excalidrawView.reload(true, excalidrawView.file);
}
}
});
};
@@ -1791,18 +1812,11 @@ export default class ExcalidrawPlugin extends Plugin {
}
public openDrawing(drawingFile: TFile, onNewPane: boolean) {
let leaf: WorkspaceLeaf = null;
let leaf = this.app.workspace.activeLeaf;
if (!leaf) {
leaf = this.app.workspace.activeLeaf;
}
if (!leaf) {
leaf = this.app.workspace.getLeaf();
}
if (onNewPane) {
leaf = this.app.workspace.createLeafBySplit(leaf);
if (!leaf || onNewPane) {
leaf = getNewOrAdjacentLeaf(this, app.workspace.activeLeaf);
}
leaf.setViewState({

View File

@@ -520,13 +520,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
)
: this.state.scriptIconMap[key].name
}
action={() => {
action={async () => {
const f =
this.props.view.app.vault.getAbstractFileByPath(key);
if (f && f instanceof TFile) {
this.props.view.plugin.scriptEngine.executeScript(
this.props.view,
f,
await this.props.view.plugin.app.vault.read(f),
this.props.view.plugin.scriptEngine.getScriptName(f)
);
}
}}

View File

@@ -100,6 +100,7 @@ export interface ExcalidrawSettings {
defaultTrayMode: boolean;
previousRelease: string;
showReleaseNotes: boolean;
mathjaxSourceURL: string;
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -179,6 +180,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
defaultTrayMode: false,
previousRelease: "1.6.13",
showReleaseNotes: true,
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js"
};
const fragWithHTML = (html: string) =>
@@ -188,6 +190,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
plugin: ExcalidrawPlugin;
private requestEmbedUpdate: boolean = false;
private requestReloadDrawings: boolean = false;
private reloadMathJax: boolean = false;
//private applyDebounceTimer: number = 0;
constructor(app: App, plugin: ExcalidrawPlugin) {
@@ -228,6 +231,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.plugin.triggerEmbedUpdates();
}
this.plugin.scriptEngine.updateScriptPath();
if(this.reloadMathJax) {
this.plugin.loadMathJax();
}
}
async display() {
@@ -1108,6 +1114,24 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628
new Setting(containerEl)
.setName(t("MATHJAX_NAME"))
.setDesc(t("MATHJAX_DESC"))
.addDropdown((dropdown) => {
dropdown
.addOption("https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js", "jsdelivr")
.addOption("https://unpkg.com/mathjax@3.2.1/es5/tex-svg.js", "unpkg")
.addOption("https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.1/es5/tex-svg-full.min.js","cdnjs")
.setValue(this.plugin.settings.mathjaxSourceURL)
.onChange((value)=> {
this.plugin.settings.mathjaxSourceURL = value;
this.reloadMathJax = true;
this.applySettingsUpdate();
})
})
this.containerEl.createEl("h1", { text: t("EXPERIMENTAL_HEAD") });
this.containerEl.createEl("p", { text: t("EXPERIMENTAL_DESC") });

21
src/types.d.ts vendored
View File

@@ -1,4 +1,4 @@
import { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
import { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, NonDeletedExcalidrawElement, StrokeSharpness, StrokeStyle } from "@zsviczian/excalidraw/types/element/types";
import { Point } from "@zsviczian/excalidraw/types/types";
import { TFile, WorkspaceLeaf } from "obsidian";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
@@ -63,6 +63,7 @@ export interface ExcalidrawAutomateInterface {
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
theme?: string,
padding?: number
): Promise<SVGSVGElement>;
createPNG(
templatePath?: string,
@@ -148,6 +149,23 @@ export interface ExcalidrawAutomateInterface {
//position in the stack of elements in the view even if modified using EA
newElementsOnTop?: boolean, //default is false, i.e. the new elements get to the bottom of the stack
): Promise<boolean>;
registerThisAsViewEA():boolean;
deregisterThisAsViewEA():boolean;
onViewUnloadHook(view: ExcalidrawView): void;
onViewModeChangeHook(isViewModeEnabled:boolean, view: ExcalidrawView, ea: ExcalidrawAutomate): void;
onLinkHoverHook(
element: NonDeletedExcalidrawElement,
linkText: string,
view: ExcalidrawView,
ea: ExcalidrawAutomate
):boolean;
onLinkClickHook(
element: ExcalidrawElement,
linkText: string,
event: MouseEvent,
view: ExcalidrawView,
ea: ExcalidrawAutomate
): boolean;
onDropHook(data: {
//if set Excalidraw will call this function onDrop events
ea: ExcalidrawAutomate;
@@ -198,6 +216,7 @@ export interface ExcalidrawAutomateInterface {
//recommended use:
//if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
verifyMinimumPluginVersion(requiredVersion: string): boolean;
isExcalidrawView(view: any): boolean;
selectElementsInView(elements: ExcalidrawElement[]): void; //sets selection in view
generateElementId(): string; //returns an 8 character long random id
cloneElement(element: ExcalidrawElement): ExcalidrawElement; //Returns a clone of the element with a new id

View File

@@ -1,4 +1,4 @@
import { normalizePath, TAbstractFile, TFolder, Vault } from "obsidian";
import { normalizePath, Notice, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
import { ExcalidrawSettings } from "src/Settings";
/**
@@ -126,6 +126,9 @@ export async function checkAndCreateFolder(vault: Vault, folderpath: string) {
if (folder && folder instanceof TFolder) {
return;
}
if (folder && folder instanceof TFile) {
new Notice(`The folder cannot be created because it already exists as a file: ${folderpath}.`)
}
await vault.createFolder(folderpath);
}

View File

@@ -14,7 +14,7 @@ export const getParentOfClass = (element: HTMLElement, cssClass: string):HTMLEle
) {
parent = parent.parentElement;
}
return parent.classList.contains(cssClass) ? parent : null;
return parent?.classList?.contains(cssClass) ? parent : null;
};
export const getNewOrAdjacentLeaf = (
@@ -29,7 +29,7 @@ export const getNewOrAdjacentLeaf = (
const mainLeaves = app.workspace.getLayout().main.children.filter((c:any) => c.type === "leaf");
if(mainLeaves.length === 0) {
//@ts-ignore
return leafToUse = app.workspace.createLeafInParent(app.workspace.rootSplit);
return app.workspace.createLeafInParent(app.workspace.rootSplit);
}
const targetLeaf = app.workspace.getLeafById(mainLeaves[0].id);
if (plugin.settings.openInAdjacentPane) {
@@ -78,7 +78,7 @@ export const getAttachmentsFolderAndFilePath = async (
const activeFileFolder = `${splitFolderAndFilename(activeViewFilePath).folderpath}/`;
folder = normalizePath(activeFileFolder + folder.substring(2));
}
if (!folder) {
if (!folder || folder === "/") {
folder = "";
}
await checkAndCreateFolder(app.vault, folder);

View File

@@ -1,4 +1,4 @@
{
"1.6.26": "0.12.16",
"1.6.32": "0.12.16",
"1.4.2": "0.11.13"
}

View File

@@ -2215,10 +2215,10 @@
dependencies:
"@zerollup/ts-helpers" "^1.7.18"
"@zsviczian/excalidraw@0.11.0-obsidian-16":
"integrity" "sha512-KVCWC7T31tXo6xfXY6AnGsDEl1j7BVwh3eSwoyn4MTS5UbhD5X0rwB8F6Yl1bdxKbrmnqQV98IKLsdgtfuHoVQ=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.11.0-obsidian-16.tgz"
"version" "0.11.0-obsidian-16"
"@zsviczian/excalidraw@0.11.0-obsidian-20":
"integrity" "sha512-9r/F1q1K/uUFrDeJ81MrzD85deB77HioNkdVRU05IeAUSk7cSImvbKrtrCYefdx2jyICb5j/dY9/PKH8G/hbrA=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.11.0-obsidian-20.tgz"
"version" "0.11.0-obsidian-20"
dependencies:
"dotenv" "10.0.0"