mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
24 Commits
2.0.1-beta
...
2.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324609999f | ||
|
|
3f54b851ae | ||
|
|
8bbb04b421 | ||
|
|
dc396c8707 | ||
|
|
52cc5d3aa7 | ||
|
|
87b6335905 | ||
|
|
17358f16c8 | ||
|
|
23eb268031 | ||
|
|
27f4cb248d | ||
|
|
bbaf4f7a34 | ||
|
|
559455bf5b | ||
|
|
bd519aff08 | ||
|
|
febeb787b5 | ||
|
|
9f8a9bfa8a | ||
|
|
8b1daed0ef | ||
|
|
44c828c7e7 | ||
|
|
afabeaa2f3 | ||
|
|
e72c1676c2 | ||
|
|
5a17eb7054 | ||
|
|
75d52c07b8 | ||
|
|
4dc6c17486 | ||
|
|
e780930799 | ||
|
|
49cd6a36a1 | ||
|
|
d4830983e2 |
296
ea-scripts/ExcaliAI.md
Normal file
296
ea-scripts/ExcaliAI.md
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
<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).link = errorDataURL;
|
||||
ea.addElementsToView(false,true);
|
||||
}
|
||||
|
||||
if(!result?.json?.hasOwnProperty("choices")) {
|
||||
await errorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(result, result.json);
|
||||
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
|
||||
|
||||
if(!content) {
|
||||
await errorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
} catch(e) {
|
||||
await 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();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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 |
296
ea-scripts/GPT-Draw-a-UI.md
Normal file
296
ea-scripts/GPT-Draw-a-UI.md
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
<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).link = errorDataURL;
|
||||
ea.addElementsToView(false,true);
|
||||
}
|
||||
|
||||
if(!result?.json?.hasOwnProperty("choices")) {
|
||||
await errorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(result, result.json);
|
||||
let content = ea.extractCodeBlocks(result.json.choices[0]?.message?.content)[0]?.data;
|
||||
|
||||
if(!content) {
|
||||
await errorMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
} catch(e) {
|
||||
await 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();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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/GPT-Draw-a-UI.svg
Normal file
1
ea-scripts/GPT-Draw-a-UI.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 |
@@ -29,7 +29,7 @@ try {
|
||||
elHeight /= 1.1;
|
||||
}
|
||||
} else if (elWidth * elHeight < areaAvailable) {
|
||||
while (elWidth * elHeight > areaAvailable) {
|
||||
while (elWidth * elHeight < areaAvailable) {
|
||||
elWidth *= 1.1;
|
||||
elHeight *= 1.1;
|
||||
}
|
||||
@@ -64,4 +64,4 @@ try {
|
||||
ea.addElementsToView(false, true, true);
|
||||
} catch (err) {
|
||||
_ = new Notice(err.toString())
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -115,6 +115,8 @@ 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]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/PDF%20Page%20Text%20to%20Clipboard.svg"/></div>|[[#PDF Page Text to Clipboard]]|
|
||||
@@ -350,6 +352,19 @@ 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">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
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Hardware%20Eraser%20Support.md
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 949 KiB After Width: | Height: | Size: 409 KiB |
BIN
images/scripts-draw-a-ui.jpg
Normal file
BIN
images/scripts-draw-a-ui.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1",
|
||||
"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
|
||||
@@ -396,6 +396,15 @@ export class EmbeddedFilesLoader {
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
|
||||
//this is a fix for backward compatibility - I messed up with generating the local link
|
||||
private getLocalPath(path: string) {
|
||||
const localPath = path.split("file://")[1]
|
||||
if(localPath.startsWith("/")) {
|
||||
return localPath.substring(1);
|
||||
}
|
||||
return localPath;
|
||||
}
|
||||
|
||||
private async _getObsidianImage(inFile: TFile | EmbeddedFile, depth: number): Promise<ImgData> {
|
||||
if (!this.plugin || !inFile) {
|
||||
return null;
|
||||
@@ -442,7 +451,7 @@ export class EmbeddedFilesLoader {
|
||||
const ab = isHyperLink || isPDF
|
||||
? null
|
||||
: isLocalLink
|
||||
? await readLocalFileBinary((inFile as EmbeddedFile).hyperlink.split("file://")[1])
|
||||
? await readLocalFileBinary(this.getLocalPath((inFile as EmbeddedFile).hyperlink))
|
||||
: await app.vault.readBinary(file);
|
||||
|
||||
let dURL: DataURL = null;
|
||||
@@ -535,7 +544,7 @@ export class EmbeddedFilesLoader {
|
||||
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
|
||||
const data = await this._getObsidianImage(embeddedFile, depth);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
const fileData: FileData = {
|
||||
mimeType: data.mimeType,
|
||||
id: entry.value[0],
|
||||
dataURL: data.dataURL,
|
||||
|
||||
@@ -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 " +
|
||||
@@ -1235,11 +1235,8 @@ export class ExcalidrawData {
|
||||
const scene = this.scene as SceneDataWithFiles;
|
||||
|
||||
//remove files and equations that no longer have a corresponding image element
|
||||
const fileIds = (
|
||||
scene.elements.filter(
|
||||
(e) => e.type === "image",
|
||||
) as ExcalidrawImageElement[]
|
||||
).map((e) => e.fileId);
|
||||
const images = scene.elements.filter((e) => e.type === "image") as ExcalidrawImageElement[];
|
||||
const fileIds = (images).map((e) => e.fileId);
|
||||
this.files.forEach((value, key) => {
|
||||
if (!fileIds.contains(key)) {
|
||||
this.files.delete(key);
|
||||
@@ -1261,22 +1258,26 @@ export class ExcalidrawData {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//check if there are any images that need to be processed in the new scene
|
||||
if (!scene.files || Object.keys(scene.files).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//assing new fileId to duplicate equation and markdown files
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/601
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/593
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/297
|
||||
const processedIds = new Set<string>();
|
||||
fileIds.forEach(fileId=>{
|
||||
fileIds.forEach((fileId,idx)=>{
|
||||
if(processedIds.has(fileId)) {
|
||||
const file = this.getFile(fileId);
|
||||
const equation = this.getEquation(fileId);
|
||||
const mermaid = this.getMermaid(fileId);
|
||||
|
||||
|
||||
|
||||
//images should have a single reference, but equations, and markdown embeds should have as many as instances of the file in the scene
|
||||
if(file && (file.isHyperLink || file.isLocalLink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
return;
|
||||
@@ -1284,6 +1285,12 @@ export class ExcalidrawData {
|
||||
if(mermaid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(getMermaidText(images[idx])) {
|
||||
this.setMermaid(fileId, {mermaid: getMermaidText(images[idx]), isLoaded: true});
|
||||
return;
|
||||
}
|
||||
|
||||
const newId = fileid();
|
||||
(scene
|
||||
.elements
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -128,7 +129,7 @@ const setStyle = ({element,imgAttributes,onCanvas}:{
|
||||
element.addClass("excalidraw-embedded-img");
|
||||
}
|
||||
if(
|
||||
window.ExcalidrawAutomate.plugin.settings.canvasImmersiveEmbed &&
|
||||
window?.ExcalidrawAutomate?.plugin?.settings?.canvasImmersiveEmbed &&
|
||||
!element.hasClass("excalidraw-canvas-immersive")
|
||||
) {
|
||||
element.addClass("excalidraw-canvas-immersive");
|
||||
@@ -399,6 +400,7 @@ const createImgElement = async (
|
||||
fheight: imgOrDiv.getAttribute("h"),
|
||||
style: [...Array.from(imgOrDiv.classList)],
|
||||
}, onCanvas);
|
||||
if(!newImg) return;
|
||||
parent.empty();
|
||||
if(!onCanvas) {
|
||||
newImg.style.maxHeight = imgMaxHeigth;
|
||||
@@ -412,7 +414,7 @@ const createImgElement = async (
|
||||
if(imgOrDiv.hasClass(cssClass)) return;
|
||||
imgOrDiv.addClass(cssClass);
|
||||
});
|
||||
if(window.ExcalidrawAutomate.plugin.settings.canvasImmersiveEmbed) {
|
||||
if(window?.ExcalidrawAutomate?.plugin?.settings?.canvasImmersiveEmbed) {
|
||||
if(!imgOrDiv.hasClass("excalidraw-canvas-immersive")) {
|
||||
imgOrDiv.addClass("excalidraw-canvas-immersive");
|
||||
}
|
||||
@@ -627,7 +629,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 +642,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 +697,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 +743,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 +774,9 @@ export const observer = new MutationObserver(async (m) => {
|
||||
});
|
||||
});
|
||||
node.appendChild(div);
|
||||
});
|
||||
};
|
||||
|
||||
export const legacyExcalidrawPopoverObserver = 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;
|
||||
@@ -139,7 +139,8 @@ export const REG_BLOCK_REF_CLEAN = /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\\r\n]/g;
|
||||
// /[!"#$%&()*+,.:;<=>?@^`{|}~\/\[\]\\]/g;
|
||||
// https://discord.com/channels/686053708261228577/989603365606531104/1000128926619816048
|
||||
// /\+|\/|~|=|%|\(|\)|{|}|,|&|\.|\$|!|\?|;|\[|]|\^|#|\*|<|>|&|@|\||\\|"|:|\s/g;
|
||||
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico"];
|
||||
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico", "jtif", "tif"];
|
||||
export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"];
|
||||
export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"];
|
||||
export const MAX_IMAGE_SIZE = 500;
|
||||
export const FRONTMATTER_KEY = "excalidraw-plugin";
|
||||
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";
|
||||
@@ -228,7 +228,7 @@ function RenderObsidianView(
|
||||
return () => {}; //cleanup on unmount
|
||||
}, [linkText, subpath, containerRef]);
|
||||
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvas: string) => {
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvasColor: string) => {
|
||||
if(!mdProps) return;
|
||||
if (!leafRef.current?.hasOwnProperty("node")) return;
|
||||
|
||||
@@ -246,28 +246,43 @@ function RenderObsidianView(
|
||||
if(mdProps.backgroundMatchElement) {
|
||||
const opacity = (mdProps?.backgroundOpacity ?? 50)/100;
|
||||
const color = element?.backgroundColor
|
||||
? ea.getCM(element.backgroundColor).alphaTo(opacity).stringHEX()
|
||||
? (element.backgroundColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(element.backgroundColor).alphaTo(opacity).stringHEX())
|
||||
: "transparent";
|
||||
|
||||
color === "transparent" ? canvasNode?.addClass("transparent") : canvasNode?.removeClass("transparent");
|
||||
canvasNode?.style.setProperty("--canvas-background", color);
|
||||
canvasNode?.style.setProperty("--background-primary", color);
|
||||
canvasNodeContainer?.style.setProperty("background-color", color);
|
||||
} else if (!(mdProps?.backgroundMatchElement ?? true )) {
|
||||
const opacity = (mdProps.backgroundOpacity??100)/100;
|
||||
const color = mdProps.backgroundMatchCanvas
|
||||
? ea.getCM(canvasColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX()
|
||||
? (canvasColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(canvasColor).alphaTo(opacity).stringHEX())
|
||||
: ea.getCM(mdProps.backgroundColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX();
|
||||
containerRef.current?.style.setProperty("--canvas-background", color);
|
||||
|
||||
color === "transparent" ? canvasNode?.addClass("transparent") : canvasNode?.removeClass("transparent");
|
||||
canvasNode?.style.setProperty("--canvas-background", color);
|
||||
canvasNode?.style.setProperty("--background-primary", color);
|
||||
canvasNodeContainer?.style.setProperty("background-color", color);
|
||||
}
|
||||
|
||||
if(mdProps.borderMatchElement) {
|
||||
const opacity = (mdProps?.borderOpacity ?? 50)/100;
|
||||
const color = element?.strokeColor
|
||||
? ea.getCM(element?.strokeColor).alphaTo(opacity).stringHEX()
|
||||
: "transparent";
|
||||
? (element.strokeColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(element.strokeColor).alphaTo(opacity).stringHEX())
|
||||
: "transparent";
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNode?.style.setProperty("--canvas-color", color);
|
||||
canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
} else if(!(mdProps?.borderMatchElement ?? true)) {
|
||||
const color = ea.getCM(mdProps.borderColor).alphaTo((mdProps.borderOpacity??100)/100).stringHEX();
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNode?.style.setProperty("--canvas-color", color);
|
||||
canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
}
|
||||
}
|
||||
@@ -398,7 +413,7 @@ export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; v
|
||||
color: `var(--text-normal)`,
|
||||
}}
|
||||
className={`${theme} canvas-node ${
|
||||
mdProps?.filenameVisible ? "" : "excalidraw-mdEmbed-hideFilename"}`}
|
||||
mdProps?.filenameVisible && !mdProps.useObsidianDefaults ? "" : "excalidraw-mdEmbed-hideFilename"}`}
|
||||
>
|
||||
<RenderObsidianView
|
||||
mdProps={mdProps}
|
||||
|
||||
@@ -8,6 +8,7 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
private contentEl: HTMLElement,
|
||||
private mdCustomData: EmbeddableMDCustomProps,
|
||||
private update?: Function,
|
||||
private isMDFile: boolean = true,
|
||||
) {
|
||||
if(!update) this.update = () => {};
|
||||
}
|
||||
@@ -33,16 +34,17 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
detailsDIV.style.display = this.mdCustomData.useObsidianDefaults ? "none" : "block";
|
||||
|
||||
const contentEl = detailsDIV
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_FILENAME_VISIBLE"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.filenameVisible)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.filenameVisible = value;
|
||||
})
|
||||
);
|
||||
|
||||
if(this.isMDFile) {
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_FILENAME_VISIBLE"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.filenameVisible)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.filenameVisible = value;
|
||||
})
|
||||
);
|
||||
}
|
||||
contentEl.createEl("h4",{text: t("ES_BACKGROUND_HEAD")});
|
||||
|
||||
let bgSetting: Setting;
|
||||
@@ -109,7 +111,7 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
|
||||
bgSetting.settingEl.style.display = (this.mdCustomData.backgroundMatchElement || this.mdCustomData.backgroundMatchCanvas) ? "none" : "";
|
||||
const opacity = (value:number):DocumentFragment => {
|
||||
return fragWithHTML(`Current transparency is <b>${value}%</b>`);
|
||||
return fragWithHTML(`Current opacity is <b>${value}%</b>`);
|
||||
}
|
||||
|
||||
const bgOpacitySetting = new Setting(contentEl)
|
||||
@@ -126,50 +128,52 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
})
|
||||
);
|
||||
|
||||
contentEl.createEl("h4",{text: t("ES_BORDER_HEAD")});
|
||||
let borderSetting: Setting;
|
||||
if(this.isMDFile) {
|
||||
contentEl.createEl("h4",{text: t("ES_BORDER_HEAD")});
|
||||
let borderSetting: Setting;
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_MATCH_ELEMENT"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.borderMatchElement)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderMatchElement = value;
|
||||
if(value) {
|
||||
borderSetting.settingEl.style.display = "none";
|
||||
} else {
|
||||
borderSetting.settingEl.style.display = "";
|
||||
}
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_COLOR"))
|
||||
.addColorPicker(colorPicker =>
|
||||
colorPicker
|
||||
.setValue(this.mdCustomData.borderColor)
|
||||
.onChange((value) => {
|
||||
this.mdCustomData.borderColor = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting.settingEl.style.display = this.mdCustomData.borderMatchElement ? "none" : "";
|
||||
|
||||
const borderOpacitySetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_OPACITY"))
|
||||
.setDesc(opacity(this.mdCustomData.borderOpacity))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,100,5)
|
||||
.setValue(this.mdCustomData.borderOpacity)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderOpacity = value;
|
||||
borderOpacitySetting.setDesc(opacity(value));
|
||||
this.update();
|
||||
})
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_MATCH_ELEMENT"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.borderMatchElement)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderMatchElement = value;
|
||||
if(value) {
|
||||
borderSetting.settingEl.style.display = "none";
|
||||
} else {
|
||||
borderSetting.settingEl.style.display = "";
|
||||
}
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_COLOR"))
|
||||
.addColorPicker(colorPicker =>
|
||||
colorPicker
|
||||
.setValue(this.mdCustomData.borderColor)
|
||||
.onChange((value) => {
|
||||
this.mdCustomData.borderColor = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting.settingEl.style.display = this.mdCustomData.borderMatchElement ? "none" : "";
|
||||
|
||||
const borderOpacitySetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_OPACITY"))
|
||||
.setDesc(opacity(this.mdCustomData.borderOpacity))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,100,5)
|
||||
.setValue(this.mdCustomData.borderOpacity)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderOpacity = value;
|
||||
borderOpacitySetting.setDesc(opacity(value));
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename }
|
||||
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/Utils";
|
||||
import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent";
|
||||
import { isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
|
||||
export type EmbeddableMDCustomProps = {
|
||||
useObsidianDefaults: boolean;
|
||||
@@ -30,7 +32,10 @@ export class EmbeddableSettings extends Modal {
|
||||
private isYouTube: boolean;
|
||||
private youtubeStart: string = null;
|
||||
private isMDFile: boolean;
|
||||
private notExcalidrawIsInternal: boolean;
|
||||
private isLocalURI: boolean;
|
||||
private mdCustomData: EmbeddableMDCustomProps;
|
||||
private onKeyDown: (ev: KeyboardEvent) => void;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
@@ -43,7 +48,9 @@ export class EmbeddableSettings extends Modal {
|
||||
this.ea.copyViewElementsToEAforEditing([this.element]);
|
||||
this.zoomValue = element.scale[0];
|
||||
this.isYouTube = isYouTube(this.element.link);
|
||||
this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file)
|
||||
this.notExcalidrawIsInternal = this.file && !this.view.plugin.isExcalidrawFile(this.file)
|
||||
this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file);
|
||||
this.isLocalURI = this.element.link.startsWith("file://");
|
||||
if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link);
|
||||
|
||||
this.mdCustomData = element.customData?.mdProps ?? view.plugin.settings.embeddableMarkdownDefaults;
|
||||
@@ -60,12 +67,12 @@ export class EmbeddableSettings extends Modal {
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(t("ES_TITLE"));
|
||||
//this.titleEl.setText(t("ES_TITLE"));
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
this.containerEl.removeEventListener("keydown",this.onKeyDown);
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
@@ -85,11 +92,21 @@ export class EmbeddableSettings extends Modal {
|
||||
}
|
||||
|
||||
const zoomValue = ():DocumentFragment => {
|
||||
return fragWithHTML(`Current zoom is <b>${Math.round(this.zoomValue*100)}%</b>`);
|
||||
}
|
||||
return fragWithHTML(`${t("ES_ZOOM_100_RELATIVE_DESC")}<br>Current zoom is <b>${Math.round(this.zoomValue*100)}%</b>`);
|
||||
}
|
||||
|
||||
const zoomSetting = new Setting(this.contentEl)
|
||||
.setName(t("ES_ZOOM"))
|
||||
.setDesc(zoomValue())
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(t("ES_ZOOM_100"))
|
||||
.onClick(() => {
|
||||
const api = this.view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
this.zoomValue = 1/api.getAppState().zoom.value;
|
||||
zoomSetting.setDesc(zoomValue());
|
||||
})
|
||||
)
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(10,400,5)
|
||||
@@ -113,68 +130,91 @@ export class EmbeddableSettings extends Modal {
|
||||
)
|
||||
}
|
||||
|
||||
if(this.isMDFile) {
|
||||
if(this.isMDFile || this.notExcalidrawIsInternal) {
|
||||
this.contentEl.createEl("h3",{text: t("ES_EMBEDDABLE_SETTINGS")});
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData).render();
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData, undefined, this.isMDFile).render();
|
||||
}
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(t("PROMPT_BUTTON_CANCEL"))
|
||||
.setTooltip("ESC")
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
})
|
||||
)
|
||||
.addButton(button =>
|
||||
button
|
||||
.setButtonText(t("PROMPT_BUTTON_OK"))
|
||||
.setTooltip("CTRL/Opt+Enter")
|
||||
.setCta()
|
||||
.onClick(()=>this.applySettings())
|
||||
)
|
||||
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
if(isWinCTRLorMacCMD(ev) && ev.key === "Enter") {
|
||||
this.applySettings();
|
||||
}
|
||||
}
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bOk = div.createEl("button", { text: t("PROMPT_BUTTON_OK"), cls: "excalidraw-prompt-button"});
|
||||
bOk.onclick = async () => {
|
||||
let dirty = false;
|
||||
const el = this.ea.getElement(this.element.id) as Mutable<ExcalidrawEmbeddableElement>;
|
||||
if(this.updatedFilepath) {
|
||||
const newPathWithExt = `${this.updatedFilepath}.${this.file.extension}`;
|
||||
if(newPathWithExt !== this.file.path) {
|
||||
const fnparts = splitFolderAndFilename(newPathWithExt);
|
||||
const newPath = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
fnparts.folderpath,
|
||||
fnparts.filename,
|
||||
);
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
|
||||
dirty = true;
|
||||
if(isValidYouTubeStart(this.youtubeStart)) {
|
||||
el.link = updateYouTubeStartTime(el.link,this.youtubeStart);
|
||||
} else {
|
||||
new Notice(t("ES_YOUTUBE_START_INVALID"));
|
||||
}
|
||||
}
|
||||
if(
|
||||
this.isMDFile && (
|
||||
this.mdCustomData.backgroundColor !== this.element.customData?.backgroundColor ||
|
||||
this.mdCustomData.borderColor !== this.element.customData?.borderColor ||
|
||||
this.mdCustomData.backgroundOpacity !== this.element.customData?.backgroundOpacity ||
|
||||
this.mdCustomData.borderOpacity !== this.element.customData?.borderOpacity ||
|
||||
this.mdCustomData.filenameVisible !== this.element.customData?.filenameVisible)
|
||||
) {
|
||||
addAppendUpdateCustomData(el,{mdProps: this.mdCustomData});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if(this.zoomValue !== this.element.scale[0]) {
|
||||
dirty = true;
|
||||
|
||||
el.scale = [this.zoomValue,this.zoomValue];
|
||||
}
|
||||
if(dirty) {
|
||||
this.ea.addElementsToView();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
const bCancel = div.createEl("button", { text: t("PROMPT_BUTTON_CANCEL"), cls: "excalidraw-prompt-button" });
|
||||
bCancel.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
this.onKeyDown = onKeyDown;
|
||||
this.containerEl.ownerDocument.addEventListener("keydown",onKeyDown);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async applySettings() {
|
||||
let dirty = false;
|
||||
const el = this.ea.getElement(this.element.id) as Mutable<ExcalidrawEmbeddableElement>;
|
||||
if(this.updatedFilepath) {
|
||||
const newPathWithExt = `${this.updatedFilepath}.${this.file.extension}`;
|
||||
if(newPathWithExt !== this.file.path) {
|
||||
const fnparts = splitFolderAndFilename(newPathWithExt);
|
||||
const newPath = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
fnparts.folderpath,
|
||||
fnparts.filename,
|
||||
);
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
|
||||
dirty = true;
|
||||
if(this.youtubeStart === "" || isValidYouTubeStart(this.youtubeStart)) {
|
||||
el.link = updateYouTubeStartTime(el.link,this.youtubeStart);
|
||||
} else {
|
||||
new Notice(t("ES_YOUTUBE_START_INVALID"));
|
||||
}
|
||||
}
|
||||
if(
|
||||
this.isMDFile && (
|
||||
this.mdCustomData.backgroundColor !== this.element.customData?.backgroundColor ||
|
||||
this.mdCustomData.borderColor !== this.element.customData?.borderColor ||
|
||||
this.mdCustomData.backgroundOpacity !== this.element.customData?.backgroundOpacity ||
|
||||
this.mdCustomData.borderOpacity !== this.element.customData?.borderOpacity ||
|
||||
this.mdCustomData.filenameVisible !== this.element.customData?.filenameVisible)
|
||||
) {
|
||||
addAppendUpdateCustomData(el,{mdProps: this.mdCustomData});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if(this.zoomValue !== this.element.scale[0]) {
|
||||
dirty = true;
|
||||
|
||||
el.scale = [this.zoomValue,this.zoomValue];
|
||||
}
|
||||
if(dirty) {
|
||||
this.ea.addElementsToView();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 @@
|
||||
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 { scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper";
|
||||
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
@@ -60,6 +59,7 @@ export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
ea.canvas.theme = this.view.excalidrawAPI.getAppState().theme;
|
||||
const scaleToFullsize = scaleToFullsizeModifier(event);
|
||||
(async () => {
|
||||
//this.view.currentPosition = this.position;
|
||||
await ea.addImage(0, 0, item, !scaleToFullsize);
|
||||
ea.addElementsToView(true, true, true);
|
||||
})();
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../constants";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getLink } from "src/utils/FileUtils";
|
||||
|
||||
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private addText: Function;
|
||||
private drawingPath: string;
|
||||
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
this.app = app;
|
||||
constructor(private plugin: ExcalidrawPlugin) {
|
||||
super(plugin.app);
|
||||
this.app = plugin.app;
|
||||
this.limit = 20;
|
||||
this.setInstructions([
|
||||
{
|
||||
@@ -45,7 +47,8 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
true,
|
||||
);
|
||||
}
|
||||
this.addText(`[[${filepath + (item.alias ? `|${item.alias}` : "")}]]`, filepath, item.alias);
|
||||
const link = getLink(this.plugin,{embed: false, path: filepath, alias: item.alias});
|
||||
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
|
||||
}
|
||||
|
||||
public start(drawingPath: string, addText: Function) {
|
||||
|
||||
@@ -17,6 +17,68 @@ 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.5":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/kp1K7GRrE6E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
# Fixed
|
||||
- Scaled-resizing a sticky note (SHIFT+resize) caused Excalidraw to choke on slower devices
|
||||
- Improved plugin performance focusing on minimizing Excalidraw's effect on Obsidian overall
|
||||
- Images embedded with a URL often did not show up in image exports, hopefully, the issue will less frequently occur in the future.
|
||||
- Local file URL now follows Obsidian standard - making it easier to navigate in Markdown view mode.
|
||||
|
||||
# New
|
||||
- In plugin settings, under "Startup Script", the button now opens the startup script if it already exists.
|
||||
- Partial support for animated GIFs (will not show up in image exports, but can be added as interactive embeddables)
|
||||
- Configurable modifier keys for link click action and drag&drop actions.
|
||||
- Improved support for drag&drop from your local drive and embedding of files external to Excalidraw.
|
||||
`,
|
||||
"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)
|
||||
- CTRL+Click on a Mermaid diagram did not open the Mermaid editor.
|
||||
- Embed color settings were not honored when the embedded markdown was focused on a section or block.
|
||||
- Scrollbars were visible when the embeddable was set to transparent (set background color to match element background, and set element background color to "transparent").
|
||||
`,
|
||||
"2.0.2":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/502swdqvZ2A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Resolved an issue where the Command Palette's "Toggle between Excalidraw and Markdown mode" failed to uncompress the Excalidraw JSON for editing.
|
||||
|
||||
## New
|
||||
- Scaling feature for embedded objects (markdown documents, pdfs, YouTube, etc.): Hold down the SHIFT key while resizing elements to adjust their size.
|
||||
- Expanded support for Canvas Candy. Regardless of Canvas Candy, you can apply CSS classes to embedded markdown documents for transparency, shape adjustments, text orientation, and more.
|
||||
- Added new functionalities to the active embeddable top-left menu:
|
||||
- Document Properties (cog icon)
|
||||
- File renaming
|
||||
- Basic styling options for embedded markdown documents
|
||||
- Setting YouTube start time
|
||||
- Zoom to full screen for PDFs
|
||||
- Improved immersive embedding of Excalidraw into Obsidian Canvas.
|
||||
- Introduced new Command Palette Actions:
|
||||
- Embeddable Properties
|
||||
- Scaling selected embeddable elements to 100% relative to the current canvas zoom.
|
||||
`,
|
||||
"2.0.1":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/xmqiBTrlbEM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
|
||||
97
src/dialogs/ModifierKeySettings.ts
Normal file
97
src/dialogs/ModifierKeySettings.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Setting } from "obsidian";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ModifierKeySet, ModifierSetType, modifierKeyTooltipMessages } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
type ModifierKeyCategories = Partial<{
|
||||
[modifierSetType in ModifierSetType]: string;
|
||||
}>;
|
||||
|
||||
const CATEGORIES: ModifierKeyCategories = {
|
||||
WebBrowserDragAction: t("WEB_BROWSER_DRAG_ACTION"),
|
||||
LocalFileDragAction: t("LOCAL_FILE_DRAG_ACTION"),
|
||||
InternalDragAction: t("INTERNAL_DRAG_ACTION"),
|
||||
LinkClickAction: t("PANE_TARGET"),
|
||||
};
|
||||
|
||||
export class ModifierKeySettingsComponent {
|
||||
private isMacOS: boolean;
|
||||
|
||||
constructor(
|
||||
private contentEl: HTMLElement,
|
||||
private modifierKeyConfig: {
|
||||
Mac: Record<string, ModifierKeySet>;
|
||||
Win: Record<string, ModifierKeySet>;
|
||||
},
|
||||
private update?: Function,
|
||||
) {
|
||||
this.isMacOS = (DEVICE.isMacOS || DEVICE.isIOS);
|
||||
}
|
||||
|
||||
render() {
|
||||
const platform = this.isMacOS ? "Mac" : "Win";
|
||||
const modifierKeysConfig = this.modifierKeyConfig[platform];
|
||||
|
||||
Object.entries(CATEGORIES).forEach(([modifierSetType, label]) => {
|
||||
const detailsEl = this.contentEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: label,
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
|
||||
const modifierKeys = modifierKeysConfig[modifierSetType];
|
||||
detailsEl.createDiv({
|
||||
//@ts-ignore
|
||||
text: t("DEFAULT_ACTION_DESC") + modifierKeyTooltipMessages()[modifierSetType][modifierKeys.defaultAction],
|
||||
cls: "setting-item-description"
|
||||
});
|
||||
Object.entries(modifierKeys.rules).forEach(([action, rule]) => {
|
||||
const setting = new Setting(detailsEl)
|
||||
//@ts-ignore
|
||||
.setName(modifierKeyTooltipMessages()[modifierSetType][rule.result]);
|
||||
|
||||
setting.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(rule.shift)
|
||||
.setTooltip("SHIFT")
|
||||
.onChange((value) => {
|
||||
rule.shift = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
setting.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(rule.ctrl_cmd)
|
||||
.setTooltip(this.isMacOS ? "CMD" : "CTRL")
|
||||
.onChange((value) => {
|
||||
rule.ctrl_cmd = value;
|
||||
this.update();
|
||||
})
|
||||
if(this.isMacOS && modifierSetType !== "LinkClickAction") {
|
||||
toggle.setDisabled(true);
|
||||
toggle.toggleEl.style.opacity = "0.5";
|
||||
}
|
||||
});
|
||||
|
||||
setting.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(rule.alt_opt)
|
||||
.setTooltip(this.isMacOS ? "OPT" : "ALT")
|
||||
.onChange((value) => {
|
||||
rule.alt_opt = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
setting.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(rule.meta_ctrl)
|
||||
.setTooltip(this.isMacOS ? "CTRL" : "META")
|
||||
.onChange((value) => {
|
||||
rule.meta_ctrl = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -14,11 +14,11 @@ import ExcalidrawPlugin from "../main";
|
||||
import { escapeRegExp, sleep } from "../utils/Utils";
|
||||
import { getLeaf } from "../utils/ObsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { KeyEvent, isCTRL } from "src/utils/ModifierkeyHelper";
|
||||
import { KeyEvent, isWinCTRLorMacCMD } 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 };
|
||||
|
||||
@@ -342,11 +342,11 @@ export class GenericInputPrompt extends Modal {
|
||||
private cancelClickCallback = () => this.cancel();
|
||||
|
||||
private keyDownCallback = (evt: KeyboardEvent) => {
|
||||
if ((evt.key === "Enter" && this.lines === 1) || (isCTRL(evt) && evt.key === "Enter")) {
|
||||
if ((evt.key === "Enter" && this.lines === 1) || (isWinCTRLorMacCMD(evt) && evt.key === "Enter")) {
|
||||
evt.preventDefault();
|
||||
this.submit();
|
||||
}
|
||||
if (this.displayEditorButtons && evt.key === "k" && isCTRL(evt)) {
|
||||
if (this.displayEditorButtons && evt.key === "k" && isWinCTRLorMacCMD(evt)) {
|
||||
evt.preventDefault();
|
||||
this.linkBtnClickCallback();
|
||||
}
|
||||
|
||||
@@ -182,8 +182,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "createPNG",
|
||||
code: "createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,): Promise<any>;",
|
||||
desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
|
||||
code: "createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,padding?: number): Promise<any>;",
|
||||
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, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
import { InsertPDFModal } from "./InsertPDFModal";
|
||||
@@ -80,6 +80,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
const ea = this.plugin.ea;
|
||||
const isMarkdown = file && file.extension === "md" && !ea.isExcalidrawFile(file);
|
||||
const isImage = file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file));
|
||||
const isAnimatedImage = file && ANIMATED_IMAGE_TYPES.contains(file.extension);
|
||||
const isIFrame = file && !isImage;
|
||||
const isPDF = file && file.extension === "pdf";
|
||||
const isExcalidraw = file && ea.isExcalidrawFile(file);
|
||||
@@ -116,7 +117,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
actionImage.buttonEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isIFrame) {
|
||||
if (isIFrame || isAnimatedImage) {
|
||||
actionIFrame.buttonEl.style.display = "block";
|
||||
} else {
|
||||
actionIFrame.buttonEl.style.display = "none";
|
||||
|
||||
@@ -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
|
||||
@@ -11,6 +11,7 @@ export default {
|
||||
// main.ts
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "Scale selected embeddable elements to 100% relative to the current canvas zoom",
|
||||
OPEN_IMAGE_SOURCE: "Open Excalidraw drawing",
|
||||
INSTALL_SCRIPT: "Install the script",
|
||||
UPDATE_SCRIPT: "Update available - Click to install",
|
||||
@@ -84,7 +85,7 @@ export default {
|
||||
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
|
||||
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
|
||||
LINK_BUTTON_CLICK_NO_TEXT:
|
||||
"Select a ImageElement, or select a TextElement that contains an internal or external link.\n",
|
||||
"Select an ImageElement, or select a TextElement that contains an internal or external link.\n",
|
||||
FILENAME_INVALID_CHARS:
|
||||
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
|
||||
FORCE_SAVE:
|
||||
@@ -139,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",
|
||||
@@ -257,6 +277,12 @@ FILENAME_HEAD: "Filename",
|
||||
"the plugin will open it in a browser. " +
|
||||
"When Obsidian files change, the matching <code>[[link]]</code> in your drawings will also change. " +
|
||||
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
|
||||
DRAG_MODIFIER_NAME: "Link Click and Drag&Drop Modifier Keys",
|
||||
DRAG_MODIFIER_DESC: "Modifier key behavior when clicking links and dragging and dropping elements. " +
|
||||
"Excalidraw will not validate your configuration... pay attention to avoid conflicting settings. " +
|
||||
"These settings are different for Apple and non-Apple. If you use Obsidian on multiple platforms, you'll need to make the settings separately. "+
|
||||
"The toggles follow the order of " +
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Windows key)."),
|
||||
ADJACENT_PANE_NAME: "Reuse adjacent pane",
|
||||
ADJACENT_PANE_DESC:
|
||||
`When ${labelCTRL()}+${labelALT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` +
|
||||
@@ -485,10 +511,24 @@ 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: "Create startup script",
|
||||
STARTUP_SCRIPT_BUTTON_OPEN: "Open 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.",
|
||||
@@ -573,11 +613,12 @@ 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: "Element Settings",
|
||||
ES_TITLE: "Embeddable Element Settings",
|
||||
ES_RENAME: "Rename File",
|
||||
ES_ZOOM: "Embedded Content Zoom",
|
||||
ES_ZOOM: "Embedded Content Scaling",
|
||||
ES_YOUTUBE_START: "YouTube Start Time",
|
||||
ES_YOUTUBE_START_DESC: "ss, mm:ss, hh:mm:ss",
|
||||
ES_YOUTUBE_START_INVALID: "The YouTube Start Time is invalid. Please check the format and try again",
|
||||
@@ -589,10 +630,12 @@ FILENAME_HEAD: "Filename",
|
||||
ES_BORDER_HEAD: "Embedded note border color",
|
||||
ES_BORDER_COLOR: "Border Color",
|
||||
ES_BORDER_MATCH_ELEMENT: "Match Element Border Color",
|
||||
ES_BACKGROUND_OPACITY: "Background Transparency",
|
||||
ES_BORDER_OPACITY: "Border Transparency",
|
||||
ES_BACKGROUND_OPACITY: "Background Opacity",
|
||||
ES_BORDER_OPACITY: "Border Opacity",
|
||||
ES_EMBEDDABLE_SETTINGS: "Embeddable Markdown Settings",
|
||||
ES_USE_OBSIDIAN_DEFAULTS: "Use Obsidian Defaults",
|
||||
ES_ZOOM_100_RELATIVE_DESC: "The button will adjust the element scale so it will show the content at 100% relative to the current zoom level of your canvas",
|
||||
ES_ZOOM_100: "Relative 100%",
|
||||
|
||||
//Prompts.ts
|
||||
PROMPT_FILE_DOES_NOT_EXIST: "File does not exist. Do you want to create it?",
|
||||
@@ -613,5 +656,11 @@ FILENAME_HEAD: "Filename",
|
||||
PROMPT_BUTTON_INSERT_SPACE: "Insert space",
|
||||
PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file",
|
||||
PROMPT_BUTTON_UPPERCASE: "Uppercase",
|
||||
|
||||
|
||||
//ModifierKeySettings
|
||||
WEB_BROWSER_DRAG_ACTION: "Web Browser Drag Action",
|
||||
LOCAL_FILE_DRAG_ACTION: "OS Local File Drag Action",
|
||||
INTERNAL_DRAG_ACTION: "Obsidian Internal Drag Action",
|
||||
PANE_TARGET: "Link click behavior",
|
||||
DEFAULT_ACTION_DESC: "In case none of the combinations apply the default action for this group is: ",
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
// 简体中文
|
||||
|
||||
459
src/main.ts
459
src/main.ts
@@ -41,16 +41,17 @@ 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,
|
||||
getMarkdownDrawingSection,
|
||||
ExcalidrawData
|
||||
ExcalidrawData,
|
||||
REGEX_LINK
|
||||
} from "./ExcalidrawData";
|
||||
import {
|
||||
ExcalidrawSettings,
|
||||
@@ -79,6 +80,7 @@ import {
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
getIMGFilename,
|
||||
getLink,
|
||||
getNewUniqueFilepath,
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
@@ -99,7 +101,7 @@ import {
|
||||
hoverEvent,
|
||||
initializeMarkdownPostProcessor,
|
||||
markdownPostProcessor,
|
||||
observer,
|
||||
legacyExcalidrawPopoverObserver,
|
||||
} from "./MarkdownPostProcessor";
|
||||
|
||||
import { FieldSuggester } from "./dialogs/FieldSuggester";
|
||||
@@ -115,9 +117,15 @@ 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";
|
||||
import { create } from "domain";
|
||||
|
||||
declare const EXCALIDRAW_PACKAGES:string;
|
||||
declare const react:any;
|
||||
@@ -143,12 +151,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 legacyExcalidrawPopoverObserver: 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
|
||||
@@ -165,6 +173,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private forceSaveCommand:Command;
|
||||
private removeEventLisnters:(()=>void)[] = [];
|
||||
private stylesManager:StylesManager;
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -198,6 +207,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);
|
||||
@@ -208,6 +249,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.ea = await initExcalidrawAutomate(this);
|
||||
this.textMeasureDiv = document.createElement("div");
|
||||
this.textMeasureDiv.setAttribute("id", "excalidraw-measure-text");
|
||||
document.body.appendChild(this.textMeasureDiv);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
@@ -223,6 +267,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));
|
||||
|
||||
@@ -270,12 +315,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
font.dataURL === "" ? VIRGIL_DATAURL : font.dataURL;
|
||||
this.fourthFontDef = font.fontDef;
|
||||
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
this.getOpenObsidianDocuments().forEach((ownerDocument) => {
|
||||
// replace the old local font <style> element with the one we just created
|
||||
const newStylesheet = ownerDocument.createElement("style");
|
||||
newStylesheet.id = "local-font-stylesheet";
|
||||
@@ -296,6 +336,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
const visitedDocs = new Set<Document>();
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = this.app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
});
|
||||
return Array.from(visitedDocs);
|
||||
}
|
||||
|
||||
private removeMathJax() {
|
||||
if("ExcalidrawMathJax" in window) {
|
||||
delete window.ExcalidrawMathJax;
|
||||
@@ -375,65 +426,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(() => {
|
||||
@@ -663,48 +655,74 @@ 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 });
|
||||
//only add the legacy file observer if there are legacy files in the vault
|
||||
if(this.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
self.enableLegacyFilePopoverObserver();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addThemeObserver() {
|
||||
this.themeObserver = new MutationObserver(async (m: MutationRecord[]) => {
|
||||
if (!this.settings.matchThemeTrigger) {
|
||||
return;
|
||||
}
|
||||
//@ts-ignore
|
||||
if (m[0]?.oldValue === m[0]?.target?.getAttribute("class")) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
m[0]?.oldValue?.includes("theme-dark") ===
|
||||
//@ts-ignore
|
||||
m[0]?.target?.classList?.contains("theme-dark")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const theme = isObsidianThemeDark() ? "dark" : "light";
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
|
||||
excalidrawView.setTheme(theme);
|
||||
}
|
||||
public enableLegacyFilePopoverObserver() {
|
||||
if(!this.legacyExcalidrawPopoverObserver) {
|
||||
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
|
||||
this.legacyExcalidrawPopoverObserver = legacyExcalidrawPopoverObserver;
|
||||
this.legacyExcalidrawPopoverObserver.observe(document.body, { childList: true, subtree: false });
|
||||
}
|
||||
}
|
||||
|
||||
public addThemeObserver() {
|
||||
if(this.themeObserver) return;
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const themeObserverFn:MutationCallback = async (mutations: MutationRecord[]) => {
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const bodyClassList = document.body.classList;
|
||||
const mutation = mutations[0];
|
||||
if (mutation?.oldValue === bodyClassList.value) return;
|
||||
|
||||
const darkClass = bodyClassList.contains('theme-dark');
|
||||
if (mutation?.oldValue?.includes('theme-dark') === darkClass) return;
|
||||
|
||||
const self = this;
|
||||
setTimeout(()=>{ //run async to avoid blocking the UI
|
||||
const theme = isObsidianThemeDark() ? "dark" : "light";
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
|
||||
excalidrawView.setTheme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = isDebugMode
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
this.themeObserver.observe(document.body, {
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
|
||||
public removeThemeObserver() {
|
||||
if(!this.themeObserver) return;
|
||||
this.themeObserver.disconnect();
|
||||
this.themeObserver = null;
|
||||
}
|
||||
|
||||
public experimentalFileTypeDisplayToggle(enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.experimentalFileTypeDisplay();
|
||||
@@ -743,31 +761,38 @@ 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerCommands() {
|
||||
this.openDialog = new OpenFileDialog(this.app, this);
|
||||
this.insertLinkDialog = new InsertLinkDialog(this.app);
|
||||
this.insertLinkDialog = new InsertLinkDialog(this);
|
||||
this.insertCommandDialog = new InsertCommandDialog(this.app);
|
||||
this.insertImageDialog = new InsertImageDialog(this);
|
||||
this.importSVGDialog = new ImportSVGDialog(this);
|
||||
@@ -780,24 +805,26 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
);
|
||||
});
|
||||
|
||||
const createNewAction = (e: MouseEvent | KeyboardEvent, file: TFile) => {
|
||||
let folderpath = file.path;
|
||||
if (file instanceof TFile) {
|
||||
folderpath = normalizePath(
|
||||
file.path.substr(0, file.path.lastIndexOf(file.name)),
|
||||
);
|
||||
}
|
||||
this.createAndOpenDrawing(
|
||||
getDrawingFilename(this.settings),
|
||||
linkClickModifierType(emulateCTRLClickForLinks(e)),
|
||||
folderpath,
|
||||
);
|
||||
}
|
||||
|
||||
const fileMenuHandlerCreateNew = (menu: Menu, file: TFile) => {
|
||||
menu.addItem((item: MenuItem) => {
|
||||
item
|
||||
.setTitle(t("CREATE_NEW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick((e) => {
|
||||
let folderpath = file.path;
|
||||
if (file instanceof TFile) {
|
||||
folderpath = normalizePath(
|
||||
file.path.substr(0, file.path.lastIndexOf(file.name)),
|
||||
);
|
||||
}
|
||||
this.createAndOpenDrawing(
|
||||
getDrawingFilename(this.settings),
|
||||
linkClickModifierType(emulateCTRLClickForLinks(e)),
|
||||
folderpath,
|
||||
);
|
||||
});
|
||||
.onClick((e) => {createNewAction(e, file)});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -868,7 +895,41 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
new EmbeddableSettings(view.plugin,view,null,els[0]).open();
|
||||
const getFile = (el:ExcalidrawEmbeddableElement):TFile => {
|
||||
const res = REGEX_LINK.getRes(el.link).next();
|
||||
if(!res || (!res.value && res.done)) {
|
||||
return null;
|
||||
}
|
||||
const link = REGEX_LINK.getLink(res);
|
||||
const { file } = processLinkText(link, view);
|
||||
return file;
|
||||
}
|
||||
new EmbeddableSettings(view.plugin,view,getFile(els[0]),els[0]).open();
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-embeddables-relative-scale",
|
||||
name: t("EMBEDDABLE_RELATIVE_ZOOM"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="embeddable") as ExcalidrawEmbeddableElement[];
|
||||
if(els.length === 0) {
|
||||
if(checking) return false;
|
||||
new Notice("Select at least one embeddable element and try again");
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const api = ea.getExcalidrawAPI() as ExcalidrawImperativeAPI;
|
||||
ea.copyViewElementsToEAforEditing(els);
|
||||
const scale = 1/api.getAppState().zoom.value;
|
||||
ea.getElements().forEach((el: Mutable<ExcalidrawEmbeddableElement>)=>{
|
||||
el.scale = [scale,scale];
|
||||
})
|
||||
ea.addElementsToView();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1334,7 +1395,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
this.insertLinkDialog.start(view.file.path, view.addLink);
|
||||
this.insertLinkDialog.start(view.file.path, (markdownlink: string, path:string, alias:string) => view.addLink(markdownlink, path, alias));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1350,7 +1411,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
this.insertCommandDialog.start(view.addText);
|
||||
this.insertCommandDialog.start((text: string, fontFamily?: 1 | 2 | 3 | 4, save?: boolean) => view.addText(text, fontFamily, save));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1644,10 +1705,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const excalidrawView = this.app.workspace.getActiveViewOfType(ExcalidrawView)
|
||||
if (excalidrawView) {
|
||||
const activeLeaf = excalidrawView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
"markdown";
|
||||
this.setMarkdownView(activeLeaf);
|
||||
excalidrawView.openAsMarkdown();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1895,9 +1953,33 @@ 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;
|
||||
const self: ExcalidrawPlugin = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
const onPasteHandler = (
|
||||
evt: ClipboardEvent,
|
||||
@@ -1927,18 +2009,15 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(sourceFile && imageFile && imageFile instanceof TFile) {
|
||||
path = self.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
|
||||
}
|
||||
//@ts-ignore
|
||||
editor.insertText(self.getLink({path}));
|
||||
editor.insertText(getLink(self, {path}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (element.type === "text") {
|
||||
//@ts-ignore
|
||||
editor.insertText(element.text);
|
||||
return;
|
||||
}
|
||||
if (element.link) {
|
||||
//@ts-ignore
|
||||
editor.insertText(`${element.link}`);
|
||||
return;
|
||||
}
|
||||
@@ -2083,19 +2162,22 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
self.activeExcalidrawView = newActiveviewEV;
|
||||
|
||||
if (newActiveviewEV) {
|
||||
self.addModalContainerObserver();
|
||||
self.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
|
||||
} else {
|
||||
self.removeModalContainerObserver();
|
||||
}
|
||||
|
||||
//!Temporary hack
|
||||
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
|
||||
if (app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
if (this.app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="relative";
|
||||
}
|
||||
}
|
||||
|
||||
if (app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
if (this.app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="";
|
||||
@@ -2259,24 +2341,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.app.workspace.on("file-menu", onFileMenuEventSaveActiveDrawing),
|
||||
);
|
||||
|
||||
//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();
|
||||
},
|
||||
);
|
||||
this.modalContainerObserver.observe(document.body, {
|
||||
childList: true,
|
||||
});
|
||||
this.addModalContainerObserver();
|
||||
|
||||
//when the user activates the sliding drawers on Obsidian Mobile
|
||||
const leftWorkspaceDrawer = document.querySelector(
|
||||
@@ -2302,12 +2367,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,
|
||||
@@ -2316,6 +2385,45 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
private activeViewDoc: Document;
|
||||
private addModalContainerObserver() {
|
||||
if(!this.activeExcalidrawView) return;
|
||||
if(this.modalContainerObserver) {
|
||||
if(this.activeViewDoc === this.activeExcalidrawView.ownerDocument) {
|
||||
return;
|
||||
}
|
||||
this.removeModalContainerObserver();
|
||||
}
|
||||
//The user clicks settings, or "open another vault", or the command palette
|
||||
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.activeViewDoc = this.activeExcalidrawView.ownerDocument;
|
||||
this.modalContainerObserver.observe(this.activeViewDoc.body, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
private removeModalContainerObserver() {
|
||||
if(!this.modalContainerObserver) return;
|
||||
this.modalContainerObserver.disconnect();
|
||||
this.activeViewDoc = null;
|
||||
this.modalContainerObserver = null;
|
||||
}
|
||||
|
||||
updateFileCache(
|
||||
file: TFile,
|
||||
frontmatter?: FrontMatterCache,
|
||||
@@ -2333,6 +2441,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
onunload() {
|
||||
document.body.removeChild(this.textMeasureDiv);
|
||||
this.stylesManager.unload();
|
||||
this.removeEventLisnters.forEach((removeEventListener) =>
|
||||
removeEventListener(),
|
||||
@@ -2342,9 +2451,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.popScope();
|
||||
this.popScope = null;
|
||||
}
|
||||
this.observer.disconnect();
|
||||
this.themeObserver.disconnect();
|
||||
this.modalContainerObserver.disconnect();
|
||||
if(this.legacyExcalidrawPopoverObserver) {
|
||||
this.legacyExcalidrawPopoverObserver.disconnect();
|
||||
}
|
||||
this.removeThemeObserver();
|
||||
this.removeModalContainerObserver();
|
||||
if (this.workspaceDrawerLeftObserver) {
|
||||
this.workspaceDrawerLeftObserver.disconnect();
|
||||
}
|
||||
@@ -2372,14 +2483,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
})
|
||||
}
|
||||
|
||||
public getLink(
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string }
|
||||
):string {
|
||||
return this.settings.embedWikiLink
|
||||
? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]`
|
||||
: `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})`
|
||||
}
|
||||
|
||||
public async embedDrawing(file: TFile) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView && activeView.file) {
|
||||
@@ -2393,7 +2496,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//embed Excalidraw
|
||||
if (this.settings.embedType === "excalidraw") {
|
||||
editor.replaceSelection(
|
||||
this.getLink({path: excalidrawRelativePath}),
|
||||
getLink(this, {path: excalidrawRelativePath}),
|
||||
);
|
||||
editor.focus();
|
||||
return;
|
||||
|
||||
@@ -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);
|
||||
@@ -190,7 +194,7 @@ export class EmbeddableMenu {
|
||||
title={t("ZOOM_TO_FIT")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
api.zoomToFit([element], view.plugin.settings.zoomToFitMaxLevel, 0.1);
|
||||
api.zoomToFit([element], 30, 0.1);
|
||||
}}
|
||||
icon={ICONS.ZoomToSelectedElement}
|
||||
view={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";
|
||||
@@ -265,6 +265,7 @@ export class ObsidianMenu {
|
||||
},
|
||||
)}
|
||||
onClick={() => {
|
||||
this.view.setCurrentPositionToCenter();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.plugin, this.view);
|
||||
insertFileModal.open();
|
||||
}}
|
||||
|
||||
@@ -3,16 +3,15 @@ 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";
|
||||
import { ReleaseNotes } from "../dialogs/ReleaseNotes";
|
||||
import { ScriptIconMap } from "../Scripts";
|
||||
import { getIMGFilename } from "../utils/FileUtils";
|
||||
import { ScriptInstallPrompt } from "src/dialogs/ScriptInstallPrompt";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import { isALT, isCTRL, isSHIFT, mdPropModifier } from "src/utils/ModifierkeyHelper";
|
||||
import { isWinALTorMacOPT, isWinCTRLorMacCMD, isSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
import { InsertPDFModal } from "src/dialogs/InsertPDFModal";
|
||||
import { ExportDialog } from "src/dialogs/ExportDialog";
|
||||
|
||||
@@ -380,7 +379,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isCTRL(e));
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isWinCTRLorMacCMD(e));
|
||||
}}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
@@ -505,7 +504,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
key={"latex"}
|
||||
title={t("INSERT_LATEX")}
|
||||
action={(e) => {
|
||||
if(isALT(e)) {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
this.props.view.openExternalLink("https://youtu.be/r08wk-58DPk");
|
||||
return;
|
||||
}
|
||||
@@ -522,7 +521,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
this.props.centerPointer();
|
||||
this.props.view.plugin.insertLinkDialog.start(
|
||||
this.props.view.file.path,
|
||||
this.props.view.addText,
|
||||
(text: string, fontFamily?: 1 | 2 | 3 | 4, save?: boolean) => this.props.view.addText (text, fontFamily, save),
|
||||
);
|
||||
}}
|
||||
icon={ICONS.insertLink}
|
||||
@@ -532,12 +531,12 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(isALT(e)) {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
this.props.view.openExternalLink("https://youtu.be/yZQoJg2RCKI");
|
||||
return;
|
||||
}
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
isCTRL(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
|
||||
267
src/settings.ts
267
src/settings.ts
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
App,
|
||||
ButtonComponent,
|
||||
DropdownComponent,
|
||||
normalizePath,
|
||||
PluginSettingTab,
|
||||
@@ -7,7 +8,7 @@ import {
|
||||
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,11 @@ 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";
|
||||
import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -153,6 +156,14 @@ export interface ExcalidrawSettings {
|
||||
};
|
||||
embeddableMarkdownDefaults: EmbeddableMDCustomProps;
|
||||
canvasImmersiveEmbed: boolean,
|
||||
startupScriptPath: string,
|
||||
openAIAPIToken: string,
|
||||
openAIDefaultTextModel: string,
|
||||
openAIDefaultVisionModel: string,
|
||||
modifierKeyConfig: {
|
||||
Mac: Record<ModifierSetType, ModifierKeySet>,
|
||||
Win: Record<ModifierSetType, ModifierKeySet>,
|
||||
}
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -295,6 +306,90 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
filenameVisible: false,
|
||||
},
|
||||
canvasImmersiveEmbed: true,
|
||||
startupScriptPath: "",
|
||||
openAIAPIToken: "",
|
||||
openAIDefaultTextModel: "gpt-3.5-turbo-1106",
|
||||
openAIDefaultVisionModel: "gpt-4-vision-preview",
|
||||
modifierKeyConfig: {
|
||||
Mac: {
|
||||
LocalFileDragAction:{
|
||||
defaultAction: "image-import",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "link" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" },
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "embeddable" },
|
||||
],
|
||||
},
|
||||
WebBrowserDragAction: {
|
||||
defaultAction: "image-url",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "link" },
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: true , meta_ctrl: false, result: "embeddable" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" },
|
||||
],
|
||||
},
|
||||
InternalDragAction: {
|
||||
defaultAction: "link",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "link" },
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: true , result: "embeddable" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: true , result: "image-fullsize" },
|
||||
],
|
||||
},
|
||||
LinkClickAction: {
|
||||
defaultAction: "new-tab",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "active-pane" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "new-tab" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "new-pane" },
|
||||
{ shift: true , ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "popout-window" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: true , result: "md-properties" },
|
||||
],
|
||||
},
|
||||
},
|
||||
Win: {
|
||||
LocalFileDragAction:{
|
||||
defaultAction: "image-import",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "link" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" },
|
||||
{ shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" },
|
||||
],
|
||||
},
|
||||
WebBrowserDragAction: {
|
||||
defaultAction: "image-url",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-url" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "link" },
|
||||
{ shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image-import" },
|
||||
],
|
||||
},
|
||||
InternalDragAction: {
|
||||
defaultAction: "link",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "link" },
|
||||
{ shift: true , ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "embeddable" },
|
||||
{ shift: true , ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "image" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "image-fullsize" },
|
||||
],
|
||||
},
|
||||
LinkClickAction: {
|
||||
defaultAction: "new-tab",
|
||||
rules: [
|
||||
{ shift: false, ctrl_cmd: false, alt_opt: false, meta_ctrl: false, result: "active-pane" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: false, result: "new-tab" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "new-pane" },
|
||||
{ shift: true , ctrl_cmd: true , alt_opt: true , meta_ctrl: false, result: "popout-window" },
|
||||
{ shift: false, ctrl_cmd: true , alt_opt: false, meta_ctrl: true , result: "md-properties" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -640,6 +735,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
|
||||
@@ -753,6 +898,11 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.matchThemeTrigger)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.matchThemeTrigger = value;
|
||||
if(value) {
|
||||
this.plugin.addThemeObserver();
|
||||
} else {
|
||||
this.plugin.removeThemeObserver();
|
||||
}
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -914,6 +1064,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_LENGTH.toString()}`;
|
||||
});
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("DRAG_MODIFIER_NAME"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" });
|
||||
|
||||
new ModifierKeySettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.modifierKeyConfig,
|
||||
this.applySettingsUpdate,
|
||||
).render();
|
||||
|
||||
// ------------------------------------------------
|
||||
// Links and Transclusions
|
||||
@@ -1808,23 +1970,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 +1983,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 +2065,77 @@ 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;
|
||||
let startupScriptButton: ButtonComponent;
|
||||
const scriptExists = () => {
|
||||
const startupPath = normalizePath(this.plugin.settings.startupScriptPath.endsWith(".md")
|
||||
? this.plugin.settings.startupScriptPath
|
||||
: this.plugin.settings.startupScriptPath + ".md");
|
||||
return Boolean(this.app.vault.getAbstractFileByPath(startupPath));
|
||||
}
|
||||
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;
|
||||
startupScriptButton.setButtonText(scriptExists() ? t("STARTUP_SCRIPT_BUTTON_OPEN") : t("STARTUP_SCRIPT_BUTTON_CREATE"));
|
||||
this.applySettingsUpdate();
|
||||
});
|
||||
})
|
||||
.addButton((button) => {
|
||||
startupScriptButton = button;
|
||||
startupScriptButton
|
||||
.setButtonText(scriptExists() ? t("STARTUP_SCRIPT_BUTTON_OPEN") : t("STARTUP_SCRIPT_BUTTON_CREATE"))
|
||||
.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");
|
||||
let f = this.app.vault.getAbstractFileByPath(startupPath);
|
||||
if(!f) {
|
||||
f = await this.app.vault.create(startupPath, startupScript());
|
||||
}
|
||||
startupScriptButton.setButtonText(t("STARTUP_SCRIPT_BUTTON_OPEN"));
|
||||
this.app.workspace.openLinkText(f.path,"",true);
|
||||
this.hide();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
// ------------------------------------------------
|
||||
@@ -2094,6 +2298,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",
|
||||
|
||||
9
src/types.d.ts
vendored
9
src/types.d.ts
vendored
@@ -49,4 +49,13 @@ declare module "obsidian" {
|
||||
ctx?: any,
|
||||
): EventRef;
|
||||
}
|
||||
interface DataAdapter {
|
||||
url: {
|
||||
pathToFileURL(path: string): URL;
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface Editor {
|
||||
insertText(data: string): void;
|
||||
}
|
||||
}
|
||||
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; //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,7 +1,8 @@
|
||||
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES } from "src/constants";
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
import { TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
|
||||
export const insertImageToView = async (
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -33,7 +34,7 @@ export const insertEmbeddableToView = async (
|
||||
ea.clear();
|
||||
ea.style.strokeColor = "transparent";
|
||||
ea.style.backgroundColor = "transparent";
|
||||
if(file && IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) {
|
||||
if(file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) && !ANIMATED_IMAGE_TYPES.contains(file.extension)) {
|
||||
return await insertImageToView(ea, position, file);
|
||||
} else {
|
||||
const id = ea.addEmbeddable(
|
||||
@@ -47,4 +48,17 @@ export const insertEmbeddableToView = async (
|
||||
await ea.addElementsToView(false, true, true);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
export const getLinkTextFromLink = (text: string): string => {
|
||||
if (!text) return;
|
||||
if (text.match(REG_LINKINDEX_HYPERLINK)) return;
|
||||
|
||||
const parts = REGEX_LINK.getRes(text).next();
|
||||
if (!parts.value) return;
|
||||
|
||||
const linktext = REGEX_LINK.getLink(parts); //parts.value[2] ? parts.value[2]:parts.value[6];
|
||||
if (linktext.match(REG_LINKINDEX_HYPERLINK)) return;
|
||||
|
||||
return linktext;
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
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 { MimeType } from "src/EmbeddedFileLoader";
|
||||
import { URLFETCHTIMEOUT } from "src/constants/constants";
|
||||
import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader";
|
||||
import { ExcalidrawSettings } from "src/settings";
|
||||
import { errorlog, getDataURL } from "./Utils";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
|
||||
/**
|
||||
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
|
||||
* @param filepath
|
||||
*/
|
||||
type ImageExtension = keyof typeof IMAGE_MIME_TYPES;
|
||||
|
||||
export function splitFolderAndFilename(filepath: string): {
|
||||
folderpath: string;
|
||||
filename: string;
|
||||
basename: string;
|
||||
extension: string;
|
||||
} {
|
||||
const lastIndex = filepath.lastIndexOf("/");
|
||||
const filename = lastIndex == -1 ? filepath : filepath.substring(lastIndex + 1);
|
||||
@@ -21,6 +24,7 @@ export function splitFolderAndFilename(filepath: string): {
|
||||
folderpath: normalizePath(filepath.substring(0, lastIndex)),
|
||||
filename,
|
||||
basename: filename.replace(/\.[^/.]+$/, ""),
|
||||
extension: filename.substring(filename.lastIndexOf(".") + 1),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,7 +138,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 +150,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 => {
|
||||
@@ -155,15 +159,10 @@ export const getURLImageExtension = (url: string):string => {
|
||||
}
|
||||
|
||||
export const getMimeType = (extension: string):MimeType => {
|
||||
if(IMAGE_MIME_TYPES.hasOwnProperty(extension)) {
|
||||
return IMAGE_MIME_TYPES[extension as ImageExtension];
|
||||
};
|
||||
switch (extension) {
|
||||
case "png": return "image/png";
|
||||
case "jpeg": return "image/jpeg";
|
||||
case "jpg": return "image/jpeg";
|
||||
case "gif": return "image/gif";
|
||||
case "webp": return "image/webp";
|
||||
case "bmp": return "image/bmp";
|
||||
case "ico": return "image/x-icon";
|
||||
case "svg": return "image/svg+xml";
|
||||
case "md": return "image/svg+xml";
|
||||
default: return "application/octet-stream";
|
||||
}
|
||||
@@ -172,14 +171,18 @@ export const getMimeType = (extension: string):MimeType => {
|
||||
// using fetch API
|
||||
const getFileFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT): Promise<RequestUrlResponse> => {
|
||||
try {
|
||||
const timeoutPromise = new Promise<Response>((resolve) =>
|
||||
setTimeout(() => resolve(null), timeout)
|
||||
);
|
||||
|
||||
const response = await Promise.race([
|
||||
fetch(url),
|
||||
new Promise<Response>((resolve) => setTimeout(() => resolve(null), timeout))
|
||||
timeoutPromise,
|
||||
]);
|
||||
|
||||
if (!response) {
|
||||
new Notice(`URL did not load within the timeout period of ${timeout}ms.\n\nTry force-saving again in a few seconds.\n\n${url}`,8000);
|
||||
throw new Error(`URL did not load within the timeout period of ${timeout}ms`);
|
||||
errorlog({ where: getFileFromURL, message: `URL did not load within the timeout period of ${timeout}ms.\n\nTry force-saving again in a few seconds.\n\n${url}`, url: url });
|
||||
return null;
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
@@ -192,8 +195,8 @@ const getFileFromURL = async (url: string, mimeType: MimeType, timeout: number =
|
||||
text: null,
|
||||
};
|
||||
} catch (e) {
|
||||
errorlog({ where: getFileFromURL, message: e.message, url: url });
|
||||
return undefined;
|
||||
//errorlog({ where: getFileFromURL, message: e.message, url: url });
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,19 +204,23 @@ const getFileFromURL = async (url: string, mimeType: MimeType, timeout: number =
|
||||
// https://firebasestorage.googleapis.com/v0/b/firescript-577a2.appspot.com/o/imgs%2Fapp%2FJSG%2FfTMP6WGQRC.png?alt=media&token=6d2993b4-e629-46b6-98d1-133af7448c49
|
||||
const getFileFromURLFallback = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT):Promise<RequestUrlResponse> => {
|
||||
try {
|
||||
const timeoutPromise = new Promise<RequestUrlResponse | null>((resolve) =>
|
||||
setTimeout(() => resolve(null), timeout)
|
||||
);
|
||||
|
||||
return await Promise.race([
|
||||
(async () => new Promise<RequestUrlResponse>((resolve) => setTimeout(()=>resolve(null), timeout)))(),
|
||||
timeoutPromise,
|
||||
requestUrl({url: url, method: "get", contentType: mimeType, throw: false })
|
||||
])
|
||||
} catch (e) {
|
||||
errorlog({where: getFileFromURL, message: `URL did not load within timeout period of ${timeout}ms`, url: url});
|
||||
return undefined;
|
||||
errorlog({where: getFileFromURLFallback, message: `URL did not load within timeout period of ${timeout}ms`, url: url});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const getDataURLFromURL = async (url: string, mimeType: MimeType, timeout: number = URLFETCHTIMEOUT): Promise<DataURL> => {
|
||||
let response = await getFileFromURL(url, mimeType, timeout);
|
||||
if(response && response.status !== 200) {
|
||||
if(!response || response?.status !== 200) {
|
||||
response = await getFileFromURLFallback(url, mimeType, timeout);
|
||||
}
|
||||
return response && response.status === 200
|
||||
@@ -272,9 +279,9 @@ export const getDataURLFromURL = async (
|
||||
export const blobToBase64 = async (blob: Blob): Promise<string> => {
|
||||
const arrayBuffer = await blob.arrayBuffer()
|
||||
const bytes = new Uint8Array(arrayBuffer)
|
||||
var binary = '';
|
||||
var len = bytes.byteLength;
|
||||
for (var i = 0; i < len; i++) {
|
||||
let binary = '';
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
@@ -318,4 +325,40 @@ export const readLocalFileBinary = async (filePath:string): Promise<ArrayBuffer>
|
||||
export const getPathWithoutExtension = (f:TFile): string => {
|
||||
if(!f) return null;
|
||||
return f.path.substring(0, f.path.lastIndexOf("."));
|
||||
}
|
||||
|
||||
const VAULT_BASE_URL = app.vault.adapter.url.pathToFileURL(app.vault.adapter.basePath).toString();
|
||||
export const getInternalLinkOrFileURLLink = (
|
||||
path: string, plugin:ExcalidrawPlugin, alias?: string, sourceFile?: TFile
|
||||
):{link: string, isInternal: boolean, file?: TFile, url?: string} => {
|
||||
const vault = plugin.app.vault;
|
||||
const fileURLString = vault.adapter.url.pathToFileURL(path).toString();
|
||||
if (fileURLString.startsWith(VAULT_BASE_URL)) {
|
||||
const internalPath = normalizePath(fileURLString.substring(VAULT_BASE_URL.length));
|
||||
const file = vault.getAbstractFileByPath(internalPath);
|
||||
if(file && file instanceof TFile) {
|
||||
const link = plugin.app.metadataCache.fileToLinktext(
|
||||
file,
|
||||
sourceFile?.path,
|
||||
true,
|
||||
);
|
||||
return {link: getLink(plugin, { embed: false, path: link, alias}), isInternal: true, file};
|
||||
};
|
||||
}
|
||||
return {link: `[${alias??""}](${fileURLString})`, isInternal: false, url: fileURLString};
|
||||
}
|
||||
|
||||
/**
|
||||
* get markdown or wiki link
|
||||
* @param plugin
|
||||
* @param param1: { embed = true, path, alias }
|
||||
* @returns
|
||||
*/
|
||||
export const getLink = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string }
|
||||
):string => {
|
||||
return plugin.settings.embedWikiLink
|
||||
? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]`
|
||||
: `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})`
|
||||
}
|
||||
@@ -1,19 +1,88 @@
|
||||
import { DEVICE, isDarwin } from "src/constants";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawSettings } from "src/settings";
|
||||
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";
|
||||
export type ExternalDragAction = "insert-link"|"image-url"|"image-import"|"embeddable";
|
||||
export type LocalFileDragAction = "insert-link"|"image-uri"|"image-import";
|
||||
export type WebBrowserDragAction = "link"|"image-url"|"image-import"|"embeddable";
|
||||
export type LocalFileDragAction = "link"|"image-url"|"image-import"|"embeddable";
|
||||
export type InternalDragAction = "link"|"image"|"image-fullsize"|"embeddable";
|
||||
export type ModifierSetType = "WebBrowserDragAction" | "LocalFileDragAction" | "InternalDragAction" | "LinkClickAction";
|
||||
|
||||
type ModifierKey = {
|
||||
shift: boolean;
|
||||
ctrl_cmd: boolean;
|
||||
alt_opt: boolean;
|
||||
meta_ctrl: boolean;
|
||||
result: WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget;
|
||||
};
|
||||
|
||||
export type ModifierKeySet = {
|
||||
defaultAction: WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget;
|
||||
rules: ModifierKey[];
|
||||
};
|
||||
|
||||
export type ModifierKeyTooltipMessages = Partial<{
|
||||
[modifierSetType in ModifierSetType]: Partial<{
|
||||
[action in WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget]: string;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
export const modifierKeyTooltipMessages = ():ModifierKeyTooltipMessages => {
|
||||
return {
|
||||
WebBrowserDragAction: {
|
||||
"image-import": "Import Image to Vault",
|
||||
"image-url": `Insert Image or YouTube Thumbnail with URL`,
|
||||
"link": "Insert Link",
|
||||
"embeddable": "Insert Interactive-Frame",
|
||||
// Add more messages for WebBrowserDragAction as needed
|
||||
},
|
||||
LocalFileDragAction: {
|
||||
"image-import": "Insert Image: import external or reuse existing if path in Vault",
|
||||
"image-url": `Insert Image: with local URI or internal-link if from Vault`,
|
||||
"link": "Insert Link: local URI or internal-link if from Vault",
|
||||
"embeddable": "Insert Interactive-Frame: local URI or internal-link if from Vault",
|
||||
},
|
||||
InternalDragAction: {
|
||||
"image": "Insert Image",
|
||||
"image-fullsize": "Insert Image @100%",
|
||||
"link": `Insert Link`,
|
||||
"embeddable": "Insert Interactive-Frame",
|
||||
},
|
||||
LinkClickAction: {
|
||||
"active-pane": "Open in current active window",
|
||||
"new-pane": "Open in a new adjacent window",
|
||||
"popout-window": "Open in a popout window",
|
||||
"new-tab": "Open in a new tab",
|
||||
"md-properties": "Show the Markdown image-properties dialog (only relevant if you have embedded a markdown document as an image)",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const processModifiers = (ev: KeyEvent, modifierType: ModifierSetType): WebBrowserDragAction | LocalFileDragAction | InternalDragAction | PaneTarget => {
|
||||
const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings;
|
||||
const keySet = ((DEVICE.isMacOS || DEVICE.isIOS) ? settings.modifierKeyConfig.Mac : settings.modifierKeyConfig.Win)[modifierType];
|
||||
for (const rule of keySet.rules) {
|
||||
const { shift, ctrl_cmd, alt_opt, meta_ctrl, result } = rule;
|
||||
if (
|
||||
(isSHIFT(ev) === shift) &&
|
||||
(isWinCTRLorMacCMD(ev) === ctrl_cmd) &&
|
||||
(isWinALTorMacOPT(ev) === alt_opt) &&
|
||||
(isWinMETAorMacCTRL(ev) === meta_ctrl)
|
||||
) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return keySet.defaultAction;
|
||||
}
|
||||
|
||||
export const labelCTRL = () => DEVICE.isIOS || DEVICE.isMacOS ? "CMD" : "CTRL";
|
||||
export const labelALT = () => DEVICE.isIOS || DEVICE.isMacOS ? "OPT" : "ALT";
|
||||
export const labelMETA = () => DEVICE.isIOS || DEVICE.isMacOS ? "CTRL" : (DEVICE.isWindows ? "WIN" : "META");
|
||||
export const labelSHIFT = () => "SHIFT";
|
||||
|
||||
export const isCTRL = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.metaKey : e.ctrlKey;
|
||||
export const isALT = (e:KeyEvent) => e.altKey;
|
||||
export const isMETA = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey;
|
||||
export const isWinCTRLorMacCMD = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.metaKey : e.ctrlKey;
|
||||
export const isWinALTorMacOPT = (e:KeyEvent) => e.altKey;
|
||||
export const isWinMETAorMacCTRL = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey;
|
||||
export const isSHIFT = (e:KeyEvent) => e.shiftKey;
|
||||
|
||||
export const setCTRL = (e:ModifierKeys, value: boolean): ModifierKeys => {
|
||||
@@ -39,50 +108,38 @@ export const setSHIFT = (e:ModifierKeys, value: boolean): ModifierKeys => {
|
||||
return e;
|
||||
}
|
||||
|
||||
export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && isMETA(ev);
|
||||
export const scaleToFullsizeModifier = (ev: KeyEvent) =>
|
||||
( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) ||
|
||||
(!isSHIFT(ev) && isCTRL(ev) && isALT(ev) && !isMETA(ev));
|
||||
|
||||
export const linkClickModifierType = (ev: KeyEvent):PaneTarget => {
|
||||
if(isCTRL(ev) && !isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "active-pane";
|
||||
if(isCTRL(ev) && !isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-tab";
|
||||
if(isCTRL(ev) && isALT(ev) && !isSHIFT(ev) && !isMETA(ev)) return "new-pane";
|
||||
if(DEVICE.isDesktop && isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev) ) return "popout-window";
|
||||
if(isCTRL(ev) && isALT(ev) && isSHIFT(ev) && !isMETA(ev)) return "new-tab";
|
||||
if(mdPropModifier(ev)) return "md-properties";
|
||||
return "active-pane";
|
||||
export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isWinCTRLorMacCMD(ev) && !isWinALTorMacOPT(ev) && isWinMETAorMacCTRL(ev);
|
||||
export const scaleToFullsizeModifier = (ev: KeyEvent) => {
|
||||
const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings;
|
||||
const keySet = ((DEVICE.isMacOS || DEVICE.isIOS) ? settings.modifierKeyConfig.Mac : settings.modifierKeyConfig.Win )["InternalDragAction"];
|
||||
const rule = keySet.rules.find(r => r.result === "image-fullsize");
|
||||
if(!rule) return false;
|
||||
const { shift, ctrl_cmd, alt_opt, meta_ctrl, result } = rule;
|
||||
return (
|
||||
(isSHIFT(ev) === shift) &&
|
||||
(isWinCTRLorMacCMD(ev) === ctrl_cmd) &&
|
||||
(isWinALTorMacOPT(ev) === alt_opt) &&
|
||||
(isWinMETAorMacCTRL(ev) === meta_ctrl)
|
||||
);
|
||||
}
|
||||
|
||||
export const externalDragModifierType = (ev: KeyEvent):ExternalDragAction => {
|
||||
if(DEVICE.isWindows && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "embeddable";
|
||||
if(DEVICE.isMacOS && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "embeddable";
|
||||
if(DEVICE.isWindows && !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link";
|
||||
if(DEVICE.isMacOS && isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "insert-link";
|
||||
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
if(DEVICE.isWindows && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
return "image-url";
|
||||
export const linkClickModifierType = (ev: KeyEvent):PaneTarget => {
|
||||
const action = processModifiers(ev, "LinkClickAction") as PaneTarget;
|
||||
if(!DEVICE.isDesktop && action === "popout-window") return "active-pane";
|
||||
return action;
|
||||
}
|
||||
|
||||
export const webbrowserDragModifierType = (ev: KeyEvent):WebBrowserDragAction => {
|
||||
return processModifiers(ev, "WebBrowserDragAction") as WebBrowserDragAction;
|
||||
}
|
||||
|
||||
export const localFileDragModifierType = (ev: KeyEvent):LocalFileDragAction => {
|
||||
if(DEVICE.isWindows && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-uri";
|
||||
if(DEVICE.isMacOS && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-uri";
|
||||
if(DEVICE.isWindows && !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link";
|
||||
if(DEVICE.isMacOS && isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "insert-link";
|
||||
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
if(DEVICE.isWindows && !isSHIFT(ev) && !isCTRL(ev) && isALT(ev) && !isMETA(ev)) return "image-import";
|
||||
return "image-import";
|
||||
return processModifiers(ev, "LocalFileDragAction") as LocalFileDragAction;
|
||||
}
|
||||
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468
|
||||
export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => {
|
||||
if( !(DEVICE.isIOS || DEVICE.isMacOS) && isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "embeddable";
|
||||
if( (DEVICE.isIOS || DEVICE.isMacOS) && !isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) return "embeddable";
|
||||
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
|
||||
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
|
||||
if(scaleToFullsizeModifier(ev)) return "image-fullsize";
|
||||
return "link";
|
||||
return processModifiers(ev, "InternalDragAction") as InternalDragAction;
|
||||
}
|
||||
|
||||
export const emulateCTRLClickForLinks = (e:KeyEvent) => {
|
||||
@@ -97,27 +154,23 @@ export const emulateCTRLClickForLinks = (e:KeyEvent) => {
|
||||
export const emulateKeysForLinkClick = (action: PaneTarget): ModifierKeys => {
|
||||
const ev = {shiftKey: false, ctrlKey: false, metaKey: false, altKey: false};
|
||||
if(!action) return ev;
|
||||
switch(action) {
|
||||
case "active-pane":
|
||||
setCTRL(ev, true);
|
||||
setSHIFT(ev, true);
|
||||
break;
|
||||
case "new-pane":
|
||||
setCTRL(ev, true);
|
||||
setALT(ev, true);
|
||||
break;
|
||||
case "popout-window":
|
||||
setCTRL(ev, true);
|
||||
setALT(ev, true);
|
||||
setSHIFT(ev, true);
|
||||
break;
|
||||
case "new-tab":
|
||||
setCTRL(ev, true);
|
||||
break;
|
||||
case "md-properties":
|
||||
setCTRL(ev, true);
|
||||
setMETA(ev, true);
|
||||
break;
|
||||
const platform = DEVICE.isMacOS || DEVICE.isIOS ? "Mac" : "Win";
|
||||
const settings:ExcalidrawSettings = window.ExcalidrawAutomate.plugin.settings;
|
||||
const modifierKeyConfig = settings.modifierKeyConfig;
|
||||
|
||||
const config = modifierKeyConfig[platform]?.LinkClickAction;
|
||||
|
||||
if (config) {
|
||||
const rule = config.rules.find(rule => rule.result === action);
|
||||
if (rule) {
|
||||
setCTRL(ev, rule.ctrl_cmd);
|
||||
setALT(ev, rule.alt_opt);
|
||||
setMETA(ev, rule.meta_ctrl);
|
||||
setSHIFT(ev, rule.shift);
|
||||
} else {
|
||||
const defaultAction = config.defaultAction as PaneTarget;
|
||||
return emulateKeysForLinkClick(defaultAction);
|
||||
}
|
||||
}
|
||||
return ev;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ 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;
|
||||
while (
|
||||
parent &&
|
||||
!(parent instanceof window.HTMLBodyElement) &&
|
||||
!parent.classList.contains(cssClass)
|
||||
!parent.classList.contains(cssClass) &&
|
||||
!(parent instanceof window.HTMLBodyElement)
|
||||
) {
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
@@ -244,7 +244,8 @@ export const getFileCSSClasses = (
|
||||
file: TFile,
|
||||
): string[] => {
|
||||
if (file) {
|
||||
const plugin = window.ExcalidrawAutomate.plugin;
|
||||
const plugin = window?.ExcalidrawAutomate?.plugin;
|
||||
if(!plugin) return [];
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if(!fileCache?.frontmatter) return [];
|
||||
const x = parseFrontMatterEntry(fileCache.frontmatter, "cssclasses");
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -12,9 +12,12 @@ export const getYouTubeStartAt = (url: string): string => {
|
||||
const hours = Math.floor(time / 3600);
|
||||
const minutes = Math.floor((time - hours * 3600) / 60);
|
||||
const seconds = time - hours * 3600 - minutes * 60;
|
||||
if(hours === 0 && minutes === 0 && seconds === 0) return "";
|
||||
if(hours === 0 && minutes === 0) return `${String(seconds).padStart(2, '0')}`;
|
||||
if(hours === 0) return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
}
|
||||
return "00:00:00";
|
||||
return "";
|
||||
};
|
||||
|
||||
export const isValidYouTubeStart = (value: string): boolean => {
|
||||
@@ -26,7 +29,9 @@ export const isValidYouTubeStart = (value: string): boolean => {
|
||||
export const updateYouTubeStartTime = (link: string, startTime: string): string => {
|
||||
const match = link.match(REG_YOUTUBE);
|
||||
if (match?.[2]) {
|
||||
const startTimeParam = `t=${timeStringToSeconds(startTime)}`;
|
||||
const startTimeParam = startTime === ""
|
||||
? ``
|
||||
: `t=${timeStringToSeconds(startTime)}`;
|
||||
let updatedLink = link;
|
||||
if (match[3]) {
|
||||
// If start time already exists, update it
|
||||
|
||||
43
styles.css
43
styles.css
@@ -477,6 +477,13 @@ hr.excalidraw-setting-hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .canvas-node:not(.is-editing).transparent {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.canvas-node:not(.is-editing):has(.excalidraw-canvas-immersive) {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
@@ -488,4 +495,40 @@ hr.excalidraw-setting-hr {
|
||||
.canvas-node:not(.is-editing) .canvas-node-container:has(.excalidraw-canvas-immersive) {
|
||||
border: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
|
||||
.excalidraw .canvas-node .ex-md-font-hand-drawn {
|
||||
--font-text: "Virgil";
|
||||
}
|
||||
|
||||
.excalidraw .canvas-node .ex-md-font-code {
|
||||
--font-text: "Cascadia";
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .workspace-leaf,
|
||||
.excalidraw__embeddable-container .workspace-leaf .view-content {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .workspace-leaf-content .view-content {
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .workspace-leaf .view-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .workspace-leaf-content .image-container,
|
||||
.excalidraw__embeddable-container .workspace-leaf-content .audio-container,
|
||||
.excalidraw__embeddable-container .workspace-leaf-content .video-container {
|
||||
display: flex;
|
||||
}
|
||||
Reference in New Issue
Block a user