This commit is contained in:
Zsolt Viczian
2021-09-16 20:39:04 +02:00
parent 8f1ec2acbc
commit 3fb2cbba14
7 changed files with 282 additions and 141 deletions

View File

@@ -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<void>;
createSVG (templatePath?:string):Promise<SVGSVGElement>;
createPNG (templatePath?:string):Promise<any>;
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<boolean>;
};
}
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<void>;
createSVG(templatePath?:string):Promise<SVGSVGElement>;
createPNG(templatePath?:string):Promise<any>;
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<boolean>;
};
```

View File

@@ -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.

View File

@@ -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",

View File

@@ -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<void>;
createSVG(templatePath?:string):Promise<SVGSVGElement>;
createPNG(templatePath?:string):Promise<any>;
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<void>;
createSVG (templatePath?:string):Promise<SVGSVGElement>;
createPNG (templatePath?:string):Promise<any>;
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<boolean>;
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<boolean>;
};
}
@@ -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) => {

View File

@@ -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<headings.length;i++) {
if(startPos && !endPos) {
endPos = headings[i].node.position.start.offset-1;
return [contents.substr(startPos,endPos-startPos),lineNum];
}
if(!startPos && headings[i].node.children[0]?.value == id) {
startPos = headings[i].node.children[0]?.position.start.offset; //
lineNum = headings[i].node.children[0]?.position.start.line; //
}
}
if(startPos) return [contents.substr(startPos),lineNum];
return [text,0];
}
}
/**
* Process aliases and block embeds
* @param text
* @returns
*/
private async parse(text:string):Promise<string>{
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<headings.length;i++) {
if(startPos && !endPos) {
endPos = headings[i].node.position.start.offset-1;
return contents.substr(startPos,endPos-startPos)
}
if(!startPos && headings[i].node.children[0]?.value == id) startPos = headings[i].node.children[0]?.position.start.offset; //
}
if(startPos) return contents.substr(startPos);
return text;
}
}
let outString = "";
let position = 0;
const res = text.matchAll(REGEX_LINK.EXPR);
@@ -325,8 +330,9 @@ export class ExcalidrawData {
let parts;
while(!(parts=res.next()).done) {
if (REGEX_LINK.isTransclusion(parts)) { //transclusion //parts.value[1] || parts.value[4]
const [contents,lineNum] = await this.getTransclusion(REGEX_LINK.getLink(parts));
outString += text.substring(position,parts.value.index) +
wrapText(await getTransclusion(REGEX_LINK.getLink(parts)),REGEX_LINK.getWrapLength(parts),this.plugin.settings.forceWrap);
wrapText(contents,REGEX_LINK.getWrapLength(parts),this.plugin.settings.forceWrap);
} else {
const parsedLink = this.parseLinks(text,position,parts);
if(parsedLink) {

View File

@@ -193,7 +193,7 @@ export default class ExcalidrawView extends TextFileView {
return this.data;
}
handleLinkClick(view: ExcalidrawView, ev:MouseEvent) {
async handleLinkClick(view: ExcalidrawView, ev:MouseEvent) {
let text:string = (this.textMode == TextMode.parsed)
? this.excalidrawData.getRawText(this.getSelectedTextElement().id)
: this.getSelectedTextElement().text;
@@ -231,24 +231,33 @@ export default class ExcalidrawView extends TextFileView {
return;
}
if(text.search("#")>-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);

View File

@@ -1,3 +1,3 @@
{
"1.3.7": "0.11.13"
"1.3.8": "0.11.13"
}