Compare commits

...

19 Commits

Author SHA1 Message Date
zsviczian
e6f5f9469a 1.8.0 2022-11-20 17:59:42 +01:00
zsviczian
0502ac3bb0 Fix tools panel icon sizes, added save and open link actions, taskbone image size normalization 2022-11-19 21:31:11 +01:00
zsviczian
36125c9b83 fullscreen finish 2022-11-19 19:06:13 +01:00
zsviczian
cced2ca2e4 new-fullscreen mode 2022-11-19 18:51:37 +01:00
zsviczian
0de2c78cac added taskbone, fixed transclusion deconstruct 2022-11-19 18:23:59 +01:00
zsviczian
7848c8f705 1.7.30 2022-11-18 18:23:32 +01:00
zsviczian
f6c135227b rename to deconstruct 2022-11-15 23:42:50 +01:00
zsviczian
95ca41fcaa fix release notes 2022-11-15 21:21:09 +01:00
zsviczian
d7648d702a fixed release notes 2022-11-15 21:09:42 +01:00
zsviczian
5033a7cccf publish decompose script 2022-11-15 20:44:25 +01:00
zsviczian
1a83abb256 added decompose script image 2022-11-15 20:32:34 +01:00
zsviczian
08f616b5d9 1.7.29 2022-11-15 19:16:16 +01:00
zsviczian
ca44699e8d manifest-beta.json 2022-11-13 20:58:16 +01:00
zsviczian
e0111e264c Merge branch 'master' of https://github.com/zsviczian/obsidian-excalidraw-plugin 2022-11-13 20:32:00 +01:00
zsviczian
c6196a86a9 1.7.28 beta 2022-11-13 20:31:57 +01:00
zsviczian
3926e5c30b Update issue templates 2022-11-02 13:59:55 +01:00
zsviczian
a1256422fa Update issue templates 2022-11-02 13:58:09 +01:00
zsviczian
eeb47d4912 Update issue templates 2022-11-02 13:56:50 +01:00
zsviczian
8ceac4ab31 1.7.27 2022-11-01 11:42:39 +01:00
33 changed files with 975 additions and 265 deletions

View File

