mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e000c6bf39 | ||
|
|
feb5b576cb | ||
|
|
2bd1f68276 | ||
|
|
22d1a9b90a | ||
|
|
00a6a3dcaf | ||
|
|
82061cd126 | ||
|
|
4bdd095ff0 | ||
|
|
f40ad62291 | ||
|
|
de4fc602ca | ||
|
|
f5faec8ac9 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.6.27",
|
||||
"version": "1.6.29",
|
||||
"minAppVersion": "0.12.16",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "1.6.27",
|
||||
"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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1380,7 +1380,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
) => 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.
|
||||
|
||||
@@ -212,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 }>();
|
||||
@@ -374,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 }
|
||||
@@ -431,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.
|
||||
@@ -552,6 +548,7 @@ export class ExcalidrawData {
|
||||
return false;
|
||||
}
|
||||
this.loaded = false;
|
||||
this.selectedElementIds = {};
|
||||
this.compatibilityMode = true;
|
||||
this.file = file;
|
||||
this.textElements = new Map<
|
||||
@@ -638,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 {
|
||||
@@ -692,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);
|
||||
@@ -709,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
|
||||
}
|
||||
@@ -954,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`;
|
||||
@@ -980,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(
|
||||
@@ -1131,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) {
|
||||
@@ -1146,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) {
|
||||
@@ -1347,6 +1356,10 @@ export class ExcalidrawData {
|
||||
});
|
||||
}
|
||||
|
||||
public getFiles(): EmbeddedFile[] {
|
||||
return Object.values(this.files);
|
||||
}
|
||||
|
||||
public getFile(fileId: FileId): EmbeddedFile {
|
||||
return this.files.get(fileId);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -434,10 +436,13 @@ 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) {
|
||||
@@ -485,6 +490,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);
|
||||
@@ -500,7 +508,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"
|
||||
}`,
|
||||
);
|
||||
|
||||
@@ -514,7 +522,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;
|
||||
}
|
||||
@@ -627,6 +637,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
|
||||
@@ -886,28 +903,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() {
|
||||
@@ -1070,14 +1091,14 @@ export default class ExcalidrawView extends TextFileView {
|
||||
errorlog({where: "ExcalidrawView.onunload", fn: this.getHookServer().onViewUnloadHook, error: e});
|
||||
}
|
||||
}
|
||||
this.removeParentMoveObserver();
|
||||
this.removeSlidingPanesListner();
|
||||
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;
|
||||
@@ -1301,6 +1322,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
|
||||
);
|
||||
@@ -1312,12 +1348,142 @@ 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
})
|
||||
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;
|
||||
@@ -1341,9 +1507,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(),
|
||||
@@ -2540,6 +2709,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;
|
||||
}
|
||||
@@ -2658,6 +2837,12 @@ 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.getHookServer().onLinkClickHook) {
|
||||
try {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,26 @@ 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.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.
|
||||
|
||||
37
src/main.ts
37
src/main.ts
@@ -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 {
|
||||
@@ -1414,7 +1414,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 +1425,18 @@ 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(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 +1801,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({
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"1.6.27": "0.12.16",
|
||||
"1.6.29": "0.12.16",
|
||||
"1.4.2": "0.11.13"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user