diff --git a/ea-scripts/ExcaliAI.md b/ea-scripts/ExcaliAI.md
index d64f350..2d8fa28 100644
--- a/ea-scripts/ExcaliAI.md
+++ b/ea-scripts/ExcaliAI.md
@@ -1,7 +1,5 @@
/*
-Based on https://github.com/SawyerHood/draw-a-ui
-
-VIDEO
+VIDEO

```js*/
@@ -32,7 +30,7 @@ const instructions = {
const defaultSystemPrompts = {
"Wireframe to code": `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
- "Challenge my thinking": `The user will provide you with a screenshot of a whiteboard covering ideas about a subject. Your task is to understand the topic of the whiteboard, to give it a title, and then to identify ideas, web links, images, and any additional content that challenges, disputes, contradicts what is on the whiteboard, plus ideas that extend, add, builds on, and takes the thinking of the user further.`
+ "Challenge my thinking": `The user will provide you with a screenshot of a whiteboard. Your task is to generate a mermaid graph based on the whiteboard and return the resulting mermaid code in a codeblock. The whiteboard will cover ideas about a subject. On the mindmap identify ideas that challenge, dispute, and contradict what is on the whiteboard, as well as also include ideas that extend, add-to, build-on, and takes the thinking of the user further. Use the graph diagram type. Return a single message containing only the mermaid diagram in a codeblock. Avoid the use of () parenthesis in the mermaid script.`
}
const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
@@ -65,7 +63,7 @@ const getRequestObjFromSelectedElements = async (view) => {
new Notice ("Aborting because there is nothing selected.",4000);
return;
}
- ea.copyViewElementsToEAforEditing(viewElements);
+ ea.copyViewElementsToEAforEditing(viewElements, true);
const bb = ea.getBoundingBox(viewElements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
@@ -226,7 +224,7 @@ configModal.onOpen = () => {
dirty = true;
agentTask = value;
textArea.setValue(allSystemPrompts[value]);
- promptTitle.setValue(value);
+ //promptTitle.setValue(value);
});
})
diff --git a/ea-scripts/GPT-Draw-a-UI.md b/ea-scripts/GPT-Draw-a-UI.md
index bcf75e8..2d8fa28 100644
--- a/ea-scripts/GPT-Draw-a-UI.md
+++ b/ea-scripts/GPT-Draw-a-UI.md
@@ -1,109 +1,61 @@
/*
-Based on https://github.com/SawyerHood/draw-a-ui
-
-VIDEO
+VIDEO

```js*/
-if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.2")) {
+if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.4")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
-settings = ea.getScriptSettings();
-//set default values on first run
-if(!settings["OPENAI_API_KEY"]) {
+// --------------------------------------
+// Initialize values and settings
+// --------------------------------------
+let settings = ea.getScriptSettings();
+
+if(!settings["Custom System Prompts"]) {
settings = {
- "OPENAI_API_KEY" : {
- value: "",
- description: `Get your api key at https://platform.openai.com/ ⚠️ Note that the gpt-4-vision-preview
- requires a minimum of $5 credit on your account.`
- },
- "FOLDER" : {
- value: "GPTPlayground",
- description: `The folder in your vault where you want to store generated html pages`
- },
- "FILENAME": {
- value: "page",
- description: `The base name of the html file that will be created. Each time you run the script
- a new file will be created using the following pattern "filename_i" where i is a counter`
- }
+ "Custom System Prompts" : {},
+ "Agent's Task": "Wireframe to code",
+ "Output Type": "html"
};
await ea.setScriptSettings(settings);
}
-const OPENAI_API_KEY = settings["OPENAI_API_KEY"].value;
-const FOLDER = settings["FOLDER"].value;
-const FILENAME = settings["FILENAME"].value;
+const instructions = {
+ "html": "Turn this into a single html file using tailwind. Return a single message containing only the html file in a codeblock.",
+ "mermaid": "Challenge my thinking. Return a single message containing only the mermaid diagram in a codeblock."
+}
-if(OPENAI_API_KEY==="") {
- new Notice("Please set an OpenAI API key in plugin settings");
+const defaultSystemPrompts = {
+ "Wireframe to code": `You are an expert tailwind developer. A user will provide you with a low-fidelity wireframe of an application and you will return a single html file that uses tailwind to create the website. Use creative license to make the application more fleshed out. Write the necessary javascript code. If you need to insert an image, use placehold.co to create a placeholder image.`,
+ "Challenge my thinking": `The user will provide you with a screenshot of a whiteboard. Your task is to generate a mermaid graph based on the whiteboard and return the resulting mermaid code in a codeblock. The whiteboard will cover ideas about a subject. On the mindmap identify ideas that challenge, dispute, and contradict what is on the whiteboard, as well as also include ideas that extend, add-to, build-on, and takes the thinking of the user further. Use the graph diagram type. Return a single message containing only the mermaid diagram in a codeblock. Avoid the use of () parenthesis in the mermaid script.`
+}
+
+const OPENAI_API_KEY = ea.plugin.settings.openAIAPIToken;
+if(!OPENAI_API_KEY || OPENAI_API_KEY === "") {
+ new Notice("You must first configure your API key in Excalidraw Plugin Settings");
return;
}
-const systemPrompt = `You are an expert tailwind developer. A user will provide you with a
- low-fidelity wireframe of an application and you will return
- a single html file that uses tailwind to create the website.
- Use creative license to make the application more fleshed out. Write the necessary javascript code.
- If you need to insert an image, use placehold.co to create a placeholder image.
- Respond only with the html file.`;
-
-const post = async (request) => {
- const { image } = await request.json();
- const body = {
- model: "gpt-4-vision-preview",
- max_tokens: 4096,
- messages: [
- {
- role: "system",
- content: systemPrompt,
- },
- {
- role: "user",
- content: [
- {
- type: "image_url",
- image_url: image,
- },
- "Turn this into a single html file using tailwind.",
- ],
- },
- ],
- };
-
- let json = null;
- try {
- const resp = await requestUrl ({
- url: "https://api.openai.com/v1/chat/completions",
- method: "post",
- contentType: "application/json",
- body: JSON.stringify(body),
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${OPENAI_API_KEY}`,
- },
- throw: false
- });
- json = resp.json;
- } catch (e) {
- console.log(e);
- }
-
- return json;
-}
-
-const blobToBase64 = async (blob) => {
- const arrayBuffer = await blob.arrayBuffer()
- const bytes = new Uint8Array(arrayBuffer)
- var binary = '';
- var len = bytes.byteLength;
- for (var i = 0; i < len; i++) {
- binary += String.fromCharCode(bytes[i]);
- }
- return btoa(binary);
+let customSystemPrompts = settings["Custom System Prompts"];
+let agentTask = settings["Agent's Task"];
+let outputType = settings["Output Type"];
+if (!Object.keys(instructions).includes(outputType)) {
+ outputType = "html";
+}
+let allSystemPrompts = {
+ ...defaultSystemPrompts,
+ ...customSystemPrompts
+};
+if(!allSystemPrompts.hasOwnProperty(agentTask)) {
+ agentTask = Object.keys(defaultSystemPrompts)[0];
}
+// --------------------------------------
+// Generate image
+// --------------------------------------
const getRequestObjFromSelectedElements = async (view) => {
await view.forceSave(true);
const viewElements = ea.getViewSelectedElements();
@@ -111,7 +63,7 @@ const getRequestObjFromSelectedElements = async (view) => {
new Notice ("Aborting because there is nothing selected.",4000);
return;
}
- ea.copyViewElementsToEAforEditing(viewElements);
+ ea.copyViewElementsToEAforEditing(viewElements, true);
const bb = ea.getBoundingBox(viewElements);
const size = (bb.width*bb.height);
const minRatio = Math.sqrt(360000/size);
@@ -123,83 +75,217 @@ const getRequestObjFromSelectedElements = async (view) => {
? 1/maxRatio
: 1
);
-
+
const loader = ea.getEmbeddedFilesLoader(false);
const exportSettings = {
withBackground: true,
withTheme: true,
};
- const img =
- await ea.createPNG(
+ const dataURL =
+ await ea.createPNGBase64(
null,
scale,
exportSettings,
loader,
"light",
);
- const dataURL = `data:image/png;base64,${await blobToBase64(img)}`;
ea.clear();
- return { json: async () => ({ image: dataURL }) }
+ return { image: dataURL };
}
-const extractHTMLFromString = (result) => {
- if(!result) return null;
- const start = result.indexOf('```html\n');
- const end = result.lastIndexOf('```');
- if (start !== -1 && end !== -1) {
- const htmlString = result.substring(start + 8, end);
- return htmlString.trim();
+// --------------------------------------
+// Submit Prompt
+// --------------------------------------
+const run = async () => {
+ const requestObject = await getRequestObjFromSelectedElements(ea.targetView);
+ requestObject.systemPrompt = allSystemPrompts[agentTask];
+ requestObject.instruction = instructions[outputType];
+
+ const spinner = await ea.convertStringToDataURL(`
+
+
+
+
+
+
+ Generating...
+ `);
+
+ const bb = ea.getBoundingBox(ea.getViewSelectedElements());
+ const id = ea.addEmbeddable(bb.topX+bb.width+100,bb.topY-(720-bb.height)/2,550,720,spinner);
+ await ea.addElementsToView(false,true);
+ ea.clear();
+ const embeddable = ea.getViewElements().filter(el=>el.id===id);
+ ea.copyViewElementsToEAforEditing(embeddable);
+ const els = ea.getViewSelectedElements();
+ ea.viewZoomToElements(false, els.concat(embeddable));
+
+ //Get result from GPT
+
+ const result = await ea.postOpenAI(requestObject);
+
+ const errorMessage = async () => {
+ const error = "Something went wrong! Check developer console for more.";
+ const errorDataURL = await ea.convertStringToDataURL(`
+
+ Error!
+ ${error}
+ `);
+ new Notice (error);
+ ea.getElement(id).src = errorDataURL;
+ ea.addElementsToView(false,true);
}
- return null;
-}
-const checkAndCreateFolder = async (folderpath) => {
- const vault = app.vault;
- folderpath = ea.obsidian.normalizePath(folderpath);
- const folder = vault.getAbstractFileByPathInsensitive(folderpath);
- if (folder) {
- return folder;
+ if(!result?.json?.hasOwnProperty("choices")) {
+ await errorMessage();
+ return;
}
- return await vault.createFolder(folderpath);
-}
-
-const getNewUniqueFilepath = (filename, folderpath) => {
- let fname = ea.obsidian.normalizePath(`${folderpath}/${filename}.html`);
- let file = app.vault.getAbstractFileByPath(fname);
- let i = 0;
- while (file) {
- fname = ea.obsidian.normalizePath(`${folderpath}/${filename}_${i++}.html`);
- file = app.vault.getAbstractFileByPath(fname);
- }
- return fname;
-}
-
-const requestObject = await getRequestObjFromSelectedElements(ea.targetView);
-const result = await post(requestObject);
-
-const errorMessage = () => {
- new Notice ("Something went wrong! Check developer console for more.");
+
console.log(result);
+ let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
+
+ if(!content) {
+ await errorMessage();
+ return;
+ }
+
+ switch(outputType) {
+ case "html":
+ ea.getElement(id).link = await ea.convertStringToDataURL(content);
+ ea.addElementsToView(false,true);
+ break;
+ case "mermaid":
+ if(content.startsWith("mermaid")) {
+ content = content.replace(/^mermaid/,"").trim();
+ }
+ ea.getElement(id).isDeleted = true;
+ try {
+ await ea.addMermaid(content);
+ } catch (e) {
+ ea.addText(0,0,content);
+ }
+ ea.targetView.currentPosition = {x: bb.topX+bb.width+100, y: bb.topY-bb.height-100};
+ await ea.addElementsToView(true, false);
+ ea.clear();
+ if(content.startsWith("graph LR") || content.startsWith("graph TD")) {
+ try {
+ if(content.startsWith("graph LR") || content.startsWith("flowchart LR")) {
+ content = content.replaceAll("graph LR", "graph TD");
+ content = content.replaceAll("flowchart LR", "flowchart TD");
+ await ea.addMermaid(content);
+ } else if (content.startsWith("graph TD") || content.startsWith("flowchart TD")) {
+ content = content.replaceAll("graph TD", "graph LR");
+ content = content.replaceAll("flowchart TD", "flowchart LR");
+ await ea.addMermaid(content);
+ }
+ } catch (e) {
+ ea.addText(0,0,content);
+ }
+ ea.targetView.currentPosition = {x: bb.topX-100, y: bb.topY + 100};
+ ea.addElementsToView(true, true);
+ }
+ break;
+ }
}
-if(!result?.hasOwnProperty("choices")) {
- errorMessage();
- return;
+// --------------------------------------
+// User Interface
+// --------------------------------------
+const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
+
+const configModal = new ea.obsidian.Modal(app);
+let dirty=false;
+configModal.onOpen = () => {
+ const contentEl = configModal.contentEl;
+ contentEl.createEl("h1", {text: "ExcaliAI"});
+
+ let textArea, promptTitle;
+
+ new ea.obsidian.Setting(contentEl)
+ .setName("Select Prompt")
+ .addDropdown(dropdown=>{
+ Object.keys(allSystemPrompts).forEach(key=>dropdown.addOption(key,key));
+ dropdown
+ .setValue(agentTask)
+ .onChange(value => {
+ dirty = true;
+ agentTask = value;
+ textArea.setValue(allSystemPrompts[value]);
+ //promptTitle.setValue(value);
+ });
+ })
+
+ new ea.obsidian.Setting(contentEl)
+ .setName("Select response type")
+ .addDropdown(dropdown=>{
+ Object.keys(instructions).forEach(key=>dropdown.addOption(key,key));
+ dropdown
+ .setValue(outputType)
+ .onChange(value => {
+ dirty = true;
+ outputType = value;
+ });
+ })
+
+ contentEl.createEl("h3", {text: "Customize Prompt"});
+/* const titleSetting = new ea.obsidian.Setting(contentEl)
+ .addText(text => {
+ promptTitle = text;
+ text.inputEl.style.width = "100%";
+ text.setValue(agentTask);
+ })
+ titleSetting.nameEl.style.display = "none";
+ titleSetting.descEl.style.display = "none";
+ titleSetting.infoEl.style.display = "none";
+*/
+ const textAreaSetting = new ea.obsidian.Setting(contentEl)
+ .addTextArea(text => {
+ textArea = text;
+ text.inputEl.style.minHeight = "10em";
+ text.inputEl.style.minWidth = "400px";
+ text.inputEl.style.width = "100%";
+ text.setValue(allSystemPrompts[agentTask]);
+ text.onChange(value => {
+ // dirty = true;
+ //Needs further work
+ allSystemPrompts[agentTask] = value;
+ })
+ })
+ textAreaSetting.nameEl.style.display = "none";
+ textAreaSetting.descEl.style.display = "none";
+ textAreaSetting.infoEl.style.display = "none";
+
+ new ea.obsidian.Setting(contentEl)
+ .addButton(button =>
+ button
+ .setButtonText("Run")
+ .onClick((event)=>{
+ setTimeout(()=>{run()},500); //Obsidian crashes otherwise, likely has to do with requesting an new frame for react
+ configModal.close();
+ })
+ );
}
-
-const htmlContent = extractHTMLFromString(result.choices[0]?.message?.content);
-
-if(!htmlContent) {
- errorMessage();
- return;
+
+configModal.onClose = () => {
+ if(dirty) {
+ settings["Custom System Prompts"] = customSystemPrompts;
+ settings["Agent's Task"] = agentTask;
+ settings["Output Type"] = outputType;
+ ea.setScriptSettings(settings);
+ }
}
-
-const folder = await checkAndCreateFolder(FOLDER);
-const filepath = getNewUniqueFilepath(FILENAME,folder.path);
-const file = await app.vault.create(filepath,htmlContent);
-const url = app.vault.adapter.getFilePath(file.path).toString();
-const bb = ea.getBoundingBox(ea.getViewSelectedElements());
-ea.addEmbeddable(bb.topX+bb.width+40,bb.topY,600,800,url);
-await ea.addElementsToView(false,true);
-ea.viewZoomToElements([]);
\ No newline at end of file
+
+configModal.open();
diff --git a/ea-scripts/directory-info.json b/ea-scripts/directory-info.json
index 23a5dea..b99abfa 100644
--- a/ea-scripts/directory-info.json
+++ b/ea-scripts/directory-info.json
@@ -1 +1 @@
-[{"fname":"Mindmap connector.md","mtime":1658686599427},{"fname":"Mindmap connector.svg","mtime":1658686599427},{"fname":"Add Connector Point.md","mtime":1645305706000},{"fname":"Add Connector Point.svg","mtime":1645944722000},{"fname":"Add Link to Existing File and Open.md","mtime":1647807918345},{"fname":"Add Link to Existing File and Open.svg","mtime":1645964261000},{"fname":"Add Link to New Page and Open.md","mtime":1654168862138},{"fname":"Add Link to New Page and Open.svg","mtime":1645960639000},{"fname":"Add Next Step in Process.md","mtime":1688304760357},{"fname":"Add Next Step in Process.svg","mtime":1645960639000},{"fname":"Box Each Selected Groups.md","mtime":1645305706000},{"fname":"Box Each Selected Groups.svg","mtime":1645967510000},{"fname":"Box Selected Elements.md","mtime":1645305706000},{"fname":"Box Selected Elements.svg","mtime":1645960639000},{"fname":"Change shape of selected elements.md","mtime":1652701169236},{"fname":"Change shape of selected elements.svg","mtime":1645960775000},{"fname":"Connect elements.md","mtime":1645305706000},{"fname":"Connect elements.svg","mtime":1645960639000},{"fname":"Convert freedraw to line.md","mtime":1645305706000},{"fname":"Convert freedraw to line.svg","mtime":1645960639000},{"fname":"Convert selected text elements to sticky notes.md","mtime":1670169501383},{"fname":"Convert selected text elements to sticky notes.svg","mtime":1645960639000},{"fname":"Convert text to link with folder and alias.md","mtime":1641639819000},{"fname":"Convert text to link with folder and alias.svg","mtime":1645960639000},{"fname":"Copy Selected Element Styles to Global.md","mtime":1642232088000},{"fname":"Copy Selected Element Styles to Global.svg","mtime":1645960639000},{"fname":"Create new markdown file and embed into active drawing.md","mtime":1640866935000},{"fname":"Create new markdown file and embed into active drawing.svg","mtime":1645960639000},{"fname":"Darken background color.md","mtime":1663059051059},{"fname":"Darken background color.svg","mtime":1645960639000},{"fname":"Elbow connectors.md","mtime":1671126911490},{"fname":"Elbow connectors.svg","mtime":1645960639000},{"fname":"Expand rectangles horizontally keep text centered.md","mtime":1646563692000},{"fname":"Expand rectangles horizontally keep text centered.svg","mtime":1645967510000},{"fname":"Expand rectangles horizontally.md","mtime":1644950235000},{"fname":"Expand rectangles horizontally.svg","mtime":1645967510000},{"fname":"Expand rectangles vertically keep text centered.md","mtime":1646563692000},{"fname":"Expand rectangles vertically keep text centered.svg","mtime":1645967510000},{"fname":"Expand rectangles vertically.md","mtime":1658686599427},{"fname":"Expand rectangles vertically.svg","mtime":1645967510000},{"fname":"Fixed horizontal distance between centers.md","mtime":1646743234000},{"fname":"Fixed horizontal distance between centers.svg","mtime":1645960639000},{"fname":"Fixed inner distance.md","mtime":1646743234000},{"fname":"Fixed inner distance.svg","mtime":1645960639000},{"fname":"Fixed spacing.md","mtime":1646743234000},{"fname":"Fixed spacing.svg","mtime":1645967510000},{"fname":"Fixed vertical distance between centers.md","mtime":1646743234000},{"fname":"Fixed vertical distance between centers.svg","mtime":1645967510000},{"fname":"Fixed vertical distance.md","mtime":1646743234000},{"fname":"Fixed vertical distance.svg","mtime":1645967510000},{"fname":"Lighten background color.md","mtime":1663059051059},{"fname":"Lighten background color.svg","mtime":1645959546000},{"fname":"Modify background color opacity.md","mtime":1644924415000},{"fname":"Modify background color opacity.svg","mtime":1645944722000},{"fname":"Normalize Selected Arrows.md","mtime":1670403743278},{"fname":"Normalize Selected Arrows.svg","mtime":1645960639000},{"fname":"Organic Line.md","mtime":1672920172531},{"fname":"Organic Line.svg","mtime":1645964261000},{"fname":"Organic Line Legacy.md","mtime":1690607372668},{"fname":"Organic Line Legacy.svg","mtime":1690607372668},{"fname":"README.md","mtime":1645175700000},{"fname":"Repeat Elements.md","mtime":1663059051059},{"fname":"Repeat Elements.svg","mtime":1645960639000},{"fname":"Reverse arrows.md","mtime":1645305706000},{"fname":"Reverse arrows.svg","mtime":1645960639000},{"fname":"Scribble Helper.md","mtime":1682228345043},{"fname":"Scribble Helper.svg","mtime":1645944722000},{"fname":"Select Elements of Type.md","mtime":1643464321000},{"fname":"Select Elements of Type.svg","mtime":1645960639000},{"fname":"Set Dimensions.md","mtime":1645305706000},{"fname":"Set Dimensions.svg","mtime":1645944722000},{"fname":"Set Font Family.md","mtime":1645305706000},{"fname":"Set Font Family.svg","mtime":1645944722000},{"fname":"Set Grid.md","mtime":1693725826368},{"fname":"Set Grid.svg","mtime":1645960639000},{"fname":"Set Link Alias.md","mtime":1645305706000},{"fname":"Set Link Alias.svg","mtime":1645960639000},{"fname":"Set Stroke Width of Selected Elements.md","mtime":1645305706000},{"fname":"Set Stroke Width of Selected Elements.svg","mtime":1645960639000},{"fname":"Set Text Alignment.md","mtime":1645305706000},{"fname":"Set Text Alignment.svg","mtime":1645960639000},{"fname":"Set background color of unclosed line object by adding a shadow clone.md","mtime":1681665030892},{"fname":"Set background color of unclosed line object by adding a shadow clone.svg","mtime":1645960639000},{"fname":"Split text by lines.md","mtime":1645305706000},{"fname":"Split text by lines.svg","mtime":1645944722000},{"fname":"Zoom to Fit Selected Elements.md","mtime":1640770602000},{"fname":"Zoom to Fit Selected Elements.svg","mtime":1645960639000},{"fname":"directory-info.json","mtime":1646583437000},{"fname":"index-new.md","mtime":1645986149000},{"fname":"index.md","mtime":1645175700000},{"fname":"Grid Selected Images.md","mtime":1649614401982},{"fname":"Grid Selected Images.svg","mtime":1649614401982},{"fname":"Palette loader.md","mtime":1686511890942},{"fname":"Palette loader.svg","mtime":1649614401982},{"fname":"Rename Image.md","mtime":1663678478785},{"fname":"Rename Image.svg","mtime":1663678478785},{"fname":"Text Arch.md","mtime":1664095143846},{"fname":"Text Arch.svg","mtime":1670403743278},{"fname":"Deconstruct selected elements into new drawing.md","mtime":1693733190088},{"fname":"Deconstruct selected elements into new drawing.svg","mtime":1668541145255},{"fname":"Slideshow.md","mtime":1700511998048},{"fname":"Slideshow.svg","mtime":1670017348333},{"fname":"Auto Layout.md","mtime":1670403743278},{"fname":"Auto Layout.svg","mtime":1670175947081},{"fname":"Uniform size.md","mtime":1670175947081},{"fname":"Uniform size.svg","mtime":1670175947081},{"fname":"Mindmap format.md","mtime":1684484694228},{"fname":"Mindmap format.svg","mtime":1674944958059},{"fname":"Text to Sticky Notes.md","mtime":1678537561724},{"fname":"Text to Sticky Notes.svg","mtime":1678537561724},{"fname":"Folder Note Core - Make Current Drawing a Folder.md","mtime":1678973697470},{"fname":"Folder Note Core - Make Current Drawing a Folder.svg","mtime":1678973697470},{"fname":"Invert colors.md","mtime":1678973697470},{"fname":"Invert colors.svg","mtime":1678973697470},{"fname":"Auto Draw for Pen.md","mtime":1680418321236},{"fname":"Auto Draw for Pen.svg","mtime":1680418321236},{"fname":"Hardware Eraser Support.md","mtime":1680418321236},{"fname":"Hardware Eraser Support.svg","mtime":1680418321236},{"fname":"PDF Page Text to Clipboard.md","mtime":1683984041712},{"fname":"PDF Page Text to Clipboard.svg","mtime":1680418321236},{"fname":"Excalidraw Collaboration Frame.md","mtime":1687881495985},{"fname":"Excalidraw Collaboration Frame.svg","mtime":1687881495985},{"fname":"Create DrawIO file.md","mtime":1688243858267},{"fname":"Create DrawIO file.svg","mtime":1688243858267},{"fname":"Ellipse Selected Elements.md","mtime":1690131476331},{"fname":"Ellipse Selected Elements.svg","mtime":1690131476331},{"fname":"Select Similar Elements.md","mtime":1691270949338},{"fname":"Select Similar Elements.svg","mtime":1691270949338},{"fname":"Toggle Grid.md","mtime":1692125382945},{"fname":"Toggle Grid.svg","mtime":1692124753386},{"fname":"Split Ellipse.md","mtime":1693134104356},{"fname":"Split Ellipse.svg","mtime":1693134104356},{"fname":"Text Aura.md","mtime":1693731979540},{"fname":"Text Aura.svg","mtime":1693731979540},{"fname":"Boolean Operations.md","mtime":1695746839537},{"fname":"Boolean Operations.svg","mtime":1695746839537},{"fname":"Concatenate lines.md","mtime":1696175301525},{"fname":"Concatenate lines.svg","mtime":1696175301525},{"fname":"GPT-Draw-a-UI.md","mtime":1700892334215},{"fname":"GPT-Draw-a-UI.svg","mtime":1700511998048},{"fname":"ExcaliAI.md","mtime":1701011028767},{"fname":"ExcaliAI.svg","mtime":1701011028767}]
\ No newline at end of file
+[{"fname":"Mindmap connector.md","mtime":1658686599427},{"fname":"Mindmap connector.svg","mtime":1658686599427},{"fname":"Add Connector Point.md","mtime":1645305706000},{"fname":"Add Connector Point.svg","mtime":1645944722000},{"fname":"Add Link to Existing File and Open.md","mtime":1647807918345},{"fname":"Add Link to Existing File and Open.svg","mtime":1645964261000},{"fname":"Add Link to New Page and Open.md","mtime":1654168862138},{"fname":"Add Link to New Page and Open.svg","mtime":1645960639000},{"fname":"Add Next Step in Process.md","mtime":1688304760357},{"fname":"Add Next Step in Process.svg","mtime":1645960639000},{"fname":"Box Each Selected Groups.md","mtime":1645305706000},{"fname":"Box Each Selected Groups.svg","mtime":1645967510000},{"fname":"Box Selected Elements.md","mtime":1645305706000},{"fname":"Box Selected Elements.svg","mtime":1645960639000},{"fname":"Change shape of selected elements.md","mtime":1652701169236},{"fname":"Change shape of selected elements.svg","mtime":1645960775000},{"fname":"Connect elements.md","mtime":1645305706000},{"fname":"Connect elements.svg","mtime":1645960639000},{"fname":"Convert freedraw to line.md","mtime":1645305706000},{"fname":"Convert freedraw to line.svg","mtime":1645960639000},{"fname":"Convert selected text elements to sticky notes.md","mtime":1670169501383},{"fname":"Convert selected text elements to sticky notes.svg","mtime":1645960639000},{"fname":"Convert text to link with folder and alias.md","mtime":1641639819000},{"fname":"Convert text to link with folder and alias.svg","mtime":1645960639000},{"fname":"Copy Selected Element Styles to Global.md","mtime":1642232088000},{"fname":"Copy Selected Element Styles to Global.svg","mtime":1645960639000},{"fname":"Create new markdown file and embed into active drawing.md","mtime":1640866935000},{"fname":"Create new markdown file and embed into active drawing.svg","mtime":1645960639000},{"fname":"Darken background color.md","mtime":1663059051059},{"fname":"Darken background color.svg","mtime":1645960639000},{"fname":"Elbow connectors.md","mtime":1671126911490},{"fname":"Elbow connectors.svg","mtime":1645960639000},{"fname":"Expand rectangles horizontally keep text centered.md","mtime":1646563692000},{"fname":"Expand rectangles horizontally keep text centered.svg","mtime":1645967510000},{"fname":"Expand rectangles horizontally.md","mtime":1644950235000},{"fname":"Expand rectangles horizontally.svg","mtime":1645967510000},{"fname":"Expand rectangles vertically keep text centered.md","mtime":1646563692000},{"fname":"Expand rectangles vertically keep text centered.svg","mtime":1645967510000},{"fname":"Expand rectangles vertically.md","mtime":1658686599427},{"fname":"Expand rectangles vertically.svg","mtime":1645967510000},{"fname":"Fixed horizontal distance between centers.md","mtime":1646743234000},{"fname":"Fixed horizontal distance between centers.svg","mtime":1645960639000},{"fname":"Fixed inner distance.md","mtime":1646743234000},{"fname":"Fixed inner distance.svg","mtime":1645960639000},{"fname":"Fixed spacing.md","mtime":1646743234000},{"fname":"Fixed spacing.svg","mtime":1645967510000},{"fname":"Fixed vertical distance between centers.md","mtime":1646743234000},{"fname":"Fixed vertical distance between centers.svg","mtime":1645967510000},{"fname":"Fixed vertical distance.md","mtime":1646743234000},{"fname":"Fixed vertical distance.svg","mtime":1645967510000},{"fname":"Lighten background color.md","mtime":1663059051059},{"fname":"Lighten background color.svg","mtime":1645959546000},{"fname":"Modify background color opacity.md","mtime":1644924415000},{"fname":"Modify background color opacity.svg","mtime":1645944722000},{"fname":"Normalize Selected Arrows.md","mtime":1670403743278},{"fname":"Normalize Selected Arrows.svg","mtime":1645960639000},{"fname":"Organic Line.md","mtime":1672920172531},{"fname":"Organic Line.svg","mtime":1645964261000},{"fname":"Organic Line Legacy.md","mtime":1690607372668},{"fname":"Organic Line Legacy.svg","mtime":1690607372668},{"fname":"README.md","mtime":1645175700000},{"fname":"Repeat Elements.md","mtime":1663059051059},{"fname":"Repeat Elements.svg","mtime":1645960639000},{"fname":"Reverse arrows.md","mtime":1645305706000},{"fname":"Reverse arrows.svg","mtime":1645960639000},{"fname":"Scribble Helper.md","mtime":1682228345043},{"fname":"Scribble Helper.svg","mtime":1645944722000},{"fname":"Select Elements of Type.md","mtime":1643464321000},{"fname":"Select Elements of Type.svg","mtime":1645960639000},{"fname":"Set Dimensions.md","mtime":1645305706000},{"fname":"Set Dimensions.svg","mtime":1645944722000},{"fname":"Set Font Family.md","mtime":1645305706000},{"fname":"Set Font Family.svg","mtime":1645944722000},{"fname":"Set Grid.md","mtime":1693725826368},{"fname":"Set Grid.svg","mtime":1645960639000},{"fname":"Set Link Alias.md","mtime":1645305706000},{"fname":"Set Link Alias.svg","mtime":1645960639000},{"fname":"Set Stroke Width of Selected Elements.md","mtime":1645305706000},{"fname":"Set Stroke Width of Selected Elements.svg","mtime":1645960639000},{"fname":"Set Text Alignment.md","mtime":1645305706000},{"fname":"Set Text Alignment.svg","mtime":1645960639000},{"fname":"Set background color of unclosed line object by adding a shadow clone.md","mtime":1681665030892},{"fname":"Set background color of unclosed line object by adding a shadow clone.svg","mtime":1645960639000},{"fname":"Split text by lines.md","mtime":1645305706000},{"fname":"Split text by lines.svg","mtime":1645944722000},{"fname":"Zoom to Fit Selected Elements.md","mtime":1640770602000},{"fname":"Zoom to Fit Selected Elements.svg","mtime":1645960639000},{"fname":"directory-info.json","mtime":1646583437000},{"fname":"index-new.md","mtime":1645986149000},{"fname":"index.md","mtime":1645175700000},{"fname":"Grid Selected Images.md","mtime":1649614401982},{"fname":"Grid Selected Images.svg","mtime":1649614401982},{"fname":"Palette loader.md","mtime":1686511890942},{"fname":"Palette loader.svg","mtime":1649614401982},{"fname":"Rename Image.md","mtime":1663678478785},{"fname":"Rename Image.svg","mtime":1663678478785},{"fname":"Text Arch.md","mtime":1664095143846},{"fname":"Text Arch.svg","mtime":1670403743278},{"fname":"Deconstruct selected elements into new drawing.md","mtime":1693733190088},{"fname":"Deconstruct selected elements into new drawing.svg","mtime":1668541145255},{"fname":"Slideshow.md","mtime":1700511998048},{"fname":"Slideshow.svg","mtime":1670017348333},{"fname":"Auto Layout.md","mtime":1670403743278},{"fname":"Auto Layout.svg","mtime":1670175947081},{"fname":"Uniform size.md","mtime":1670175947081},{"fname":"Uniform size.svg","mtime":1670175947081},{"fname":"Mindmap format.md","mtime":1684484694228},{"fname":"Mindmap format.svg","mtime":1674944958059},{"fname":"Text to Sticky Notes.md","mtime":1678537561724},{"fname":"Text to Sticky Notes.svg","mtime":1678537561724},{"fname":"Folder Note Core - Make Current Drawing a Folder.md","mtime":1678973697470},{"fname":"Folder Note Core - Make Current Drawing a Folder.svg","mtime":1678973697470},{"fname":"Invert colors.md","mtime":1678973697470},{"fname":"Invert colors.svg","mtime":1678973697470},{"fname":"Auto Draw for Pen.md","mtime":1680418321236},{"fname":"Auto Draw for Pen.svg","mtime":1680418321236},{"fname":"Hardware Eraser Support.md","mtime":1680418321236},{"fname":"Hardware Eraser Support.svg","mtime":1680418321236},{"fname":"PDF Page Text to Clipboard.md","mtime":1683984041712},{"fname":"PDF Page Text to Clipboard.svg","mtime":1680418321236},{"fname":"Excalidraw Collaboration Frame.md","mtime":1687881495985},{"fname":"Excalidraw Collaboration Frame.svg","mtime":1687881495985},{"fname":"Create DrawIO file.md","mtime":1688243858267},{"fname":"Create DrawIO file.svg","mtime":1688243858267},{"fname":"Ellipse Selected Elements.md","mtime":1690131476331},{"fname":"Ellipse Selected Elements.svg","mtime":1690131476331},{"fname":"Select Similar Elements.md","mtime":1691270949338},{"fname":"Select Similar Elements.svg","mtime":1691270949338},{"fname":"Toggle Grid.md","mtime":1692125382945},{"fname":"Toggle Grid.svg","mtime":1692124753386},{"fname":"Split Ellipse.md","mtime":1693134104356},{"fname":"Split Ellipse.svg","mtime":1693134104356},{"fname":"Text Aura.md","mtime":1693731979540},{"fname":"Text Aura.svg","mtime":1693731979540},{"fname":"Boolean Operations.md","mtime":1695746839537},{"fname":"Boolean Operations.svg","mtime":1695746839537},{"fname":"Concatenate lines.md","mtime":1696175301525},{"fname":"Concatenate lines.svg","mtime":1696175301525},{"fname":"GPT-Draw-a-UI.md","mtime":1701018386413},{"fname":"GPT-Draw-a-UI.svg","mtime":1700511998048},{"fname":"ExcaliAI.md","mtime":1701018386413},{"fname":"ExcaliAI.svg","mtime":1701011028767}]
\ No newline at end of file
diff --git a/ea-scripts/index-new.md b/ea-scripts/index-new.md
index d9a9028..5a9de2d 100644
--- a/ea-scripts/index-new.md
+++ b/ea-scripts/index-new.md
@@ -356,14 +356,13 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.md
```
-
-
+
## GPT Draw-a-UI
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
```
-
+Author @zsviczian Source File on GitHub Description This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.VIDEO
## Hardware Eraser Support
diff --git a/manifest.json b/manifest.json
index 24117f6..5d3aa4b 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
- "version": "2.0.3",
+ "version": "2.0.4",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
diff --git a/package.json b/package.json
index 0b08d78..c85b60d 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"author": "",
"license": "MIT",
"dependencies": {
- "@zsviczian/excalidraw": "0.16.1-obsidian-8",
+ "@zsviczian/excalidraw": "0.17.0-obsidian-2",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"colormaster": "^1.2.1",
diff --git a/rollup.config.js b/rollup.config.js
index 5d26200..4d3bdc0 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -78,7 +78,10 @@ const BUILD_CONFIG = {
nodeResolve({ browser: true, preferBuiltins: false }),
...isProd
? [
- terser({toplevel: false, compress: {passes: 2}}),
+ terser({
+ toplevel: false,
+ compress: {passes: 2}
+ }),
//!postprocess - the version available on npmjs does not work, need this update:
// npm install brettz9/rollup-plugin-postprocess#update --save-dev
// https://github.com/developit/rollup-plugin-postprocess/issues/10
diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts
index 4c693ef..c7706e1 100644
--- a/src/EmbeddedFileLoader.ts
+++ b/src/EmbeddedFileLoader.ts
@@ -8,7 +8,7 @@ import {
ASSISTANT_FONT,
CASCADIA_FONT,
VIRGIL_FONT,
-} from "./constFonts";
+} from "./constants/constFonts";
import {
DEFAULT_MD_EMBED_CSS,
fileid,
@@ -19,7 +19,7 @@ import {
IMAGE_TYPES,
nanoid,
THEME_FILTER,
-} from "./constants";
+} from "./constants/constants";
import { createSVG } from "./ExcalidrawAutomate";
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
import { ExportSettings } from "./ExcalidrawView";
@@ -43,7 +43,7 @@ import {
} from "./utils/Utils";
import { ValueOf } from "./types";
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
-import { mermaidToExcalidraw } from "src/constants";
+import { mermaidToExcalidraw } from "src/constants/constants";
//An ugly workaround for the following situation.
//File A is a markdown file that has an embedded Excalidraw file B
diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts
index 2beabbe..2177a56 100644
--- a/src/ExcalidrawAutomate.ts
+++ b/src/ExcalidrawAutomate.ts
@@ -11,7 +11,7 @@ import {
StrokeRoundness,
RoundnessType,
} from "@zsviczian/excalidraw/types/element/types";
-import { Editor, normalizePath, Notice, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
+import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
import * as obsidian_module from "obsidian";
import ExcalidrawView, { ExportSettings, TextMode } from "src/ExcalidrawView";
import { ExcalidrawData, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
@@ -34,8 +34,8 @@ import {
REG_LINKINDEX_INVALIDCHARS,
THEME_FILTER,
mermaidToExcalidraw,
-} from "src/constants";
-import { getDrawingFilename, getNewUniqueFilepath, } from "src/utils/FileUtils";
+} from "src/constants/constants";
+import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getNewUniqueFilepath, } from "src/utils/FileUtils";
import {
//debug,
embedFontsInSVG,
@@ -74,13 +74,19 @@ import RYBPlugin from "colormaster/plugins/ryb";
import CMYKPlugin from "colormaster/plugins/cmyk";
import { TInput } from "colormaster/types";
import {ConversionResult, svgToExcalidraw} from "src/svgToExcalidraw/parser"
-import { ROUNDNESS } from "src/constants";
+import { ROUNDNESS } from "src/constants/constants";
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
import PolyBool from "polybooljs";
import { compressToBase64, decompressFromBase64 } from "lz-string";
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
+import {
+ AIRequest,
+ postOpenAI as _postOpenAI,
+ extractCodeBlocks as _extractCodeBlocks,
+} from "./utils/AIUtils";
+import ExcalidrawScene from "./svgToExcalidraw/elements/ExcalidrawScene";
extendPlugins([
HarmonyPlugin,
@@ -117,7 +123,73 @@ export class ExcalidrawAutomate {
get DEVICE():DeviceType {
return DEVICE;
}
+
+ /**
+ * Post's an AI request to the OpenAI API and returns the response.
+ * @param request
+ * @returns
+ */
+ public async postOpenAI (request: AIRequest): Promise {
+ return await _postOpenAI(request);
+ }
+
+ /**
+ * Grabs the codeblock contents from the supplied markdown string.
+ * @param markdown
+ * @param codeblockType
+ * @returns an array of dictionaries with the codeblock contents and type
+ */
+ public extractCodeBlocks(markdown: string): { data: string, type: string }[] {
+ return _extractCodeBlocks(markdown);
+ }
+
+ /**
+ * converts a string to a DataURL
+ * @param htmlString
+ * @returns dataURL
+ */
+ public async convertStringToDataURL (data:string, type: string = "text/html"):Promise {
+ // Create a blob from the HTML string
+ const blob = new Blob([data], { type });
+ // Read the blob as Data URL
+ const base64String = await new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.onload = () => {
+ if(typeof reader.result === "string") {
+ const base64String = reader.result.split(',')[1];
+ resolve(base64String);
+ } else {
+ resolve(null);
+ }
+ };
+ reader.readAsDataURL(blob);
+ });
+ if(base64String) {
+ return `data:${type};base64,${base64String}`;
+ }
+ return "about:blank";
+ }
+
+ /**
+ * Checks if the folder exists, if not, creates it.
+ * @param folderpath
+ * @returns
+ */
+ public async checkAndCreateFolder(folderpath: string): Promise {
+ return await checkAndCreateFolder(folderpath);
+ }
+
+ /**
+ * Checks if the filepath already exists, if so, returns a new filepath with a number appended to the filename.
+ * @param filename
+ * @param folderpath
+ * @returns
+ */
+ public getNewUniqueFilepath(filename: string, folderpath: string): string {
+ return getNewUniqueFilepath(app.vault, filename, folderpath);
+ }
+
public async getAttachmentFilepath(filename: string): Promise {
if (!this.targetView || !this.targetView?.file) {
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
@@ -653,6 +725,7 @@ export class ExcalidrawAutomate {
);
};
+
/**
*
* @param templatePath
@@ -708,6 +781,28 @@ export class ExcalidrawAutomate {
);
};
+ /**
+ * Wrapper for createPNG() that returns a base64 encoded string
+ * @param templatePath
+ * @param scale
+ * @param exportSettings
+ * @param loader
+ * @param theme
+ * @param padding
+ * @returns
+ */
+ async createPNGBase64(
+ templatePath?: string,
+ scale: number = 1,
+ exportSettings?: ExportSettings,
+ loader?: EmbeddedFilesLoader,
+ theme?: string,
+ padding?: number,
+ ): Promise {
+ const png = await this.createPNG(templatePath,scale,exportSettings,loader,theme,padding);
+ return `data:image/png;base64,${await blobToBase64(png)}`
+ }
+
/**
*
* @param text
@@ -1168,11 +1263,13 @@ export class ExcalidrawAutomate {
/**
* Adds a mermaid diagram to ExcalidrawAutomate elements
- * @param diagram
+ * @param diagram string containing the mermaid diagram
+ * @param groupElements default is trud. If true, the elements will be grouped
* @returns the ids of the elements that were created
*/
async addMermaid(
diagram: string,
+ groupElements: boolean = true,
): Promise {
const result = await mermaidToExcalidraw(diagram, {fontSize: this.style.fontSize});
const ids:string[] = [];
@@ -1198,6 +1295,10 @@ export class ExcalidrawAutomate {
}
}
}
+
+ if(groupElements && result?.elements && ids.length > 1) {
+ this.addToGroup(ids);
+ }
return ids;
}
@@ -1647,10 +1748,26 @@ export class ExcalidrawAutomate {
* copies elements from view to elementsDict for editing
* @param elements
*/
- copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void {
- elements.forEach((el) => {
- this.elementsDict[el.id] = cloneElement(el);
- });
+ copyViewElementsToEAforEditing(elements: ExcalidrawElement[], copyImages: boolean = false): void {
+ if(copyImages && elements.some(el=>el.type === "image")) {
+ //@ts-ignore
+ if (!this.targetView || !this.targetView?._loaded) {
+ errorMessage("targetView not set", "copyViewElementsToEAforEditing()");
+ return;
+ }
+ const sceneFiles = this.targetView.getScene().files;
+ elements.forEach((el) => {
+ this.elementsDict[el.id] = cloneElement(el);
+ if(el.type === "image") {
+ this.imagesDict[el.fileId] = sceneFiles?.[el.fileId];
+ }
+ });
+
+ } else {
+ elements.forEach((el) => {
+ this.elementsDict[el.id] = cloneElement(el);
+ });
+ }
};
/**
diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts
index 312ef0c..8454824 100644
--- a/src/ExcalidrawData.ts
+++ b/src/ExcalidrawData.ts
@@ -25,7 +25,7 @@ import {
wrapText,
ERROR_IFRAME_CONVERSION_CANCELED,
JSON_parse,
-} from "./constants";
+} from "./constants/constants";
import { _measureText } from "./ExcalidrawAutomate";
import ExcalidrawPlugin from "./main";
import { TextMode } from "./ExcalidrawView";
@@ -287,7 +287,7 @@ export class ExcalidrawData {
const elements = this.scene.elements;
for (const el of elements) {
- if(el.type === "iframe") {
+ if(el.type === "iframe" && !el.customData) {
el.type = "embeddable";
}
@@ -515,7 +515,7 @@ export class ExcalidrawData {
}
//once off migration of legacy scenes
- if(this.scene?.elements?.some((el:any)=>el.type==="iframe")) {
+ if(this.scene?.elements?.some((el:any)=>el.type==="iframe" && !el.customData)) {
const prompt = new ConfirmationPrompt(
this.plugin,
"This file contains embedded frames " +
diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts
index 2c3e1c3..b5bfcd2 100644
--- a/src/ExcalidrawView.ts
+++ b/src/ExcalidrawView.ts
@@ -9,6 +9,7 @@ import {
MarkdownView,
request,
requireApiVersion,
+ requestUrl,
} from "obsidian";
//import * as React from "react";
//import * as ReactDOM from "react-dom";
@@ -50,7 +51,7 @@ import {
restore,
obsidianToExcalidrawMap,
MAX_IMAGE_SIZE,
-} from "./constants";
+} from "./constants/constants";
import ExcalidrawPlugin from "./main";
import {
repositionElementsToCursor,
@@ -129,6 +130,8 @@ import { useDefaultExcalidrawFrame } from "./utils/CustomEmbeddableUtils";
import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal";
import { getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
import { nanoid } from "nanoid";
+import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
+import { extractCodeBlocks, postOpenAI } from "./utils/AIUtils";
declare const PLUGIN_VERSION:string;
@@ -243,7 +246,7 @@ export default class ExcalidrawView extends TextFileView {
public excalidrawWrapperRef: React.MutableRefObject = null;
public toolsPanelRef: React.MutableRefObject = null;
public embeddableMenuRef: React.MutableRefObject = null;
- private parentMoveObserver: MutationObserver;
+ private parentMoveObserver: MutationObserver | CustomMutationObserver;
public linksAlwaysOpenInANewPane: boolean = false; //override the need for SHIFT+CTRL+click (used by ExcaliBrain)
public allowFrameButtonsInViewMode: boolean = false; //override for ExcaliBrain
private hookServer: ExcalidrawAutomate;
@@ -1027,7 +1030,7 @@ export default class ExcalidrawView extends TextFileView {
if (this.excalidrawData.hasMermaid(selectedImage.fileId) || getMermaidText(imageElement)) {
if(shouldRenderMermaid) {
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
- api.updateScene({appState: { openDialog: "mermaid" }});
+ api.updateScene({appState: {openDialog: { name: "ttd", tab: "mermaid" }}})
}
return;
}
@@ -1337,22 +1340,23 @@ export default class ExcalidrawView extends TextFileView {
this.offsetLeft = parent.offsetLeft;
this.offsetTop = parent.offsetTop;
const self = this;
- this.parentMoveObserver = new MutationObserver(
- async (m: MutationRecord[]) => {
- const target = m[0].target;
- if (!(target instanceof HTMLElement)) {
- return;
+ const observerFn = async (m: MutationRecord[]) => {
+ const target = m[0].target;
+ if (!(target instanceof HTMLElement)) {
+ return;
+ }
+ const { offsetLeft, offsetTop } = target;
+ if (offsetLeft !== self.offsetLeft || offsetTop != self.offsetTop) {
+ if (self.refresh) {
+ self.refresh();
}
- const { offsetLeft, offsetTop } = target;
- if (offsetLeft !== self.offsetLeft || offsetTop != self.offsetTop) {
- if (self.refresh) {
- self.refresh();
- }
- self.offsetLeft = offsetLeft;
- self.offsetTop = offsetTop;
- }
- },
- );
+ self.offsetLeft = offsetLeft;
+ self.offsetTop = offsetTop;
+ }
+ };
+ this.parentMoveObserver = isDebugMode
+ ? new CustomMutationObserver(observerFn, "parentMoveObserver")
+ : new MutationObserver(observerFn)
this.parentMoveObserver.observe(parent, {
attributeOldValue: true,
@@ -2140,7 +2144,7 @@ export default class ExcalidrawView extends TextFileView {
}
public setDirty(debug?:number) {
- //console.log(debug);
+ if(isDebugMode) console.log(debug);
this.semaphores.dirty = this.file?.path;
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
if(!this.semaphores.viewunload && this.toolsPanelRef?.current) {
@@ -3155,7 +3159,9 @@ export default class ExcalidrawView extends TextFileView {
const {
Excalidraw,
MainMenu,
- WelcomeScreen
+ WelcomeScreen,
+ TTDDialogTrigger,
+ TTDDialog,
} = this.plugin.getPackage(this.ownerWindow).excalidrawLib;
const onKeyDown = (e: any) => {
@@ -3337,7 +3343,7 @@ export default class ExcalidrawView extends TextFileView {
}
if(st.theme !== this.previousTheme && this.file === this.excalidrawData.file) {
this.previousTheme = st.theme;
- this.setDirty(5);
+ this.setDirty(5.1);
}
if(st.viewBackgroundColor !== this.previousBackgroundColor && this.file === this.excalidrawData.file) {
this.previousBackgroundColor = st.viewBackgroundColor;
@@ -3369,7 +3375,7 @@ export default class ExcalidrawView extends TextFileView {
sceneVersion !== this.previousSceneVersion)
) {
this.previousSceneVersion = sceneVersion;
- this.setDirty(6);
+ this.setDirty(6.1);
}
}
}
@@ -3923,7 +3929,7 @@ export default class ExcalidrawView extends TextFileView {
const modal = new UniversalInsertFileModal(this.plugin, this);
modal.open(file, center);
}
- this.setDirty();
+ this.setDirty(9);
}
});
return [null, null, null];
@@ -3967,7 +3973,7 @@ export default class ExcalidrawView extends TextFileView {
elements[elements.indexOf(el[0])] = clone;
this.updateScene({elements});
if(clone.containerId) this.updateContainerSize(clone.containerId);
- this.setDirty();
+ this.setDirty(8.1);
}
api.history.clear();
};
@@ -4203,7 +4209,8 @@ export default class ExcalidrawView extends TextFileView {
}
}
-
+
+ const self = this;
const renderEmbeddable = (
element: NonDeletedExcalidrawElement,
appState: UIAppState,
@@ -4211,13 +4218,13 @@ export default class ExcalidrawView extends TextFileView {
try {
const useExcalidrawFrame = useDefaultExcalidrawFrame(element);
- if(!this.file || !element || !element.link || element.link.length === 0 || useExcalidrawFrame) {
+ if(!self.file || !element || !element.link || element.link.length === 0 || useExcalidrawFrame) {
return null;
}
- if(element.link.match(REG_LINKINDEX_HYPERLINK)) {
+ if(element.link.match(REG_LINKINDEX_HYPERLINK) || element.link.startsWith("data:")) {
if(!useExcalidrawFrame) {
- return renderWebView(element.link, this, element.id, appState);
+ return renderWebView(element.link, self, element.id, appState);
} else {
return null;
}
@@ -4232,13 +4239,13 @@ export default class ExcalidrawView extends TextFileView {
if(linkText.match(REG_LINKINDEX_HYPERLINK)) {
if(!useExcalidrawFrame) {
- return renderWebView(linkText, this, element.id, appState);
+ return renderWebView(linkText, self, element.id, appState);
} else {
return null;
}
}
- return React.createElement(CustomEmbeddable, {element,view:this, appState, linkText});
+ return React.createElement(CustomEmbeddable, {element,view:self, appState, linkText});
} catch(e) {
return null;
}
@@ -4314,6 +4321,66 @@ export default class ExcalidrawView extends TextFileView {
React.createElement(MainMenu.DefaultItems.ClearCanvas),
);
+ const ttdDialog = () => React.createElement(
+ TTDDialog,
+ {
+ onTextSubmit: async (input:string) => {
+ try {
+ const response = await postOpenAI({
+ systemPrompt: "The user will provide you with a text prompt. Your task is to generate a mermaid graph based on the prompt. Use the graph, sequence-diagram or flowchart type based on what best fits the request. Return a single message containing only the mermaid diagram in a codeblock. Avoid the use of () parenthesis in the mermaid script.",
+ text: input,
+ instruction: "Return a single message containing only the mermaid diagram in a codeblock.",
+ })
+
+ if(!response) {
+ return {
+ error: new Error("Request failed"),
+ };
+ }
+
+ const json = response.json;
+ if (isDebugMode) console.log(response);
+
+ if (json?.error) {
+ console.log(response);
+ return {
+ error: new Error(json.error.message),
+ };
+ }
+
+ if(!json?.choices?.[0]?.message?.content) {
+ console.log(response);
+ return {
+ error: new Error("Generation failed... see console log for details"),
+ };
+ }
+
+ let generatedResponse = extractCodeBlocks(json.choices[0]?.message?.content)[0]?.data;
+
+ if(!generatedResponse) {
+ console.log(response);
+ return {
+ error: new Error("Generation failed... see console log for details"),
+ };
+ }
+
+ if(generatedResponse.startsWith("mermaid")) {
+ generatedResponse = generatedResponse.replace(/^mermaid/,"").trim();
+ }
+
+ return { generatedResponse, rateLimit:100, rateLimitRemaining:100 };
+ } catch (err: any) {
+ throw new Error("Request failed");
+ }
+ },
+ }
+ );
+
+ const ttdDialogTrigger = () => React.createElement(
+ TTDDialogTrigger,
+ {},
+ )
+
const renderWelcomeScreen = () => React.createElement(
WelcomeScreen,
{},
@@ -4467,6 +4534,7 @@ export default class ExcalidrawView extends TextFileView {
libraryReturnUrl: "app://obsidian.md",
autoFocus: true,
langCode: obsidianToExcalidrawMap[this.plugin.locale]??"en-EN",
+ aiEnabled: true,
onChange,
onLibraryChange,
// TODO: Potentially better way to block middle mouse paste on linux:
@@ -4491,6 +4559,8 @@ export default class ExcalidrawView extends TextFileView {
},//,React.createElement(Footer,{},React.createElement(customTextEditor.render)),
renderCustomActionsMenu(),
renderWelcomeScreen(),
+ ttdDialog(),
+ ttdDialogTrigger(),
),
renderToolsPanel(),
)
diff --git a/src/LaTeX.ts b/src/LaTeX.ts
index 16cb1f0..eed1ab8 100644
--- a/src/LaTeX.ts
+++ b/src/LaTeX.ts
@@ -4,7 +4,7 @@ import ExcalidrawPlugin from "./main";
import { FileData, MimeType } from "./EmbeddedFileLoader";
import { FileId } from "@zsviczian/excalidraw/types/element/types";
import { errorlog, getImageSize, log, sleep, svgToBase64 } from "./utils/Utils";
-import { fileid } from "./constants";
+import { fileid } from "./constants/constants";
import html2canvas from "html2canvas";
import { Notice } from "obsidian";
diff --git a/src/MarkdownPostProcessor.ts b/src/MarkdownPostProcessor.ts
index b691423..f81c5e8 100644
--- a/src/MarkdownPostProcessor.ts
+++ b/src/MarkdownPostProcessor.ts
@@ -4,7 +4,7 @@ import {
TFile,
Vault,
} from "obsidian";
-import { RERENDER_EVENT } from "./constants";
+import { RERENDER_EVENT } from "./constants/constants";
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
import { createPNG, createSVG } from "./ExcalidrawAutomate";
import { ExportSettings } from "./ExcalidrawView";
@@ -24,6 +24,7 @@ import { getParentOfClass, isObsidianThemeDark, getFileCSSClasses } from "./util
import { linkClickModifierType } from "./utils/ModifierkeyHelper";
import { ImageKey, imageCache } from "./utils/ImageCache";
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
+import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
interface imgElementAttributes {
file?: TFile;
@@ -627,7 +628,7 @@ const tmpObsidianWYSIWYG = async (
//timer to avoid the image flickering when the user is typing
let timer: NodeJS.Timeout = null;
- const observer = new MutationObserver((m) => {
+ const markdownObserverFn: MutationCallback = (m) => {
if (!["alt", "width", "height"].contains(m[0]?.attributeName)) {
return;
}
@@ -640,7 +641,10 @@ const tmpObsidianWYSIWYG = async (
const imgDiv = await processInternalEmbed(internalEmbedDiv,file);
internalEmbedDiv.appendChild(imgDiv);
}, 500);
- });
+ }
+ const observer = isDebugMode
+ ? new CustomMutationObserver(markdownObserverFn, "markdowPostProcessorObserverFn")
+ : new MutationObserver(markdownObserverFn);
observer.observe(internalEmbedDiv, {
attributes: true, //configure it to listen to attribute changes
});
@@ -692,13 +696,16 @@ export const hoverEvent = (e: any) => {
};
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
-export const observer = new MutationObserver(async (m) => {
- if (m.length == 0) {
+const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
+ if (m.length === 0) {
return;
}
if (!plugin.hover.linkText) {
return;
}
+ if (!plugin.hover.linkText.endsWith("excalidraw")) {
+ return;
+ }
const file = metadataCache.getFirstLinkpathDest(
plugin.hover.linkText,
plugin.hover.sourcePath ? plugin.hover.sourcePath : "",
@@ -735,9 +742,7 @@ export const observer = new MutationObserver(async (m) => {
return;
}
if (
- //@ts-ignore
- !m[0].addedNodes[0].classNames !=
- "popover hover-popover file-embed is-loaded"
+ (m[0].addedNodes[0] as HTMLElement).className !== "popover hover-popover"
) {
return;
}
@@ -768,5 +773,9 @@ export const observer = new MutationObserver(async (m) => {
});
});
node.appendChild(div);
-});
+};
+
+export const observer = isDebugMode
+ ? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn")
+ : new MutationObserver(legacyExcalidrawPopoverObserverFn);
diff --git a/src/Scripts.ts b/src/Scripts.ts
index 8f3f0f3..644d0ba 100644
--- a/src/Scripts.ts
+++ b/src/Scripts.ts
@@ -5,7 +5,7 @@ import {
TFile,
WorkspaceLeaf,
} from "obsidian";
-import { PLUGIN_ID, VIEW_TYPE_EXCALIDRAW } from "./constants";
+import { PLUGIN_ID, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./dialogs/Prompt";
@@ -43,6 +43,7 @@ export class ScriptEngine {
this.loadScript(scriptFile);
}
};
+
const deleteEventHandler = async (file: TFile) => {
if (!(file instanceof TFile)) {
return;
@@ -104,7 +105,7 @@ export class ScriptEngine {
public getListofScripts(): TFile[] {
this.scriptPath = this.plugin.settings.scriptFolderPath;
if (!app.vault.getAbstractFileByPath(this.scriptPath)) {
- this.scriptPath = null;
+ //this.scriptPath = null;
return;
}
return app.vault
diff --git a/src/constFonts.ts b/src/constants/constFonts.ts
similarity index 100%
rename from src/constFonts.ts
rename to src/constants/constFonts.ts
diff --git a/src/constMathJaxSource.ts b/src/constants/constMathJaxSource.ts
similarity index 100%
rename from src/constMathJaxSource.ts
rename to src/constants/constMathJaxSource.ts
diff --git a/src/constants.ts b/src/constants/constants.ts
similarity index 99%
rename from src/constants.ts
rename to src/constants/constants.ts
index 3108af6..142a003 100644
--- a/src/constants.ts
+++ b/src/constants/constants.ts
@@ -1,6 +1,6 @@
import { customAlphabet } from "nanoid";
-import { DeviceType } from "./types";
-import { ExcalidrawLib } from "./ExcalidrawLib";
+import { DeviceType } from "../types";
+import { ExcalidrawLib } from "../ExcalidrawLib";
import { moment } from "obsidian";
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
declare const PLUGIN_VERSION:string;
diff --git a/src/constants/startupScript.md b/src/constants/startupScript.md
new file mode 100644
index 0000000..ce04eb7
--- /dev/null
+++ b/src/constants/startupScript.md
@@ -0,0 +1,116 @@
+/*
+#exclude
+```js*/
+/**
+ * If set, this callback is triggered when the user closes an Excalidraw view.
+ * onViewUnloadHook: (view: ExcalidrawView) => void = null;
+ */
+//ea.onViewUnloadHook = (view) => {};
+
+/**
+ * If set, this callback is triggered, when the user changes the view mode.
+ * You can use this callback in case you want to do something additional when the user switches to view mode and back.
+ * onViewModeChangeHook: (isViewModeEnabled:boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void = null;
+ */
+//ea.onViewModeChangeHook = (isViewModeEnabled, view, ea) => {};
+
+/**
+ * If set, this callback is triggered, when the user hovers a link in the scene.
+ * You can use this callback in case you want to do something additional when the onLinkHover event occurs.
+ * This callback must return a boolean value.
+ * In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
+ * onLinkHoverHook: (
+ * element: NonDeletedExcalidrawElement,
+ * linkText: string,
+ * view: ExcalidrawView,
+ * ea: ExcalidrawAutomate
+ * ) => boolean = null;
+ */
+//ea.onLinkHoverHook = (element, linkText, view, ea) => {};
+
+/**
+ * If set, this callback is triggered, when the user clicks a link in the scene.
+ * You can use this callback in case you want to do something additional when the onLinkClick event occurs.
+ * This callback must return a boolean value.
+ * In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
+ * onLinkClickHook:(
+ * element: ExcalidrawElement,
+ * linkText: string,
+ * event: MouseEvent,
+ * view: ExcalidrawView,
+ * ea: ExcalidrawAutomate
+ * ) => boolean = null;
+ */
+//ea.onLinkClickHook = (element,linkText,event, view, ea) => {};
+
+/**
+ * If set, this callback is triggered, when Excalidraw receives an onDrop event.
+ * You can use this callback in case you want to do something additional when the onDrop event occurs.
+ * This callback must return a boolean value.
+ * In case you want to prevent the excalidraw onDrop action you must return false, it will stop the native excalidraw onDrop management flow.
+ * onDropHook: (data: {
+ * ea: ExcalidrawAutomate;
+ * event: React.DragEvent;
+ * draggable: any; //Obsidian draggable object
+ * type: "file" | "text" | "unknown";
+ * payload: {
+ * files: TFile[]; //TFile[] array of dropped files
+ * text: string; //string
+ * };
+ * excalidrawFile: TFile; //the file receiving the drop event
+ * view: ExcalidrawView; //the excalidraw view receiving the drop
+ * pointerPosition: { x: number; y: number }; //the pointer position on canvas at the time of drop
+ * }) => boolean = null;
+ */
+//ea.onDropHook = (data) => {};
+
+/**
+ * If set, this callback is triggered, when Excalidraw receives an onPaste event.
+ * You can use this callback in case you want to do something additional when the
+ * onPaste event occurs.
+ * This callback must return a boolean value.
+ * In case you want to prevent the excalidraw onPaste action you must return false,
+ * it will stop the native excalidraw onPaste management flow.
+ * onPasteHook: (data: {
+ * ea: ExcalidrawAutomate;
+ * payload: ClipboardData;
+ * event: ClipboardEvent;
+ * excalidrawFile: TFile; //the file receiving the paste event
+ * view: ExcalidrawView; //the excalidraw view receiving the paste
+ * pointerPosition: { x: number; y: number }; //the pointer position on canvas
+ * }) => boolean = null;
+ */
+//ea.onPasteHook = (data) => {};
+
+/**
+ * if set, this callback is triggered, when an Excalidraw file is opened
+ * You can use this callback in case you want to do something additional when the file is opened.
+ * This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
+ * onFileOpenHook: (data: {
+ * ea: ExcalidrawAutomate;
+ * excalidrawFile: TFile; //the file being loaded
+ * view: ExcalidrawView;
+ * }) => Promise;
+ */
+//ea.onFileOpenHook = (data) => {};
+
+/**
+ * if set, this callback is triggered, when an Excalidraw file is created
+ * see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
+ * onFileCreateHook: (data: {
+ * ea: ExcalidrawAutomate;
+ * excalidrawFile: TFile; //the file being created
+ * view: ExcalidrawView;
+ * }) => Promise;
+ */
+//ea.onFileCreateHook = (data) => {};
+
+/**
+ * If set, this callback is triggered whenever the active canvas color changes
+ * onCanvasColorChangeHook: (
+ * ea: ExcalidrawAutomate,
+ * view: ExcalidrawView, //the excalidraw view
+ * color: string,
+ * ) => void = null;
+ */
+//ea.onCanvasColorChangeHook = (ea, view, color) => {};
\ No newline at end of file
diff --git a/src/constants/starutpscript.ts b/src/constants/starutpscript.ts
new file mode 100644
index 0000000..c046d4b
--- /dev/null
+++ b/src/constants/starutpscript.ts
@@ -0,0 +1,7 @@
+/*
+f = app.vault.getAbstractFileByPath("startuphooks.md");
+s = await app.vault.read(f);
+ea = ExcalidrawAutomate;
+url = await ea.convertStringToDataURL(s);
+*/
+export const startupScript = () => atob("LyoKI2V4Y2x1ZGUKYGBganMqLwovKioKICogSWYgc2V0LCB0aGlzIGNhbGxiYWNrIGlzIHRyaWdnZXJlZCB3aGVuIHRoZSB1c2VyIGNsb3NlcyBhbiBFeGNhbGlkcmF3IHZpZXcuCiAqICAgb25WaWV3VW5sb2FkSG9vazogKHZpZXc6IEV4Y2FsaWRyYXdWaWV3KSA9PiB2b2lkID0gbnVsbDsKICovCi8vZWEub25WaWV3VW5sb2FkSG9vayA9ICh2aWV3KSA9PiB7fTsKCi8qKgogKiBJZiBzZXQsIHRoaXMgY2FsbGJhY2sgaXMgdHJpZ2dlcmVkLCB3aGVuIHRoZSB1c2VyIGNoYW5nZXMgdGhlIHZpZXcgbW9kZS4KICogWW91IGNhbiB1c2UgdGhpcyBjYWxsYmFjayBpbiBjYXNlIHlvdSB3YW50IHRvIGRvIHNvbWV0aGluZyBhZGRpdGlvbmFsIHdoZW4gdGhlIHVzZXIgc3dpdGNoZXMgdG8gdmlldyBtb2RlIGFuZCBiYWNrLgogKiAgIG9uVmlld01vZGVDaGFuZ2VIb29rOiAoaXNWaWV3TW9kZUVuYWJsZWQ6Ym9vbGVhbiwgdmlldzogRXhjYWxpZHJhd1ZpZXcsIGVhOiBFeGNhbGlkcmF3QXV0b21hdGUpID0+IHZvaWQgPSBudWxsOwogKi8KLy9lYS5vblZpZXdNb2RlQ2hhbmdlSG9vayA9IChpc1ZpZXdNb2RlRW5hYmxlZCwgdmlldywgZWEpID0+IHt9OwoKLyoqCiAqIElmIHNldCwgdGhpcyBjYWxsYmFjayBpcyB0cmlnZ2VyZWQsIHdoZW4gdGhlIHVzZXIgaG92ZXJzIGEgbGluayBpbiB0aGUgc2NlbmUuCiAqIFlvdSBjYW4gdXNlIHRoaXMgY2FsbGJhY2sgaW4gY2FzZSB5b3Ugd2FudCB0byBkbyBzb21ldGhpbmcgYWRkaXRpb25hbCB3aGVuIHRoZSBvbkxpbmtIb3ZlciBldmVudCBvY2N1cnMuCiAqIFRoaXMgY2FsbGJhY2sgbXVzdCByZXR1cm4gYSBib29sZWFuIHZhbHVlLgogKiBJbiBjYXNlIHlvdSB3YW50IHRvIHByZXZlbnQgdGhlIGV4Y2FsaWRyYXcgb25MaW5rSG92ZXIgYWN0aW9uIHlvdSBtdXN0IHJldHVybiBmYWxzZSwgaXQgd2lsbCBzdG9wIHRoZSBuYXRpdmUgZXhjYWxpZHJhdyBvbkxpbmtIb3ZlciBtYW5hZ2VtZW50IGZsb3cuCiAqICAgb25MaW5rSG92ZXJIb29rOiAoCiAqICAgICBlbGVtZW50OiBOb25EZWxldGVkRXhjYWxpZHJhd0VsZW1lbnQsCiAqICAgICBsaW5rVGV4dDogc3RyaW5nLAogKiAgICAgdmlldzogRXhjYWxpZHJhd1ZpZXcsCiAqICAgICBlYTogRXhjYWxpZHJhd0F1dG9tYXRlCiAqICAgKSA9PiBib29sZWFuID0gbnVsbDsKICovCi8vZWEub25MaW5rSG92ZXJIb29rID0gKGVsZW1lbnQsIGxpbmtUZXh0LCB2aWV3LCBlYSkgPT4ge307CiAgIAovKioKICogSWYgc2V0LCB0aGlzIGNhbGxiYWNrIGlzIHRyaWdnZXJlZCwgd2hlbiB0aGUgdXNlciBjbGlja3MgYSBsaW5rIGluIHRoZSBzY2VuZS4KICogWW91IGNhbiB1c2UgdGhpcyBjYWxsYmFjayBpbiBjYXNlIHlvdSB3YW50IHRvIGRvIHNvbWV0aGluZyBhZGRpdGlvbmFsIHdoZW4gdGhlIG9uTGlua0NsaWNrIGV2ZW50IG9jY3Vycy4KICogVGhpcyBjYWxsYmFjayBtdXN0IHJldHVybiBhIGJvb2xlYW4gdmFsdWUuCiAqIEluIGNhc2UgeW91IHdhbnQgdG8gcHJldmVudCB0aGUgZXhjYWxpZHJhdyBvbkxpbmtDbGljayBhY3Rpb24geW91IG11c3QgcmV0dXJuIGZhbHNlLCBpdCB3aWxsIHN0b3AgdGhlIG5hdGl2ZSBleGNhbGlkcmF3IG9uTGlua0NsaWNrIG1hbmFnZW1lbnQgZmxvdy4KICogICBvbkxpbmtDbGlja0hvb2s6KAogKiAgICAgZWxlbWVudDogRXhjYWxpZHJhd0VsZW1lbnQsCiAqICAgICBsaW5rVGV4dDogc3RyaW5nLAogKiAgICAgZXZlbnQ6IE1vdXNlRXZlbnQsCiAqICAgICB2aWV3OiBFeGNhbGlkcmF3VmlldywKICogICAgIGVhOiBFeGNhbGlkcmF3QXV0b21hdGUKICogICApID0+IGJvb2xlYW4gPSBudWxsOwogKi8KLy9lYS5vbkxpbmtDbGlja0hvb2sgPSAoZWxlbWVudCxsaW5rVGV4dCxldmVudCwgdmlldywgZWEpID0+IHt9OwogICAKLyoqCiAqIElmIHNldCwgdGhpcyBjYWxsYmFjayBpcyB0cmlnZ2VyZWQsIHdoZW4gRXhjYWxpZHJhdyByZWNlaXZlcyBhbiBvbkRyb3AgZXZlbnQuIAogKiBZb3UgY2FuIHVzZSB0aGlzIGNhbGxiYWNrIGluIGNhc2UgeW91IHdhbnQgdG8gZG8gc29tZXRoaW5nIGFkZGl0aW9uYWwgd2hlbiB0aGUgb25Ecm9wIGV2ZW50IG9jY3Vycy4KICogVGhpcyBjYWxsYmFjayBtdXN0IHJldHVybiBhIGJvb2xlYW4gdmFsdWUuCiAqIEluIGNhc2UgeW91IHdhbnQgdG8gcHJldmVudCB0aGUgZXhjYWxpZHJhdyBvbkRyb3AgYWN0aW9uIHlvdSBtdXN0IHJldHVybiBmYWxzZSwgaXQgd2lsbCBzdG9wIHRoZSBuYXRpdmUgZXhjYWxpZHJhdyBvbkRyb3AgbWFuYWdlbWVudCBmbG93LgogKiAgIG9uRHJvcEhvb2s6IChkYXRhOiB7CiAqICAgICBlYTogRXhjYWxpZHJhd0F1dG9tYXRlOwogKiAgICAgZXZlbnQ6IFJlYWN0LkRyYWdFdmVudDxIVE1MRGl2RWxlbWVudD47CiAqICAgICBkcmFnZ2FibGU6IGFueTsgLy9PYnNpZGlhbiBkcmFnZ2FibGUgb2JqZWN0CiAqICAgICB0eXBlOiAiZmlsZSIgfCAidGV4dCIgfCAidW5rbm93biI7CiAqICAgICBwYXlsb2FkOiB7CiAqICAgICAgIGZpbGVzOiBURmlsZVtdOyAvL1RGaWxlW10gYXJyYXkgb2YgZHJvcHBlZCBmaWxlcwogKiAgICAgICB0ZXh0OiBzdHJpbmc7IC8vc3RyaW5nCiAqICAgICB9OwogKiAgICAgZXhjYWxpZHJhd0ZpbGU6IFRGaWxlOyAvL3RoZSBmaWxlIHJlY2VpdmluZyB0aGUgZHJvcCBldmVudAogKiAgICAgdmlldzogRXhjYWxpZHJhd1ZpZXc7IC8vdGhlIGV4Y2FsaWRyYXcgdmlldyByZWNlaXZpbmcgdGhlIGRyb3AKICogICAgIHBvaW50ZXJQb3NpdGlvbjogeyB4OiBudW1iZXI7IHk6IG51bWJlciB9OyAvL3RoZSBwb2ludGVyIHBvc2l0aW9uIG9uIGNhbnZhcyBhdCB0aGUgdGltZSBvZiBkcm9wCiAqICAgfSkgPT4gYm9vbGVhbiA9IG51bGw7CiAqLwovL2VhLm9uRHJvcEhvb2sgPSAoZGF0YSkgPT4ge307CiAKLyoqCiAqIElmIHNldCwgdGhpcyBjYWxsYmFjayBpcyB0cmlnZ2VyZWQsIHdoZW4gRXhjYWxpZHJhdyByZWNlaXZlcyBhbiBvblBhc3RlIGV2ZW50LgogKiBZb3UgY2FuIHVzZSB0aGlzIGNhbGxiYWNrIGluIGNhc2UgeW91IHdhbnQgdG8gZG8gc29tZXRoaW5nIGFkZGl0aW9uYWwgd2hlbiB0aGUKICogb25QYXN0ZSBldmVudCBvY2N1cnMuCiAqIFRoaXMgY2FsbGJhY2sgbXVzdCByZXR1cm4gYSBib29sZWFuIHZhbHVlLgogKiBJbiBjYXNlIHlvdSB3YW50IHRvIHByZXZlbnQgdGhlIGV4Y2FsaWRyYXcgb25QYXN0ZSBhY3Rpb24geW91IG11c3QgcmV0dXJuIGZhbHNlLAogKiBpdCB3aWxsIHN0b3AgdGhlIG5hdGl2ZSBleGNhbGlkcmF3IG9uUGFzdGUgbWFuYWdlbWVudCBmbG93LgogKiAgIG9uUGFzdGVIb29rOiAoZGF0YTogewogKiAgICAgZWE6IEV4Y2FsaWRyYXdBdXRvbWF0ZTsKICogICAgIHBheWxvYWQ6IENsaXBib2FyZERhdGE7CiAqICAgICBldmVudDogQ2xpcGJvYXJkRXZlbnQ7CiAqICAgICBleGNhbGlkcmF3RmlsZTogVEZpbGU7IC8vdGhlIGZpbGUgcmVjZWl2aW5nIHRoZSBwYXN0ZSBldmVudAogKiAgICAgdmlldzogRXhjYWxpZHJhd1ZpZXc7IC8vdGhlIGV4Y2FsaWRyYXcgdmlldyByZWNlaXZpbmcgdGhlIHBhc3RlCiAqICAgICBwb2ludGVyUG9zaXRpb246IHsgeDogbnVtYmVyOyB5OiBudW1iZXIgfTsgLy90aGUgcG9pbnRlciBwb3NpdGlvbiBvbiBjYW52YXMKICogICB9KSA9PiBib29sZWFuID0gbnVsbDsKICovCi8vZWEub25QYXN0ZUhvb2sgPSAoZGF0YSkgPT4ge307CgovKioKICogaWYgc2V0LCB0aGlzIGNhbGxiYWNrIGlzIHRyaWdnZXJlZCwgd2hlbiBhbiBFeGNhbGlkcmF3IGZpbGUgaXMgb3BlbmVkCiAqIFlvdSBjYW4gdXNlIHRoaXMgY2FsbGJhY2sgaW4gY2FzZSB5b3Ugd2FudCB0byBkbyBzb21ldGhpbmcgYWRkaXRpb25hbCB3aGVuIHRoZSBmaWxlIGlzIG9wZW5lZC4KICogVGhpcyB3aWxsIHJ1biBiZWZvcmUgdGhlIGZpbGUgbGV2ZWwgc2NyaXB0IGRlZmluZWQgaW4gdGhlIGBleGNhbGlkcmF3LW9ubG9hZC1zY3JpcHRgIGZyb250bWF0dGVyLgogKiAgIG9uRmlsZU9wZW5Ib29rOiAoZGF0YTogewogKiAgICAgZWE6IEV4Y2FsaWRyYXdBdXRvbWF0ZTsKICogICAgIGV4Y2FsaWRyYXdGaWxlOiBURmlsZTsgLy90aGUgZmlsZSBiZWluZyBsb2FkZWQKICogICAgIHZpZXc6IEV4Y2FsaWRyYXdWaWV3OwogKiAgIH0pID0+IFByb21pc2U8dm9pZD47CiAqLwovL2VhLm9uRmlsZU9wZW5Ib29rID0gKGRhdGEpID0+IHt9OwoKLyoqCiAqIGlmIHNldCwgdGhpcyBjYWxsYmFjayBpcyB0cmlnZ2VyZWQsIHdoZW4gYW4gRXhjYWxpZHJhdyBmaWxlIGlzIGNyZWF0ZWQKICogc2VlIGFsc286IGh0dHBzOi8vZ2l0aHViLmNvbS96c3ZpY3ppYW4vb2JzaWRpYW4tZXhjYWxpZHJhdy1wbHVnaW4vaXNzdWVzLzExMjQKICogICBvbkZpbGVDcmVhdGVIb29rOiAoZGF0YTogewogKiAgICAgZWE6IEV4Y2FsaWRyYXdBdXRvbWF0ZTsKICogICAgIGV4Y2FsaWRyYXdGaWxlOiBURmlsZTsgLy90aGUgZmlsZSBiZWluZyBjcmVhdGVkCiAqICAgICB2aWV3OiBFeGNhbGlkcmF3VmlldzsKICogICB9KSA9PiBQcm9taXNlPHZvaWQ+OwogKi8KLy9lYS5vbkZpbGVDcmVhdGVIb29rID0gKGRhdGEpID0+IHt9OyAKCi8qKgogKiBJZiBzZXQsIHRoaXMgY2FsbGJhY2sgaXMgdHJpZ2dlcmVkIHdoZW5ldmVyIHRoZSBhY3RpdmUgY2FudmFzIGNvbG9yIGNoYW5nZXMKICogICBvbkNhbnZhc0NvbG9yQ2hhbmdlSG9vazogKAogKiAgICAgZWE6IEV4Y2FsaWRyYXdBdXRvbWF0ZSwKICogICAgIHZpZXc6IEV4Y2FsaWRyYXdWaWV3LCAvL3RoZSBleGNhbGlkcmF3IHZpZXcgCiAqICAgICBjb2xvcjogc3RyaW5nLAogKiAgICkgPT4gdm9pZCA9IG51bGw7CiAqLwovL2VhLm9uQ2FudmFzQ29sb3JDaGFuZ2VIb29rID0gKGVhLCB2aWV3LCBjb2xvcikgPT4ge307");
\ No newline at end of file
diff --git a/src/customEmbeddable.tsx b/src/customEmbeddable.tsx
index eaac787..af5e800 100644
--- a/src/customEmbeddable.tsx
+++ b/src/customEmbeddable.tsx
@@ -3,7 +3,7 @@ import ExcalidrawView from "./ExcalidrawView";
import { Notice, WorkspaceLeaf, WorkspaceSplit } from "obsidian";
import * as React from "react";
import { ConstructableWorkspaceSplit, getContainerForDocument, isObsidianThemeDark } from "./utils/ObsidianUtils";
-import { DEVICE, EXTENDED_EVENT_TYPES, KEYBOARD_EVENT_TYPES } from "./constants";
+import { DEVICE, EXTENDED_EVENT_TYPES, KEYBOARD_EVENT_TYPES } from "./constants/constants";
import { ExcalidrawImperativeAPI, UIAppState } from "@zsviczian/excalidraw/types/types";
import { ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
import { processLinkText, patchMobileView } from "./utils/CustomEmbeddableUtils";
diff --git a/src/dialogs/ExportDialog.ts b/src/dialogs/ExportDialog.ts
index bb25f67..fb98137 100644
--- a/src/dialogs/ExportDialog.ts
+++ b/src/dialogs/ExportDialog.ts
@@ -1,7 +1,7 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { Modal, Setting, TFile } from "obsidian";
import { getEA } from "src";
-import { DEVICE } from "src/constants";
+import { DEVICE } from "src/constants/constants";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
diff --git a/src/dialogs/ImportSVGDialog.ts b/src/dialogs/ImportSVGDialog.ts
index 41a22c8..a3b64ea 100644
--- a/src/dialogs/ImportSVGDialog.ts
+++ b/src/dialogs/ImportSVGDialog.ts
@@ -1,5 +1,5 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
-import { REG_LINKINDEX_INVALIDCHARS } from "../constants";
+import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import ExcalidrawView from "../ExcalidrawView";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "../main";
diff --git a/src/dialogs/InsertCommandDialog.ts b/src/dialogs/InsertCommandDialog.ts
index 9c79c47..eabb5da 100644
--- a/src/dialogs/InsertCommandDialog.ts
+++ b/src/dialogs/InsertCommandDialog.ts
@@ -1,5 +1,5 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
-import { REG_LINKINDEX_INVALIDCHARS } from "../constants";
+import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import { t } from "../lang/helpers";
export class InsertCommandDialog extends FuzzySuggestModal {
diff --git a/src/dialogs/InsertImageDialog.ts b/src/dialogs/InsertImageDialog.ts
index 0e8a047..acff742 100644
--- a/src/dialogs/InsertImageDialog.ts
+++ b/src/dialogs/InsertImageDialog.ts
@@ -1,7 +1,7 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import { isALT, scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper";
import { fileURLToPath } from "url";
-import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants";
+import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import ExcalidrawView from "../ExcalidrawView";
import { t } from "../lang/helpers";
import ExcalidrawPlugin from "../main";
diff --git a/src/dialogs/InsertLinkDialog.ts b/src/dialogs/InsertLinkDialog.ts
index dfea2cb..514c753 100644
--- a/src/dialogs/InsertLinkDialog.ts
+++ b/src/dialogs/InsertLinkDialog.ts
@@ -1,5 +1,5 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
-import { REG_LINKINDEX_INVALIDCHARS } from "../constants";
+import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
import { t } from "../lang/helpers";
export class InsertLinkDialog extends FuzzySuggestModal {
diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts
index 2d7e570..b462da0 100644
--- a/src/dialogs/Messages.ts
+++ b/src/dialogs/Messages.ts
@@ -17,6 +17,22 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
`,
+"2.0.4":`
+
+
+## New
+- ExcaliAI
+- You can now add ${String.fromCharCode(96)}ex-md-font-hand-drawn${String.fromCharCode(96)} or ${String.fromCharCode(96)}ex-md-font-hand-drawn${String.fromCharCode(96)} to the ${String.fromCharCode(96)}cssclasses:${String.fromCharCode(96)} frontmatter property in embedded markdown nodes and their font face will match the respective Excalidraw fonts.
+
+## Fixed
+- Adding a script for the very first time (when the script folder did not yet exist) did not show up in the tools panel. Required an Obsidian restart.
+- Performance improvements
+
+## New and updated In Excalidraw Automate
+- Added many new functions and some features to existing functions. See the [release notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.0.3) for details
+`,
"2.0.3":`
## Fixed
- Mermaid to Excalidraw stopped working after installing the Obsidian 1.5.0 insider build. [#1450](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1450)
diff --git a/src/dialogs/OpenDrawing.ts b/src/dialogs/OpenDrawing.ts
index 90073dc..48ac25e 100644
--- a/src/dialogs/OpenDrawing.ts
+++ b/src/dialogs/OpenDrawing.ts
@@ -1,6 +1,6 @@
import { App, FuzzySuggestModal, TFile } from "obsidian";
import ExcalidrawPlugin from "../main";
-import { EMPTY_MESSAGE } from "../constants";
+import { EMPTY_MESSAGE } from "../constants/constants";
import { t } from "../lang/helpers";
export enum openDialogAction {
diff --git a/src/dialogs/PenSettingsModal.ts b/src/dialogs/PenSettingsModal.ts
index dc21855..69f6548 100644
--- a/src/dialogs/PenSettingsModal.ts
+++ b/src/dialogs/PenSettingsModal.ts
@@ -1,6 +1,6 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { ColorComponent, Modal, Setting, SliderComponent, TextComponent, ToggleComponent } from "obsidian";
-import { COLOR_NAMES, VIEW_TYPE_EXCALIDRAW } from "src/constants";
+import { COLOR_NAMES, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
import { setPen } from "src/menu/ObsidianMenu";
diff --git a/src/dialogs/Prompt.ts b/src/dialogs/Prompt.ts
index 536fc47..5abd410 100644
--- a/src/dialogs/Prompt.ts
+++ b/src/dialogs/Prompt.ts
@@ -18,7 +18,7 @@ import { KeyEvent, isCTRL } from "src/utils/ModifierkeyHelper";
import { t } from "src/lang/helpers";
import { ExcalidrawElement, getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
-import { MAX_IMAGE_SIZE } from "src/constants";
+import { MAX_IMAGE_SIZE } from "src/constants/constants";
export type ButtonDefinition = { caption: string; tooltip?:string; action: Function };
diff --git a/src/dialogs/SuggesterInfo.ts b/src/dialogs/SuggesterInfo.ts
index bc31045..972e6e2 100644
--- a/src/dialogs/SuggesterInfo.ts
+++ b/src/dialogs/SuggesterInfo.ts
@@ -183,7 +183,13 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
{
field: "createPNG",
code: "createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,padding?: number): Promise;",
- desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
+ desc: "Create an image based on the objects in ea.getElements(). The elements in ea will be merged with the elements from the provided template file - if any. Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
+ after: "",
+ },
+ {
+ field: "createPNGBase64",
+ code: "craetePNGBase64(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,padding?: number): Promise;",
+ desc: "The same as createPNG but returns a base64 encoded string instead of a file.",
after: "",
},
{
@@ -252,10 +258,16 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.",
after: "",
},
+ {
+ field: "addMermaid",
+ code: "async addMermaid(diagram: string, groupElements: boolean = true,): Promise;",
+ desc: "Creates a mermaid diagram and returns the ids of the created elements as a string[]. The elements will be added to ea. To add them to the canvas you'll need to use addElementsToView. Depending on the diagram type the result will be either a single SVG image, or a number of excalidraw elements.",
+ after: "",
+ },
{
field: "addLaTex",
- code: "addLaTex(topX: number, topY: number, tex: string): Promise;",
- desc: null,
+ code: "async addLaTex(topX: number, topY: number, tex: string): Promise;",
+ desc: "This is an async function, you need to avait the results. Adds a LaTex element to the drawing. The tex string is the LaTex code. The function returns the id of the created element.",
after: "",
},
{
@@ -338,8 +350,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
},
{
field: "copyViewElementsToEAforEditing",
- code: "copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;",
- desc: "Copies elements from view to elementsDict for editing",
+ code: "copyViewElementsToEAforEditing(elements: ExcalidrawElement[], copyImages: boolean = false): void;",
+ desc: "Copies elements from view to elementsDict for editing. If copyImages is true, then relevant entries from scene.files will also be copied. This is required if you want to generate a PNG for a subset of the elements in the drawing (e.g. for AI generation)",
after: "",
},
{
@@ -522,6 +534,43 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
desc: "This asynchronous function should be awaited. It retrieves the filepath to a new file, taking into account the attachments preference settings in Obsidian. If the attachment folder doesn't exist, it creates it. The function returns the complete path to the file. If the provided filename already exists, the function will append '_[number]' before the extension to generate a unique filename.",
after: "",
},
+ {
+ field: "checkAndCreateFolder",
+ code: "async checkAndCreateFolder(folderpath: string): Promise",
+ desc: "Checks if the folder exists, if not, creates it.",
+ after: "",
+ },
+ {
+ field: "getNewUniqueFilepath",
+ code: "getNewUniqueFilepath(filename: string, folderpath: string): string",
+ desc: "Checks if the filepath already exists, if so, returns a new filepath with a number appended to the filename else returns the filepath as provided.",
+ after: "",
+ },
+ {
+ field: "extractCodeBlocks",
+ code: "extractCodeBlocks(markdown: string): { data: string, type: string }[]",
+ desc: "Grabs the codeblock content from the supplied markdown string. Returns an array of dictionaries with the codeblock content and type",
+ after: "",
+ },
+ {
+ field: "postOpenAI",
+ code: "async postOpenAI(requst: AIRequest): Promise",
+ desc:
+ "This asynchronous function should be awaited. It posts the supplied request to the OpenAI API and returns the response. " +
+ "The response is a dictionary with the following keys:{image, text, instruction, systemPrompt} "+
+ "image should be a dataURL - use ea.createPNGBase64() "+
+ "systemPrompt : if undefined the message to OpenAI will not include a system prompt "+
+ "text is the actual user prompt, a request must have either an image or a text "+
+ "instruction is a user prompt sent as a separate element in the message - I use it to reinforce the type of response I am seeing (e.g. mermaid in a codeblock) "+
+ "RequestUrlResponse is defined in the Obsidian API ",
+ after: "",
+ },
+ {
+ field: "convertStringToDataURL",
+ code: 'async convertStringToDataURL (data:string, type: string = "text/html"):Promise',
+ desc: "Converts a string to a DataURL.",
+ after: "",
+ },
{
field: "setViewModeEnabled",
code: "setViewModeEnabled(enabled: boolean): void;",
diff --git a/src/dialogs/UniversalInsertFileModal.ts b/src/dialogs/UniversalInsertFileModal.ts
index 036e652..d36fc57 100644
--- a/src/dialogs/UniversalInsertFileModal.ts
+++ b/src/dialogs/UniversalInsertFileModal.ts
@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
-import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE } from "src/constants";
+import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE } from "src/constants/constants";
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
import { getEA } from "src";
import { InsertPDFModal } from "./InsertPDFModal";
diff --git a/src/lang/helpers.ts b/src/lang/helpers.ts
index 5d77062..a639e99 100644
--- a/src/lang/helpers.ts
+++ b/src/lang/helpers.ts
@@ -25,7 +25,7 @@ import ru from "./locale/ru";
import tr from "./locale/tr";
import zhCN from "./locale/zh-cn";
import zhTW from "./locale/zh-tw";
-import { LOCALE } from "src/constants";
+import { LOCALE } from "src/constants/constants";
const localeMap: { [k: string]: Partial } = {
ar,
diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts
index fbc6107..a95856a 100644
--- a/src/lang/locale/en.ts
+++ b/src/lang/locale/en.ts
@@ -3,7 +3,7 @@ import {
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
FRONTMATTER_KEY_CUSTOM_PREFIX,
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
-} from "src/constants";
+} from "src/constants/constants";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
// English
@@ -140,6 +140,25 @@ export default {
"You can access your scripts from Excalidraw via the Obsidian Command Palette. Assign " +
"hotkeys to your favorite scripts just like to any other Obsidian command. " +
"The folder may not be the root folder of your Vault. ",
+ AI_HEAD: "AI Settings - Experimental",
+ AI_DESC: `In the "AI" settings, you can configure options for using OpenAI's GPT API. ` +
+ `While the OpenAI API is in beta, its use is strictly limited — as such we require you use your own API key. ` +
+ `You can create an OpenAI account, add a small credit (5 USD minimum), and generate your own API key. ` +
+ `Once API key is set, you can use the AI tools in Excalidraw.`,
+ AI_OPENAI_TOKEN_NAME: "OpenAI API key",
+ AI_OPENAI_TOKEN_DESC:
+ "You can get your OpenAI API key from your OpenAI account .",
+ AI_OPENAI_TOKEN_PLACEHOLDER: "Enter your OpenAI API key here",
+ AI_OPENAI_DEFAULT_MODEL_NAME: "Default AI model",
+ AI_OPENAI_DEFAULT_MODEL_DESC:
+ "The default AI model to use when generating text. This is a freetext field, so you can enter any valid OpenAI model name. " +
+ "Find out more about the available models on the OpenAI website .",
+ AI_OPENAI_DEFAULT_MODEL_PLACEHOLDER: "Enter your default AI model here",
+ AI_OPENAI_DEFAULT_VISION_MODEL_NAME: "Default AI vision model",
+ AI_OPENAI_DEFAULT_VISION_MODEL_DESC:
+ "The default AI vision model to use when generating text from images. This is a freetext field, so you can enter any valid OpenAI model name. " +
+ "Find out more about the available models on the OpenAI website .",
+ AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Enter your default AI vision model here",
SAVING_HEAD: "Saving",
SAVING_DESC: "In the 'Saving' section of Excalidraw Settings, you can configure how your drawings are saved. This includes options for compressing Excalidraw JSON in Markdown, setting autosave intervals for both desktop and mobile, defining filename formats, and choosing whether to use the .excalidraw.md or .md file extension. ",
COMPRESS_NAME: "Compress Excalidraw JSON in Markdown",
@@ -486,10 +505,23 @@ FILENAME_HEAD: "Filename",
CUSTOM_PEN_DESC: "You will see these pens next to the Obsidian Menu on the canvas. You can customize the pens on the canvas by long-pressing the pen button.",
EXPERIMENTAL_HEAD: "Miscellaneous features",
EXPERIMENTAL_DESC: `These miscellaneous features in Excalidraw include options for setting default LaTeX formulas for new equations, enabling a Field Suggester for autocompletion, displaying type indicators for Excalidraw files, enabling immersive image embedding in live preview editing mode, and experimenting with Taskbone Optical Character Recognition for text extraction from images and drawings. Users can also enter a Taskbone API key for extended usage of the OCR service.`,
+ EA_HEAD: "Excalidraw Automate",
+ EA_DESC:
+ "ExcalidrawAutomate is a scripting and automation API for Excalidraw. Unfortunately, the documentation of the API is sparse. " +
+ "I recommend reading the ExcalidrawAutomate.d.ts file, " +
+ "visiting the ExcalidrawAutomate How-to page - though the information " +
+ "here has not been updated for a long while -, and finally to enable the field suggester below. The field suggester will show you the available " +
+ "functions, their parameters and short description as you type. The field suggester is the most up-to-date documentation of the API.",
FIELD_SUGGESTER_NAME: "Enable Field Suggester",
FIELD_SUGGESTER_DESC:
"Field Suggester borrowed from Breadcrumbs and Templater plugins. The Field Suggester will show an autocomplete menu " +
"when you type excalidraw- or ea. with function description as hints on the individual items in the list.",
+ STARTUP_SCRIPT_NAME: "Startup script",
+ STARTUP_SCRIPT_DESC:
+ "If set, excalidraw will execute the script at plugin startup. This is useful if you want to set any of the Excalidraw Automate hooks. The startup script is a markdown file " +
+ "that should contain the javascript code you want to execute when Excalidraw starts.",
+ STARTUP_SCRIPT_BUTTON: "Create startup script",
+ STARTUP_SCRIPT_EXISTS: "Startup script file already exists",
FILETYPE_NAME: "Display type (✏️) for excalidraw.md files in File Explorer",
FILETYPE_DESC:
"Excalidraw files will receive an indicator using the emoji or text defined in the next setting.",
@@ -574,6 +606,7 @@ FILENAME_HEAD: "Filename",
RELOAD: "Reload original link",
OPEN_IN_BROWSER: "Open current link in browser",
PROPERTIES: "Properties",
+ COPYCODE: "Copy source to clipboard",
//EmbeddableSettings.tsx
ES_TITLE: "Embeddable Element Settings",
diff --git a/src/lang/locale/zh-cn.ts b/src/lang/locale/zh-cn.ts
index 487f90c..111b4e8 100644
--- a/src/lang/locale/zh-cn.ts
+++ b/src/lang/locale/zh-cn.ts
@@ -3,7 +3,7 @@ import {
FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
FRONTMATTER_KEY_CUSTOM_PREFIX,
FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
-} from "src/constants";
+} from "src/constants/constants";
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
// 简体中文
diff --git a/src/main.ts b/src/main.ts
index 7a334e0..eae0f6d 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -41,11 +41,11 @@ import {
EXPORT_IMG_ICON_NAME,
EXPORT_IMG_ICON,
LOCALE,
-} from "./constants";
+} from "./constants/constants";
import {
VIRGIL_FONT,
VIRGIL_DATAURL,
-} from "./constFonts";
+} from "./constants/constFonts";
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
import {
changeThemeOfExcalidrawMD,
@@ -116,13 +116,14 @@ import { ExportDialog } from "./dialogs/ExportDialog";
import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal";
import { imageCache } from "./utils/ImageCache";
import { StylesManager } from "./utils/StylesManager";
-import { MATHJAX_SOURCE_LZCOMPRESSED } from "./constMathJaxSource";
+import { MATHJAX_SOURCE_LZCOMPRESSED } from "./constants/constMathJaxSource";
import { PublishOutOfDateFilesDialog } from "./dialogs/PublishOutOfDateFiles";
import { EmbeddableSettings } from "./dialogs/EmbeddableSettings";
import { processLinkText } from "./utils/CustomEmbeddableUtils";
import { getEA } from "src";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
+import { CustomMutationObserver, durationTreshold, isDebugMode } from "./utils/DebugHelper";
declare const EXCALIDRAW_PACKAGES:string;
declare const react:any;
@@ -148,12 +149,12 @@ export default class ExcalidrawPlugin extends Plugin {
linkText: null,
sourcePath: null,
};
- private observer: MutationObserver;
- private themeObserver: MutationObserver;
- private fileExplorerObserver: MutationObserver;
- private modalContainerObserver: MutationObserver;
- private workspaceDrawerLeftObserver: MutationObserver;
- private workspaceDrawerRightObserver: MutationObserver;
+ private observer: MutationObserver | CustomMutationObserver;
+ private themeObserver: MutationObserver | CustomMutationObserver;
+ private fileExplorerObserver: MutationObserver | CustomMutationObserver;
+ private modalContainerObserver: MutationObserver | CustomMutationObserver;
+ private workspaceDrawerLeftObserver: MutationObserver | CustomMutationObserver;
+ private workspaceDrawerRightObserver: MutationObserver | CustomMutationObserver;
public opencount: number = 0;
public ea: ExcalidrawAutomate;
//A master list of fileIds to facilitate copy / paste
@@ -203,6 +204,38 @@ export default class ExcalidrawPlugin extends Plugin {
return {react:r, reactDOM:rd, excalidrawLib:e};
}
+ public registerEvent(event: any) {
+ if(!isDebugMode) {
+ super.registerEvent(event);
+ return;
+ }
+
+ const originalHandler = event.fn;
+
+ // Wrap the original event handler
+ const wrappedHandler = async (...args: any[]) => {
+ const startTime = performance.now(); // Get start time
+
+ // Invoke the original event handler
+ const result = await originalHandler(...args);
+
+ const endTime = performance.now(); // Get end time
+ const executionTime = endTime - startTime;
+
+ if(executionTime > durationTreshold) {
+ console.log(`Excalidraw Event '${event.name}' took ${executionTime}ms to execute`);
+ }
+
+ return result;
+ };
+
+ // Replace the original event handler with the wrapped one
+ event.fn = wrappedHandler;
+
+ // Register the modified event
+ super.registerEvent(event);
+ }
+
async onload() {
addIcon(ICON_NAME, EXCALIDRAW_ICON);
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
@@ -228,6 +261,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
this.registerCommands();
this.registerEventListeners();
+ this.runStartupScript();
this.initializeFourthFont();
this.registerEditorSuggest(new FieldSuggester(this));
@@ -380,65 +414,6 @@ export default class ExcalidrawPlugin extends Plugin {
});
}
-/* public loadMathJax() {
- const self = this;
- this.app.workspace.onLayoutReady(async () => {
- //loading Obsidian MathJax as fallback
- await loadMathJax();
- //@ts-ignore
- try {
- if(self.mathjaxDiv) {
- document.body.removeChild(self.mathjaxDiv);
- self.mathjax = null;
- self.mathjaxLoaderFinished = false;
- }
- self.mathjaxDiv = document.body.createDiv();
- self.mathjaxDiv.title = "Excalidraw MathJax Support";
- self.mathjaxDiv.style.display = "none";
-
- const iframe = self.mathjaxDiv.createEl("iframe");
- iframe.title = "Excalidraw MathJax Support";
- const doc = iframe.contentWindow.document;
-
- const script = doc.createElement("script");
- script.type = "text/javascript";
- script.onload = () => {
- const win = iframe.contentWindow;
- //@ts-ignore
- win.MathJax.startup.pagePromise.then(async () => {
- //https://github.com/xldenis/obsidian-latex/blob/master/main.ts
- const file = this.app.vault.getAbstractFileByPath("preamble.sty");
- const preamble: string =
- file && file instanceof TFile
- ? await this.app.vault.read(file)
- : null;
- try {
- if (preamble) {
- //@ts-ignore
- await win.MathJax.tex2svg(preamble);
- }
- } catch (e) {
- errorlog({
- where: self.loadMathJax,
- description: "Unexpected error while loading preamble.sty",
- error: e,
- });
- }
- //@ts-ignore
- self.mathjax = win.MathJax;
- self.mathjaxLoaderFinished = true;
- });
- };
- script.src = "data:text/javascript;base64," + decompressFromBase64(MATHJAX_SOURCE_LZCOMPRESSED); //self.settings.mathjaxSourceURL; // "https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-svg.js";
- //script.src = MATHJAX_DATAURL;
- doc.head.appendChild(script);
- } catch {
- new Notice("Excalidraw: Error initializing LaTeX support");
- self.mathjaxLoaderFinished = true;
- }
- });
- }*/
-
private switchToExcalidarwAfterLoad() {
const self = this;
this.app.workspace.onLayoutReady(() => {
@@ -668,19 +643,22 @@ export default class ExcalidrawPlugin extends Plugin {
* Displays a transcluded .excalidraw image in markdown preview mode
*/
private addMarkdownPostProcessor() {
- initializeMarkdownPostProcessor(this);
- this.registerMarkdownPostProcessor(markdownPostProcessor);
+ const self = this;
+ this.app.workspace.onLayoutReady(() => {
+ initializeMarkdownPostProcessor(self);
+ self.registerMarkdownPostProcessor(markdownPostProcessor);
- // internal-link quick preview
- this.registerEvent(this.app.workspace.on("hover-link", hoverEvent));
+ // internal-link quick preview
+ self.registerEvent(self.app.workspace.on("hover-link", hoverEvent));
- //monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
- this.observer = observer;
- this.observer.observe(document, { childList: true, subtree: true });
+ //monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
+ self.observer = observer;
+ self.observer.observe(document.body, { childList: true, subtree: false });
+ });
}
private addThemeObserver() {
- this.themeObserver = new MutationObserver(async (m: MutationRecord[]) => {
+ const themeObserverFn:MutationCallback = async (m: MutationRecord[]) => {
if (!this.settings.matchThemeTrigger) {
return;
}
@@ -703,7 +681,12 @@ export default class ExcalidrawPlugin extends Plugin {
excalidrawView.setTheme(theme);
}
});
- });
+ };
+
+ this.themeObserver = isDebugMode
+ ? new CustomMutationObserver(themeObserverFn, "themeObserver")
+ : new MutationObserver(themeObserverFn);
+
this.themeObserver.observe(document.body, {
attributeOldValue: true,
attributeFilter: ["class"],
@@ -748,25 +731,32 @@ export default class ExcalidrawPlugin extends Plugin {
}
};
- this.fileExplorerObserver = new MutationObserver((m) => {
- const mutationsWithNodes = m.filter((v) => v.addedNodes.length > 0);
- mutationsWithNodes.forEach((mu) => {
- mu.addedNodes.forEach((n) => {
- if (!(n instanceof Element)) {
+ const fileExplorerObserverFn:MutationCallback = (mutationsList) => {
+ const mutationsWithNodes = mutationsList.filter((mutation) => mutation.addedNodes.length > 0);
+ mutationsWithNodes.forEach((mutationNode) => {
+ mutationNode.addedNodes.forEach((node) => {
+ if (!(node instanceof Element)) {
return;
}
- n.querySelectorAll(".nav-file-title").forEach(insertFiletype);
+ node.querySelectorAll(".nav-file-title").forEach(insertFiletype);
});
});
- });
+ };
+
+ this.fileExplorerObserver = isDebugMode
+ ? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
+ : new MutationObserver(fileExplorerObserverFn);
const self = this;
this.app.workspace.onLayoutReady(() => {
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
- self.fileExplorerObserver.observe(document.querySelector(".workspace"), {
- childList: true,
- subtree: true,
- });
+ const container = document.querySelector(".nav-files-container");
+ if (container) {
+ self.fileExplorerObserver.observe(container, {
+ childList: true,
+ subtree: true,
+ });
+ }
});
}
@@ -1931,6 +1921,30 @@ export default class ExcalidrawPlugin extends Plugin {
);
}
+ private runStartupScript() {
+ if(!this.settings.startupScriptPath || this.settings.startupScriptPath === "") {
+ return;
+ }
+ const self = this;
+ this.app.workspace.onLayoutReady(async () => {
+ const path = self.settings.startupScriptPath.endsWith(".md")
+ ? self.settings.startupScriptPath
+ : `${self.settings.startupScriptPath}.md`;
+ const f = self.app.vault.getAbstractFileByPath(path);
+ if (!f || !(f instanceof TFile)) {
+ new Notice(`Startup script not found: ${path}`);
+ return;
+ }
+ const script = await self.app.vault.read(f);
+ const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
+ try {
+ await new AsyncFunction("ea", script)(self.ea);
+ } catch (e) {
+ new Notice(`Error running startup script: ${e}`);
+ }
+ });
+ }
+
private popScope: Function = null;
private registerEventListeners() {
const self = this;
@@ -2296,20 +2310,22 @@ export default class ExcalidrawPlugin extends Plugin {
);
//The user clicks settings, or "open another vault", or the command palette
- this.modalContainerObserver = new MutationObserver(
- async (m: MutationRecord[]) => {
- if (
- m.length !== 1 ||
- m[0].type !== "childList" ||
- m[0].addedNodes.length !== 1 ||
- !this.activeExcalidrawView ||
- !this.activeExcalidrawView.semaphores.dirty
- ) {
- return;
- }
- this.activeExcalidrawView.save();
- },
- );
+ const modalContainerObserverFn: MutationCallback = async (m: MutationRecord[]) => {
+ if (
+ (m.length !== 1) ||
+ (m[0].type !== "childList") ||
+ (m[0].addedNodes.length !== 1) ||
+ (!this.activeExcalidrawView) ||
+ (!this.activeExcalidrawView.semaphores.dirty)
+ ) {
+ return;
+ }
+ this.activeExcalidrawView.save();
+ };
+
+ this.modalContainerObserver = isDebugMode
+ ? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
+ : new MutationObserver(modalContainerObserverFn);
this.modalContainerObserver.observe(document.body, {
childList: true,
});
@@ -2338,12 +2354,16 @@ export default class ExcalidrawPlugin extends Plugin {
};
if (leftWorkspaceDrawer) {
- this.workspaceDrawerLeftObserver = new MutationObserver(action);
+ this.workspaceDrawerLeftObserver = isDebugMode
+ ? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
+ : new MutationObserver(action);
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
}
if (rightWorkspaceDrawer) {
- this.workspaceDrawerRightObserver = new MutationObserver(action);
+ this.workspaceDrawerRightObserver = isDebugMode
+ ? new CustomMutationObserver(action, "slidingDrawerRightObserver")
+ : new MutationObserver(action);
this.workspaceDrawerRightObserver.observe(
rightWorkspaceDrawer,
options,
diff --git a/src/menu/ActionIcons.tsx b/src/menu/ActionIcons.tsx
index efe5542..d288285 100644
--- a/src/menu/ActionIcons.tsx
+++ b/src/menu/ActionIcons.tsx
@@ -1,4 +1,4 @@
-import { Cog, Globe, RotateCcw, Scan, Settings } from "lucide-react";
+import { Copy, Globe, RotateCcw, Scan, Settings } from "lucide-react";
import * as React from "react";
import { PenStyle } from "src/PenTypes";
@@ -27,6 +27,7 @@ export const ICONS = {
),
Reload: ( ),
+ Copy: ( ),
Globe: ( ),
ZoomToSelectedElement: ( ),
Properties: ( ),
diff --git a/src/menu/EmbeddableActionsMenu.tsx b/src/menu/EmbeddableActionsMenu.tsx
index d0838af..c694884 100644
--- a/src/menu/EmbeddableActionsMenu.tsx
+++ b/src/menu/EmbeddableActionsMenu.tsx
@@ -7,7 +7,7 @@ import { ActionButton } from "./ActionButton";
import { ICONS } from "./ActionIcons";
import { t } from "src/lang/helpers";
import { ScriptEngine } from "src/Scripts";
-import { ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants";
+import { ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants/constants";
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
import { processLinkText, useDefaultExcalidrawFrame } from "src/utils/CustomEmbeddableUtils";
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
@@ -78,17 +78,21 @@ export class EmbeddableMenu {
if(!link) return null;
const isExcalidrawiFrame = useDefaultExcalidrawFrame(element);
- let isObsidianiFrame = element.link?.match(REG_LINKINDEX_HYPERLINK);
+ let isObsidianiFrame = Boolean(element.link?.match(REG_LINKINDEX_HYPERLINK));
if(!isExcalidrawiFrame && !isObsidianiFrame) {
- const res = REGEX_LINK.getRes(element.link).next();
- if(!res || (!res.value && res.done)) {
- return null;
+ if(link.startsWith("data:text/html")) {
+ isObsidianiFrame = true;
+ } else {
+ const res = REGEX_LINK.getRes(element.link).next();
+ if(!res || (!res.value && res.done)) {
+ return null;
+ }
+
+ link = REGEX_LINK.getLink(res);
+
+ isObsidianiFrame = Boolean(link.match(REG_LINKINDEX_HYPERLINK));
}
-
- link = REGEX_LINK.getLink(res);
-
- isObsidianiFrame = link.match(REG_LINKINDEX_HYPERLINK);
if(!isObsidianiFrame) {
const { subpath, file } = processLinkText(link, view);
@@ -282,6 +286,18 @@ export class EmbeddableMenu {
icon={ICONS.Properties}
view={view}
/>
+ {link?.startsWith("data:text/html") && (
+ {
+ if(!element) return;
+ navigator.clipboard.writeText(atob(link.split(",")[1]));
+ }}
+ icon={ICONS.Copy}
+ view={view}
+ />
+ )}
);
diff --git a/src/menu/ObsidianMenu.tsx b/src/menu/ObsidianMenu.tsx
index dc762e4..ed5b592 100644
--- a/src/menu/ObsidianMenu.tsx
+++ b/src/menu/ObsidianMenu.tsx
@@ -2,7 +2,7 @@ import { AppState, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/t
import clsx from "clsx";
import { TFile } from "obsidian";
import * as React from "react";
-import { VIEW_TYPE_EXCALIDRAW } from "src/constants";
+import { VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { PenSettingsModal } from "src/dialogs/PenSettingsModal";
import ExcalidrawView from "src/ExcalidrawView";
import { PenStyle } from "src/PenTypes";
diff --git a/src/menu/ToolsPanel.tsx b/src/menu/ToolsPanel.tsx
index 9020b8e..53ebb34 100644
--- a/src/menu/ToolsPanel.tsx
+++ b/src/menu/ToolsPanel.tsx
@@ -3,7 +3,7 @@ import { Notice, TFile } from "obsidian";
import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
-import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../constants";
+import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../constants/constants";
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
import { t } from "../lang/helpers";
diff --git a/src/settings.ts b/src/settings.ts
index 012ee14..c230e15 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -2,12 +2,13 @@ import {
App,
DropdownComponent,
normalizePath,
+ Notice,
PluginSettingTab,
Setting,
TextComponent,
TFile,
} from "obsidian";
-import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants";
+import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
@@ -27,9 +28,9 @@ import {
} from "./utils/Utils";
import { imageCache } from "./utils/ImageCache";
import { ConfirmationPrompt } from "./dialogs/Prompt";
-import de from "./lang/locale/de";
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/EmbeddableMDFileCustomDataSettingsComponent";
+import { startupScript } from "./constants/starutpscript";
export interface ExcalidrawSettings {
folder: string;
@@ -153,6 +154,10 @@ export interface ExcalidrawSettings {
};
embeddableMarkdownDefaults: EmbeddableMDCustomProps;
canvasImmersiveEmbed: boolean,
+ startupScriptPath: string,
+ openAIAPIToken: string,
+ openAIDefaultTextModel: string,
+ openAIDefaultVisionModel: string,
}
declare const PLUGIN_VERSION:string;
@@ -295,6 +300,10 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
filenameVisible: false,
},
canvasImmersiveEmbed: true,
+ startupScriptPath: "",
+ openAIAPIToken: "",
+ openAIDefaultTextModel: "gpt-3.5-turbo-1106",
+ openAIDefaultVisionModel: "gpt-4-vision-preview",
};
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -640,6 +649,56 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
);
+ //------------------------------------------------
+ // AI Settings
+ //------------------------------------------------
+ containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
+ containerEl.createDiv({ text: t("AI_DESC"), cls: "setting-item-description" });
+ detailsEl = this.containerEl.createEl("details");
+ const aiDetailsEl = detailsEl;
+ detailsEl.createEl("summary", {
+ text: t("AI_HEAD"),
+ cls: "excalidraw-setting-h1",
+ });
+
+ new Setting(detailsEl)
+ .setName(t("AI_OPENAI_TOKEN_NAME"))
+ .setDesc(fragWithHTML(t("AI_OPENAI_TOKEN_DESC")))
+ .addText((text) =>
+ text
+ .setPlaceholder(t("AI_OPENAI_TOKEN_PLACEHOLDER"))
+ .setValue(this.plugin.settings.openAIAPIToken)
+ .onChange(async (value) => {
+ this.plugin.settings.openAIAPIToken = value;
+ this.applySettingsUpdate();
+ }),
+ );
+
+ new Setting(detailsEl)
+ .setName(t("AI_OPENAI_DEFAULT_MODEL_NAME"))
+ .setDesc(fragWithHTML(t("AI_OPENAI_DEFAULT_MODEL_DESC")))
+ .addText((text) =>
+ text
+ .setPlaceholder(t("AI_OPENAI_DEFAULT_MODEL_PLACEHOLDER"))
+ .setValue(this.plugin.settings.openAIDefaultTextModel)
+ .onChange(async (value) => {
+ this.plugin.settings.openAIDefaultTextModel = value;
+ this.applySettingsUpdate();
+ }),
+ );
+
+ new Setting(detailsEl)
+ .setName(t("AI_OPENAI_DEFAULT_VISION_MODEL_NAME"))
+ .setDesc(fragWithHTML(t("AI_OPENAI_DEFAULT_VISION_MODEL_DESC")))
+ .addText((text) =>
+ text
+ .setPlaceholder(t("AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER"))
+ .setValue(this.plugin.settings.openAIDefaultVisionModel)
+ .onChange(async (value) => {
+ this.plugin.settings.openAIDefaultVisionModel = value;
+ this.applySettingsUpdate();
+ }),
+ );
// ------------------------------------------------
// Display
@@ -1808,23 +1867,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
cls: "excalidraw-setting-h1",
});
- //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/628
- /*new Setting(detailsEl)
- .setName(t("MATHJAX_NAME"))
- .setDesc(t("MATHJAX_DESC"))
- .addDropdown((dropdown) => {
- dropdown
- .addOption("https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js", "jsdelivr")
- .addOption("https://unpkg.com/mathjax@3.2.1/es5/tex-svg.js", "unpkg")
- .addOption("https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.1/es5/tex-svg-full.min.js","cdnjs")
- .setValue(this.plugin.settings.mathjaxSourceURL)
- .onChange((value)=> {
- this.plugin.settings.mathjaxSourceURL = value;
- this.reloadMathJax = true;
- this.applySettingsUpdate();
- })
- })*/
-
addIframe(detailsEl, "r08wk-58DPk");
new Setting(detailsEl)
.setName(t("LATEX_DEFAULT_NAME"))
@@ -1838,18 +1880,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}),
);
- new Setting(detailsEl)
- .setName(t("FIELD_SUGGESTER_NAME"))
- .setDesc(fragWithHTML(t("FIELD_SUGGESTER_DESC")))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.settings.fieldSuggester)
- .onChange(async (value) => {
- this.plugin.settings.fieldSuggester = value;
- this.applySettingsUpdate();
- }),
- );
-
new Setting(detailsEl)
.setName(t("FILETYPE_NAME"))
.setDesc(fragWithHTML(t("FILETYPE_DESC")))
@@ -1932,6 +1962,68 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
);
+ // ------------------------------------------------
+ // ExcalidrawAutomate
+ // ------------------------------------------------
+ containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
+ containerEl.createDiv( { cls: "setting-item-description" }, (el)=>{
+ el.innerHTML = t("EA_DESC");
+ });
+ detailsEl = containerEl.createEl("details");
+ const eaDetailsEl = detailsEl;
+ detailsEl.createEl("summary", {
+ text: t("EA_HEAD"),
+ cls: "excalidraw-setting-h1",
+ });
+
+ new Setting(detailsEl)
+ .setName(t("FIELD_SUGGESTER_NAME"))
+ .setDesc(fragWithHTML(t("FIELD_SUGGESTER_DESC")))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.fieldSuggester)
+ .onChange(async (value) => {
+ this.plugin.settings.fieldSuggester = value;
+ this.applySettingsUpdate();
+ }),
+ );
+
+ //STARTUP_SCRIPT_NAME
+ //STARTUP_SCRIPT_BUTTON
+ let startupScriptPathText: TextComponent;
+ new Setting(detailsEl)
+ .setName(t("STARTUP_SCRIPT_NAME"))
+ .setDesc(fragWithHTML(t("STARTUP_SCRIPT_DESC")))
+ .addText((text) => {
+ startupScriptPathText = text;
+ text
+ .setValue(this.plugin.settings.startupScriptPath)
+ .onChange( (value) => {
+ this.plugin.settings.startupScriptPath = value;
+ this.applySettingsUpdate();
+ });
+ })
+ .addButton((button) =>
+ button
+ .setButtonText(t("STARTUP_SCRIPT_BUTTON"))
+ .onClick(async () => {
+ if(this.plugin.settings.startupScriptPath === "") {
+ this.plugin.settings.startupScriptPath = normalizePath(normalizePath(this.plugin.settings.folder) + "/ExcalidrawStartup");
+ startupScriptPathText.setValue(this.plugin.settings.startupScriptPath);
+ this.applySettingsUpdate();
+ }
+ const startupPath = normalizePath(this.plugin.settings.startupScriptPath.endsWith(".md")
+ ? this.plugin.settings.startupScriptPath
+ : this.plugin.settings.startupScriptPath + ".md");
+ const f = this.app.vault.getAbstractFileByPath(startupPath);
+ if(f) {
+ new Notice(t("STARTUP_SCRIPT_EXISTS"));
+ return;
+ }
+ const newFile = await this.app.vault.create(startupPath, startupScript());
+ this.app.workspace.openLinkText(newFile.path,"",true);
+ })
+ );
// ------------------------------------------------
@@ -2094,6 +2186,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.addTextArea((text) => {
text.inputEl.style.minHeight = textAreaHeight(scriptName, variableName);
text.inputEl.style.minWidth = "400px";
+ text.inputEl.style.width = "100%";
text
.setValue(getValue(scriptName, variableName))
.onChange(async (value) => {
diff --git a/src/svgToExcalidraw/elements/ExcalidrawScene.ts b/src/svgToExcalidraw/elements/ExcalidrawScene.ts
index beee7e9..fa78b84 100644
--- a/src/svgToExcalidraw/elements/ExcalidrawScene.ts
+++ b/src/svgToExcalidraw/elements/ExcalidrawScene.ts
@@ -1,4 +1,4 @@
-import { GITHUB_RELEASES } from "src/constants";
+import { GITHUB_RELEASES } from "src/constants/constants";
import { ExcalidrawGenericElement } from "./ExcalidrawElement";
declare const PLUGIN_VERSION:string;
diff --git a/src/svgToExcalidraw/walker.ts b/src/svgToExcalidraw/walker.ts
index 2d8f4ba..9a90ba8 100644
--- a/src/svgToExcalidraw/walker.ts
+++ b/src/svgToExcalidraw/walker.ts
@@ -25,7 +25,7 @@ import {
import { getTransformMatrix, transformPoints } from "./transform";
import { pointsOnPath } from "points-on-path";
import { randomId, getWindingOrder } from "./utils";
-import { ROUNDNESS } from "../constants";
+import { ROUNDNESS } from "../constants/constants";
const SUPPORTED_TAGS = [
"svg",
diff --git a/src/utils/AIUtils.ts b/src/utils/AIUtils.ts
new file mode 100644
index 0000000..c612f25
--- /dev/null
+++ b/src/utils/AIUtils.ts
@@ -0,0 +1,129 @@
+import { Notice, RequestUrlResponse, requestUrl } from "obsidian";
+import ExcalidrawPlugin from "src/main";
+
+type MessageContent =
+ | string
+ | (string | { type: "image_url"; image_url: string })[];
+
+export type GPTCompletionRequest = {
+ model: string;
+ messages: {
+ role: "system" | "user" | "assistant" | "function";
+ content: MessageContent;
+ name?: string | undefined;
+ }[];
+ functions?: any[] | undefined;
+ function_call?: any | undefined;
+ stream?: boolean | undefined;
+ temperature?: number | undefined;
+ top_p?: number | undefined;
+ max_tokens?: number | undefined;
+ n?: number | undefined;
+ best_of?: number | undefined;
+ frequency_penalty?: number | undefined;
+ presence_penalty?: number | undefined;
+ logit_bias?:
+ | {
+ [x: string]: number;
+ }
+ | undefined;
+ stop?: (string[] | string) | undefined;
+};
+
+export type AIRequest = {
+ image?: string;
+ text?: string;
+ instruction?: string;
+ systemPrompt?: string;
+};
+
+export const postOpenAI = async (request: AIRequest) : Promise => {
+ const plugin: ExcalidrawPlugin = window.ExcalidrawAutomate.plugin;
+ const { openAIAPIToken, openAIDefaultTextModel, openAIDefaultVisionModel} = plugin.settings;
+ const { image, text, instruction, systemPrompt } = request;
+ const requestType = image ? "image" : "text";
+ let body: GPTCompletionRequest;
+
+ if(openAIAPIToken === "") {
+ new Notice("OpenAI API Token is not set. Please set it in plugin settings.");
+ return null;
+ }
+ switch (requestType) {
+ case "text":
+ body = {
+ model: openAIDefaultTextModel,
+ max_tokens: 4096,
+ messages: [
+ ...(systemPrompt ? [{role: "system" as const,content: systemPrompt}] : []),
+ {
+ role: "user",
+ content: text,
+ },
+ ...(instruction ? [{role: "user" as const,content: instruction}] : [])
+ ],
+ };
+ break;
+ case "image":
+ body = {
+ model: openAIDefaultVisionModel,
+ max_tokens: 4096,
+ messages: [
+ ...(systemPrompt ? [{role: "system" as const,content: [systemPrompt]}] : []),
+ {
+ role: "user",
+ content: [
+ {
+ type: "image_url",
+ image_url: image,
+ },
+ ...(instruction ? [instruction] : []), //"Turn this into a single html file using tailwind.",
+ ],
+ },
+ ],
+ };
+ break;
+ default:
+ return null;
+ }
+
+ try {
+ const resp = await requestUrl ({
+ url: "https://api.openai.com/v1/chat/completions",
+ method: "post",
+ contentType: "application/json",
+ body: JSON.stringify(body),
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${openAIAPIToken}`,
+ },
+ throw: false
+ });
+ return resp;
+ } catch (e) {
+ console.log(e);
+ }
+ return null;
+}
+
+/**
+ * Grabs the codeblock contents from the supplied markdown string.
+ * @param markdown
+ * @param codeblockType
+ * @returns an array of dictionaries with the codeblock contents and type
+ */
+export const extractCodeBlocks = (markdown: string): { data: string, type: string }[] => {
+ if (!markdown) return [];
+
+ markdown = markdown.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
+ const result: { data: string, type: string }[] = [];
+ const regex = /```([a-zA-Z0-9]*)\n([\s\S]+?)```/g;
+ let match;
+
+ while ((match = regex.exec(markdown)) !== null) {
+ const codeblockType = match[1]??"";
+ const codeblockString = match[2].trim();
+ result.push({ data: codeblockString, type: codeblockType });
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/src/utils/CanvasNodeFactory.ts b/src/utils/CanvasNodeFactory.ts
index 133b86a..a023ef2 100644
--- a/src/utils/CanvasNodeFactory.ts
+++ b/src/utils/CanvasNodeFactory.ts
@@ -11,6 +11,7 @@ container.appendChild(node.contentEl)
import { TFile, WorkspaceLeaf, WorkspaceSplit } from "obsidian";
import ExcalidrawView from "src/ExcalidrawView";
import { getContainerForDocument, ConstructableWorkspaceSplit, isObsidianThemeDark } from "./ObsidianUtils";
+import { CustomMutationObserver, isDebugMode } from "./DebugHelper";
declare module "obsidian" {
interface Workspace {
@@ -94,8 +95,8 @@ export class CanvasNodeFactory {
if (!node.child.editor?.containerEl?.parentElement?.parentElement) return;
node.child.editor.containerEl.parentElement.parentElement.classList.remove(obsidianTheme);
node.child.editor.containerEl.parentElement.parentElement.classList.add(theme);
-
- const observer = new MutationObserver((mutationsList) => {
+
+ const nodeObserverFn: MutationCallback = (mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const targetElement = mutation.target as HTMLElement;
@@ -105,7 +106,10 @@ export class CanvasNodeFactory {
}
}
}
- });
+ };
+ const observer = isDebugMode
+ ? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
+ : new MutationObserver(nodeObserverFn);
observer.observe(node.child.editor.containerEl.parentElement.parentElement, { attributes: true });
})();
diff --git a/src/utils/CustomEmbeddableUtils.ts b/src/utils/CustomEmbeddableUtils.ts
index 1024e1b..3dddf1a 100644
--- a/src/utils/CustomEmbeddableUtils.ts
+++ b/src/utils/CustomEmbeddableUtils.ts
@@ -1,12 +1,12 @@
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
-import { DEVICE, REG_LINKINDEX_INVALIDCHARS } from "src/constants";
+import { DEVICE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
import { getParentOfClass } from "./ObsidianUtils";
import { TFile, WorkspaceLeaf } from "obsidian";
import { getLinkParts } from "./Utils";
import ExcalidrawView from "src/ExcalidrawView";
export const useDefaultExcalidrawFrame = (element: NonDeletedExcalidrawElement) => {
- return !element.link.startsWith("["); // && !element.link.match(TWITTER_REG);
+ return !(element.link.startsWith("[") || element.link.startsWith("file:") || element.link.startsWith("data:")); // && !element.link.match(TWITTER_REG);
}
export const leafMap = new Map();
diff --git a/src/utils/DebugHelper.ts b/src/utils/DebugHelper.ts
new file mode 100644
index 0000000..5d292c5
--- /dev/null
+++ b/src/utils/DebugHelper.ts
@@ -0,0 +1,38 @@
+export const isDebugMode = false;
+export const durationTreshold = 0.05; //ms
+
+export class CustomMutationObserver {
+ private originalCallback: MutationCallback;
+ private observer: MutationObserver | null;
+ private name: string;
+
+ constructor(callback: MutationCallback, name: string) {
+ this.originalCallback = callback;
+ this.observer = null;
+ this.name = name;
+ }
+
+ observe(target: Node, options: MutationObserverInit) {
+ const wrappedCallback: MutationCallback = async (mutationsList, observer) => {
+ const startTime = performance.now(); // Get start time
+ await this.originalCallback(mutationsList, observer); // Invoke the original callback
+ const endTime = performance.now(); // Get end time
+ const executionTime = endTime - startTime;
+ if (executionTime > durationTreshold) {
+ console.log(`Excalidraw ${this.name} MutationObserver callback took ${executionTime}ms to execute`);
+ }
+ };
+
+ this.observer = new MutationObserver(wrappedCallback);
+
+ // Start observing with the modified callback
+ this.observer.observe(target, options);
+ }
+
+ disconnect() {
+ if (this.observer) {
+ this.observer.disconnect();
+ this.observer = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/utils/DynamicStyling.ts b/src/utils/DynamicStyling.ts
index 069c0f3..28dd3b2 100644
--- a/src/utils/DynamicStyling.ts
+++ b/src/utils/DynamicStyling.ts
@@ -6,7 +6,7 @@ import { DynamicStyle } from "src/types";
import { cloneElement } from "src/ExcalidrawAutomate";
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/element/types";
import { addAppendUpdateCustomData } from "./Utils";
-import { mutateElement } from "src/constants";
+import { mutateElement } from "src/constants/constants";
export const setDynamicStyle = (
ea: ExcalidrawAutomate,
diff --git a/src/utils/ExcalidrawViewUtils.ts b/src/utils/ExcalidrawViewUtils.ts
index a8aea5f..27728d4 100644
--- a/src/utils/ExcalidrawViewUtils.ts
+++ b/src/utils/ExcalidrawViewUtils.ts
@@ -1,5 +1,5 @@
-import { MAX_IMAGE_SIZE, IMAGE_TYPES } from "src/constants";
+import { MAX_IMAGE_SIZE, IMAGE_TYPES } from "src/constants/constants";
import { TFile } from "obsidian";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts
index c9b3b12..feff407 100644
--- a/src/utils/FileUtils.ts
+++ b/src/utils/FileUtils.ts
@@ -1,6 +1,6 @@
import { DataURL } from "@zsviczian/excalidraw/types/types";
import { loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
-import { URLFETCHTIMEOUT } from "src/constants";
+import { URLFETCHTIMEOUT } from "src/constants/constants";
import { MimeType } from "src/EmbeddedFileLoader";
import { ExcalidrawSettings } from "src/settings";
import { errorlog, getDataURL } from "./Utils";
@@ -134,7 +134,7 @@ export function getEmbedFilename(
* Open or create a folderpath if it does not exist
* @param folderpath
*/
-export async function checkAndCreateFolder(folderpath: string) {
+export async function checkAndCreateFolder(folderpath: string):Promise {
const vault = app.vault;
folderpath = normalizePath(folderpath);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/658
@@ -146,7 +146,7 @@ export async function checkAndCreateFolder(folderpath: string) {
if (folder && folder instanceof TFile) {
new Notice(`The folder cannot be created because it already exists as a file: ${folderpath}.`)
}
- await vault.createFolder(folderpath);
+ return await vault.createFolder(folderpath);
}
export const getURLImageExtension = (url: string):string => {
diff --git a/src/utils/ModifierkeyHelper.ts b/src/utils/ModifierkeyHelper.ts
index da90ffe..f07f24c 100644
--- a/src/utils/ModifierkeyHelper.ts
+++ b/src/utils/ModifierkeyHelper.ts
@@ -1,4 +1,4 @@
-import { DEVICE, isDarwin } from "src/constants";
+import { DEVICE, isDarwin } from "src/constants/constants";
export type ModifierKeys = {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean};
export type KeyEvent = PointerEvent | MouseEvent | KeyboardEvent | React.DragEvent | React.PointerEvent | React.MouseEvent | ModifierKeys;
export type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
diff --git a/src/utils/ObsidianUtils.ts b/src/utils/ObsidianUtils.ts
index dc63a2f..fc65e4e 100644
--- a/src/utils/ObsidianUtils.ts
+++ b/src/utils/ObsidianUtils.ts
@@ -5,7 +5,7 @@ import {
import ExcalidrawPlugin from "../main";
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
-import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants";
+import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
let parent = element.parentElement;
diff --git a/src/utils/StylesManager.ts b/src/utils/StylesManager.ts
index 279058f..09d09eb 100644
--- a/src/utils/StylesManager.ts
+++ b/src/utils/StylesManager.ts
@@ -11,6 +11,8 @@ export class StylesManager {
private styleDark: string;
private plugin: ExcalidrawPlugin;
+
+
constructor(plugin: ExcalidrawPlugin) {
this.plugin = plugin;
plugin.app.workspace.onLayoutReady(async () => {
diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts
index 89c6004..8fd6201 100644
--- a/src/utils/Utils.ts
+++ b/src/utils/Utils.ts
@@ -13,7 +13,7 @@ import {
ASSISTANT_FONT,
CASCADIA_FONT,
VIRGIL_FONT,
-} from "src/constFonts";
+} from "src/constants/constFonts";
import {
FRONTMATTER_KEY_EXPORT_DARK,
FRONTMATTER_KEY_EXPORT_TRANSPARENT,
@@ -23,7 +23,7 @@ import {
exportToSvg,
exportToBlob,
IMAGE_TYPES
-} from "../constants";
+} from "../constants/constants";
import ExcalidrawPlugin from "../main";
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
import { ExportSettings } from "../ExcalidrawView";
diff --git a/styles.css b/styles.css
index 2b670dd..0395d1d 100644
--- a/styles.css
+++ b/styles.css
@@ -497,3 +497,10 @@ hr.excalidraw-setting-hr {
box-shadow: unset;
}
+.excalidraw .canvas-node .ex-md-font-hand-drawn {
+ --font-text: "Virgil";
+}
+
+.excalidraw .canvas-node .ex-md-font-code {
+ --font-text: "Cascadia";
+}