@@ -1,32 +1,30 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):**
- OS including version: [e.g. iOS 15.1, Android 9, Windows 11, etc]
- Plugin version:
- Obsidian version:
**Additional context**
Add any other context about the problem here.
---
name: Bug report
about: Create a report to help me improve Excalidraw
title: 'BUG: '
labels: ''
assignees: ''
---
**Your environment**
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
---
name: Feature request
about: Suggest an idea for this project
title: 'FR: '
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -19,7 +19,7 @@ Please upgrade to Obsidian v0.12.19 or higher to get the latest release.
|[![thumbnail](https://user-images.githubusercontent.com/14358394/156931461-0979b821-315a-41dd-86f1-31d169b7c127.jpg)](https://youtu.be/Etskjw7a5zo)|[![Thumbnail](https://user-images.githubusercontent.com/14358394/158008902-12c6a851-237e-4edd-a631-d48e81c904b2.jpg)](https://youtu.be/4N6efq1DtH0)|[![thumbnail](https://user-images.githubusercontent.com/14358394/159369910-6371f08d-b5fa-454d-9c6c-948f7e7a7d26.jpg)](https://youtu.be/U2LkBRBk4LY)|
| [![6 strategies for linking your visual thoughts v4](https://user-images.githubusercontent.com/14358394/171635214-30533c45-94fa-436e-83a9-b2ec99f190e2.jpg)](https://youtu.be/qiKuqMcNWgU)|[![Video thumbnail small](https://user-images.githubusercontent.com/14358394/185791706-3d9983ab-7cb1-4b27-a016-30c039d84e34.jpg)](https://youtu.be/yZQoJg2RCKI)|[![Thumbnail - Colors - Excalidraw Basics (Custom)](https://user-images.githubusercontent.com/14358394/194773147-5418a0ab-6be5-4eb0-a8e4-d6af21b1b483.png)](https://youtu.be/6PLGHBH9VZ4) |
|[![Thumbnail - Excalidraw color palettes (Custom)](https://user-images.githubusercontent.com/14358394/194773211-9e871be7-0795-4dc7-947e-c6c275e690d0.png)](https://youtu.be/epYNx2FSf2w) | [![Thumbnail (Custom)](https://user-images.githubusercontent.com/14358394/194773268-400cfb1b-6bde-45e0-9e4b-91bbaa461cf0.png)](https://youtu.be/Amhlv6r9WvM) | [![Thumbnail - Simple rules for beautiful sketches (Custom) (1)](https://user-images.githubusercontent.com/14358394/194773527-ef35c8b9-1a6d-4415-9c7e-b667fb17535d.png)](https://youtu.be/r9oB1SlK1GU) |
|[![Thumbnail - ColorMaster Scripting (Custom)](https://user-images.githubusercontent.com/14358394/195988535-a133a9b9-d094-45ba-ba64-c994b9a1e0ef.png)](https://youtu.be/7gJDwNgQ6NU) | | |
|[![Thumbnail - ColorMaster Scripting (Custom)](https://user-images.githubusercontent.com/14358394/195988535-a133a9b9-d094-45ba-ba64-c994b9a1e0ef.png)](https://youtu.be/7gJDwNgQ6NU) | [![Thumbnail - 1 7 27 SVG import (Custom)](https://user-images.githubusercontent.com/14358394/199207784-8bbe14e0-7d10-47d7-971d-20dce8dbd659.png)](https://youtu.be/vlC1-iBvIfo) | [![Thumbnail - 1 8 0 OCR (Custom)](https://user-images.githubusercontent.com/14358394/202914706-f43938ac-9efc-49ad-9c0b-919ffafef91d.png)](https://youtu.be/7gu4ETx7zro)|

View File

@@ -0,0 +1,71 @@
/*
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-deconstruct.jpg)
Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.
```javascript
*/
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.7.29")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
const els = ea.getViewSelectedElements();
if (els.length === 0) {
new Notice("You must select elements first")
return;
}
const bb = ea.getBoundingBox(els);
ea.copyViewElementsToEAforEditing(els);
ea.getElements().filter(el=>el.type==="image").forEach(el=>{
const img = ea.targetView.excalidrawData.getFile(el.fileId);
const path = (img?.linkParts?.original)??(img?.file?.path);
if(img && path) {
ea.imagesDict[el.fileId] = {
mimeType: img.mimeType,
id: el.fileId,
dataURL: img.img,
created: img.mtime,
file: path,
hasSVGwithBitmap: img.isSVGwithBitmap,
latex: null,
};
return;
}
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
eqImg = ea.targetView.getScene()?.files[el.fileId]
if(equation && eqImg) {
ea.imagesDict[el.fileId] = {
mimeType: eqImg.mimeType,
id: el.fileId,
dataURL: eqImg.dataURL,
created: eqImg.created,
file: null,
hasSVGwithBitmap: null,
latex: equation.latex,
};
return;
}
});
let folder = ea.targetView.file.path;
folder = folder.lastIndexOf("/")===-1?"":folder.substring(0,folder.lastIndexOf("/"))+"/";
const fname = await utils.inputPrompt("Filename for new file","Filename","");
const template = app.metadataCache.getFirstLinkpathDest(ea.plugin.settings.templateFilePath,"");
const newPath = await ea.create ({
filename: fname + ".md",
foldername: folder,
templatePath: template?.path,
onNewPane: true
});
setTimeout(async ()=>{
const file = app.metadataCache.getFirstLinkpathDest(newPath,"")
ea.deleteViewElements(els);
ea.clear();
await ea.addImage(bb.topX,bb.topY,file,false);
ea.addElementsToView(false, true, true);
},1000);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M169.7 .9c-22.8-1.6-41.9 14-47.5 34.7L110.4 80c.5 0 1.1 0 1.6 0c176.7 0 320 143.3 320 320c0 .5 0 1.1 0 1.6l44.4-11.8c20.8-5.5 36.3-24.7 34.7-47.5C498.5 159.5 352.5 13.5 169.7 .9zM399.8 410.2c.1-3.4 .2-6.8 .2-10.2c0-159.1-128.9-288-288-288c-3.4 0-6.8 .1-10.2 .2L.5 491.9c-1.5 5.5 .1 11.4 4.1 15.4s9.9 5.6 15.4 4.1L399.8 410.2zM176 272c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32zm128 64c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32zM160 384c0 17.7-14.3 32-32 32s-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32z"/></svg>

After

Width:  |  Height:  |  Size: 624 B

File diff suppressed because one or more lines are too long

View File

@@ -41,6 +41,7 @@ I would love to include your contribution in the script library. If you have a s
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/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/Create%20new%20markdown%20file%20and%20embed%20into%20active%20drawing.svg"/></div>|[[#Create new markdown file and embed into active drawing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Darken%20background%20color.svg"/></div>|[[#Darken background color]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.svg"/></div>|[[#Deconstruct selected elements into new drawing]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.svg"/></div>|[[#Elbow connectors]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally%20keep%20text%20centered.svg"/></div>|[[#Expand rectangles horizontally keep text centered]]|
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Expand%20rectangles%20horizontally.svg"/></div>|[[#Expand rectangles horizontally]]|
@@ -160,6 +161,12 @@ 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/1-2-3'>@1-2-3</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/Darken%20background%20color.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script darkens the background color of the selected element by 2% at a time. You can use this script several times until you are satisfied. It is recommended to set a shortcut key for this script so that you can quickly try to DARKEN and LIGHTEN the color effect. In contrast to the `Modify background color opacity` script, the advantage is that the background color of the element is not affected by the canvas color, and the color value does not appear in a strange rgba() form.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/darken-lighten-background-color.png'></td></tr></table>
## Deconstruct selected elements into new drawing
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.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/Deconstruct%20selected%20elements%20into%20new%20drawing.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Select some elements in the scene. The script will take these elements and move them into a new Excalidraw file, and open that file. The selected elements will also be replaced in your original drawing with the embedded Excalidraw file (the one that was just created). You will be prompted for the file name of the new deconstructed image. The script is useful if you want to break a larger drawing into smaller reusable parts that you want to reference in multiple drawings.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-deconstruct.jpg'></td></tr></table>
## Elbow connectors
```excalidraw-script-install
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Elbow%20connectors.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

10
manifest-beta.json Normal file
View File

@@ -0,0 +1,10 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.7.28-beta",
"minAppVersion": "0.15.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://zsolt.blog",
"isDesktopOnly": false
}

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "1.7.26",
"version": "1.8.0",
"minAppVersion": "0.15.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -18,7 +18,7 @@
"license": "MIT",
"dependencies": {
"@types/lz-string": "^1.3.34",
"@zsviczian/excalidraw": "0.13.0-obsidian",
"@zsviczian/excalidraw": "0.13.0-obsidian-1",
"clsx": "^1.1.1",
"lz-string": "^1.4.4",
"monkey-around": "^2.3.0",

View File

@@ -8,10 +8,11 @@ import {
FileId,
NonDeletedExcalidrawElement,
ExcalidrawImageElement,
ExcalidrawTextElement,
} from "@zsviczian/excalidraw/types/element/types";
import { normalizePath, Notice, TFile, WorkspaceLeaf } from "obsidian";
import ExcalidrawView, { ExportSettings, TextMode } from "./ExcalidrawView";
import { ExcalidrawData } from "./ExcalidrawData";
import { ExcalidrawData, getMarkdownDrawingSection } from "./ExcalidrawData";
import {
FRONTMATTER,
nanoid,
@@ -32,7 +33,7 @@ import {
isVersionNewerThanOther,
log,
scaleLoadedImage,
wrapText,
wrapTextAtCharLength,
} from "./utils/Utils";
import { getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils";
import { AppState, Point } from "@zsviczian/excalidraw/types/types";
@@ -123,6 +124,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
viewBackgroundColor: string;
gridSize: number;
};
colorPalette: {};
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView) {
this.plugin = plugin;
@@ -387,10 +389,38 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
template?.appState?.currentItemLinearStrokeSharpness ??
this.style.strokeSharpness,
gridSize: template?.appState?.gridSize ?? this.canvas.gridSize,
colorPalette: template?.appState?.colorPalette ?? this.colorPalette,
},
files: template?.files ?? {},
};
const generateMD = ():string => {
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
let outString = "# Text Elements\n";
textElements.forEach(te=> {
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
});
const elementsWithLinks = this.getElements().filter( el => el.type !== "text" && el.link)
elementsWithLinks.forEach(el=>{
outString += `${el.link} ^${el.id}\n\n`;
})
outString += Object.keys(this.imagesDict).length > 0
? "\n# Embedded files\n"
: "";
Object.keys(this.imagesDict).forEach((key: FileId)=> {
const item = this.imagesDict[key];
if(item.latex) {
outString += `${key}: $$${item.latex}$$\n`;
} else {
outString += `${key}: [[${item.file}]]\n`;
}
})
return outString;
}
return this.plugin.createAndOpenDrawing(
params?.filename
? params.filename + (params.filename.endsWith(".md") ? "": ".excalidraw.md")
@@ -399,8 +429,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
params?.foldername ? params.foldername : this.plugin.settings.folder,
this.plugin.settings.compatibilityMode
? JSON.stringify(scene, null, "\t")
: frontmatter +
(await this.plugin.exportSceneToMD(JSON.stringify(scene, null, "\t"))),
: frontmatter + generateMD() +
getMarkdownDrawingSection(JSON.stringify(scene, null, "\t"),this.plugin.settings.compress)
);
};
@@ -454,7 +484,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
this.getElements(),
this.plugin,
0,
padding
padding,
this.imagesDict
);
};
@@ -508,7 +539,8 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
this.getElements(),
this.plugin,
0,
padding
padding,
this.imagesDict,
);
};
@@ -519,7 +551,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
* @returns
*/
wrapText(text: string, lineLen: number): string {
return wrapText(text, lineLen, this.plugin.settings.forceWrap);
return wrapTextAtCharLength(text, lineLen, this.plugin.settings.forceWrap);
};
private boxedElement(
@@ -1221,11 +1253,11 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
errorMessage("targetView not set", "getViewElements()");
return [];
}
const current = this.targetView?.excalidrawRef?.current;
if (!current) {
const api = this.targetView.excalidrawAPI;
if (!api) {
return [];
}
return current?.getSceneElements();
return api.getSceneElements();
};
/**
@@ -2042,6 +2074,14 @@ async function getTemplate(
}
}
if(filenameParts.hasTaskbone) {
groupElements = groupElements.filter( el =>
el.type==="freedraw" ||
( el.type==="image" &&
!plugin.isExcalidrawFile(excalidrawData.getFile(el.fileId)?.file)
));
}
return {
elements: groupElements,
appState: scene.appState,
@@ -2071,6 +2111,7 @@ export async function createPNG(
plugin: ExcalidrawPlugin,
depth: number,
padding?: number,
imagesDict?: any,
) {
if (!loader) {
loader = new EmbeddedFilesLoader(plugin);
@@ -2081,6 +2122,13 @@ export async function createPNG(
: null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
const files = imagesDict ?? {};
if(template?.files) {
Object.values(template.files).forEach((f:any)=>{
files[f.id]=f;
});
}
return await getPNG(
{
type: "excalidraw",
@@ -2092,7 +2140,7 @@ export async function createPNG(
viewBackgroundColor:
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
},
files: template?.files ?? {},
files,
},
{
withBackground:
@@ -2116,6 +2164,7 @@ export async function createSVG(
plugin: ExcalidrawPlugin,
depth: number,
padding?: number,
imagesDict?: any,
): Promise<SVGSVGElement> {
if (!loader) {
loader = new EmbeddedFilesLoader(plugin);
@@ -2126,6 +2175,12 @@ export async function createSVG(
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
padding = padding ?? plugin.settings.exportPaddingSVG;
const files = imagesDict ?? {};
if(template?.files) {
Object.values(template.files).forEach((f:any)=>{
files[f.id]=f;
});
}
const svg = await getSVG(
{
//createAndOpenDrawing
@@ -2138,7 +2193,7 @@ export async function createSVG(
viewBackgroundColor:
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
},
files: template?.files ?? {},
files,
},
{
withBackground:

View File

@@ -27,11 +27,12 @@ import {
decompress,
//getBakPath,
getBinaryFileFromDataURL,
getContainerElement,
getExportTheme,
getLinkParts,
hasExportTheme,
LinkParts,
wrapText,
wrapTextAtCharLength,
} from "./utils/Utils";
import { getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
import {
@@ -52,6 +53,13 @@ declare module "obsidian" {
}
}
const {
wrapText,
getFontString,
getMaxContainerWidth,
//@ts-ignore
} = excalidrawLib;
export enum AutoexportPreference {
none,
both,
@@ -210,15 +218,16 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
return null;
}
for (const line of splitText) {
if (line.length > maxLineLen) {
maxLineLen = line.length;
const l = line.trim();
if (l.length > maxLineLen) {
maxLineLen = l.length;
}
}
return maxLineLen;
};
const wrap = (text: string, lineLen: number) =>
lineLen ? wrapText(text, lineLen, false, 0) : text;
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
export class ExcalidrawData {
public textElements: Map<
@@ -638,12 +647,17 @@ export class ExcalidrawData {
//first get scene text elements
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
for (const te of texts) {
const container = getContainerElement(te,this.scene);
const originalText =
(await this.getText(te.id, false)) ?? te.originalText ?? te.text;
(await this.getText(te.id)) ?? te.originalText ?? te.text;
const wrapAt = this.textElements.get(te.id)?.wrapAt;
this.updateTextElement(
te,
wrap(originalText, wrapAt),
wrapAt ? wrapText(
originalText,
getFontString({fontSize: te.fontSize, fontFamily: te.fontFamily}),
getMaxContainerWidth(container)
) : originalText,
originalText,
forceupdate,
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
@@ -652,7 +666,6 @@ export class ExcalidrawData {
private async getText(
id: string,
wrapResult: boolean = true,
): Promise<string> {
const text = this.textElements.get(id);
if (!text) {
@@ -667,7 +680,7 @@ export class ExcalidrawData {
});
}
//console.log("parsed",this.textElements.get(id).parsed);
return wrapResult ? wrap(text.parsed, text.wrapAt) : text.parsed;
return text.parsed;
}
//console.log("raw",this.textElements.get(id).raw);
return text.raw;
@@ -794,7 +807,7 @@ export class ExcalidrawData {
if (el.length === 0) {
this.textElements.delete(key); //if no longer in the scene, delete the text element
} else {
const text = await this.getText(key, false);
const text = await this.getText(key);
const raw = this.scene.prevTextMode === TextMode.parsed
? el[0].rawText
: (el[0].originalText ?? el[0].text);
@@ -887,7 +900,7 @@ export class ExcalidrawData {
}
outString +=
text.substring(position, parts.value.index) +
wrapText(
wrapTextAtCharLength(
contents,
REGEX_LINK.getWrapLength(
parts,
@@ -1434,7 +1447,7 @@ export class ExcalidrawData {
const parts = data.linkParts.original.split("#");
this.plugin.filesMaster.set(fileId, {
path:data.file.path,
path:data.file.path + (data.shouldScale()?"":"|100%"),
blockrefData: parts.length === 1
? null
: parts[1],
@@ -1479,16 +1492,18 @@ export class ExcalidrawData {
}
if (this.plugin.filesMaster.has(fileId)) {
const masterFile = this.plugin.filesMaster.get(fileId);
if (!this.app.vault.getAbstractFileByPath(masterFile.path)) {
const path = masterFile.path.split("|")[0].split("#")[0];
if (!this.app.vault.getAbstractFileByPath(path)) {
this.plugin.filesMaster.delete(fileId);
return true;
} // the file no longer exists
const fixScale = masterFile.path.endsWith("100%");
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
masterFile.blockrefData
? masterFile.path + "#" + masterFile.blockrefData
: masterFile.path
(masterFile.blockrefData
? path + "#" + masterFile.blockrefData
: path) + (fixScale?"|100%":"")
);
this.files.set(fileId, embeddedFile);
return true;

View File

@@ -92,6 +92,7 @@ import { ObsidianMenu } from "./menu/ObsidianMenu";
import { ToolsPanel } from "./menu/ToolsPanel";
import { ScriptEngine } from "./Scripts";
import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAtPointer } from "./utils/GetElementAtPointer";
import { MenuLinks } from "./menu/menuLinks";
export enum TextMode {
parsed = "parsed",
@@ -107,6 +108,9 @@ export interface ExportSettings {
withTheme: boolean;
}
const HIDE = "excalidraw-hidden";
const SHOW = "excalidraw-visible";
export const addFiles = async (
files: FileData[],
view: ExcalidrawView,
@@ -255,6 +259,7 @@ export default class ExcalidrawView extends TextFileView {
private linkAction_Element: HTMLElement;
public compatibilityMode: boolean = false;
private obsidianMenu: ObsidianMenu;
private menuLinks: MenuLinks;
//https://stackoverflow.com/questions/27132796/is-there-any-javascript-event-fired-when-the-on-screen-keyboard-on-mobile-safari
private isEditingTextResetTimer: NodeJS.Timeout = null;
@@ -610,22 +615,6 @@ export default class ExcalidrawView extends TextFileView {
return this.data;
}
addFullscreenchangeEvent() {
//excalidrawWrapperRef.current
this.contentEl.onfullscreenchange = () => {
if (this.plugin.settings.zoomToFitOnResize) {
this.zoomToFit();
}
if (!this.isFullscreen()) {
this.clearFullscreenObserver();
this.contentEl.removeAttribute("style");
}
if (this.toolsPanelRef && this.toolsPanelRef.current) {
this.toolsPanelRef.current.setFullscreen(this.isFullscreen());
}
};
}
fullscreenModalObserver: MutationObserver = null;
private hiddenMobileLeaves:[WorkspaceLeaf,string][] = [];
@@ -650,99 +639,39 @@ export default class ExcalidrawView extends TextFileView {
if (this.toolsPanelRef && this.toolsPanelRef.current) {
this.toolsPanelRef.current.setFullscreen(true);
}
if (this.plugin.device.isPhone) {
if(Platform.isIosApp) {
this.restoreMobileLeaves();
app.workspace.getLayout().main.children
.filter((child:any)=>child.type==="leaf")
.forEach((listItem:any)=> {
const l = app.workspace.getLeafById(listItem.id);
if(l!==this.leaf) {
//@ts-ignore
this.hiddenMobileLeaves.push([l,l.containerEl.style.display]);
//@ts-ignore
l.containerEl.style.display = "none";
}
});
}
const newStylesheet = document.createElement("style");
newStylesheet.id = "excalidraw-full-screen";
newStylesheet.textContent = `
.workspace-leaf-content .view-content {
padding: 0px !important;
}
.view-header {
height: 1px !important;
}
.status-bar {
display: none !important;
}`;
const oldStylesheet = document.getElementById(newStylesheet.id);
if (oldStylesheet) {
document.head.removeChild(oldStylesheet);
const hide = (el:HTMLElement) => {
while(el && !el.hasClass("workspace-split")) {
el.addClass(SHOW);
el = el.parentElement;
}
document.head.appendChild(newStylesheet);
//return;
if(el) el.addClass(SHOW);
const doc = this.ownerDocument;
doc.body.querySelectorAll(`div.workspace-split:not(.${SHOW})`).forEach(el=>el.addClass(HIDE));
doc.body.querySelector(`div.workspace-leaf-content.${SHOW} > .view-header`).addClass(HIDE);
doc.body.querySelectorAll(`div.workspace-tab-container.${SHOW} > div.workspace-leaf:not(.${SHOW})`).forEach(el=>el.addClass(HIDE));
doc.body.querySelectorAll(`div.workspace-tabs.${SHOW} > div.workspace-tab-header-container`).forEach(el=>el.addClass(HIDE));
doc.body.querySelectorAll(`div.workspace-split.${SHOW} > div.workspace-tabs:not(.${SHOW})`).forEach(el=>el.addClass(HIDE));
doc.body.querySelectorAll(`div.workspace-ribbon`).forEach(el=>el.addClass(HIDE));
doc.body.querySelectorAll(`div.mobile-navbar`).forEach(el=>el.addClass(HIDE));
doc.body.querySelectorAll(`div.status-bar`).forEach(el=>el.addClass(HIDE));
}
this.contentEl.requestFullscreen(); //{navigationUI: "hide"});
this.excalidrawWrapperRef.current.firstElementChild?.focus();
this.contentEl.setAttribute("style", "padding:0px;margin:0px;");
this.fullscreenModalObserver = new MutationObserver((m) => {
if (m.length !== 1) {
return;
}
if (!m[0].addedNodes || m[0].addedNodes.length !== 1) {
return;
}
const node: Node = m[0].addedNodes[0];
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
const element = node as HTMLElement;
if (!element.classList.contains("modal-container")) {
return;
}
this.contentEl.appendChild(element);
element.querySelector("input").focus();
});
this.fullscreenModalObserver.observe(this.ownerDocument.body, {
childList: true,
subtree: false,
});
hide(this.contentEl);
}
clearFullscreenObserver() {
if (this.fullscreenModalObserver) {
this.fullscreenModalObserver.disconnect();
this.fullscreenModalObserver = null;
}
}
isFullscreen(): boolean {
return (
this.hiddenMobileLeaves.length > 0 || (
this.ownerDocument.fullscreenEnabled &&
this.ownerDocument.fullscreenElement === this.contentEl) // excalidrawWrapperRef?.current
); //this.contentEl;
return Boolean(document.body.querySelector(".excalidraw-hidden"));
}
exitFullscreen() {
console.log("Exit Fullscreen");
if (this.toolsPanelRef && this.toolsPanelRef.current) {
this.toolsPanelRef.current.setFullscreen(false);
}
if (this.plugin.device.isPhone) {
this.restoreMobileLeaves();
const oldStylesheet = document.getElementById("excalidraw-full-screen");
if (oldStylesheet) {
document.head.removeChild(oldStylesheet);
}
//return;
}
this.ownerDocument.exitFullscreen();
const doc = this.ownerDocument;
doc.querySelectorAll(".excalidraw-hidden").forEach(el=>el.removeClass(HIDE));
doc.querySelectorAll(".excalidraw-visible").forEach(el=>el.removeClass(SHOW));
}
async handleLinkClick(view: ExcalidrawView, ev: MouseEvent) {
@@ -983,6 +912,24 @@ export default class ExcalidrawView extends TextFileView {
wheelEvent: (ev:WheelEvent)=>void;
clearHoverPreview: Function;
public async forceSave(silent:boolean=false) {
if (this.semaphores.autosaving || this.semaphores.saving) {
if(!silent) new Notice("Force Save aborted because saving is in progress)")
return;
}
if(this.preventReloadResetTimer) {
clearTimeout(this.preventReloadResetTimer);
this.preventReloadResetTimer = null;
}
this.semaphores.preventReload = false;
this.semaphores.forceSaving = true;
await this.save(false, true);
this.plugin.triggerEmbedUpdates();
this.loadSceneFiles();
this.semaphores.forceSaving = false;
if(!silent) new Notice("Save successful", 1000);
}
onload() {
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
//@ts-ignore
@@ -1013,23 +960,7 @@ export default class ExcalidrawView extends TextFileView {
this.diskIcon = this.addAction(
DISK_ICON_NAME,
t("FORCE_SAVE"),
async () => {
if (this.semaphores.autosaving || this.semaphores.saving) {
new Notice("Force Save aborted because saving is in progress)")
return;
}
if(this.preventReloadResetTimer) {
clearTimeout(this.preventReloadResetTimer);
this.preventReloadResetTimer = null;
}
this.semaphores.preventReload = false;
this.semaphores.forceSaving = true;
await this.save(false, true);
this.plugin.triggerEmbedUpdates();
this.loadSceneFiles();
this.semaphores.forceSaving = false;
new Notice("Save successful", 1000);
},
async () => this.forceSave(),
);
this.textIsRaw_Element = this.addAction(
@@ -1791,6 +1722,9 @@ export default class ExcalidrawView extends TextFileView {
//console.log(debug);
this.semaphores.dirty = this.file?.path;
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
if(this.toolsPanelRef?.current) {
this.toolsPanelRef.current.setDirty(true);
}
if(!app.isMobile) {
if(requireApiVersion("0.16.0")) {
//@ts-ignore
@@ -1805,6 +1739,9 @@ export default class ExcalidrawView extends TextFileView {
return;
}
this.semaphores.dirty = null;
if(this.toolsPanelRef?.current) {
this.toolsPanelRef.current.setDirty(false);
}
const el = api.getSceneElements();
if (el) {
this.previousSceneVersion = this.getSceneVersion(el);
@@ -2013,6 +1950,8 @@ export default class ExcalidrawView extends TextFileView {
let currentPosition = { x: 0, y: 0 };
const excalidrawWrapperRef = React.useRef(null);
const toolsPanelRef = React.useRef(null);
const menuLinksRef = React.useRef(null);
const [dimensions, setDimensions] = React.useState({
width: undefined,
height: undefined,
@@ -2027,6 +1966,7 @@ export default class ExcalidrawView extends TextFileView {
this.toolsPanelRef = toolsPanelRef;
this.obsidianMenu = new ObsidianMenu(this.plugin, toolsPanelRef);
this.menuLinks = new MenuLinks(this.plugin, menuLinksRef);
//excalidrawRef readypromise based on
//https://codesandbox.io/s/eexcalidraw-resolvable-promise-d0qg3?file=/src/App.js:167-760
@@ -2062,7 +2002,6 @@ export default class ExcalidrawView extends TextFileView {
this.loadSceneFiles();
this.updateContainerSize(null, true);
this.excalidrawWrapperRef.current.firstElementChild?.focus();
this.addFullscreenchangeEvent();
this.initializeToolsIconPanelAfterLoading();
},
);
@@ -2730,7 +2669,7 @@ export default class ExcalidrawView extends TextFileView {
loadScene: false,
saveScene: false,
saveAsScene: false,
export: { saveFileToDisk: false },
export: false,
saveAsImage: false,
saveToActiveFile: false,
},
@@ -2781,6 +2720,8 @@ export default class ExcalidrawView extends TextFileView {
},
libraryReturnUrl: "app://obsidian.md",
autoFocus: true,
hideWelcomeScreen: true,
renderMenuLinks: null, //this.menuLinks.render,
onChange: (et: ExcalidrawElement[], st: AppState) => {
const canvasColorChangeHook = () => {
if(this.plugin.ea.onCanvasColorChangeHook) {

View File

@@ -79,7 +79,7 @@ const getIMG = async (
withTheme: forceTheme ? true : plugin.settings.exportWithTheme,
};
const img = createEl("img");
let style = `max-width:${imgAttributes.fwidth}px !important; width:100%;`;
let style = `max-width:${imgAttributes.fwidth}px; width:100%;`; //removed !important https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/886
if (imgAttributes.fheight) {
style += `height:${imgAttributes.fheight}px;`;
}

View File

@@ -210,10 +210,9 @@ COLOR_NAMES.set("white", "#ffffff");
COLOR_NAMES.set("whitesmoke", "#f5f5f5");
COLOR_NAMES.set("yellow", "#ffff00");
COLOR_NAMES.set("yellowgreen", "#9acd32");
export const DEFAULT_MD_EMBED_CSS = `.excalidraw-md-host{padding:0px 10px}.excalidraw-md-footer{height:5px}foreignObject{background-color:transparent}p{display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:0px;margin-inline-end:0px;color:inherit}table,tr,th,td{color:inherit;border:1px solid;border-collapse:collapse;padding:3px}th{font-weight:bold;border-bottom:double;background-color:silver}.copy-code-button{display:none}code[class*=language-],pre[class*=language-]{color:#393a34;font-family:"Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;font-size:.9em;line-height:1.2em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre>code[class*=language-]{font-size:1em}pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection{background:#C1DEF1}pre[class*=language-]::selection,pre[class*=language-] ::selection,code[class*=language-]::selection,code[class*=language-] ::selection{background:#C1DEF1}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background-color:#0000001a}:not(pre)>code[class*=language-]{padding:.2em;padding-top:1px;padding-bottom:1px;background:#f8f8f8;border:1px solid #dddddd}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:green;font-style:italic}.token.namespace{opacity:.7}.token.string{color:#a31515}.token.punctuation,.token.operator{color:#393a34}.token.url,.token.symbol,.token.number,.token.boolean,.token.variable,.token.constant,.token.inserted{color:#36acaa}.token.atrule,.token.keyword,.token.attr-value,.language-autohotkey .token.selector,.language-json .token.boolean,.language-json .token.number,code[class*=language-css]{color:#00f}.token.function{color:#393a34}.token.deleted,.language-autohotkey .token.tag{color:#9a050f}.token.selector,.language-autohotkey .token.keyword{color:#00009f}.token.important{color:#e90}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.class-name,.language-json .token.property{color:#2b91af}.token.tag,.token.selector{color:maroon}.token.attr-name,.token.property,.token.regex,.token.entity{color:red}.token.directive.tag .tag{background:#ffff00;color:#393a34}.line-numbers.line-numbers .line-numbers-rows{border-right-color:#a5a5a5}.line-numbers .line-numbers-rows>span:before{color:#2b91af}.line-highlight.line-highlight{background:rgba(193,222,241,.2);background:-webkit-linear-gradient(left,rgba(193,222,241,.2) 70%,rgba(221,222,241,0));background:linear-gradient(to right,rgba(193,222,241,.2) 70%,rgba(221,222,241,0))}blockquote{ font-style:italic;background-color:rgb(46,43,42,0.1);margin:0;margin-left:1em;border-radius:0 4px 4px 0;border:1px solid hsl(0,80%,32%);border-left-width:8px;border-top-width:0px;border-right-width:0px;border-bottom-width:0px;padding:10px 20px;margin-inline-start:30px;margin-inline-end:30px;}`;
export const DEFAULT_MD_EMBED_CSS = `.snw-reference{display: none;}.excalidraw-md-host{padding:0px 10px}.excalidraw-md-footer{height:5px}foreignObject{background-color:transparent}p{display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:0px;margin-inline-end:0px;color:inherit}table,tr,th,td{color:inherit;border:1px solid;border-collapse:collapse;padding:3px}th{font-weight:bold;border-bottom:double;background-color:silver}.copy-code-button{display:none}code[class*=language-],pre[class*=language-]{color:#393a34;font-family:"Consolas","Bitstream Vera Sans Mono","Courier New",Courier,monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;font-size:.9em;line-height:1.2em;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre>code[class*=language-]{font-size:1em}pre[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,code[class*=language-] ::-moz-selection{background:#C1DEF1}pre[class*=language-]::selection,pre[class*=language-] ::selection,code[class*=language-]::selection,code[class*=language-] ::selection{background:#C1DEF1}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;background-color:#0000001a}:not(pre)>code[class*=language-]{padding:.2em;padding-top:1px;padding-bottom:1px;background:#f8f8f8;border:1px solid #dddddd}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:green;font-style:italic}.token.namespace{opacity:.7}.token.string{color:#a31515}.token.punctuation,.token.operator{color:#393a34}.token.url,.token.symbol,.token.number,.token.boolean,.token.variable,.token.constant,.token.inserted{color:#36acaa}.token.atrule,.token.keyword,.token.attr-value,.language-autohotkey .token.selector,.language-json .token.boolean,.language-json .token.number,code[class*=language-css]{color:#00f}.token.function{color:#393a34}.token.deleted,.language-autohotkey .token.tag{color:#9a050f}.token.selector,.language-autohotkey .token.keyword{color:#00009f}.token.important{color:#e90}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.class-name,.language-json .token.property{color:#2b91af}.token.tag,.token.selector{color:maroon}.token.attr-name,.token.property,.token.regex,.token.entity{color:red}.token.directive.tag .tag{background:#ffff00;color:#393a34}.line-numbers.line-numbers .line-numbers-rows{border-right-color:#a5a5a5}.line-numbers .line-numbers-rows>span:before{color:#2b91af}.line-highlight.line-highlight{background:rgba(193,222,241,.2);background:-webkit-linear-gradient(left,rgba(193,222,241,.2) 70%,rgba(221,222,241,0));background:linear-gradient(to right,rgba(193,222,241,.2) 70%,rgba(221,222,241,0))}blockquote{ font-style:italic;background-color:rgb(46,43,42,0.1);margin:0;margin-left:1em;border-radius:0 4px 4px 0;border:1px solid hsl(0,80%,32%);border-left-width:8px;border-top-width:0px;border-right-width:0px;border-bottom-width:0px;padding:10px 20px;margin-inline-start:30px;margin-inline-end:30px;}`;
export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.318 37.983c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749m.126-.104c-1.234-1.232-8.433-3.903-7.401-7.387 1.057-3.484 9.893-12.443 13.669-13.517 3.776-1.074 6.142 6.523 9.012 7.073 2.87.55 6.797-1.572 8.207-3.694 1.384-2.148-3.147-7.413.15-9.168 3.298-1.755 16.389-2.646 19.611-1.284 3.247 1.363-1.611 7.335-.151 9.483 1.46 2.148 6.067 3.746 8.836 3.38 2.769-.368 4.154-6.733 7.728-5.633 3.575 1.1 12.36 8.828 13.67 12.233 1.308 3.406-5.186 5.423-5.79 8.2-.58 2.75-.026 6.705 2.265 8.355 2.266 1.65 9.642-1.78 11.404 1.598 1.762 3.38 1.007 15.35-.806 18.651-1.787 3.353-7.753-.367-9.969 1.31-2.215 1.65-3.901 5.92-3.373 8.67.504 2.777 7.754 4.48 6.445 7.885C96.49 87.543 87.15 95.454 83.5 96.685c-3.65 1.231-4.96-4.741-7.577-5.16-2.593-.393-6.57.707-8.03 2.75-1.436 2.017 2.668 7.806-.63 9.483-3.323 1.676-15.759 2.226-19.157.655-3.373-1.598.554-7.964-1.108-10.138-1.687-2.174-6.394-3.431-9.012-2.907-2.643.55-3.273 7.282-6.747 6.103-3.499-1.126-12.788-9.535-14.172-13.019-1.36-3.484 5.437-5.108 5.966-7.858.529-2.777-.68-7.073-2.744-8.697-2.064-1.624-7.93 2.41-9.642-1.126-1.737-3.537-2.441-16.765-.654-20.118 1.787-3.3 9.062 1.598 11.429.183 2.366-1.44 2.316-7.282 2.769-8.749" fill="none" stroke-width="2" stroke-linecap="round" stroke="currentColor"/><path d="M81.235 56.502a23.3 23.3 0 0 1-1.46 8.068 20.785 20.785 0 0 1-1.762 3.72 24.068 24.068 0 0 1-5.337 6.26 22.575 22.575 0 0 1-3.449 2.358 23.726 23.726 0 0 1-7.803 2.803 24.719 24.719 0 0 1-8.333 0 24.102 24.102 0 0 1-4.028-1.074 23.71 23.71 0 0 1-3.776-1.729 23.259 23.259 0 0 1-6.369-5.265 23.775 23.775 0 0 1-2.416-3.353 24.935 24.935 0 0 1-1.762-3.72 23.765 23.765 0 0 1-1.083-3.981 23.454 23.454 0 0 1 0-8.173c.252-1.336.604-2.698 1.083-3.956a24.935 24.935 0 0 1 1.762-3.72 22.587 22.587 0 0 1 2.416-3.378c.881-1.048 1.888-2.017 2.946-2.908a24.38 24.38 0 0 1 3.423-2.357 23.71 23.71 0 0 1 3.776-1.73 21.74 21.74 0 0 1 4.028-1.047 23.437 23.437 0 0 1 8.333 0 24.282 24.282 0 0 1 7.803 2.777 26.198 26.198 0 0 1 3.45 2.357 24.62 24.62 0 0 1 5.336 6.287 20.785 20.785 0 0 1 1.762 3.72 21.32 21.32 0 0 1 1.083 3.955c.251 1.336.302 3.405.377 4.086.05.681.05-.68 0 0" fill="none" stroke-width="4" stroke-linecap="round" stroke="currentColor"/><path d="M69.404 56.633c-6.596-3.3-13.216-6.6-19.51-9.744m19.51 9.744c-6.747-3.379-13.493-6.758-19.51-9.744m0 0v19.489m0-19.49v19.49m0 0c4.355-2.148 8.71-4.322 19.51-9.745m-19.51 9.745c3.978-1.965 7.93-3.956 19.51-9.745m0 0h0m0 0h0" fill="currentColor" stroke-linecap="round" stroke="currentColor" stroke-width="4"/></g>`;
export const DISK_ICON_NAME = "disk";
export const DISK_ICON = `<path fill="none" stroke="currentColor" fill="#fff" d="M0 0h100v100H0z"/><path fill="none" stroke="currentColor" d="M20.832 4.168c21.824.145 43.645.289 74.68.5m-74.68-.5c17.09.113 34.176.227 74.68.5m0 0c.094 27.3.191 54.602.32 91.164m-.32-91.164c.113 32.633.23 65.27.32 91.164m0 0H4.168m91.664 0H4.168m0 0v-75m0 75v-75m0 0L20.832 4.168M4.168 20.832L20.832 4.168M20.832 4.168h58.336m-58.336 0h58.336m0 0v25m0-25v25m0 0H20.832m58.336 0H20.832m0 0v-25m0 25v-25" stroke-width="1.66668" /><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664v16.664H29.168"/><path fill="none" stroke="currentColor" d="M29.168 4.168h16.664m-16.664 0h16.664m0 0v16.664m0-16.664v16.664m0 0H29.168m16.664 0H29.168m0 0V4.168m0 16.664V4.168M12.5 54.168h75m-75 0h75m0 0v41.664m0-41.664v41.664m0 0h-75m75 0h-75m0 0V54.168m0 41.664V54.168M20.832 62.5c20.11-.18 40.219-.36 55.68-.5m-55.68.5c14.656-.133 29.313-.262 55.68-.5M20.832 71.332c13.098-.117 26.2-.234 55.68-.5m-55.68.5l55.68-.5M21.117 79.582c20.645-.184 41.285-.371 55.68-.5m-55.68.5c18.153-.16 36.301-.324 55.68-.5" stroke-width="1.66668"/>`;
export const DISK_ICON_NAME = "save";
export const PNG_ICON_NAME = "save-png";
export const PNG_ICON = `<defs><symbol overflow="visible" id="aa"><path fill="currentColor" stroke="currentColor" d="M6.578-10.984h8.188c2.03 0 3.64-.594 5.046-1.844 1.563-1.422 2.25-3.094 2.25-5.469 0-4.875-2.906-7.61-8.046-7.61H3.25V0h3.328zm0-2.907v-9.093h6.938c3.171 0 5.078 1.703 5.078 4.547 0 2.843-1.907 4.546-5.078 4.546zm0 0"></path></symbol><symbol overflow="visible" id="bb"><path fill="currentColor" stroke="currentColor" d="M23.094-25.906h-3.14V-4.72L6.327-25.906h-3.61V0H5.86v-21L19.344 0h3.75zm0 0"></path></symbol><symbol overflow="visible" id="cc"><path fill="currentColor" stroke="currentColor" d="M25.344-13.672h-10.86v2.906h7.938v.704c0 4.624-3.438 7.968-8.188 7.968-2.656 0-5.046-.969-6.578-2.625-1.718-1.86-2.765-4.953-2.765-8.14 0-6.36 3.656-10.563 9.156-10.563 3.969 0 6.828 2.031 7.547 5.375h3.39c-.922-5.265-4.922-8.281-10.906-8.281-3.172 0-5.75.812-7.781 2.484-3.047 2.485-4.719 6.5-4.719 11.157 0 7.968 4.89 13.5 11.938 13.5 3.53 0 6.328-1.313 8.906-4.11l.812 3.438h2.11zm0 0"></path></symbol></defs><path fill="none" stroke="currentColor" d="M-.003.003v59.999m0-60v60m0 0h220.006m-220.006 0h220.006m0 0v-60m0 60v-60" transform="matrix(.40833 0 0 .40574 4.083 68.975)" stroke-width="4"></path><use xlink:href="#aa" x="11.023" y="86.651"></use><use xlink:href="#bb" x="33.944" y="86.651"></use><use xlink:href="#cc" x="59.724" y="86.651"></use><path fill="currentColor" stroke="currentColor" d="M40.832 4.059h16.336v32.457h8.164L49 52.746l-16.332-16.23h8.164V4.059" fill-rule="evenodd"></path><path fill="currentColor" stroke="currentColor" d="M-.003.003h40.006m-40.006 0h40.006m0 0v79.995m0-79.995v79.995m0 0h19.994m-19.994 0h19.994m0 0C51.55 88.451 43.093 96.904 20 120m39.997-40.002A196001.962 196001.962 0 0120 120m0 0C8.406 108.41-3.18 96.817-19.997 79.998M20 120C9.43 109.43-1.142 98.858-19.997 79.998m0 0H-.003m-19.994 0H-.003m0 0V.003m0 79.995V.003m0 0h0m0 0h0" transform="matrix(.40833 0 0 .40574 40.833 4.057)" stroke-width="4"></path>`;
export const SVG_ICON_NAME = "save-svg";

View File

@@ -11,12 +11,53 @@ Thank you & Enjoy!
`;
export const RELEASE_NOTES: { [k: string]: string } = {
Intro: `I want to help you keep up with all the updates. After installing each release, you'll be prompted with a summary of new features and fixes. You can disable these popup messages in plugin settings.
Intro: `After each update you'll be prompted with the release notes. You can disable this in plugin settings.
I develop this plugin as a hobby, spending most of my free time doing this. If you'd like to contribute to the on-going work, I have a simple membership scheme with Bronze, Silver and Gold tiers. Many of you have already bought me a coffee. THANK YOU! It really means a lot to me! If you find this plugin valuable, please consider supporting me.
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
<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>
`,
"1.8.0": `
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/7gu4ETx7zro" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## New
- Optical Character Recognition (OCR). Introducing the MVP (minimum viable product) release of the integration of [Taskbone](https://taskbone.com) OCR into Excalidraw. See the new scan button on the Obsidian tools panel.
- New and improved full-screen mode
- Activate using the Obsidian tools panel, the Obsidian Command Palette, or the Alt+F11 shortcut
- The ESC key no longer closes full-screen
- Full-screen mode works properly on iOS as well
- Improved Icon visibility on the Obsidian tools panel
- Added 3 additional buttons to the tools panel
- Force save
- Open link (useful on Mobile devices). In the case of LaTeX equations, the button opens the equation properties.
- Open the link in a new pane. In the case of embedded markdown documents, the button opens the embed properties.
## Fixed
- The [deconstruct selected elements into a new drawing](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md) script now also correctly decomposes transcluded text elements.
`,
"1.7.30":`
Fix:
- Forcing the embedded image to always scale to 100% (a feature introduced in [1.7.26](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.7.26)) scaled the embedded excalidraw drawings incorrectly on devices with a pixel ratio of 2 or 3 (e.g. iPads). This is now fixed, however, this fix might retrospectively impact drawings that use this feature. Sorry for that.
`,
"1.7.29":`
- This is a big update that accommodates the **UI redesign** on Excalidraw.com [#5780](https://github.com/excalidraw/excalidraw/pull/5780). The change on the surface may seem superficial, however, I had to tweak a number of things to make it work in Obsidian. I hope I found everything that broke and fixed it, if not, I'll try to fix it quickly...
- This update also comes with changes under the hood that **fix issues with Excalidraw Automate** - paving the way for further scripts, plus some smaller bug fixes.
- I **reworked text wrapping**. In some cases, text wrapping in SVG exports looked different compared to how the text looked in Excalidraw. This should now be fixed.
- If you are using the **Experimental Dynamic Styling** of the Excalidraw Toolbar, then I recommend updating your styling script following base on [this](https://gist.github.com/zsviczian/c7223c5b4af30d5c88a0cae05300305c)
`,
"1.7.27":`## New
- Import SVG drawing as an Excalidraw object. [#679](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/679)
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/vlC1-iBvIfo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## Fixed
- Large drawings freeze on the iPad when opening the file. I implemented a workaround whereby Excalidraw will avoid zoom-to-fit drawings with over 1000 elements. [#863](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/863)
- Reintroduced copy/paste to the context menu
`,
"1.7.26":`## Fixed
- Transcluded block with a parent bullet does not embed sub-bullet [#853](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/853)
- Transcluded text will now exclude ^block-references at end of lines

View File

@@ -35,7 +35,7 @@ export class ReleaseNotes extends Modal {
const message = this.version
? Object.keys(RELEASE_NOTES)
.filter((key) => key === "Intro" || isVersionNewerThanOther(key,prevRelease))
.map((key: string) => `# ${key}\n${RELEASE_NOTES[key]}`)
.map((key: string) => `${key==="Intro" ? "" : `# ${key}\n`}${RELEASE_NOTES[key]}`)
.slice(0, 10)
.join("\n\n---\n")
: FIRST_RUN;

View File

@@ -224,8 +224,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
},
{
field: "addImage",
code: "addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;",
desc: null,
code: "addImage(topX: number, topY: number, imageFile: TFile, scale: boolean): Promise<string>;",
desc: "set scale to false if you want to embed the image at 100% of its original size. Default is true which will insert a scaled image",
after: "",
},
{

View File

@@ -59,6 +59,7 @@ export default {
"Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!})",
ENTER_LATEX: "Enter a valid LaTeX expression",
READ_RELEASE_NOTES: "Read latest release notes",
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
TRAY_MODE: "Toggle property-panel tray-mode",
SEARCH: "Search for text in drawing",
RESET_IMG_TO_100: "Set selected image element size to 100% of original",
@@ -418,6 +419,18 @@ export default {
"Select a .ttf, .woff or .woff2 font file from your vault to use as the fourth font. " +
"If no file is selected, Excalidraw will use the Virgil font by default.",
SCRIPT_SETTINGS_HEAD: "Settings for installed Scripts",
TASKBONE_HEAD: "Taskbone Optical Character Recogntion",
TASKBONE_DESC: "This is an experimental integration of optical character recognition into Excalidraw. Please note, that taskbone is an independent external service not provided by Excalidraw, nor the Excalidraw-Obsidian plugin project. " +
"The OCR service will grab legible text from freedraw lines and embedded pictures on your canvas and place the recognized text in the frontmatter of your drawing as well as onto clipboard. " +
"Having the text in the frontmatter will enable you to search in Obsidian for the text contents of these. " +
"Note, that the process of extracting the text from the image is not done locally, but via an online API. The taskbone service stores the image on its servers only as long as necessary for the text extraction. However, if this is a dealbreaker, then please don't use this feature.",
TASKBONE_ENABLE_NAME: "Enable Taskbone",
TASKBONE_ENABLE_DESC: "By enabling this service your agree to the Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>Terms and Conditaions</a> and the " +
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>Privacy Policy</a>.",
TASKBONE_APIKEY_NAME: "Taskbone API Key",
TASKBONE_APIKEY_DESC: "Taskbone offers a free service with a reasonable number of scans per month. If you want to use this feature more frequently, or you want to supoprt " +
"the developer of Taskbone (as you can imagine, there is no such thing as 'free', providing this awesome OCR service costs some money to the developer of Taskbone), you can " +
"purchase a paid API key from <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a>. In case you have purchased a key, simply overwrite this auto generated free-tier API-key with your paid key.",
//openDrawings.ts
SELECT_FILE: "Select a file then press enter.",
@@ -446,4 +459,6 @@ export default {
GOTO_FULLSCREEN: "Goto fullscreen mode",
EXIT_FULLSCREEN: "Exit fullscreen mode",
TOGGLE_FULLSCREEN: "Toggle fullscreen mode",
OPEN_LINK_CLICK: "Navigate to selected element link",
OPEN_LINK_PROPS: "Open markdown-embed properties or open link in new window"
};

View File

@@ -25,8 +25,6 @@ import {
ICON_NAME,
SCRIPTENGINE_ICON,
SCRIPTENGINE_ICON_NAME,
DISK_ICON,
DISK_ICON_NAME,
PNG_ICON,
PNG_ICON_NAME,
SVG_ICON,
@@ -105,6 +103,7 @@ import { Packages } from "./types";
import * as React from "react";
import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
import { check } from "prettier";
import Taskbone from "./ocr/Taskbone";
declare module "obsidian" {
@@ -133,6 +132,7 @@ declare const excalidrawLib: any;
declare const PLUGIN_VERSION:string;
export default class ExcalidrawPlugin extends Plugin {
public taskbone: Taskbone;
private excalidrawFiles: Set<TFile> = new Set<TFile>();
public excalidrawFileModes: { [file: string]: string } = {};
private _loaded: boolean = false;
@@ -222,7 +222,6 @@ export default class ExcalidrawPlugin extends Plugin {
addIcon(ICON_NAME, EXCALIDRAW_ICON);
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
addIcon(DISK_ICON_NAME, DISK_ICON);
addIcon(PNG_ICON_NAME, PNG_ICON);
addIcon(SVG_ICON_NAME, SVG_ICON);
@@ -255,8 +254,8 @@ export default class ExcalidrawPlugin extends Plugin {
// const patches = new OneOffs(this);
if (this.settings.showReleaseNotes) {
//I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian.
const obsidianJustInstalled = this.settings.imageElementNotice;
const obsidianJustInstalled = this.settings.previousRelease === "0.0.0"
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) {
new ReleaseNotes(
this.app,
@@ -274,6 +273,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.app.workspace.onLayoutReady(() => {
this.scriptEngine = new ScriptEngine(self);
});
this.taskbone = new Taskbone(this);
}
public initializeFourthFont() {
@@ -395,23 +395,12 @@ export default class ExcalidrawPlugin extends Plugin {
}
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
(async()=>{
if (view.semaphores.autosaving) {
return;
}
view.semaphores.forceSaving = true;
await view.save(false, true);
view.plugin.triggerEmbedUpdates();
view.loadSceneFiles();
view.semaphores.forceSaving = false;
new Notice("Save successful", 1000);
})();
view.forceSave();
return true;
}
return false;
}
private registerInstallCodeblockProcessor() {
const codeblockProcessor = async (source: string, el: HTMLElement) => {
//Button next to the "List of available scripts" at the top
@@ -966,6 +955,28 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "run-ocr",
name: t("RUN_OCR"),
checkCallback: (checking: boolean) => {
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (checking) {
return (
Boolean(view)
);
}
if (view) {
if(!this.settings.taskboneEnabled) {
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
return true;
}
this.taskbone.getTextForView(view, false);
return true;
}
return false;
},
});
this.addCommand({
id: "search-text",
name: t("SEARCH"),
@@ -2259,7 +2270,8 @@ export default class ExcalidrawPlugin extends Plugin {
}
public isExcalidrawFile(f: TFile) {
if (f.extension == "excalidraw") {
if(!f) return false;
if (f.extension === "excalidraw") {
return true;
}
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;

View File

@@ -26,9 +26,9 @@ export class ActionButton extends React.Component<ButtonProps, ButtonState> {
return (
<button
style={{
width: "fit-content",
padding: "2px",
margin: "4px",
//width: "fit-content",
//padding: "2px",
//margin: "4px",
}}
className="ToolIcon_type_button ToolIcon_size_small ToolIcon_type_button--show ToolIcon"
title={this.props.title}

File diff suppressed because one or more lines are too long

21
src/menu/MenuLinks.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { AppState } from "@zsviczian/excalidraw/types/types";
import clsx from "clsx";
import * as React from "react";
import ExcalidrawPlugin from "../main";
export class MenuLinks {
plugin: ExcalidrawPlugin;
ref: React.MutableRefObject<any>;
constructor(plugin: ExcalidrawPlugin, ref: React.MutableRefObject<any>) {
this.plugin = plugin;
this.ref = ref;
}
render = (isMobile: boolean, appState: AppState) => {
return (
<div>Hello</div>
);
}
}

View File

@@ -2,7 +2,7 @@ import clsx from "clsx";
import { Notice, TFile } from "obsidian";
import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, stringToSVG } from "./ActionIcons";
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
import { SCRIPT_INSTALL_FOLDER, CTRL_OR_CMD } from "../Constants";
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
@@ -10,6 +10,7 @@ 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";
declare const PLUGIN_VERSION:string;
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
@@ -28,6 +29,7 @@ export type PanelState = {
theme: "dark" | "light";
excalidrawViewMode: boolean;
minimized: boolean;
isDirty: boolean;
isFullscreen: boolean;
isPreviewMode: boolean;
scriptIconMap: ScriptIconMap;
@@ -59,6 +61,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
theme: "dark",
excalidrawViewMode: false,
minimized: false,
isDirty: false,
isFullscreen: false,
isPreviewMode: true,
scriptIconMap: {},
@@ -87,6 +90,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
});
}
setDirty(isDirty: boolean) {
this.setState(()=> {
return {
isDirty,
};
});
}
setExcalidrawViewMode(isViewModeEnabled: boolean) {
this.setState(() => {
return {
@@ -257,7 +268,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
className="Island App-menu__left scrollbar"
style={{
maxHeight: "350px",
backgroundColor: "transparent",
width: "initial",
//@ts-ignore
"--padding": 2,
display: this.state.minimized ? "none" : "block",
@@ -267,13 +278,13 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
<fieldset>
<legend>Utility actions</legend>
<div className="buttonList buttonListIcon">
<ActionButton
key={"search"}
title={t("SEARCH")}
<ActionButton
key={"scriptEngine"}
title={t("INSTALL_SCRIPT_BUTTON")}
action={() => {
search(this.props.view);
new ScriptInstallPrompt(this.props.view.plugin).open();
}}
icon={ICONS.search}
icon={ICONS.scriptEngine}
view={this.props.view}
/>
<ActionButton
@@ -348,6 +359,67 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
}
view={this.props.view}
/>
<ActionButton
key={"search"}
title={t("SEARCH")}
action={() => {
search(this.props.view);
}}
icon={ICONS.search}
view={this.props.view}
/>
<ActionButton
key={"ocr"}
title={t("RUN_OCR")}
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if(!this.props.view.plugin.settings.taskboneEnabled) {
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, e[CTRL_OR_CMD]);
}}
icon={ICONS.ocr}
view={this.props.view}
/>
<ActionButton
key={"openLink"}
title={t("OPEN_LINK_CLICK")}
action={() => {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: false,
shiftKey: false,
altKey: false,
});
this.props.view.handleLinkClick(this.props.view, event);
}}
icon={ICONS.openLink}
view={this.props.view}
/>
<ActionButton
key={"openLinkProperties"}
title={t("OPEN_LINK_PROPS")}
action={() => {
const event = new MouseEvent("click", {
ctrlKey: true,
metaKey: false,
shiftKey: true,
altKey: true,
});
this.props.view.handleLinkClick(this.props.view, event);
}}
icon={ICONS.openLinkProperties}
view={this.props.view}
/>
<ActionButton
key={"save"}
title={t("FORCE_SAVE")}
action={() => {
this.props.view.forceSave();
}}
icon={saveIcon(this.state.isDirty)}
view={this.props.view}
/>
</div>
</fieldset>
<fieldset>

147
src/ocr/Taskbone.ts Normal file
View File

@@ -0,0 +1,147 @@
import { createPNG, ExcalidrawAutomate } from "../ExcalidrawAutomate";
import {Notice, requestUrl} from "obsidian"
import ExcalidrawPlugin from "../main"
import {log} from "../utils/Utils"
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
import FrontmatterEditor from "src/utils/Frontmatter";
import { ExcalidrawElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/element/types";
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
export default class Taskbone {
get apiKey() {
return this.plugin.settings.taskboneAPIkey;
}
constructor(
private plugin: ExcalidrawPlugin
) {
}
public async initialize(save:boolean = true):Promise<string> {
if(this.plugin.settings.taskboneAPIkey !== "") return;
const response = await requestUrl({
url: `${TASKBONE_URL}users/excalidraw-obsidian/identities`,
method: "post",
contentType: "application/json",
throw: false
});
if(!response) return;
const apiKey = response.json?.apiKey;
if(apiKey && typeof apiKey === "string") {
if(save) await this.plugin.loadSettings();
this.plugin.settings.taskboneAPIkey = apiKey;
if(save) await this.plugin.saveSettings();
}
return apiKey;
}
public async getTextForView(view: ExcalidrawView, forceReScan: boolean) {
await view.forceSave(true);
const viewElements = view.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement) =>
el.type==="freedraw" ||
( el.type==="image" &&
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
));
if(viewElements.length === 0) {
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
return;
}
const fe = new FrontmatterEditor(view.data);
if(fe.hasKey("taskbone-ocr") && !forceReScan) {
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
return;
}
const bb = this.plugin.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 = new EmbeddedFilesLoader(
this.plugin,
false,
);
const exportSettings: ExportSettings = {
withBackground: true,
withTheme: true,
};
const img =
await createPNG(
view.file.path + "#^taskbone",
scale,
exportSettings,
loader,
"light",
null,
null,
[],
this.plugin,
0
);
const text = await this.getTextForImage(img);
if(text) {
fe.setKey("taskbone-ocr",text);
view.data = fe.data;
view.save(false);
window.navigator.clipboard.writeText(text);
new Notice("I placed the recognized in the drawing's frontmatter and onto the system clipboard.");
}
}
private async getTextForImage(image: Blob):Promise<string> {
const url = TASKBONE_URL+TASKBONE_OCR_FN;
if(this.apiKey === "") {
await this.initialize();
}
const base64Image = await this.blobToBase64(image);
const input = {
records: [{
image: base64Image
}]
};
const apiResponse = await requestUrl ({
url: url,
method: "post",
contentType: "application/json",
body: JSON.stringify(input),
headers: {
authorization: `Bearer ${this.apiKey}`
},
throw: false
});
const content = apiResponse?.json;
if(!content || apiResponse.status !== 200) {
new Notice("Something went wrong while processing your request. Please check developer console for more information");
log(apiResponse);
return;
}
return content.records[0].text;
}
private async blobToBase64(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++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
}

View File

@@ -110,6 +110,8 @@ export interface ExcalidrawSettings {
showReleaseNotes: boolean;
showNewVersionNotification: boolean;
mathjaxSourceURL: string;
taskboneEnabled: boolean;
taskboneAPIkey: string;
}
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
@@ -193,10 +195,12 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
mdCSS: "",
scriptEngineSettings: {},
defaultTrayMode: false,
previousRelease: "1.6.13",
previousRelease: "0.0.0",
showReleaseNotes: true,
showNewVersionNotification: true,
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js"
mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
taskboneEnabled: false,
taskboneAPIkey: "",
};
export class ExcalidrawSettingTab extends PluginSettingTab {
@@ -1334,6 +1338,44 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
);
});
this.containerEl.createEl("h2", { text: t("TASKBONE_HEAD") });
this.containerEl.createEl("p", { text: t("TASKBONE_DESC") });
let taskboneAPIKeyText: TextComponent;
new Setting(containerEl)
.setName(t("TASKBONE_ENABLE_NAME"))
.setDesc(fragWithHTML(t("TASKBONE_ENABLE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.taskboneEnabled)
.onChange(async (value) => {
taskboneAPIKeyText.setDisabled(!value);
this.plugin.settings.taskboneEnabled = value;
if(this.plugin.settings.taskboneAPIkey === "") {
const apiKey = await this.plugin.taskbone.initialize(false);
if(apiKey) {
taskboneAPIKeyText.setValue(apiKey);
}
}
this.applySettingsUpdate();
}),
);
new Setting(containerEl)
.setName(t("TASKBONE_APIKEY_NAME"))
.setDesc(fragWithHTML(t("TASKBONE_APIKEY_DESC")))
.addText((text) => {
taskboneAPIKeyText = text;
taskboneAPIKeyText
.setValue(this.plugin.settings.taskboneAPIkey)
.onChange(async (value) => {
this.plugin.settings.taskboneAPIkey = value;
this.applySettingsUpdate();
})
.setDisabled(!this.plugin.settings.taskboneEnabled);
}
);
//-------------------------------------
//Script settings
//-------------------------------------

42
src/utils/Frontmatter.ts Normal file
View File

@@ -0,0 +1,42 @@
// alternative https://github.com/OPD-libs/OPD-libs
export default class FrontmatterEditor {
private frontmatterStr:string;
private dataWOfrontmatter: string;
private initialized:boolean = false;
constructor (data:string) {
this.dataWOfrontmatter = data;
data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
const tmp = data.split(/^---(?:.|\n)*(?:^---\n)/gm);
if(tmp.length!==2) return;
this.dataWOfrontmatter = tmp[1];
this.frontmatterStr = data.match(/^---((?:.|\n)*)(?:^---\n)/gm)[0].replaceAll(/(^---\n|^\n)/gm,"").trim()+"\n";
this.initialized = true;
}
public hasKey(key:string):boolean {
if(!this.initialized) return false;
const reg = new RegExp(`^${key}:`,"gm");
return Boolean(this.frontmatterStr.match(reg));
}
public setKey(key:string, value:string) {
if(!this.initialized) return;
value = value.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replaceAll(":",";").trim().split("\n").join(" ");
if(this.hasKey(key)) {
const reg = new RegExp(`^${key}:.*\\n(?:\\s\\s.*\\n)*`,"gm");
this.frontmatterStr =
this.frontmatterStr.split(reg).join("\n").trim() +
`\n${key}: ${value}`;
return;
}
this.frontmatterStr = this.frontmatterStr.trim()+`\n${key}: ${value}`;
}
get data() {
if(!this.initialized) return this.dataWOfrontmatter;
return ["---",this.frontmatterStr,"---",this.dataWOfrontmatter].join("\n");
}
}

View File

@@ -22,6 +22,7 @@ import { ExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
import { ExportSettings } from "../ExcalidrawView";
import { compressToBase64, decompressFromBase64 } from "lz-string";
import { getIMGFilename } from "./FileUtils";
import ExcalidrawScene from "lib/svgToExcalidraw/elements/ExcalidrawScene";
declare const PLUGIN_VERSION:string;
@@ -85,7 +86,7 @@ const random = new Random(Date.now());
export const randomInteger = () => Math.floor(random.next() * 2 ** 31);
//https://macromates.com/blog/2006/wrapping-text-with-regular-expressions/
export function wrapText(
export function wrapTextAtCharLength(
text: string,
lineLen: number,
forceWrap: boolean = false,
@@ -361,7 +362,10 @@ export const getImageSize = async (
): Promise<{ height: number; width: number }> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve({ height: img.naturalHeight, width: img.naturalWidth });
img.onload = () => {
//console.log({ height: img.naturalHeight, width: img.naturalWidth, img});
resolve({ height: img.naturalHeight, width: img.naturalWidth });
};
img.onerror = reject;
img.src = src;
});
@@ -596,6 +600,7 @@ export const getEmbeddedFilenameParts = (fname:string):{
filepath: string,
hasBlockref: boolean,
hasGroupref: boolean,
hasTaskbone: boolean,
hasArearef: boolean,
blockref: string,
hasSectionref: boolean,
@@ -603,13 +608,14 @@ export const getEmbeddedFilenameParts = (fname:string):{
linkpartReference: string,
linkpartAlias: string
} => {
// 0 1 23 4 5 6 7 8 9
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=)?([^\|]*)|(#)(group=|area=)?([^\^\|]*))(.*)/);
// 0 1 23 4 5 6 7 8 9
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=|taskbone)?([^\|]*)|(#)(group=|area=|taskbone)?([^\^\|]*))(.*)/);
if(!parts) {
return {
filepath: fname,
hasBlockref: false,
hasGroupref: false,
hasTaskbone: false,
hasArearef: false,
blockref: "",
hasSectionref: false,
@@ -622,6 +628,7 @@ export const getEmbeddedFilenameParts = (fname:string):{
filepath: parts[1],
hasBlockref: Boolean(parts[3]),
hasGroupref: (parts[4]==="group=") || (parts[7]==="group="),
hasTaskbone: (parts[4]==="taskbone") || (parts[7]==="taskbone"),
hasArearef: (parts[4]==="area=") || (parts[7]==="area="),
blockref: parts[5],
hasSectionref: Boolean(parts[6]),
@@ -649,3 +656,19 @@ export const awaitNextAnimationFrame = async () => new Promise(requestAnimationF
export const log = console.log.bind(window.console);
export const debug = console.log.bind(window.console);
//export const debug = function(){};
export const getContainerElement = (
element:
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
| null,
scene: ExcalidrawScene,
) => {
if (!element) {
return null;
}
if (element.containerId) {
return scene.elements.filter(el=>el.id === element.containerId)[0] ?? null;
}
return null;
};

View File

@@ -96,8 +96,7 @@ li[data-testid] {
.ex-coffee-div {
text-align: center;
margin-bottom: 20px;
margin-bottom: 10px;
}
.excalidraw-scriptengine-install td>img {
@@ -184,9 +183,8 @@ li[data-testid] {
}
.excalidraw-release .modal {
max-height: 90%;
width: auto;
max-width: 130ch;
max-height: 80%;
max-width: 100ch;
}
.excalidraw .Island .scrollbar {
@@ -225,6 +223,69 @@ textarea.excalidraw-wysiwyg {
border-radius: 0;
}
.is-tablet .excalidraw button {
.is-tablet .excalidraw button,
.is-mobile .excalidraw button {
padding: initial;
height: 1.8rem;
}
.excalidraw button,
.ToolIcon button {
box-shadow: none;
justify-content: initial;
}
.excalidraw {
--default-button-size: 2rem !important;
--default-icon-size: 1rem !important;
--lg-button-size: 1.8rem !important;
--lg-icon-size: 1rem !important;
}
.excalidraw .tray-zoom {
pointer-events: initial;
padding-bottom: 0.05rem;
padding-top: 0.05rem;
}
.excalidraw-container.theme--dark {
background-color: #121212;
color: #fff;
}
/* https://discordapp.com/channels/686053708261228577/989603365606531104/1041266507256184863 */
/*.workspace-leaf {
contain: none !important;
}*/
.color-picker-content {
overflow-y: auto;
max-height: 10rem;
}
.excalidraw .FixedSideContainer_side_top {
top: 0.3rem;
}
.excalidraw .ToolIcon__keybinding {
font-size: 0.45rem !important;
}
.Island > .Stack > .Stack {
padding:0.2rem;
}
label.color-input-container > input {
max-width: 8rem;
}
.excalidraw .FixedSideContainer_side_top {
left: 10px !important;
top: 10px !important;
right: 10px !important;
bottom: 10px !important;
}
.excalidraw-hidden {
display: none !important;
}

View File

@@ -3,7 +3,7 @@
"baseUrl": ".",
"sourceMap": true,
"module": "es2015",
"target": "es2017",
"target": "es2017", //script engine requires for async execution
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
@@ -12,7 +12,7 @@
"lib": [
"dom",
"scripthost",
"es2017",
"es2015",
"esnext",
"DOM.Iterable"
],

View File

@@ -11,7 +11,7 @@
"lib": [
"dom",
"scripthost",
"es2017",
"es2015",
"esnext",
"DOM.Iterable"
],

View File

@@ -2221,10 +2221,10 @@
dependencies:
"@zerollup/ts-helpers" "^1.7.18"
"@zsviczian/excalidraw@0.13.0-obsidian":
"integrity" "sha512-c4SnBEGKtenLB/1gSjXe3BVA+yZfo8b1p2E7sVcaPG8MTz6cpQsCB2+cv7Zta5ihIxuGfK3ZSepVhMbN7RFY2w=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.13.0-obsidian.tgz"
"version" "0.13.0-obsidian"
"@zsviczian/excalidraw@0.13.0-obsidian-1":
"integrity" "sha512-gHfuEX/qrBa+4kolxEkQ/3W5hGfSLoJSXDpuhb8Mvvyyl148hsuWmhUQGFWcNee73YbuQ0arb3hXqwnMUgK0Ig=="
"resolved" "https://registry.npmjs.org/@zsviczian/excalidraw/-/excalidraw-0.13.0-obsidian-1.tgz"
"version" "0.13.0-obsidian-1"
"abab@^2.0.3", "abab@^2.0.5":
"integrity" "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q=="