mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
13 Commits
ImageEleme
...
1.3.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a57db43f2 | ||
|
|
fed106c811 | ||
|
|
7ebdec7713 | ||
|
|
1917dad8cd | ||
|
|
3bbff7f8d5 | ||
|
|
034927ada0 | ||
|
|
0cccdad13f | ||
|
|
fe7f3f58c5 | ||
|
|
23da271b73 | ||
|
|
59db43c3f0 | ||
|
|
597ee4f70e | ||
|
|
8222d8c146 | ||
|
|
f785d756be |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.3.16",
|
||||
"version": "1.3.20",
|
||||
"minAppVersion": "0.12.0",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
20
package.json
20
package.json
@@ -11,7 +11,7 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zsviczian/excalidraw": "0.9.0-obsidian-image-support-3",
|
||||
"@zsviczian/excalidraw": "0.9.0-obsidian-13",
|
||||
"monkey-around": "^2.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -19,22 +19,22 @@
|
||||
"roughjs": "4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^15.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/react-dom": "^17.0.8",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"cross-env": "^7.0.3",
|
||||
"nanoid": "^3.1.23",
|
||||
"obsidian": "^0.12.16",
|
||||
"rollup": "^2.52.3",
|
||||
"rollup-plugin-visualizer": "^5.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "^4.3.4"
|
||||
"rollup-plugin-visualizer": "^5.5.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ import {
|
||||
FRONTMATTER,
|
||||
nanoid,
|
||||
JSON_parse,
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
MAX_IMAGE_SIZE
|
||||
VIEW_TYPE_EXCALIDRAW
|
||||
} from "./constants";
|
||||
import { getObsidianImage, wrapText } from "./Utils";
|
||||
import { wrapText } from "./Utils";
|
||||
import { AppState } from "@zsviczian/excalidraw/types/types";
|
||||
|
||||
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
|
||||
@@ -27,7 +26,6 @@ export interface ExcalidrawAutomate extends Window {
|
||||
ExcalidrawAutomate: {
|
||||
plugin: ExcalidrawPlugin;
|
||||
elementsDict: {};
|
||||
imagesDict: {};
|
||||
style: {
|
||||
strokeColor: string;
|
||||
backgroundColor: string;
|
||||
@@ -104,7 +102,6 @@ export interface ExcalidrawAutomate extends Window {
|
||||
endObjectId?:string
|
||||
}
|
||||
):string ;
|
||||
addImage(topX:number, topY:number, imageFile: TFile):Promise<string>;
|
||||
connectObjects (
|
||||
objectA: string,
|
||||
connectionA: ConnectionPoint,
|
||||
@@ -163,7 +160,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
window.ExcalidrawAutomate = {
|
||||
plugin: plugin,
|
||||
elementsDict: {},
|
||||
imagesDict: {},
|
||||
style: {
|
||||
strokeColor: "#000000",
|
||||
backgroundColor: "transparent",
|
||||
@@ -330,7 +326,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
currentItemLinearStrokeSharpness: template? template.appState.currentItemLinearStrokeSharpness : this.style.strokeSharpness,
|
||||
gridSize: template ? template.appState.gridSize : this.canvas.gridSize
|
||||
}
|
||||
}))
|
||||
},null,"\t"))
|
||||
);
|
||||
},
|
||||
async createSVG(templatePath?:string):Promise<SVGSVGElement> {
|
||||
@@ -517,25 +513,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
}
|
||||
return id;
|
||||
},
|
||||
async addImage(topX:number, topY:number, imageFile: TFile):Promise<string> {
|
||||
const id = nanoid();
|
||||
const image = await getObsidianImage(this.plugin.app,imageFile);
|
||||
if(!image) return null;
|
||||
this.imagesDict[image.imageId] = {
|
||||
type:"image",
|
||||
id: image.imageId,
|
||||
dataURL: image.dataURL
|
||||
}
|
||||
if (Math.max(image.size.width,image.size.height) > MAX_IMAGE_SIZE) {
|
||||
const scale = MAX_IMAGE_SIZE/Math.max(image.size.width,image.size.height);
|
||||
image.size.width = scale*image.size.width;
|
||||
image.size.height = scale*image.size.height;
|
||||
}
|
||||
this.elementsDict[id] = boxedElement(id,"image",topX,topY,image.size.width,image.size.height);
|
||||
this.elementsDict[id].imageId = image.imageId;
|
||||
this.elementsDict[id].scale = [1,1];
|
||||
return id;
|
||||
},
|
||||
connectObjects(objectA: string, connectionA: ConnectionPoint, objectB: string, connectionB: ConnectionPoint, formatting?:{numberOfPoints?: number,startArrowHead?:string,endArrowHead?:string, padding?: number}):void {
|
||||
if(!(this.elementsDict[objectA] && this.elementsDict[objectB])) {
|
||||
return;
|
||||
@@ -575,7 +552,6 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
},
|
||||
clear() {
|
||||
this.elementsDict = {};
|
||||
this.imagesDict = {};
|
||||
},
|
||||
reset() {
|
||||
this.clear();
|
||||
@@ -706,7 +682,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
return false;
|
||||
}
|
||||
const elements = this.getElements();
|
||||
return await this.targetView.addElements(elements,repositionToCursor,save,this.imagesDict);
|
||||
return await this.targetView.addElements(elements,repositionToCursor,save);
|
||||
},
|
||||
onDropHook:null,
|
||||
};
|
||||
|
||||
@@ -25,7 +25,10 @@ declare module "obsidian" {
|
||||
export const REGEX_LINK = {
|
||||
//![[link|alias]] [alias](link){num}
|
||||
// 1 2 3 4 5 6 7 8 9
|
||||
EXPR: /(!)?(\[\[([^|\]]+)\|?(.+)?]]|\[(.*)\]\((.*)\))(\{(\d+)\})?/g,
|
||||
EXPR: /(!)?(\[\[([^|\]]+)\|?([^\]]+)?]]|\[([^\]]*)]\(([^)]*)\))(\{(\d+)\})?/g, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
getRes: (text:string):IterableIterator<RegExpMatchArray> => {
|
||||
return text.matchAll(REGEX_LINK.EXPR);
|
||||
},
|
||||
isTransclusion: (parts: IteratorResult<RegExpMatchArray, any>):boolean => {
|
||||
return parts.value[1] ? true:false;
|
||||
},
|
||||
@@ -48,7 +51,7 @@ export const REGEX_LINK = {
|
||||
|
||||
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
|
||||
|
||||
const DRAWING_REG = /\n%%\n# Drawing\n(```json\n)(.*)\n```%%/gm;
|
||||
const DRAWING_REG = /\n%%\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
export function getJSON(data:string):[string,number] {
|
||||
let res = data.matchAll(DRAWING_REG);
|
||||
@@ -136,6 +139,9 @@ export class ExcalidrawData {
|
||||
}
|
||||
position += data.match(/((^%%\n)?# Text Elements\n)/m)[0].length
|
||||
|
||||
data = data.substring(position);
|
||||
position = 0;
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN:number = " ^12345678\n\n".length;
|
||||
@@ -347,7 +353,7 @@ export class ExcalidrawData {
|
||||
private async parse(text:string):Promise<string>{
|
||||
let outString = "";
|
||||
let position = 0;
|
||||
const res = text.matchAll(REGEX_LINK.EXPR);
|
||||
const res = REGEX_LINK.getRes(text);
|
||||
let linkIcon = false;
|
||||
let urlIcon = false;
|
||||
let parts;
|
||||
@@ -387,7 +393,7 @@ export class ExcalidrawData {
|
||||
*/
|
||||
private quickParse(text:string):string {
|
||||
const hasTransclusion = (text:string):boolean => {
|
||||
const res = text.matchAll(REGEX_LINK.EXPR);
|
||||
const res = REGEX_LINK.getRes(text);
|
||||
let parts;
|
||||
while(!(parts=res.next()).done) {
|
||||
if (REGEX_LINK.isTransclusion(parts)) return true;
|
||||
@@ -398,7 +404,7 @@ export class ExcalidrawData {
|
||||
|
||||
let outString = "";
|
||||
let position = 0;
|
||||
const res = text.matchAll(REGEX_LINK.EXPR);
|
||||
const res = REGEX_LINK.getRes(text);
|
||||
let linkIcon = false;
|
||||
let urlIcon = false;
|
||||
let parts;
|
||||
@@ -432,7 +438,7 @@ export class ExcalidrawData {
|
||||
for(const key of this.textElements.keys()){
|
||||
outString += this.textElements.get(key).raw+' ^'+key+'\n\n';
|
||||
}
|
||||
return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene));
|
||||
return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene,null,"\t"));
|
||||
}
|
||||
|
||||
public async syncElements(newScene:any):Promise<boolean> {
|
||||
|
||||
@@ -28,14 +28,13 @@ import {
|
||||
TEXT_DISPLAY_RAW_ICON_NAME,
|
||||
TEXT_DISPLAY_PARSED_ICON_NAME,
|
||||
FULLSCREEN_ICON_NAME,
|
||||
JSON_parse,
|
||||
IMAGE_TYPES
|
||||
JSON_parse
|
||||
} from './constants';
|
||||
import ExcalidrawPlugin from './main';
|
||||
import {estimateBounds, ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
|
||||
import { t } from "./lang/helpers";
|
||||
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
|
||||
import { checkAndCreateFolder, download, getNewUniqueFilepath, splitFolderAndFilename, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { checkAndCreateFolder, download, getNewOrAdjacentLeaf, getNewUniqueFilepath, rotatedDimensions, splitFolderAndFilename, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { Prompt } from "./Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
|
||||
@@ -97,8 +96,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
const filepath = this.file.path.substring(0,this.file.path.lastIndexOf('.md')) + '.excalidraw';
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
if(file && file instanceof TFile) this.app.vault.modify(file,JSON.stringify(scene));
|
||||
else this.app.vault.create(filepath,JSON.stringify(scene));
|
||||
if(file && file instanceof TFile) this.app.vault.modify(file,JSON.stringify(scene,null,"\t"));
|
||||
else this.app.vault.create(filepath,JSON.stringify(scene,null,"\t"));
|
||||
}
|
||||
|
||||
public saveSVG(scene?: any) {
|
||||
@@ -198,7 +197,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(this.plugin.settings.autoexportSVG) this.saveSVG(scene);
|
||||
if(this.plugin.settings.autoexportPNG) this.savePNG(scene);
|
||||
}
|
||||
return JSON.stringify(scene);
|
||||
return JSON.stringify(scene,null,"\t");
|
||||
}
|
||||
return this.data;
|
||||
}
|
||||
@@ -211,12 +210,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"),20000);
|
||||
return;
|
||||
}
|
||||
text = text.replaceAll("\n",""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(text,"_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = text.matchAll(REGEX_LINK.EXPR).next();
|
||||
const parts = REGEX_LINK.getRes(text).next();
|
||||
if(!parts.value) {
|
||||
const tags = text.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/ug).next();
|
||||
if(!tags.value || tags.value.length<2) {
|
||||
@@ -228,7 +228,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//@ts-ignore
|
||||
search[0].view.setQuery("tag:"+tags.value[1]);
|
||||
this.app.workspace.revealLeaf(search[0]);
|
||||
//if(this.gotoFullscreen.style.display=="none") this.toggleFullscreen();
|
||||
if(document.fullscreenElement === this.contentEl) {
|
||||
document.exitFullscreen();
|
||||
this.zoomToFit();
|
||||
@@ -236,7 +235,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
|
||||
text = REGEX_LINK.getLink(parts); //parts.value[2] ? parts.value[2]:parts.value[6];
|
||||
text = REGEX_LINK.getLink(parts);
|
||||
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(text,"_blank");
|
||||
@@ -265,12 +264,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
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);
|
||||
const leaf = ev.shiftKey ? getNewOrAdjacentLeaf(this.plugin,view.leaf) : view.leaf;
|
||||
view.app.workspace.setActiveLeaf(leaf);
|
||||
leaf.view.app.workspace.openLinkText(text,view.file.path);
|
||||
} catch (e) {
|
||||
new Notice(e,4000);
|
||||
}
|
||||
@@ -460,12 +456,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
setMarkdownView() {
|
||||
if(this.excalidrawRef) {
|
||||
const el = this.excalidrawRef.current.getSceneElements();
|
||||
if(el.filter((e:any)=>e.type==="image").length>0) {
|
||||
new Notice(t("DRAWING_CONTAINS_IMAGE"),6000);
|
||||
}
|
||||
}
|
||||
this.plugin.excalidrawFileModes[this.id || this.file.path] = "markdown";
|
||||
this.plugin.setMarkdownView(this.leaf);
|
||||
}
|
||||
@@ -497,12 +487,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const folderpath = splitFolderAndFilename(this.file.path).folderpath;
|
||||
await checkAndCreateFolder(this.app.vault,folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(this.app.vault,filename,folderpath);
|
||||
this.app.vault.create(fname,JSON.stringify(this.getScene()));
|
||||
this.app.vault.create(fname,JSON.stringify(this.getScene(),null,"\t"));
|
||||
new Notice("Exported to " + fname,6000);
|
||||
});
|
||||
return;
|
||||
}
|
||||
download('data:text/plain;charset=utf-8',encodeURIComponent(JSON.stringify(this.getScene())), this.file.basename+'.excalidraw');
|
||||
download('data:text/plain;charset=utf-8',encodeURIComponent(JSON.stringify(this.getScene(),null,"\t")), this.file.basename+'.excalidraw');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -647,7 +637,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.addElements(window.ExcalidrawAutomate.getElements(),false,true);
|
||||
}
|
||||
|
||||
this.addElements = async (newElements:ExcalidrawElement[],repositionToCursor:boolean = false, save:boolean=false, images:any):Promise<boolean> => {
|
||||
this.addElements = async (newElements:ExcalidrawElement[],repositionToCursor:boolean = false, save:boolean=false):Promise<boolean> => {
|
||||
if(!excalidrawRef?.current) return false;
|
||||
|
||||
const textElements = newElements.filter((el)=>el.type=="text");
|
||||
@@ -660,20 +650,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
};
|
||||
|
||||
const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements();
|
||||
let st: AppState = excalidrawRef.current.getAppState();
|
||||
if(!st.files) {
|
||||
st.files = {};
|
||||
}
|
||||
if(images) {
|
||||
Object.keys(images).forEach((k)=>{
|
||||
st.files[k]={
|
||||
type:images[k].type,
|
||||
id: images[k].id,
|
||||
dataURL: images[k].dataURL
|
||||
}
|
||||
});
|
||||
}
|
||||
//merge appstate.files with files
|
||||
const st: AppState = excalidrawRef.current.getAppState();
|
||||
if(repositionToCursor) newElements = repositionElementsToCursor(newElements,currentPosition,true);
|
||||
this.excalidrawRef.current.updateScene({
|
||||
elements: el.concat(newElements),
|
||||
@@ -690,13 +667,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements();
|
||||
const st: AppState = excalidrawRef.current.getAppState();
|
||||
|
||||
if(st.files) {
|
||||
const imgIds = el.filter((e)=>e.type=="image").map((e:any)=>e.imageId);
|
||||
const toDelete = Object.keys(st.files).filter((k)=>!imgIds.contains(k));
|
||||
toDelete.forEach((k)=>delete st.files[k]);
|
||||
}
|
||||
|
||||
return {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
@@ -720,7 +690,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
currentItemEndArrowhead: st.currentItemEndArrowhead,
|
||||
currentItemLinearStrokeSharpness: st.currentItemLinearStrokeSharpness,
|
||||
gridSize: st.gridSize,
|
||||
files: st.files??{},
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -738,12 +707,28 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const getTextElementAtPointer = (pointer:any) => {
|
||||
const elements = this.excalidrawRef.current.getSceneElements()
|
||||
.filter((e:ExcalidrawElement)=>{
|
||||
return e.type == "text"
|
||||
&& e.x<=pointer.x && (e.x+e.width)>=pointer.x
|
||||
&& e.y<=pointer.y && (e.y+e.height)>=pointer.y;
|
||||
if (e.type !== "text") return false;
|
||||
const [x,y,w,h] = rotatedDimensions(e);
|
||||
return x<=pointer.x && x+w>=pointer.x
|
||||
&& y<=pointer.y && y+h>=pointer.y;
|
||||
});
|
||||
if(elements.length==0) return null;
|
||||
return {id:elements[0].id,text:elements[0].text};
|
||||
if(elements.length===1) return {id:elements[0].id,text:elements[0].text};
|
||||
//if more than 1 text elements are at the location, look for one that has a link
|
||||
const elementsWithLinks = elements.filter((e:ExcalidrawTextElement)=> {
|
||||
const text:string = (this.textMode == TextMode.parsed)
|
||||
? this.excalidrawData.getRawText(e.id)
|
||||
: e.text;
|
||||
if(!text) return false;
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) return true;
|
||||
const parts = REGEX_LINK.getRes(text).next();
|
||||
if(!parts.value) return false;
|
||||
return true;
|
||||
});
|
||||
//if there are no text elements with links, return the first element without a link
|
||||
if(elementsWithLinks.length==0) return {id:elements[0].id,text:elements[0].text};
|
||||
//if there are still multiple text elements with links on top of each other, return the first
|
||||
return {id:elementsWithLinks[0].id,text:elementsWithLinks[0].text};
|
||||
}
|
||||
|
||||
let hoverPoint = {x:0,y:0};
|
||||
@@ -783,6 +768,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
key: "abc",
|
||||
tabIndex: 0,
|
||||
onKeyDown: (e:any) => {
|
||||
//@ts-ignore
|
||||
if(e.target === excalidrawDiv.ref.current) return; //event should originate from the canvas
|
||||
if(document.fullscreenEnabled && document.fullscreenElement == this.contentEl && e.keyCode==27) {
|
||||
document.exitFullscreen();
|
||||
this.zoomToFit();
|
||||
@@ -802,7 +789,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(!text) return;
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) return;
|
||||
|
||||
const parts = text.matchAll(REGEX_LINK.EXPR).next();
|
||||
const parts = REGEX_LINK.getRes(text).next();
|
||||
if(!parts.value) return;
|
||||
let linktext = REGEX_LINK.getLink(parts); //parts.value[2] ? parts.value[2]:parts.value[6];
|
||||
|
||||
@@ -975,19 +962,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
switch(draggable?.type) {
|
||||
case "file":
|
||||
if (!onDropHook("file",[draggable.file],null)) {
|
||||
if((event.ctrlKey || event.metaKey) && IMAGE_TYPES.contains(draggable.file.extension)) {
|
||||
const f = draggable.file;
|
||||
const topX = currentPosition.x;
|
||||
const topY = currentPosition.y;
|
||||
const ea = window.ExcalidrawAutomate;
|
||||
ea.reset();
|
||||
ea.setView(this);
|
||||
(async () => {
|
||||
await ea.addImage(currentPosition.x,currentPosition.y,draggable.file);
|
||||
ea.addElementsToView(false,false);
|
||||
})();
|
||||
return false;
|
||||
}
|
||||
this.addText(`[[${this.app.metadataCache.fileToLinktext(draggable.file,this.file.path,true)}]]`);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MigrationPrompt extends Modal {
|
||||
|
||||
createForm(): void {
|
||||
const div = this.contentEl.createDiv();
|
||||
div.addClass("excalidarw-prompt-div");
|
||||
div.addClass("excalidraw-prompt-div");
|
||||
div.style.maxWidth = "600px";
|
||||
div.createEl('p',{text: "This version comes with tons of new features and possibilities. Please read the description in Community Plugins to find out more."});
|
||||
div.createEl('p',{text: ""} , (el) => {
|
||||
|
||||
115
src/Utils.ts
115
src/Utils.ts
@@ -1,9 +1,14 @@
|
||||
import { App, normalizePath, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { normalizePath, TAbstractFile, TFolder, Vault, WorkspaceLeaf } from "obsidian";
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { Zoom } from "@zsviczian/excalidraw/types/types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { IMAGE_TYPES } from "./constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
|
||||
declare module "obsidian" {
|
||||
interface Workspace {
|
||||
getAdjacentLeafInDirection(leaf: WorkspaceLeaf, direction: string): WorkspaceLeaf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
|
||||
@@ -105,6 +110,38 @@ export function wrapText(text:string, lineLen:number, forceWrap:boolean=false):s
|
||||
return outstring.replace(/\n$/, '');
|
||||
}
|
||||
|
||||
const rotate = (
|
||||
pointX: number,
|
||||
pointY: number,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
angle: number,
|
||||
): [number, number] =>
|
||||
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
||||
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
||||
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
||||
[
|
||||
(pointX - centerX) * Math.cos(angle) - (pointY - centerY) * Math.sin(angle) + centerX,
|
||||
(pointX - centerX) * Math.sin(angle) + (pointY - centerY) * Math.cos(angle) + centerY,
|
||||
];
|
||||
|
||||
export const rotatedDimensions = (
|
||||
element: ExcalidrawElement
|
||||
): [number, number, number, number] => {
|
||||
if(element.angle===0) [element.x,element.y,element.width,element.height];
|
||||
const centerX = element.x+element.width/2;
|
||||
const centerY = element.y+element.height/2;
|
||||
const [left,top] = rotate(element.x,element.y,centerX,centerY,element.angle);
|
||||
const [right,bottom] = rotate(element.x+element.width,element.y+element.height,centerX,centerY,element.angle);
|
||||
return [
|
||||
left<right ? left : right,
|
||||
top<bottom ? top : bottom,
|
||||
Math.abs(left-right),
|
||||
Math.abs(top-bottom)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
export const viewportCoordsToSceneCoords = (
|
||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||
{
|
||||
@@ -127,64 +164,14 @@ export const viewportCoordsToSceneCoords = (
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
export const getObsidianImage = async (app: App, file: TFile)
|
||||
:Promise<{
|
||||
imageId: string,
|
||||
dataURL: string,
|
||||
size: {height: number, width: number},
|
||||
}> => {
|
||||
if(!app || !file) return null;
|
||||
if (!IMAGE_TYPES.contains(file.extension)) return null;
|
||||
const ab = await app.vault.readBinary(file);
|
||||
return {
|
||||
imageId: await generateIdFromFile(ab),
|
||||
dataURL: file.extension==="svg" ? await getSVGData(app,file) : await getDataURL(ab),
|
||||
size: await getImageSize(app,file)
|
||||
}
|
||||
}
|
||||
|
||||
const getSVGData = async (app: App, file: TFile): Promise<string> => {
|
||||
const svg = await app.vault.read(file);
|
||||
return "data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.replaceAll(" "," "))))
|
||||
}
|
||||
|
||||
const getDataURL = async (file: ArrayBuffer): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const dataURL = reader.result as string;
|
||||
resolve(dataURL);
|
||||
};
|
||||
reader.onerror = (error) => reject(error);
|
||||
reader.readAsDataURL(new Blob([new Uint8Array(file)]));
|
||||
});
|
||||
};
|
||||
|
||||
const generateIdFromFile = async (file: ArrayBuffer):Promise<string> => {
|
||||
let id: string;
|
||||
try {
|
||||
const hashBuffer = await window.crypto.subtle.digest(
|
||||
"SHA-1",
|
||||
file,
|
||||
);
|
||||
id =
|
||||
// convert buffer to byte array
|
||||
Array.from(new Uint8Array(hashBuffer))
|
||||
// convert to hex string
|
||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
id = nanoid(40);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
const getImageSize = async (app: App, file:TFile):Promise<{height:number, width:number}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let img = new Image()
|
||||
img.onload = () => resolve({height: img.height, width:img.width});
|
||||
img.onerror = reject;
|
||||
img.src = app.vault.getResourcePath(file);
|
||||
})
|
||||
}
|
||||
export const getNewOrAdjacentLeaf = (plugin: ExcalidrawPlugin, leaf: WorkspaceLeaf):WorkspaceLeaf => {
|
||||
if(plugin.settings.openInAdjacentPane) {
|
||||
let leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "right");
|
||||
if(!leafToUse){leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "left");}
|
||||
if(!leafToUse){leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "bottom");}
|
||||
if(!leafToUse){leafToUse = plugin.app.workspace.getAdjacentLeafInDirection(leaf, "top");}
|
||||
if(!leafToUse){leafToUse = plugin.app.workspace.createLeafBySplit(leaf);}
|
||||
return leafToUse;
|
||||
}
|
||||
return plugin.app.workspace.createLeafBySplit(leaf);
|
||||
}
|
||||
@@ -3,8 +3,6 @@ export function JSON_parse(x:string):any {return JSON.parse(x.replaceAll("["
|
||||
|
||||
import {customAlphabet} from "nanoid";
|
||||
export const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',8);
|
||||
export const IMAGE_TYPES = ['jpeg', 'jpg', 'png', 'gif', 'svg', 'bmp'];
|
||||
export const MAX_IMAGE_SIZE = 600;
|
||||
export const FRONTMATTER_KEY = "excalidraw-plugin";
|
||||
export const FRONTMATTER_KEY_CUSTOM_PREFIX = "excalidraw-link-prefix";
|
||||
export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix";
|
||||
@@ -15,6 +13,7 @@ export const MAX_COLORS = 5;
|
||||
export const COLOR_FREQ = 6;
|
||||
export const RERENDER_EVENT = "excalidraw-embed-rerender";
|
||||
export const BLANK_DRAWING = '{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}';
|
||||
export const DARK_BLANK_DRAWING = '{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"theme":"dark","gridSize":null,"viewBackgroundColor":"#ffffff"}}';
|
||||
export const FRONTMATTER = ["---","",`${FRONTMATTER_KEY}: parsed`,"","---", "==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==", "",""].join("\n");
|
||||
export const EMPTY_MESSAGE = "Hit enter to create a new drawing";
|
||||
export const TEXT_DISPLAY_PARSED_ICON_NAME = "quote-glyph";
|
||||
|
||||
@@ -1,162 +1,168 @@
|
||||
import { FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, FRONTMATTER_KEY_CUSTOM_PREFIX, FRONTMATTER_KEY_CUSTOM_URL_PREFIX } from "src/constants";
|
||||
|
||||
// English
|
||||
export default {
|
||||
// main.ts
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW : "New Excalidraw drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (Logseq compatibility)",
|
||||
DOWNLOAD_LIBRARY: "Export stencil library as an *.excalidrawlib file",
|
||||
OPEN_EXISTING_NEW_PANE: "Open an existing drawing - IN A NEW PANE",
|
||||
OPEN_EXISTING_ACTIVE_PANE: "Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
TRANSCLUDE: "Transclude (embed) a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Transclude (embed) the most recently edited drawing",
|
||||
NEW_IN_NEW_PANE: "Create a new drawing - IN A NEW PANE",
|
||||
NEW_IN_ACTIVE_PANE: "Create a new drawing - IN THE CURRENT ACTIVE PANE",
|
||||
NEW_IN_NEW_PANE_EMBED: "Create a new drawing - IN A NEW PANE - and embed into active document",
|
||||
NEW_IN_ACTIVE_PANE_EMBED: "Create a new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document",
|
||||
EXPORT_SVG: "Save as SVG next to the current file",
|
||||
EXPORT_PNG: "Save as PNG next to the current file",
|
||||
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
|
||||
INSERT_LINK: "Insert link to file",
|
||||
INSERT_LATEX: "Insert LaTeX-symbol (e.g. $\\theta$)",
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
OPEN_AS_MD: "Open as Markdown",
|
||||
SAVE_AS_PNG: "Save as PNG into Vault (CTRL/META+CLICK to export)",
|
||||
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/META+CLICK to export)",
|
||||
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
|
||||
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
|
||||
LINK_BUTTON_CLICK_NO_TEXT: 'Select a Text Element containing an internal or external link.\n'+
|
||||
'SHIFT CLICK this button to open the link in a new pane.\n'+
|
||||
'CTRL/META CLICK the Text Element on the canvas has the same effect!',
|
||||
TEXT_ELEMENT_EMPTY: "Text Element is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
|
||||
FILENAME_INVALID_CHARS: 'File name cannot contain any of the following characters: * " \\ < > : | ?',
|
||||
FILE_DOES_NOT_EXIST: "File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
|
||||
FORCE_SAVE: "Force-save to update transclusions in adjacent panes.\n(Please note, that autosave is always on)",
|
||||
RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)",
|
||||
PARSED: "Change to RAW mode (only effects text-elements with links or transclusions)",
|
||||
NOFILE: "Excalidraw (no file)",
|
||||
COMPATIBILITY_MODE: "*.excalidraw file opened in compatibility mode. Convert to new format for full plugin functionality.",
|
||||
CONVERT_FILE: "Convert to new format",
|
||||
DRAWING_CONTAINS_IMAGE: "Warning! The drawing contains image elements. Depending on the number and size of the images, " +
|
||||
"loading Markdown View may take a while. Please be patient. ",
|
||||
|
||||
//settings.ts
|
||||
FOLDER_NAME: "Excalidraw folder",
|
||||
FOLDER_DESC: "Default location for new drawings. If empty, drawings will be created in the Vault root.",
|
||||
TEMPLATE_NAME: "Excalidraw template file",
|
||||
TEMPLATE_DESC: "Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and it's name is " +
|
||||
"Template.md, the setting would be: Excalidraw/Template.md (or just Excalidraw/Template - you may ommit the .md file extension" +
|
||||
"If you are using Excalidraw in compatibility mode, then your template must be a legacy excalidraw file as well " +
|
||||
"such as Excalidraw/Template.excalidraw.",
|
||||
AUTOSAVE_NAME: "Autosave",
|
||||
AUTOSAVE_DESC: "Automatically save the active drawing every 30 seconds. Save normally happens when you close Excalidraw or Obsidian, or move "+
|
||||
"focus to another pane. In rare cases autosave may slightly disrupt your drawing flow. I created this feature with mobile " +
|
||||
"phones in mind (I only have experience with Android), where 'swiping out Obsidian to close it' led to some data loss, and because " +
|
||||
"I wasn't able to force save on application termination on mobiles. If you use Excalidraw on a desktop this is likely not needed.",
|
||||
FILENAME_HEAD: "Filename",
|
||||
FILENAME_DESC: "<p>The auto-generated filename consists of a prefix and a date. " +
|
||||
"e.g.'Drawing 2021-05-24 12.58.07'.</p>"+
|
||||
"<p>Click this link for the <a href='https://momentjs.com/docs/#/displaying/format/'>"+
|
||||
"date and time format reference</a>.</p>",
|
||||
FILENAME_SAMPLE: "The current file format is: <b>",
|
||||
FILENAME_PREFIX_NAME: "Filename prefix",
|
||||
FILENAME_PREFIX_DESC: "The first part of the filename",
|
||||
FILENAME_DATE_NAME: "Filename date",
|
||||
FILENAME_DATE_DESC: "The second part of the filename",
|
||||
DISPLAY_HEAD: "Display",
|
||||
ZOOM_TO_FIT_NAME: "Zoom to fit on view resize",
|
||||
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized",
|
||||
LINKS_HEAD: "Links and transclusion",
|
||||
LINKS_DESC: "CTRL/META + CLICK on Text Elements to open them as links. " +
|
||||
"If the selected text has more than one [[valid Obsidian links]], only the first will be opened. " +
|
||||
"If the text starts as a valid web link (i.e. https:// or http://), then " +
|
||||
"the plugin will open it in a browser. " +
|
||||
"When Obsidian files change, the matching [[link]] in your drawings will also change. " +
|
||||
"If you don't want text accidentally changing in your drawings use [[links|with aliases]].",
|
||||
LINK_BRACKETS_NAME: "Show [[brackets]] around links",
|
||||
LINK_BRACKETS_DESC: "In PREVIEW mode, when parsing Text Elements, place brackets around links. " +
|
||||
"You can override this setting for a specific drawing by adding '" + FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS +
|
||||
": true/false' to the file\'s frontmatter.",
|
||||
LINK_PREFIX_NAME:"Link prefix",
|
||||
LINK_PREFIX_DESC:"In PREVIEW mode, if the Text Element contains a link, precede the text with these characters. " +
|
||||
"You can override this setting for a specific drawing by adding \'" + FRONTMATTER_KEY_CUSTOM_PREFIX +
|
||||
': "📍 "\' to the file\'s frontmatter.',
|
||||
URL_PREFIX_NAME:"URL prefix",
|
||||
URL_PREFIX_DESC:"In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " +
|
||||
"You can override this setting for a specific drawing by adding \'" + FRONTMATTER_KEY_CUSTOM_URL_PREFIX +
|
||||
': "🌐 "\' to the file\'s frontmatter.',
|
||||
LINK_CTRL_CLICK_NAME: "CTRL + CLICK on text to open them as links",
|
||||
LINK_CTRL_CLICK_DESC: "You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
TRANSCLUSION_WRAP_NAME: "Overflow wrap behavior of transcluded text",
|
||||
TRANSCLUSION_WRAP_DESC: "Number specifies the character count where the text should be wrapped. " +
|
||||
"Set the text wrapping behavior of transcluded text. Turn this ON to force-wrap " +
|
||||
"text (i.e. no overflow), or OFF to soft-warp text (at the nearest whitespace).",
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_NAME: "Page transclusion max char count",
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_DESC: "The maximum number of characters to display from the page when transcluding an entire page with the "+
|
||||
"![[markdown page]] format.",
|
||||
EMBED_HEAD: "Embed & Export",
|
||||
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
|
||||
EMBED_PREVIEW_SVG_DESC: "The default is to display drawings as SVG images in the markdown preview. Turning this feature off, the markdown preview will display the drawing as an embedded PNG image.",
|
||||
EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image",
|
||||
EMBED_WIDTH_DESC: "Only relevant if embed type is excalidraw. Has no effect on PNG and SVG embeds. The default width of an embedded drawing. You can specify a custom " +
|
||||
"width when embedding an image using the ![[drawing.excalidraw|100]] or " +
|
||||
"[[drawing.excalidraw|100x100]] format.",
|
||||
EMBED_TYPE_NAME: "Type of file to insert into the document",
|
||||
EMBED_TYPE_DESC: "When you embed an image into a document using the command palette this setting will specify if Excalidraw should embed the original excalidraw file "+
|
||||
"or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
|
||||
"a correspondign PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " +
|
||||
"This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.",
|
||||
EXPORT_PNG_SCALE_NAME: "PNG export image scale",
|
||||
EXPORT_PNG_SCALE_DESC: "The size-scale of the exported PNG image",
|
||||
EXPORT_BACKGROUND_NAME: "Export image with background",
|
||||
EXPORT_BACKGROUND_DESC: "If turned off, the exported image will be transparent.",
|
||||
EXPORT_THEME_NAME: "Export image with theme",
|
||||
EXPORT_THEME_DESC: "Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
"drawings created in drak mode will appear as they would in light mode.",
|
||||
EXPORT_HEAD: "Export Settings",
|
||||
EXPORT_SYNC_NAME:"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
EXPORT_SYNC_DESC:"When turned on, the plugin will automaticaly update the filename of the .SVG and/or .PNG files when the drawing in the same folder (and same name) is renamed. " +
|
||||
"The plugin will also automatically delete the .SVG and/or .PNG files when the drawing in the same folder (and same name) is deleted. ",
|
||||
EXPORT_SVG_NAME: "Auto-export SVG",
|
||||
EXPORT_SVG_DESC: "Automatically create an SVG export of your drawing matching the title of your file. " +
|
||||
"The plugin will save the *.SVG file in the same folder as the drawing. "+
|
||||
"Embed the .svg file into your documents instead of excalidraw making you embeds platform independent. " +
|
||||
"While the auto-export switch is on, this file will get updated every time you edit the excalidraw drawing with the matching name.",
|
||||
EXPORT_PNG_NAME: "Auto-export PNG",
|
||||
EXPORT_PNG_DESC: "Same as the auto-export SVG, but for *.PNG",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
EXPORT_EXCALIDRAW_NAME: "Auto-export Excalidraw",
|
||||
EXPORT_EXCALIDRAW_DESC: "Same as the auto-export SVG, but for *.Excalidraw",
|
||||
SYNC_EXCALIDRAW_NAME: "Sync *.excalidraw with *.md version of the same drawing",
|
||||
SYNC_EXCALIDRAW_DESC: "If the modified date of the *.excalidraw file is more recent than the modified date of the *.md file " +
|
||||
"then update the drawing in the .md file based on the .excalidraw file",
|
||||
COMPATIBILITY_MODE_NAME: "New drawings as legacy files",
|
||||
COMPATIBILITY_MODE_DESC: "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.",
|
||||
EXPERIMENTAL_HEAD: "Experimental features",
|
||||
EXPERIMENTAL_DESC: "These setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",
|
||||
FILETYPE_NAME: "Display type (✏️) for excalidraw.md files in File Explorer",
|
||||
FILETYPE_DESC: "Excalidraw files will receive an indicator using the emojii or text defined in the next setting.",
|
||||
FILETAG_NAME: "Set the type indicator for excalidraw.md files",
|
||||
FILETAG_DESC: "The text or emojii to display as type indicator.",
|
||||
INSERT_EMOJI: "Insert an emoji",
|
||||
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "Select a file then press enter.",
|
||||
NO_MATCH: "No file matches your query.",
|
||||
SELECT_FILE_TO_LINK: "Select the file you want to insert the link for.",
|
||||
TYPE_FILENAME: "Type name of drawing to select.",
|
||||
SELECT_FILE_OR_TYPE_NEW: "Select existing drawing or type name of a new drawing then press Enter.",
|
||||
SELECT_TO_EMBED: "Select the drawing to insert into active document.",
|
||||
};
|
||||
import { FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, FRONTMATTER_KEY_CUSTOM_PREFIX, FRONTMATTER_KEY_CUSTOM_URL_PREFIX } from "src/constants";
|
||||
|
||||
// English
|
||||
export default {
|
||||
// main.ts
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW : "New Excalidraw drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (Logseq compatibility)",
|
||||
DOWNLOAD_LIBRARY: "Export stencil library as an *.excalidrawlib file",
|
||||
OPEN_EXISTING_NEW_PANE: "Open an existing drawing - IN A NEW PANE",
|
||||
OPEN_EXISTING_ACTIVE_PANE: "Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
TRANSCLUDE: "Transclude (embed) a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Transclude (embed) the most recently edited drawing",
|
||||
NEW_IN_NEW_PANE: "Create a new drawing - IN A NEW PANE",
|
||||
NEW_IN_ACTIVE_PANE: "Create a new drawing - IN THE CURRENT ACTIVE PANE",
|
||||
NEW_IN_NEW_PANE_EMBED: "Create a new drawing - IN A NEW PANE - and embed into active document",
|
||||
NEW_IN_ACTIVE_PANE_EMBED: "Create a new drawing - IN THE CURRENT ACTIVE PANE - and embed into active document",
|
||||
EXPORT_SVG: "Save as SVG next to the current file",
|
||||
EXPORT_PNG: "Save as PNG next to the current file",
|
||||
TOGGLE_LOCK: "Toggle Text Element edit RAW/PREVIEW",
|
||||
INSERT_LINK: "Insert link to file",
|
||||
INSERT_LATEX: "Insert LaTeX-symbol (e.g. $\\theta$)",
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
OPEN_AS_MD: "Open as Markdown",
|
||||
SAVE_AS_PNG: "Save as PNG into Vault (CTRL/META+CLICK to export)",
|
||||
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/META+CLICK to export)",
|
||||
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
|
||||
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
|
||||
LINK_BUTTON_CLICK_NO_TEXT: 'Select a Text Element containing an internal or external link.\n'+
|
||||
'SHIFT CLICK this button to open the link in a new pane.\n'+
|
||||
'CTRL/META CLICK the Text Element on the canvas has the same effect!',
|
||||
TEXT_ELEMENT_EMPTY: "Text Element is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
|
||||
FILENAME_INVALID_CHARS: 'File name cannot contain any of the following characters: * " \\ < > : | ?',
|
||||
FILE_DOES_NOT_EXIST: "File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
|
||||
FORCE_SAVE: "Force-save to update transclusions in adjacent panes.\n(Please note, that autosave is always on)",
|
||||
RAW: "Change to PREVIEW mode (only effects text-elements with links or transclusions)",
|
||||
PARSED: "Change to RAW mode (only effects text-elements with links or transclusions)",
|
||||
NOFILE: "Excalidraw (no file)",
|
||||
COMPATIBILITY_MODE: "*.excalidraw file opened in compatibility mode. Convert to new format for full plugin functionality.",
|
||||
CONVERT_FILE: "Convert to new format",
|
||||
|
||||
//settings.ts
|
||||
FOLDER_NAME: "Excalidraw folder",
|
||||
FOLDER_DESC: "Default location for new drawings. If empty, drawings will be created in the Vault root.",
|
||||
TEMPLATE_NAME: "Excalidraw template file",
|
||||
TEMPLATE_DESC: "Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and it's name is " +
|
||||
"Template.md, the setting would be: Excalidraw/Template.md (or just Excalidraw/Template - you may ommit the .md file extension" +
|
||||
"If you are using Excalidraw in compatibility mode, then your template must be a legacy excalidraw file as well " +
|
||||
"such as Excalidraw/Template.excalidraw.",
|
||||
AUTOSAVE_NAME: "Autosave",
|
||||
AUTOSAVE_DESC: "Automatically save the active drawing every 30 seconds. Save normally happens when you close Excalidraw or Obsidian, or move "+
|
||||
"focus to another pane. In rare cases autosave may slightly disrupt your drawing flow. I created this feature with mobile " +
|
||||
"phones in mind (I only have experience with Android), where 'swiping out Obsidian to close it' led to some data loss, and because " +
|
||||
"I wasn't able to force save on application termination on mobiles. If you use Excalidraw on a desktop this is likely not needed.",
|
||||
FILENAME_HEAD: "Filename",
|
||||
FILENAME_DESC: "<p>The auto-generated filename consists of a prefix and a date. " +
|
||||
"e.g.'Drawing 2021-05-24 12.58.07'.</p>"+
|
||||
"<p>Click this link for the <a href='https://momentjs.com/docs/#/displaying/format/'>"+
|
||||
"date and time format reference</a>.</p>",
|
||||
FILENAME_SAMPLE: "The current file format is: <b>",
|
||||
FILENAME_PREFIX_NAME: "Filename prefix",
|
||||
FILENAME_PREFIX_DESC: "The first part of the filename",
|
||||
FILENAME_DATE_NAME: "Filename date",
|
||||
FILENAME_DATE_DESC: "The second part of the filename",
|
||||
DISPLAY_HEAD: "Display",
|
||||
MATCH_THEME_NAME: "New drawing to match Obsidian theme",
|
||||
MATCH_THEME_DESC: "If theme is dark, new drawing will be created in dark mode. This does not apply when you use a template for new drawings. " +
|
||||
"Also this will not effect when you open an existing drawing. Those will follow the theme of the template/drawing respectively.",
|
||||
ZOOM_TO_FIT_NAME: "Zoom to fit on view resize",
|
||||
ZOOM_TO_FIT_DESC: "Zoom to fit drawing when the pane is resized",
|
||||
LINKS_HEAD: "Links and transclusion",
|
||||
LINKS_DESC: "CTRL/META + CLICK on Text Elements to open them as links. " +
|
||||
"If the selected text has more than one [[valid Obsidian links]], only the first will be opened. " +
|
||||
"If the text starts as a valid web link (i.e. https:// or http://), then " +
|
||||
"the plugin will open it in a browser. " +
|
||||
"When Obsidian files change, the matching [[link]] in your drawings will also change. " +
|
||||
"If you don't want text accidentally changing in your drawings use [[links|with aliases]].",
|
||||
ADJACENT_PANE_NAME: "Open in adjacent pane",
|
||||
ADJACENT_PANE_DESC: "When CTRL+SHIFT clicking a link in Excalidraw by default the plugin will open the link in a new pane. " +
|
||||
"Turning this setting on, Excalidraw will first look for an existing adjacent pane, and try to open the link there. " +
|
||||
"Excalidraw will first look too the right, then to the left, then down, then up. If no pane is found, Excalidraw will open " +
|
||||
"a new pane.",
|
||||
LINK_BRACKETS_NAME: "Show [[brackets]] around links",
|
||||
LINK_BRACKETS_DESC: "In PREVIEW mode, when parsing Text Elements, place brackets around links. " +
|
||||
"You can override this setting for a specific drawing by adding '" + FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS +
|
||||
": true/false' to the file\'s frontmatter.",
|
||||
LINK_PREFIX_NAME:"Link prefix",
|
||||
LINK_PREFIX_DESC:"In PREVIEW mode, if the Text Element contains a link, precede the text with these characters. " +
|
||||
"You can override this setting for a specific drawing by adding \'" + FRONTMATTER_KEY_CUSTOM_PREFIX +
|
||||
': "📍 "\' to the file\'s frontmatter.',
|
||||
URL_PREFIX_NAME:"URL prefix",
|
||||
URL_PREFIX_DESC:"In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " +
|
||||
"You can override this setting for a specific drawing by adding \'" + FRONTMATTER_KEY_CUSTOM_URL_PREFIX +
|
||||
': "🌐 "\' to the file\'s frontmatter.',
|
||||
LINK_CTRL_CLICK_NAME: "CTRL + CLICK on text to open them as links",
|
||||
LINK_CTRL_CLICK_DESC: "You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
TRANSCLUSION_WRAP_NAME: "Overflow wrap behavior of transcluded text",
|
||||
TRANSCLUSION_WRAP_DESC: "Number specifies the character count where the text should be wrapped. " +
|
||||
"Set the text wrapping behavior of transcluded text. Turn this ON to force-wrap " +
|
||||
"text (i.e. no overflow), or OFF to soft-warp text (at the nearest whitespace).",
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_NAME: "Page transclusion max char count",
|
||||
PAGE_TRANSCLUSION_CHARCOUNT_DESC: "The maximum number of characters to display from the page when transcluding an entire page with the "+
|
||||
"![[markdown page]] format.",
|
||||
EMBED_HEAD: "Embed & Export",
|
||||
EMBED_PREVIEW_SVG_NAME: "Display SVG in markdown preview",
|
||||
EMBED_PREVIEW_SVG_DESC: "The default is to display drawings as SVG images in the markdown preview. Turning this feature off, the markdown preview will display the drawing as an embedded PNG image.",
|
||||
EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image",
|
||||
EMBED_WIDTH_DESC: "Only relevant if embed type is excalidraw. Has no effect on PNG and SVG embeds. The default width of an embedded drawing. You can specify a custom " +
|
||||
"width when embedding an image using the ![[drawing.excalidraw|100]] or " +
|
||||
"[[drawing.excalidraw|100x100]] format.",
|
||||
EMBED_TYPE_NAME: "Type of file to insert into the document",
|
||||
EMBED_TYPE_DESC: "When you embed an image into a document using the command palette this setting will specify if Excalidraw should embed the original excalidraw file "+
|
||||
"or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
|
||||
"a correspondign PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " +
|
||||
"This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.",
|
||||
EXPORT_PNG_SCALE_NAME: "PNG export image scale",
|
||||
EXPORT_PNG_SCALE_DESC: "The size-scale of the exported PNG image",
|
||||
EXPORT_BACKGROUND_NAME: "Export image with background",
|
||||
EXPORT_BACKGROUND_DESC: "If turned off, the exported image will be transparent.",
|
||||
EXPORT_THEME_NAME: "Export image with theme",
|
||||
EXPORT_THEME_DESC: "Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
"drawings created in drak mode will appear as they would in light mode.",
|
||||
EXPORT_HEAD: "Export Settings",
|
||||
EXPORT_SYNC_NAME:"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
EXPORT_SYNC_DESC:"When turned on, the plugin will automaticaly update the filename of the .SVG and/or .PNG files when the drawing in the same folder (and same name) is renamed. " +
|
||||
"The plugin will also automatically delete the .SVG and/or .PNG files when the drawing in the same folder (and same name) is deleted. ",
|
||||
EXPORT_SVG_NAME: "Auto-export SVG",
|
||||
EXPORT_SVG_DESC: "Automatically create an SVG export of your drawing matching the title of your file. " +
|
||||
"The plugin will save the *.SVG file in the same folder as the drawing. "+
|
||||
"Embed the .svg file into your documents instead of excalidraw making you embeds platform independent. " +
|
||||
"While the auto-export switch is on, this file will get updated every time you edit the excalidraw drawing with the matching name.",
|
||||
EXPORT_PNG_NAME: "Auto-export PNG",
|
||||
EXPORT_PNG_DESC: "Same as the auto-export SVG, but for *.PNG",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
EXPORT_EXCALIDRAW_NAME: "Auto-export Excalidraw",
|
||||
EXPORT_EXCALIDRAW_DESC: "Same as the auto-export SVG, but for *.Excalidraw",
|
||||
SYNC_EXCALIDRAW_NAME: "Sync *.excalidraw with *.md version of the same drawing",
|
||||
SYNC_EXCALIDRAW_DESC: "If the modified date of the *.excalidraw file is more recent than the modified date of the *.md file " +
|
||||
"then update the drawing in the .md file based on the .excalidraw file",
|
||||
COMPATIBILITY_MODE_NAME: "New drawings as legacy files",
|
||||
COMPATIBILITY_MODE_DESC: "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.",
|
||||
EXPERIMENTAL_HEAD: "Experimental features",
|
||||
EXPERIMENTAL_DESC: "These setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",
|
||||
FILETYPE_NAME: "Display type (✏️) for excalidraw.md files in File Explorer",
|
||||
FILETYPE_DESC: "Excalidraw files will receive an indicator using the emojii or text defined in the next setting.",
|
||||
FILETAG_NAME: "Set the type indicator for excalidraw.md files",
|
||||
FILETAG_DESC: "The text or emojii to display as type indicator.",
|
||||
INSERT_EMOJI: "Insert an emoji",
|
||||
|
||||
|
||||
//openDrawings.ts
|
||||
SELECT_FILE: "Select a file then press enter.",
|
||||
NO_MATCH: "No file matches your query.",
|
||||
SELECT_FILE_TO_LINK: "Select the file you want to insert the link for.",
|
||||
TYPE_FILENAME: "Type name of drawing to select.",
|
||||
SELECT_FILE_OR_TYPE_NEW: "Select existing drawing or type name of a new drawing then press Enter.",
|
||||
SELECT_TO_EMBED: "Select the drawing to insert into active document.",
|
||||
};
|
||||
13
src/main.ts
13
src/main.ts
@@ -31,7 +31,8 @@ import {
|
||||
FRONTMATTER_KEY,
|
||||
FRONTMATTER,
|
||||
JSON_parse,
|
||||
nanoid
|
||||
nanoid,
|
||||
DARK_BLANK_DRAWING
|
||||
} from "./constants";
|
||||
import ExcalidrawView, {ExportSettings, TextMode} from "./ExcalidrawView";
|
||||
import {getJSON} from "./ExcalidrawData";
|
||||
@@ -112,6 +113,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
new Notice("Excalidraw was updated. Files opened with this version will not open with the older version. Please update plugin on all your devices.\n\nI will remove this message with next update.",8000);
|
||||
if(this.settings.loadCount<1) this.migrationNotice();
|
||||
const electron:string = process.versions.electron;
|
||||
if(electron.startsWith("8.")) {
|
||||
@@ -1101,16 +1103,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
if (this.settings.compatibilityMode) {
|
||||
return BLANK_DRAWING;
|
||||
return this.settings.matchTheme && document.body.classList.contains("theme-dark") ? DARK_BLANK_DRAWING : BLANK_DRAWING;
|
||||
}
|
||||
return FRONTMATTER + '\n' + this.getMarkdownDrawingSection(BLANK_DRAWING);
|
||||
const blank = this.settings.matchTheme && document.body.classList.contains("theme-dark") ? DARK_BLANK_DRAWING : BLANK_DRAWING;
|
||||
return FRONTMATTER + '\n' + this.getMarkdownDrawingSection(blank);
|
||||
}
|
||||
|
||||
public getMarkdownDrawingSection(jsonString: string) {
|
||||
return '%%\n# Drawing\n'
|
||||
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96)+'json\n'
|
||||
+ jsonString + '\n'
|
||||
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96) + '%%';
|
||||
+ String.fromCharCode(96)+String.fromCharCode(96)+String.fromCharCode(96) + '\n%%';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1135,7 +1138,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
outString += te.text+' ^'+id+'\n\n';
|
||||
}
|
||||
return outString + this.getMarkdownDrawingSection(JSON.stringify(JSON_parse(data)));
|
||||
return outString + this.getMarkdownDrawingSection(JSON.stringify(JSON_parse(data),null,"\t"));
|
||||
}
|
||||
|
||||
public async createDrawing(filename: string, onNewPane: boolean, foldername?: string, initData?:string):Promise<string> {
|
||||
|
||||
@@ -17,7 +17,9 @@ export interface ExcalidrawSettings {
|
||||
drawingFilenameDateTime: string,
|
||||
displaySVGInPreview: boolean,
|
||||
width: string,
|
||||
matchTheme: boolean,
|
||||
zoomToFitOnResize: boolean,
|
||||
openInAdjacentPane: boolean,
|
||||
showLinkBrackets: boolean,
|
||||
linkPrefix: string,
|
||||
urlPrefix: string,
|
||||
@@ -49,9 +51,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
drawingFilenameDateTime: 'YYYY-MM-DD HH.mm.ss',
|
||||
displaySVGInPreview: true,
|
||||
width: '400',
|
||||
matchTheme: false,
|
||||
zoomToFitOnResize: true,
|
||||
linkPrefix: "📍",
|
||||
urlPrefix: "🌐",
|
||||
openInAdjacentPane: false,
|
||||
showLinkBrackets: true,
|
||||
allowCtrlClick: true,
|
||||
forceWrap: false,
|
||||
@@ -189,6 +193,17 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}));
|
||||
|
||||
this.containerEl.createEl('h1', {text: t("DISPLAY_HEAD")});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("MATCH_THEME_NAME"))
|
||||
.setDesc(t("MATCH_THEME_DESC"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.matchTheme)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.matchTheme = value;
|
||||
this.applySettingsUpdate();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ZOOM_TO_FIT_NAME"))
|
||||
.setDesc(t("ZOOM_TO_FIT_DESC"))
|
||||
@@ -203,6 +218,16 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.containerEl.createEl('p',{
|
||||
text: t("LINKS_DESC")});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("ADJACENT_PANE_NAME"))
|
||||
.setDesc(t("ADJACENT_PANE_DESC"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.openInAdjacentPane)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.openInAdjacentPane = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("LINK_BRACKETS_NAME"))
|
||||
.setDesc(t("LINK_BRACKETS_DESC"))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"1.3.16": "0.11.13"
|
||||
"1.3.20": "0.11.13"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user