mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f8a9bfa8a | ||
|
|
8b1daed0ef | ||
|
|
44c828c7e7 |
291
ea-scripts/ExcaliAI.md
Normal file
291
ea-scripts/ExcaliAI.md
Normal file
@@ -0,0 +1,291 @@
|
||||
/*
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||

|
||||
```js*/
|
||||
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.4")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Initialize values and settings
|
||||
// --------------------------------------
|
||||
let settings = ea.getScriptSettings();
|
||||
|
||||
if(!settings["Custom System Prompts"]) {
|
||||
settings = {
|
||||
"Custom System Prompts" : {},
|
||||
"Agent's Task": "Wireframe to code",
|
||||
"Output Type": "html"
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting because there is nothing selected.",4000);
|
||||
return;
|
||||
}
|
||||
ea.copyViewElementsToEAforEditing(viewElements, true);
|
||||
const bb = ea.getBoundingBox(viewElements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
const maxRatio = Math.sqrt(size/16000000);
|
||||
const scale = minRatio > 1
|
||||
? minRatio
|
||||
: (
|
||||
maxRatio > 1
|
||||
? 1/maxRatio
|
||||
: 1
|
||||
);
|
||||
|
||||
const loader = ea.getEmbeddedFilesLoader(false);
|
||||
const exportSettings = {
|
||||
withBackground: true,
|
||||
withTheme: true,
|
||||
};
|
||||
|
||||
const dataURL =
|
||||
await ea.createPNGBase64(
|
||||
null,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
);
|
||||
ea.clear();
|
||||
return { image: dataURL };
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// Submit Prompt
|
||||
// --------------------------------------
|
||||
const run = async () => {
|
||||
const requestObject = await getRequestObjFromSelectedElements(ea.targetView);
|
||||
requestObject.systemPrompt = allSystemPrompts[agentTask];
|
||||
requestObject.instruction = instructions[outputType];
|
||||
|
||||
const spinner = await ea.convertStringToDataURL(`
|
||||
<html><head><style>
|
||||
html, body {width: 100%; height: 100%; color: ${ea.getExcalidrawAPI().getAppState().theme === "dark" ? "white" : "black"};}
|
||||
body {display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; overflow: hidden;}
|
||||
.Spinner {display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto;}
|
||||
.Spinner svg {animation: rotate 1.6s linear infinite; transform-origin: center center; width: 40px; height: 40px;}
|
||||
.Spinner circle {stroke: currentColor; animation: dash 1.6s linear 0s infinite; stroke-linecap: round;}
|
||||
@keyframes rotate {100% {transform: rotate(360deg);}}
|
||||
@keyframes dash {0% {stroke-dasharray: 1, 300; stroke-dashoffset: 0;} 50% {stroke-dasharray: 150, 300; stroke-dashoffset: -200;} 100% {stroke-dasharray: 1, 300; stroke-dashoffset: -280;}}
|
||||
</style></head><body>
|
||||
<div class="Spinner">
|
||||
<svg viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="46" stroke-width="8" fill="none" stroke-miter-limit="10"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>Generating...</div>
|
||||
</body></html>`);
|
||||
|
||||
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(`
|
||||
<html><head><style>
|
||||
html, body {height: 100%;}
|
||||
body {display: flex; flex-direction: column; align-items: center; justify-content: center; color: red;}
|
||||
h1, h3 {margin-top: 0;margin-bottom: 0.5rem;}
|
||||
</style></head><body>
|
||||
<h1>Error!</h1>
|
||||
<h3>${error}</h3>
|
||||
</body></html>`);
|
||||
new Notice (error);
|
||||
ea.getElement(id).src = errorDataURL;
|
||||
ea.addElementsToView(false,true);
|
||||
}
|
||||
|
||||
if(!result?.json?.hasOwnProperty("choices")) {
|
||||
await errorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------
|
||||
// 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();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
configModal.onClose = () => {
|
||||
if(dirty) {
|
||||
settings["Custom System Prompts"] = customSystemPrompts;
|
||||
settings["Agent's Task"] = agentTask;
|
||||
settings["Output Type"] = outputType;
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
configModal.open();
|
||||
1
ea-scripts/ExcaliAI.svg
Normal file
1
ea-scripts/ExcaliAI.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M320 0c17.7 0 32 14.3 32 32V96H472c39.8 0 72 32.2 72 72V440c0 39.8-32.2 72-72 72H168c-39.8 0-72-32.2-72-72V168c0-39.8 32.2-72 72-72H288V32c0-17.7 14.3-32 32-32zM208 384c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H208zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H304zm96 0c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H400zM264 256a40 40 0 1 0 -80 0 40 40 0 1 0 80 0zm152 40a40 40 0 1 0 0-80 40 40 0 1 0 0 80zM48 224H64V416H48c-26.5 0-48-21.5-48-48V272c0-26.5 21.5-48 48-48zm544 0c26.5 0 48 21.5 48 48v96c0 26.5-21.5 48-48 48H576V224h16z"/></svg>
|
||||
|
After Width: | Height: | Size: 694 B |
@@ -1,109 +1,61 @@
|
||||
/*
|
||||
Based on https://github.com/SawyerHood/draw-a-ui
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||

|
||||
```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 <a href="https://platform.openai.com/">https://platform.openai.com/</a><br>⚠️ 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,6 +63,7 @@ const getRequestObjFromSelectedElements = async (view) => {
|
||||
new Notice ("Aborting because there is nothing selected.",4000);
|
||||
return;
|
||||
}
|
||||
ea.copyViewElementsToEAforEditing(viewElements, true);
|
||||
const bb = ea.getBoundingBox(viewElements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
@@ -122,82 +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(
|
||||
view.file.path,
|
||||
const dataURL =
|
||||
await ea.createPNGBase64(
|
||||
null,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
);
|
||||
const dataURL = `data:image/png;base64,${await blobToBase64(img)}`;
|
||||
return { json: async () => ({ image: dataURL }) }
|
||||
ea.clear();
|
||||
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(`
|
||||
<html><head><style>
|
||||
html, body {width: 100%; height: 100%; color: ${ea.getExcalidrawAPI().getAppState().theme === "dark" ? "white" : "black"};}
|
||||
body {display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; overflow: hidden;}
|
||||
.Spinner {display: flex; align-items: center; justify-content: center; margin-left: auto; margin-right: auto;}
|
||||
.Spinner svg {animation: rotate 1.6s linear infinite; transform-origin: center center; width: 40px; height: 40px;}
|
||||
.Spinner circle {stroke: currentColor; animation: dash 1.6s linear 0s infinite; stroke-linecap: round;}
|
||||
@keyframes rotate {100% {transform: rotate(360deg);}}
|
||||
@keyframes dash {0% {stroke-dasharray: 1, 300; stroke-dashoffset: 0;} 50% {stroke-dasharray: 150, 300; stroke-dashoffset: -200;} 100% {stroke-dasharray: 1, 300; stroke-dashoffset: -280;}}
|
||||
</style></head><body>
|
||||
<div class="Spinner">
|
||||
<svg viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="46" stroke-width="8" fill="none" stroke-miter-limit="10"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>Generating...</div>
|
||||
</body></html>`);
|
||||
|
||||
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(`
|
||||
<html><head><style>
|
||||
html, body {height: 100%;}
|
||||
body {display: flex; flex-direction: column; align-items: center; justify-content: center; color: red;}
|
||||
h1, h3 {margin-top: 0;margin-bottom: 0.5rem;}
|
||||
</style></head><body>
|
||||
<h1>Error!</h1>
|
||||
<h3>${error}</h3>
|
||||
</body></html>`);
|
||||
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([]);
|
||||
|
||||
configModal.open();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -115,6 +115,7 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Auto%20Draw%20for%20Pen.svg"/></div>|[[#Auto Draw for Pen]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Boolean%20Operations.svg"/></div>|[[#Boolean Operations]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Copy%20Selected%20Element%20Styles%20to%20Global.svg"/></div>|[[#Copy Selected Element Styles to Global]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.svg"/></div>|[[#ExcaliAI]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.svg"/></div>|[[#GPT Draw-a-UI]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.svg"/></div>|[[#Hardware Eraser Support]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Palette%20loader.svg"/></div>|[[#Palette Loader]]|
|
||||
@@ -351,11 +352,17 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/7flash'>@7flash</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Grid%20Selected%20Images.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script arranges selected images into compact grid view, removing gaps in-between, resizing when necessary and breaking into multiple rows/columns.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-grid-selected-images.png'></td></tr></table>
|
||||
|
||||
## ExcaliAI
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/ExcaliAI.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/ExcaliAI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Various AI features based on GPT Vision.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
|
||||
|
||||
## GPT Draw-a-UI
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
|
||||
|
||||
|
||||
## Hardware Eraser Support
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<RequestUrlResponse> {
|
||||
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<string> {
|
||||
// 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<TFolder> {
|
||||
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<string> {
|
||||
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<string> {
|
||||
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<string[]> {
|
||||
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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 " +
|
||||
|
||||
@@ -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<any> = null;
|
||||
public toolsPanelRef: React.MutableRefObject<any> = null;
|
||||
public embeddableMenuRef: React.MutableRefObject<any> = 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(),
|
||||
)
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
116
src/constants/startupScript.md
Normal file
116
src/constants/startupScript.md
Normal file
@@ -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<HTMLDivElement>;
|
||||
* 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<void>;
|
||||
*/
|
||||
//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<void>;
|
||||
*/
|
||||
//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) => {};
|
||||
7
src/constants/starutpscript.ts
Normal file
7
src/constants/starutpscript.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<TFile> {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<TFile> {
|
||||
|
||||
@@ -17,6 +17,22 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.0.4":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/A1vrSGBbWgo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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<any>;",
|
||||
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<string>;",
|
||||
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<string[]>;",
|
||||
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<string>;",
|
||||
desc: null,
|
||||
code: "async addLaTex(topX: number, topY: number, tex: string): Promise<string>;",
|
||||
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<TFolder>",
|
||||
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<RequestUrlResponse>",
|
||||
desc:
|
||||
"This asynchronous function should be awaited. It posts the supplied request to the OpenAI API and returns the response.<br>" +
|
||||
"The response is a dictionary with the following keys:<br><code>{image, text, instruction, systemPrompt}</code><br>"+
|
||||
"<b>image</b> should be a dataURL - use ea.createPNGBase64()<br>"+
|
||||
"<b>systemPrompt</b>: if <code>undefined</code> the message to OpenAI will not include a system prompt<br>"+
|
||||
"<b>text</b> is the actual user prompt, a request must have either an image or a text<br>"+
|
||||
"<b>instruction</b> 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)<br>"+
|
||||
"RequestUrlResponse is defined in the <a onclick='window.open(\"https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts\")'>Obsidian API</a>",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "convertStringToDataURL",
|
||||
code: 'async convertStringToDataURL (data:string, type: string = "text/html"):Promise<string>',
|
||||
desc: "Converts a string to a DataURL.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "setViewModeEnabled",
|
||||
code: "setViewModeEnabled(enabled: boolean): void;",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<typeof en> } = {
|
||||
ar,
|
||||
|
||||
@@ -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 <a href='https://platform.openai.com/api-keys'>OpenAI account</a>.",
|
||||
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 <a href='https://platform.openai.com/docs/models'>OpenAI website</a>.",
|
||||
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 <a href='https://platform.openai.com/docs/models'>OpenAI website</a>.",
|
||||
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 <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/API/ExcalidrawAutomate.d.ts'>ExcalidrawAutomate.d.ts</a> file, " +
|
||||
"visiting the <a href='https://zsviczian.github.io/obsidian-excalidraw-plugin/'>ExcalidrawAutomate How-to</a> 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 <code>excalidraw-</code> or <code>ea.</code> 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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
// 简体中文
|
||||
|
||||
228
src/main.ts
228
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,
|
||||
|
||||
@@ -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 = {
|
||||
</svg>
|
||||
),
|
||||
Reload: (<RotateCcw />),
|
||||
Copy: (<Copy /> ),
|
||||
Globe: (<Globe />),
|
||||
ZoomToSelectedElement: (<Scan />),
|
||||
Properties: (<Settings />),
|
||||
|
||||
@@ -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") && (
|
||||
<ActionButton
|
||||
key={"CopyCode"}
|
||||
title={t("COPYCODE")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
navigator.clipboard.writeText(atob(link.split(",")[1]));
|
||||
}}
|
||||
icon={ICONS.Copy}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
155
src/settings.ts
155
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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
129
src/utils/AIUtils.ts
Normal file
129
src/utils/AIUtils.ts
Normal file
@@ -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<RequestUrlResponse> => {
|
||||
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;
|
||||
}
|
||||
@@ -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 });
|
||||
})();
|
||||
|
||||
@@ -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<string, WorkspaceLeaf>();
|
||||
|
||||
38
src/utils/DebugHelper.ts
Normal file
38
src/utils/DebugHelper.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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<TFolder> {
|
||||
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 => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,8 @@ export class StylesManager {
|
||||
private styleDark: string;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user