diff --git a/docs/API/attributes_functions_overview.md b/docs/API/attributes_functions_overview.md index 292d58c..36aa1b5 100644 --- a/docs/API/attributes_functions_overview.md +++ b/docs/API/attributes_functions_overview.md @@ -3,59 +3,115 @@ Here's the interface implemented by ExcalidrawAutomate: ```typescript -ExcalidrawAutomate: { -plugin: ExcalidrawPlugin; -elementsDict: {}; -style: { - strokeColor: string; - backgroundColor: string; - angle: number; - fillStyle: FillStyle; - strokeWidth: number; - storkeStyle: StrokeStyle; - roughness: number; - opacity: number; - strokeSharpness: StrokeSharpness; - fontFamily: number; - fontSize: number; - textAlign: string; - verticalAlign: string; - startArrowHead: string; - endArrowHead: string; +export interface ExcalidrawAutomate extends Window { + ExcalidrawAutomate: { + plugin: ExcalidrawPlugin; + elementsDict: {}; + style: { + strokeColor: string; + backgroundColor: string; + angle: number; + fillStyle: FillStyle; + strokeWidth: number; + storkeStyle: StrokeStyle; + roughness: number; + opacity: number; + strokeSharpness: StrokeSharpness; + fontFamily: number; + fontSize: number; + textAlign: string; + verticalAlign: string; + startArrowHead: string; + endArrowHead: string; + } + canvas: { + theme: string, + viewBackgroundColor: string, + gridSize: number + }; + setFillStyle (val:number): void; + setStrokeStyle (val:number): void; + setStrokeSharpness (val:number): void; + setFontFamily (val:number): void; + setTheme (val:number): void; + addToGroup (objectIds:[]):string; + toClipboard (templatePath?:string): void; + getElements ():ExcalidrawElement[]; + getElement (id:string):ExcalidrawElement; + create ( + params?: { + filename?: string, + foldername?:string, + templatePath?:string, + onNewPane?: boolean + } + ):Promise; + createSVG (templatePath?:string):Promise; + createPNG (templatePath?:string):Promise; + wrapText (text:string, lineLen:number):string; + addRect (topX:number, topY:number, width:number, height:number):string; + addDiamond (topX:number, topY:number, width:number, height:number):string; + addEllipse (topX:number, topY:number, width:number, height:number):string; + addBlob (topX:number, topY:number, width:number, height:number):string; + addText ( + topX:number, + topY:number, + text:string, + formatting?: { + wrapAt?:number, + width?:number, + height?:number, + textAlign?: string, + box?: "box"|"blob"|"ellipse"|"diamond", + boxPadding?: number + }, + id?:string + ):string; + addLine(points: [[x:number,y:number]]):string; + addArrow ( + points: [[x:number,y:number]], + formatting?: { + startArrowHead?:string, + endArrowHead?:string, + startObjectId?:string, + endObjectId?:string + } + ):string ; + connectObjects ( + objectA: string, + connectionA: ConnectionPoint, + objectB: string, + connectionB: ConnectionPoint, + formatting?: { + numberOfPoints?: number, + startArrowHead?:string, + endArrowHead?:string, + padding?: number + } + ):void; + clear (): void; + reset (): void; + isExcalidrawFile (f:TFile): boolean; + //view manipulation + targetView: ExcalidrawView; + setView (view:ExcalidrawView|"first"|"active"):ExcalidrawView; + getExcalidrawAPI ():any; + getViewSelectedElement( ):ExcalidrawElement; + getViewSelectedElements ():ExcalidrawElement[]; + viewToggleFullScreen (forceViewMode?:boolean):void; + connectObjectWithViewSelectedElement ( + objectA:string, + connectionA: ConnectionPoint, + connectionB: ConnectionPoint, + formatting?: { + numberOfPoints?: number, + startArrowHead?:string, + endArrowHead?:string, + padding?: number + } + ):boolean; + addElementsToView (repositionToCursor:boolean, save:boolean):Promise; + }; } -canvas: {theme: string, viewBackgroundColor: string, gridSize: number}; -setFillStyle(val:number): void; -setStrokeStyle(val:number): void; -setStrokeSharpness(val:number): void; -setFontFamily(val:number): void; -setTheme(val:number): void; -addToGroup(objectIds:[]):string; -toClipboard(templatePath?:string): void; -getElements():ExcalidrawElement[]; -getElement(id:string):ExcalidrawElement; -create(params?:{filename?: string, foldername?:string, templatePath?:string, onNewPane?: boolean}):Promise; -createSVG(templatePath?:string):Promise; -createPNG(templatePath?:string):Promise; -wrapText(text:string, lineLen:number):string; -addRect(topX:number, topY:number, width:number, height:number):string; -addDiamond(topX:number, topY:number, width:number, height:number):string; -addEllipse(topX:number, topY:number, width:number, height:number):string; -addText(topX:number, topY:number, text:string, formatting?:{width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string; -addLine(points: [[x:number,y:number]]):string; -addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead?:string,endArrowHead?:string,startObjectId?:string,endObjectId?:string}):string; -connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):void; -clear(): void; -reset(): void; -isExcalidrawFile(f:TFile): boolean; -//view manipulation -targetView: ExcalidrawView; -setView(view:ExcalidrawView|"first"|"active"):ExcalidrawView; -getExcalidrawAPI():any; -getViewSelectedElement():ExcalidrawElement; -getViewSelectedElements():ExcalidrawElement[]; -viewToggleFullScreen(forceViewMode?:boolean):void; -connectObjectWithViewSelectedElement(objectA:string,connectionA: ConnectionPoint, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):boolean; -addElementsToView(repositionToCursor:boolean, save:boolean):Promise; -}; ``` diff --git a/docs/API/objects.md b/docs/API/objects.md index a0f66e3..e0f3531 100644 --- a/docs/API/objects.md +++ b/docs/API/objects.md @@ -14,7 +14,20 @@ Returns the `id` of the object. The `id` is required when connecting objects wit ### addText() ```typescript -addText(topX:number, topY:number, text:string, formatting?:{width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string; +addText( + topX:number, + topY:number, + text:string, + formatting?:{ + wrapAt?:number, + width?:number, + height?:number, + textAlign?:string, + box?: "box"|"blob"|"ellipse"|"diamond", + boxPadding?:number + }, + id?:string +):string ``` Adds text to the drawing. @@ -22,9 +35,9 @@ Adds text to the drawing. Formatting parameters are optional: - If `width` and `height` are not specified, the function will calculate the width and height based on the fontFamily, the fontSize and the text provided. - In case you want to position a text in the center compared to other elements on the drawing, you can provide a fixed height and width, and you can also specify `textAlign` and `verticalAlign` as described above. e.g.: `{width:500, textAlign:"center"}` -- If you want to add a box around the text, set `{box:true}` +- If you want to add a box around the text, set `{box:"box"|"blob"|"ellipse"|"diamond"}` -Returns the `id` of the object. The `id` is required when connecting objects with lines. See later. If `{box:true}` then returns the id of the enclosing box. +Returns the `id` of the object. The `id` is required when connecting objects with lines. See later. If `{box:}` then returns the id of the enclosing box object. ### addLine() ```typescript @@ -52,7 +65,7 @@ Returns the `id` of the object. declare type ConnectionPoint = "top"|"bottom"|"left"|"right"; connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints: number,startArrowHead:string,endArrowHead:string, padding: number}):void ``` -Connects two objects with an arrow. +Connects two objects with an arrow. Will do nothing if either of the two elements is of type `line`, `arrow`, or `freedraw`. `objectA` and `objectB` are strings. These are the ids of the objects to connect. These IDs are returned by addRect(), addDiamond(), addEllipse() and addText() when creating those objects. diff --git a/manifest.json b/manifest.json index 335d0f2..4dfc342 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "1.3.7", + "version": "1.3.8", "minAppVersion": "0.12.0", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index 731ae85..c7f8b44 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -42,40 +42,93 @@ export interface ExcalidrawAutomate extends Window { startArrowHead: string; endArrowHead: string; } - canvas: {theme: string, viewBackgroundColor: string, gridSize: number}; - setFillStyle(val:number): void; - setStrokeStyle(val:number): void; - setStrokeSharpness(val:number): void; - setFontFamily(val:number): void; - setTheme(val:number): void; - addToGroup(objectIds:[]):string; - toClipboard(templatePath?:string): void; - getElements():ExcalidrawElement[]; - getElement(id:string):ExcalidrawElement; - create(params?:{filename?: string, foldername?:string, templatePath?:string, onNewPane?: boolean}):Promise; - createSVG(templatePath?:string):Promise; - createPNG(templatePath?:string):Promise; - wrapText(text:string, lineLen:number):string; - addRect(topX:number, topY:number, width:number, height:number):string; - addDiamond(topX:number, topY:number, width:number, height:number):string; - addEllipse(topX:number, topY:number, width:number, height:number):string; - addBlob(topX:number, topY:number, width:number, height:number):string; - addText(topX:number, topY:number, text:string, formatting?:{wrapAt?:number, width?:number, height?:number,textAlign?: string, verticalAlign?:string, box?: boolean, boxPadding?: number},id?:string):string; + canvas: { + theme: string, + viewBackgroundColor: string, + gridSize: number + }; + setFillStyle (val:number): void; + setStrokeStyle (val:number): void; + setStrokeSharpness (val:number): void; + setFontFamily (val:number): void; + setTheme (val:number): void; + addToGroup (objectIds:[]):string; + toClipboard (templatePath?:string): void; + getElements ():ExcalidrawElement[]; + getElement (id:string):ExcalidrawElement; + create ( + params?: { + filename?: string, + foldername?:string, + templatePath?:string, + onNewPane?: boolean + } + ):Promise; + createSVG (templatePath?:string):Promise; + createPNG (templatePath?:string):Promise; + wrapText (text:string, lineLen:number):string; + addRect (topX:number, topY:number, width:number, height:number):string; + addDiamond (topX:number, topY:number, width:number, height:number):string; + addEllipse (topX:number, topY:number, width:number, height:number):string; + addBlob (topX:number, topY:number, width:number, height:number):string; + addText ( + topX:number, + topY:number, + text:string, + formatting?: { + wrapAt?:number, + width?:number, + height?:number, + textAlign?: string, + box?: boolean|"box"|"blob"|"ellipse"|"diamond", + boxPadding?: number + }, + id?:string + ):string; addLine(points: [[x:number,y:number]]):string; - addArrow(points: [[x:number,y:number]],formatting?:{startArrowHead?:string,endArrowHead?:string,startObjectId?:string,endObjectId?:string}):string ; - connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):void; - clear(): void; - reset(): void; - isExcalidrawFile(f:TFile): boolean; + addArrow ( + points: [[x:number,y:number]], + formatting?: { + startArrowHead?:string, + endArrowHead?:string, + startObjectId?:string, + endObjectId?:string + } + ):string ; + connectObjects ( + objectA: string, + connectionA: ConnectionPoint, + objectB: string, + connectionB: ConnectionPoint, + formatting?: { + numberOfPoints?: number, + startArrowHead?:string, + endArrowHead?:string, + padding?: number + } + ):void; + clear (): void; + reset (): void; + isExcalidrawFile (f:TFile): boolean; //view manipulation targetView: ExcalidrawView; - setView(view:ExcalidrawView|"first"|"active"):ExcalidrawView; - getExcalidrawAPI():any; - getViewSelectedElement():ExcalidrawElement; - getViewSelectedElements():ExcalidrawElement[]; - viewToggleFullScreen(forceViewMode?:boolean):void; - connectObjectWithViewSelectedElement(objectA:string,connectionA: ConnectionPoint, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):boolean; - addElementsToView(repositionToCursor:boolean, save:boolean):Promise; + setView (view:ExcalidrawView|"first"|"active"):ExcalidrawView; + getExcalidrawAPI ():any; + getViewSelectedElement( ):ExcalidrawElement; + getViewSelectedElements ():ExcalidrawElement[]; + viewToggleFullScreen (forceViewMode?:boolean):void; + connectObjectWithViewSelectedElement ( + objectA:string, + connectionA: ConnectionPoint, + connectionB: ConnectionPoint, + formatting?: { + numberOfPoints?: number, + startArrowHead?:string, + endArrowHead?:string, + padding?: number + } + ):boolean; + addElementsToView (repositionToCursor:boolean, save:boolean):Promise; }; } @@ -332,7 +385,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { width?:number, height?:number, textAlign?:string, - verticalAlign?:string, box?: boolean|"box"|"blob"|"ellipse"|"diamond", boxPadding?:number }, @@ -348,9 +400,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { const boxPadding = formatting?.boxPadding ?? 10; if(formatting?.box) { switch(formatting?.box) { - case true || "box": - boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding); - break; case "ellipse": boxId = this.addEllipse(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding); break; @@ -360,6 +409,8 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { case "blob": boxId = this.addBlob(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding); break; + default: + boxId = this.addRect(topX-boxPadding,topY-boxPadding,width+2*boxPadding,height+2*boxPadding); } } this.elementsDict[id] = { @@ -367,7 +418,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { fontSize: window.ExcalidrawAutomate.style.fontSize, fontFamily: window.ExcalidrawAutomate.style.fontFamily, textAlign: formatting?.textAlign ? formatting.textAlign : window.ExcalidrawAutomate.style.textAlign, - verticalAlign: formatting?.verticalAlign ? formatting.verticalAlign : window.ExcalidrawAutomate.style.verticalAlign, + verticalAlign: window.ExcalidrawAutomate.style.verticalAlign, baseline: baseline, ... boxedElement(id,"text",topX,topY,width,height) }; @@ -416,6 +467,12 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) { if(!(this.elementsDict[objectA] && this.elementsDict[objectB])) { return; } + + if(["line","arrow","freedraw"].includes(this.elementsDict[objectA].type) || + ["line","arrow","freedraw"].includes(this.elementsDict[objectB].type)) { + return; + } + const padding = formatting?.padding ? formatting.padding : 10; const numberOfPoints = formatting?.numberOfPoints ? formatting.numberOfPoints : 0; const getSidePoints = (side:string, el:any) => { diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 3c99730..24231c3 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -275,48 +275,53 @@ export class ExcalidrawData { (this.showLinkBrackets ? "]]" : ""); } + public async getTransclusion (text:string):Promise<[string,number]> { + //file-name#^blockref + //1 2 3 + const REG_FILE_BLOCKREF = /(.*)#(\^)?(.*)/g; + const parts=text.matchAll(REG_FILE_BLOCKREF).next(); + if(parts.done || !parts.value[1] || !parts.value[3]) return [text,0]; //filename and/or blockref not found + const file = this.app.metadataCache.getFirstLinkpathDest(parts.value[1],this.file.path); + const contents = await this.app.vault.cachedRead(file); + const isParagraphRef = parts.value[2] ? true : false; //does the reference contain a ^ character? + const id = parts.value[3]; //the block ID or heading text + const blocks = (await this.app.metadataCache.blockCache.getForFile({isCancelled: ()=>false},file)).blocks.filter((block:any)=>block.node.type!="comment"); + if(!blocks) return [text,0]; + if(isParagraphRef) { + let para = blocks.filter((block:any)=>block.node.id == id)[0]?.node; + if(!para) return [text,0]; + if(["blockquote","listItem"].includes(para.type)) para = para.children[0]; //blockquotes are special, they have one child, which has the paragraph + const startPos = para.position.start.offset; + const lineNum = para.position.start.line; + const endPos = para.children[para.children.length-1]?.position.start.offset-1; //alternative: filter((c:any)=>c.type=="blockid")[0] + return [contents.substr(startPos,endPos-startPos),lineNum] + + } else { + const headings = blocks.filter((block:any)=>block.display.startsWith("#")); + let startPos:number = null; + let lineNum:number = 0; + let endPos:number = null; + for(let i=0;i{ - const getTransclusion = async (text:string) => { - //file-name#^blockref - //1 2 3 - const REG_FILE_BLOCKREF = /(.*)#(\^)?(.*)/g; - const parts=text.matchAll(REG_FILE_BLOCKREF).next(); - if(parts.done || !parts.value[1] || !parts.value[3]) return text; //filename and/or blockref not found - const file = this.app.metadataCache.getFirstLinkpathDest(parts.value[1],this.file.path); - const contents = await this.app.vault.cachedRead(file); - const isParagraphRef = parts.value[2] ? true : false; //does the reference contain a ^ character? - const id = parts.value[3]; //the block ID or heading text - const blocks = (await this.app.metadataCache.blockCache.getForFile({isCancelled: ()=>false},file)).blocks.filter((block:any)=>block.node.type!="comment"); - if(!blocks) return text; - if(isParagraphRef) { - let para = blocks.filter((block:any)=>block.node.id == id)[0]?.node; - if(!para) return text; - if(["blockquote","listItem"].includes(para.type)) para = para.children[0]; //blockquotes are special, they have one child, which has the paragraph - const startPos = para.position.start.offset; - const endPos = para.children[para.children.length-1]?.position.start.offset-1; //alternative: filter((c:any)=>c.type=="blockid")[0] - return contents.substr(startPos,endPos-startPos) - - } else { - const headings = blocks.filter((block:any)=>block.display.startsWith("#")); - let startPos:number = null; - let endPos:number = null; - for(let i=0;i-1) text = text.substring(0,text.search("#")); + let lineNum = null; + if(text.search("#")>-1) { + let t; + [t,lineNum] = await this.excalidrawData.getTransclusion(text); + text = text.substring(0,text.search("#")); + } if(text.match(REG_LINKINDEX_INVALIDCHARS)) { new Notice(t("FILENAME_INVALID_CHARS"),4000); return; } - if (!ev.altKey) { - const file = view.app.metadataCache.getFirstLinkpathDest(text,view.file.path); - if (!file) { - new Notice(t("FILE_DOES_NOT_EXIST"), 4000); - return; - } + const file = view.app.metadataCache.getFirstLinkpathDest(text,view.file.path); + if (!ev.altKey && !file) { + new Notice(t("FILE_DOES_NOT_EXIST"), 4000); + return; } + try { const f = view.file; - if(ev.shiftKey) { + if(ev.shiftKey && document.fullscreenElement === this.contentEl) { document.exitFullscreen(); this.zoomToFit(); } + if(lineNum) { + const leaf = ev.shiftKey ? view.app.workspace.createLeafBySplit(view.leaf) : view.leaf; + leaf.openFile(file,{eState: {line: lineNum-1}}); + return; + } view.app.workspace.openLinkText(text,view.file.path,ev.shiftKey); } catch (e) { new Notice(e,4000); diff --git a/versions.json b/versions.json index d19c7c9..f1798aa 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.3.7": "0.11.13" + "1.3.8": "0.11.13" }