mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
24 Commits
2.7.5-beta
...
imagepathh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5475bfde6 | ||
|
|
5171978c37 | ||
|
|
ea4a0c91e8 | ||
|
|
34af6dd447 | ||
|
|
ed2e700946 | ||
|
|
7eb23ab5e1 | ||
|
|
7cccf1d4e2 | ||
|
|
2a5545964c | ||
|
|
4ce22883cc | ||
|
|
272804afc8 | ||
|
|
dc0b50f717 | ||
|
|
a0eb625b8a | ||
|
|
524dc54d03 | ||
|
|
918718be90 | ||
|
|
78ee784be1 | ||
|
|
7e0e016bf9 | ||
|
|
4f875a03a0 | ||
|
|
63c56e0e98 | ||
|
|
46477208be | ||
|
|
3194c014c7 | ||
|
|
25ccb9dc43 | ||
|
|
fa46f8c39d | ||
|
|
8ffe5c3942 | ||
|
|
d759abbc47 |
9
.github/ISSUE_TEMPLATE/How-to.yml
vendored
9
.github/ISSUE_TEMPLATE/How-to.yml
vendored
@@ -12,6 +12,8 @@ body:
|
||||
Before submitting a support request, please:
|
||||
1. **Review the [documentation](https://github.com/zsviczian/obsidian-excalidraw-plugin/wiki)** – your question may already be answered.
|
||||
2. **[Search issues](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) (including closed ones)** to see if your question has already been addressed.
|
||||
3. **[Watch the Feature Walkthrough Video](https://youtu.be/P_Q6avJGoWI)**: As it infact answers 90% of the typical questions I receive
|
||||
4. **[Consult NotebookLM with your question](https://excalidraw-obsidian.online/WIKI/09+Video+Transcripts/Videos/Turn+any+YouTube+Channel+into+your+AI+Mentor+-+Obsidian+is+the+ultimate+automation+workbench+for+PKM)**
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -31,6 +33,13 @@ body:
|
||||
options:
|
||||
- label: Yes, I have reviewed the documentation and searched for related issues.
|
||||
|
||||
- type: textarea
|
||||
id: notebook_lm
|
||||
attributes:
|
||||
label: "Your NotebookLM query"
|
||||
description: "See point 4) above. Paste the question and answer you received from NotebookLM. This serves partly as proof, partly to help me see where the model is incorrect"
|
||||
placeholder: "Copy/Paste your question and the resulting answer you got from NotebookLM"
|
||||
|
||||
- type: textarea
|
||||
id: support_question
|
||||
attributes:
|
||||
|
||||
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
11
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,5 +1,5 @@
|
||||
name: Bug report
|
||||
description: If something is clearly broken, it’s a bug. Everything else is a feature or support request. Most reported “bugs” are actually how-to questions or feature requests.
|
||||
description: If something is clearly broken, it’s a bug. **Everything else** is a feature or support request. Most reported “bugs” are actually how-to questions or feature requests.
|
||||
title: "BUG: "
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -12,6 +12,8 @@ body:
|
||||
Before creating a bug report, please:
|
||||
1. **Review recent [release notes](https://github.com/zsviczian/obsidian-excalidraw-plugin/releases)** – maybe there is already an answer.
|
||||
2. **[Search issues](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) (including closed ones)** to see if there is anything similar.
|
||||
3. **[Watch the Feature Walkthrough Video](https://youtu.be/P_Q6avJGoWI)**: As it infact answers 90% of the typical questions I receive
|
||||
4. **[Consult NotebookLM with your question](https://excalidraw-obsidian.online/WIKI/09+Video+Transcripts/Videos/Turn+any+YouTube+Channel+into+your+AI+Mentor+-+Obsidian+is+the+ultimate+automation+workbench+for+PKM)**
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -46,6 +48,13 @@ body:
|
||||
description: "Run `Command Palette/Show Debug info` in Obsidian and paste the result here."
|
||||
placeholder: "Paste your Obsidian debug info here..."
|
||||
|
||||
- type: textarea
|
||||
id: notebook_lm
|
||||
attributes:
|
||||
label: "Your NotebookLM query"
|
||||
description: "See point 4) above. Paste the question and answer you received from NotebookLM. This serves partly as proof, partly to help me see where the model is incorrect"
|
||||
placeholder: "Copy/Paste your question and the resulting answer you got from NotebookLM"
|
||||
|
||||
- type: textarea
|
||||
id: bug_description
|
||||
attributes:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,7 +4,6 @@
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# build
|
||||
main.js
|
||||
|
||||
1340
MathjaxToSVG/package-lock.json
generated
Normal file
1340
MathjaxToSVG/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,11 +13,12 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"obsidian": "1.5.7-1",
|
||||
"rollup": "^2.70.1",
|
||||
"typescript": "^5.2.2",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ export default {
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: '../tsconfig.json',
|
||||
tsconfig: 'tsconfig.json',
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve({
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
@@ -32,4 +32,4 @@ export default {
|
||||
}
|
||||
})
|
||||
].filter(Boolean)
|
||||
};
|
||||
};
|
||||
|
||||
26
MathjaxToSVG/tsconfig.json
Normal file
26
MathjaxToSVG/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"sourceMap": false,
|
||||
"module": "es2020",
|
||||
"target": "es2022", //min es2017 because script engine requires for async execution and min es2018 for named capture groups
|
||||
"allowJs": false,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"importHelpers": true,
|
||||
"resolveJsonModule": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"scripthost",
|
||||
"es2022",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "react",
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx", "src/shared/Dialogs/OpenDrawing.ts",
|
||||
"src/types/types.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
The project runs with `node 18`.
|
||||
|
||||
After running `npm -i` you'll need to make two manual changes:
|
||||
|
||||
1026
docs/API/ExcalidrawAutomate.d.ts
vendored
1026
docs/API/ExcalidrawAutomate.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -8,20 +8,76 @@ if(lines.length !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
||||
const rotate = (point, element) => {
|
||||
const [x1, y1] = point;
|
||||
const x2 = element.x + element.width/2;
|
||||
const y2 = element.y - element.height/2;
|
||||
const angle = element.angle;
|
||||
return [
|
||||
(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
|
||||
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
|
||||
];
|
||||
//Same line but with angle=0
|
||||
function getNormalizedLine(originalElement) {
|
||||
if(originalElement.angle === 0) return originalElement;
|
||||
|
||||
// Get absolute coordinates for all points first
|
||||
const pointRotateRads = (point, center, angle) => {
|
||||
const [x, y] = point;
|
||||
const [cx, cy] = center;
|
||||
return [
|
||||
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
|
||||
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy
|
||||
];
|
||||
};
|
||||
|
||||
// Get element absolute coordinates (matching Excalidraw's approach)
|
||||
const getElementAbsoluteCoords = (element) => {
|
||||
const points = element.points;
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const [x, y] of points) {
|
||||
const absX = x + element.x;
|
||||
const absY = y + element.y;
|
||||
minX = Math.min(minX, absX);
|
||||
minY = Math.min(minY, absY);
|
||||
maxX = Math.max(maxX, absX);
|
||||
maxY = Math.max(maxY, absY);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
};
|
||||
|
||||
// Calculate center point based on absolute coordinates
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement);
|
||||
const centerX = (x1 + x2) / 2;
|
||||
const centerY = (y1 + y2) / 2;
|
||||
|
||||
// Calculate absolute coordinates of all points
|
||||
const absolutePoints = originalElement.points.map(([x, y]) => [
|
||||
x + originalElement.x,
|
||||
y + originalElement.y
|
||||
]);
|
||||
|
||||
// Rotate all points around the center
|
||||
const rotatedPoints = absolutePoints.map(point =>
|
||||
pointRotateRads(point, [centerX, centerY], originalElement.angle)
|
||||
);
|
||||
|
||||
// Convert back to relative coordinates
|
||||
const newPoints = rotatedPoints.map(([x, y]) => [
|
||||
x - rotatedPoints[0][0],
|
||||
y - rotatedPoints[0][1]
|
||||
]);
|
||||
|
||||
const newLineId = ea.addLine(newPoints);
|
||||
|
||||
// Set the position of the new line to the first rotated point
|
||||
const newLine = ea.getElement(newLineId);
|
||||
newLine.x = rotatedPoints[0][0];
|
||||
newLine.y = rotatedPoints[0][1];
|
||||
newLine.angle = 0;
|
||||
delete ea.elementsDict[newLine.id];
|
||||
return newLine;
|
||||
}
|
||||
|
||||
const points = lines.map(
|
||||
el=>el.points.map(p=>rotate([p[0]+el.x, p[1]+el.y],el))
|
||||
|
||||
const points = lines.map(getNormalizedLine).map(
|
||||
el=>el.points.map(p=>[p[0]+el.x, p[1]+el.y])
|
||||
);
|
||||
|
||||
const last = (p) => p[p.length-1];
|
||||
@@ -99,4 +155,4 @@ switch (lineTypes) {
|
||||
}
|
||||
|
||||
|
||||
ea.addElementsToView();
|
||||
await ea.addElementsToView();
|
||||
@@ -7,16 +7,41 @@ This script enables the selection of elements based on matching properties. Sele
|
||||
```js */
|
||||
|
||||
let config = window.ExcalidrawSelectConfig;
|
||||
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
const isValidConfig = config && (Date.now() - config.timestamp < 60000);
|
||||
config = isValidConfig ? config : null;
|
||||
|
||||
let elements = ea.getViewSelectedElements();
|
||||
if(!config && (elements.length !==1)) {
|
||||
new Notice("Select a single element");
|
||||
return;
|
||||
} else {
|
||||
if(elements.length === 0) {
|
||||
elements = ea.getViewElements();
|
||||
if(!config) {
|
||||
|
||||
async function shouldAbort() {
|
||||
if(elements.length === 1) return false;
|
||||
if(elements.length !== 2) return true;
|
||||
|
||||
//maybe container?
|
||||
const textEl = elements.find(el=>el.type==="text");
|
||||
if(!textEl || !textEl.containerId) return true;
|
||||
|
||||
const containerEl = elements.find(el=>el.id === textEl.containerId);
|
||||
if(!containerEl) return true;
|
||||
|
||||
const id = await utils.suggester(
|
||||
elements.map(el=>el.type),
|
||||
elements.map(el=>el.id),
|
||||
"Select container component"
|
||||
);
|
||||
if(!id) return true;
|
||||
elements = elements.filter(el=>el.id === id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(await shouldAbort()) {
|
||||
new Notice("Select a single element");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(Boolean(config) && elements.length === 0) {
|
||||
elements = ea.getViewElements();
|
||||
}
|
||||
|
||||
const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, opacity, roughness, roundness, strokeColor, strokeStyle, strokeWidth, type, startArrowhead, endArrowhead, fileId} = ea.getViewSelectedElement();
|
||||
|
||||
@@ -18,6 +18,7 @@ if (!ellipse) return;
|
||||
|
||||
let lines = elements.filter(el => el.type == "line" || el.type == "arrow");
|
||||
if (lines.length == 0) lines = ea.getViewElements().filter(el => el.type == "line" || el.type == "arrow");
|
||||
lines = lines.map(getNormalizedLine);
|
||||
const subLines = getSubLines(lines);
|
||||
|
||||
const angles = subLines.flatMap(line => {
|
||||
@@ -206,3 +207,70 @@ function isBetween(num, min, max) {
|
||||
function clamp(number, min, max) {
|
||||
return Math.max(min, Math.min(number, max));
|
||||
}
|
||||
|
||||
//Same line but with angle=0
|
||||
function getNormalizedLine(originalElement) {
|
||||
if(originalElement.angle === 0) return originalElement;
|
||||
|
||||
// Get absolute coordinates for all points first
|
||||
const pointRotateRads = (point, center, angle) => {
|
||||
const [x, y] = point;
|
||||
const [cx, cy] = center;
|
||||
return [
|
||||
(x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx,
|
||||
(x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy
|
||||
];
|
||||
};
|
||||
|
||||
// Get element absolute coordinates (matching Excalidraw's approach)
|
||||
const getElementAbsoluteCoords = (element) => {
|
||||
const points = element.points;
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const [x, y] of points) {
|
||||
const absX = x + element.x;
|
||||
const absY = y + element.y;
|
||||
minX = Math.min(minX, absX);
|
||||
minY = Math.min(minY, absY);
|
||||
maxX = Math.max(maxX, absX);
|
||||
maxY = Math.max(maxY, absY);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
};
|
||||
|
||||
// Calculate center point based on absolute coordinates
|
||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement);
|
||||
const centerX = (x1 + x2) / 2;
|
||||
const centerY = (y1 + y2) / 2;
|
||||
|
||||
// Calculate absolute coordinates of all points
|
||||
const absolutePoints = originalElement.points.map(([x, y]) => [
|
||||
x + originalElement.x,
|
||||
y + originalElement.y
|
||||
]);
|
||||
|
||||
// Rotate all points around the center
|
||||
const rotatedPoints = absolutePoints.map(point =>
|
||||
pointRotateRads(point, [centerX, centerY], originalElement.angle)
|
||||
);
|
||||
|
||||
// Convert back to relative coordinates
|
||||
const newPoints = rotatedPoints.map(([x, y]) => [
|
||||
x - rotatedPoints[0][0],
|
||||
y - rotatedPoints[0][1]
|
||||
]);
|
||||
|
||||
const newLineId = ea.addLine(newPoints);
|
||||
|
||||
// Set the position of the new line to the first rotated point
|
||||
const newLine = ea.getElement(newLineId);
|
||||
newLine.x = rotatedPoints[0][0];
|
||||
newLine.y = rotatedPoints[0][1];
|
||||
newLine.angle = 0;
|
||||
delete ea.elementsDict[newLine.id];
|
||||
return newLine;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.4",
|
||||
"version": "2.7.6-beta-1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.4",
|
||||
"version": "2.7.5",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
9290
package-lock.json
generated
Normal file
9290
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -24,7 +24,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-24",
|
||||
"@zsviczian/excalidraw": "0.17.6-26",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
@@ -58,7 +58,7 @@
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/js-beautify": "^1.14.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
@@ -79,10 +79,10 @@
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"@zsviczian/rollup-plugin-postprocess": "^1.0.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.34.1",
|
||||
"tslib": "^2.6.1",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"tslib": "^2.8.1",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript": "^5.7.3",
|
||||
"fs-extra": "^11.2.0",
|
||||
"uglify-js": "^3.19.3"
|
||||
},
|
||||
|
||||
@@ -105,6 +105,41 @@
|
||||
*/
|
||||
//ea.onFileCreateHook = (data) => {};
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
* You can use this callback to customize the naming and path of pasted images to avoid
|
||||
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
|
||||
* and instead use more meaningful names based on the Excalidraw file or other criteria,
|
||||
* plus save the image in a different folder.
|
||||
*
|
||||
* If the function returns null or undefined, the normal Excalidraw operation will continue
|
||||
* with the excalidraw generated name and default path.
|
||||
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
|
||||
* with the file extension.
|
||||
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
|
||||
*
|
||||
* @param data - An object containing the following properties:
|
||||
* @property {string} [currentImageName] - Default name for the image.
|
||||
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
|
||||
*
|
||||
* @returns {string} - The new filepath for the image including full vault path and extension.
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* onImageFilePathHook: (data) => {
|
||||
* const { currentImageName, drawingFilePath } = data;
|
||||
* const ext = currentImageName.split('.').pop();
|
||||
* // Generate a new filepath based on the drawing file name and other criteria
|
||||
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
|
||||
* }
|
||||
* ```
|
||||
* onImageFilePathHook: (data: {
|
||||
* currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
|
||||
* drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
|
||||
* }) => string = null;
|
||||
*/
|
||||
//ea.onImageFileNameHook = (data) => {};
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
* onCanvasColorChangeHook: (
|
||||
|
||||
@@ -198,7 +198,7 @@ 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", "jtif", "tif"];
|
||||
export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", "ico", "jtif", "tif", "jfif", "avif"];
|
||||
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;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -17,6 +17,32 @@ 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://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
|
||||
`,
|
||||
"2.7.5":`
|
||||
## Fixed
|
||||
- PDF export scenario described in [#2184](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2184)
|
||||
- Elbow arrows do not work within frames [#2187](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2187)
|
||||
- Embedding images into Excalidraw with areaRef links did not work as expected due to conflicting SVG viewbox and width and height values
|
||||
- Can't exit full-screen mode in popout windows using the Command Palette toggle action [#2188](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2188)
|
||||
- If the image mask extended beyond the image in "Mask and Crop" image mode, the mask got misaligned from the image.
|
||||
- PDF image embedding fixes that impacted some PDF files (not all):
|
||||
- When cropping the PDF page in the scene (by double-clicking the image to crop), the size and position of the PDF cutout drifted.
|
||||
- Using PDF++ there was a small offset in the position of the cutout in PDF++ and the image in Excalidraw.
|
||||
- Updated a number of scripts including Split Ellipse, Select Similar Elements, and Concatenate Lines
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
${String.fromCharCode(96,96,96)}
|
||||
/**
|
||||
* Add, modify, or delete keys in element.customData and preserve existing keys.
|
||||
* Creates customData={} if it does not exist.
|
||||
* Takes the element id for an element in ea.elementsDict and the newData to add or modify.
|
||||
* To delete keys set key value in newData to undefined. So {keyToBeDeleted:undefined} will be deleted.
|
||||
* @param id
|
||||
* @param newData
|
||||
* @returns undefined if element does not exist in elementsDict, returns the modified element otherwise.
|
||||
*/
|
||||
public addAppendUpdateCustomData(id:string, newData: Partial<Record<string, unknown>>);
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.7.4":`
|
||||
## Fixed
|
||||
- Regression from 2.7.3 where image fileId got overwritten in some cases
|
||||
|
||||
@@ -19,7 +19,7 @@ import { ExportSettings } from "../view/ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, hasExcalidrawEmbeddedImagesTreeChanged, readLocalFileBinary } from "../utils/fileUtils";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, readLocalFileBinary } from "../utils/fileUtils";
|
||||
import {
|
||||
errorlog,
|
||||
getDataURL,
|
||||
@@ -91,6 +91,7 @@ export type PDFPageViewProps = {
|
||||
bottom: number;
|
||||
right: number;
|
||||
top: number;
|
||||
rotate?: number; //may be undefined in legacy files
|
||||
}
|
||||
|
||||
export type Size = {
|
||||
@@ -866,19 +867,58 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
const [left, bottom, right, top] = page.view;
|
||||
viewProps = {left, bottom, right, top};
|
||||
viewProps.rotate = page.rotate;
|
||||
|
||||
if(validRect) {
|
||||
const pageHeight = top - bottom;
|
||||
width = (cropRect[2] - cropRect[0]) * scale;
|
||||
height = (cropRect[3] - cropRect[1]) * scale;
|
||||
|
||||
const crop = validRect ? {
|
||||
left: (cropRect[0] - left) * scale,
|
||||
top: (bottom + pageHeight - cropRect[3]) * scale,
|
||||
width,
|
||||
height,
|
||||
} : undefined;
|
||||
if(crop) {
|
||||
const pageHeight = top - bottom;
|
||||
const pageWidth = right - left;
|
||||
|
||||
if(!page.rotate || page.rotate === 0) {
|
||||
width = (cropRect[2] - cropRect[0]) * scale;
|
||||
height = (cropRect[3] - cropRect[1]) * scale;
|
||||
|
||||
const crop = {
|
||||
left: (cropRect[0] - left) * scale,
|
||||
top: (bottom + pageHeight - cropRect[3]) * scale,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return cropCanvas(canvas, crop);
|
||||
}
|
||||
if(page.rotate === 90) {
|
||||
width = (cropRect[3] - cropRect[1]) * scale;
|
||||
height = (cropRect[2] - cropRect[0]) * scale;
|
||||
const crop = {
|
||||
left: cropRect[1] * scale,
|
||||
top: (pageHeight - cropRect[2]) * scale,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return cropCanvas(canvas, crop);
|
||||
}
|
||||
|
||||
if(page.rotate === 180) {
|
||||
width = (cropRect[2] - cropRect[0]) * scale;
|
||||
height = (cropRect[3] - cropRect[1]) * scale;
|
||||
const crop = {
|
||||
left: (pageWidth - cropRect[2]) * scale,
|
||||
top: cropRect[1] * scale,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return cropCanvas(canvas, crop);
|
||||
}
|
||||
|
||||
if(page.rotate === 270) {
|
||||
width = (cropRect[3] - cropRect[1]) * scale;
|
||||
height = (cropRect[2] - cropRect[0]) * scale;
|
||||
const crop = {
|
||||
left: (pageWidth - cropRect[3]) * scale,
|
||||
top: cropRect[0] * scale,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return cropCanvas(canvas, crop);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ import {
|
||||
loadSceneFonts,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { TextMode } from "../view/ExcalidrawView";
|
||||
import ExcalidrawView, { TextMode } from "../view/ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
@@ -52,7 +52,7 @@ import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "..
|
||||
import { DEBUGGING, debug } from "../utils/debugHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { updateElementIdsInScene } from "../utils/excalidrawSceneUtils";
|
||||
import { getNewUniqueFilepath } from "../utils/fileUtils";
|
||||
import { getNewUniqueFilepath, splitFolderAndFilename } from "../utils/fileUtils";
|
||||
import { t } from "../lang/helpers";
|
||||
import { displayFontMessage } from "../utils/excalidrawViewUtils";
|
||||
import { getPDFRect } from "../utils/PDFUtils";
|
||||
@@ -480,7 +480,7 @@ export class ExcalidrawData {
|
||||
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private plugin: ExcalidrawPlugin, private view?: ExcalidrawView,
|
||||
) {
|
||||
this.app = this.plugin.app;
|
||||
this.files = new Map<FileId, EmbeddedFile>();
|
||||
@@ -1546,13 +1546,23 @@ export class ExcalidrawData {
|
||||
}
|
||||
}
|
||||
|
||||
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
|
||||
const filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
|
||||
let hookFilepath:string;
|
||||
const ea = this.view?.getHookServer();
|
||||
if(ea?.onImageFilePathHook) {
|
||||
hookFilepath = ea.onImageFilePathHook({
|
||||
currentImageName: fname,
|
||||
drawingFilePath: this.view?.file?.path,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
const filepath = (
|
||||
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
|
||||
).filepath;*/
|
||||
let filepath:string;
|
||||
if(hookFilepath) {
|
||||
const {folderpath, filename} = splitFolderAndFilename(hookFilepath);
|
||||
filepath = getNewUniqueFilepath(this.app.vault,filename,folderpath);
|
||||
} else {
|
||||
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
|
||||
filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
|
||||
}
|
||||
|
||||
const arrayBuffer = await getBinaryFileFromDataURL(dataURL);
|
||||
if(!arrayBuffer) return null;
|
||||
|
||||
@@ -15,17 +15,76 @@ export function getPDFCropRect (props: {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rotate = props.pdfPageViewProps.rotate ?? 0;
|
||||
const { left, bottom } = props.pdfPageViewProps;
|
||||
const R0 = parseInt(rectVal[1]);
|
||||
const R1 = parseInt(rectVal[2]);
|
||||
const R2 = parseInt(rectVal[3]);
|
||||
const R3 = parseInt(rectVal[4]);
|
||||
|
||||
if(rotate === 90) {
|
||||
const _top = R0;
|
||||
const _left = R1;
|
||||
const _bottom = R2;
|
||||
const _right = R3;
|
||||
|
||||
const x = _left * props.scale;
|
||||
const y = _top * props.scale;
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: _right*props.scale - x,
|
||||
height: _bottom*props.scale - y,
|
||||
naturalWidth: props.naturalWidth,
|
||||
naturalHeight: props.naturalHeight,
|
||||
}
|
||||
}
|
||||
if(rotate === 180) {
|
||||
const _right = R0;
|
||||
const _top = R1;
|
||||
const _left = R2;
|
||||
const _bottom = R3;
|
||||
|
||||
const y = _top * props.scale;
|
||||
const x = props.naturalWidth - _left * props.scale;
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: props.naturalWidth - x - _right * props.scale,
|
||||
height: _bottom * props.scale - y,
|
||||
naturalWidth: props.naturalWidth,
|
||||
naturalHeight: props.naturalHeight,
|
||||
}
|
||||
}
|
||||
if(rotate === 270) {
|
||||
const _bottom = R0;
|
||||
const _right = R1;
|
||||
const _top = R2;
|
||||
const _left = R3;
|
||||
|
||||
const x = props.naturalWidth - _left * props.scale;
|
||||
const y = props.naturalHeight - _top * props.scale;
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width: props.naturalWidth - x - _right * props.scale,
|
||||
height: props.naturalHeight - y - _bottom * props.scale,
|
||||
naturalWidth: props.naturalWidth,
|
||||
naturalHeight: props.naturalHeight,
|
||||
}
|
||||
}
|
||||
// default to 0° rotation
|
||||
const _left = R0;
|
||||
const _bottom = R1;
|
||||
const _right = R2;
|
||||
const _top = R3;
|
||||
|
||||
return {
|
||||
x: (R0 - left) * props.scale,
|
||||
y: (bottom + props.naturalHeight/props.scale - R3) * props.scale,
|
||||
width: (R2 - R0) * props.scale,
|
||||
height: (R3 - R1) * props.scale,
|
||||
x: (_left - left) * props.scale,
|
||||
y: props.naturalHeight - (_top - bottom) * props.scale,
|
||||
width: (_right - _left) * props.scale,
|
||||
height: (_top - _bottom) * props.scale,
|
||||
naturalWidth: props.naturalWidth,
|
||||
naturalHeight: props.naturalHeight,
|
||||
}
|
||||
@@ -34,13 +93,36 @@ export function getPDFCropRect (props: {
|
||||
export function getPDFRect({elCrop, scale, customData}:{
|
||||
elCrop: ImageCrop, scale: number, customData: Record<string, unknown>
|
||||
}): string {
|
||||
const rotate = (customData.pdfPageViewProps as PDFPageViewProps)?.rotate ?? 0;
|
||||
const { left, bottom } = (customData && customData.pdfPageViewProps)
|
||||
? customData.pdfPageViewProps as PDFPageViewProps
|
||||
: { left: 0, bottom: 0 };
|
||||
|
||||
const R0 = elCrop.x / scale + left;
|
||||
const R2 = elCrop.width / scale + R0;
|
||||
const R3 = bottom + (elCrop.naturalHeight - elCrop.y) / scale;
|
||||
const R1 = R3 - elCrop.height / scale;
|
||||
return `&rect=${Math.round(R0)},${Math.round(R1)},${Math.round(R2)},${Math.round(R3)}`;
|
||||
}
|
||||
if(rotate === 90) {
|
||||
const _top = (elCrop.y) / scale;
|
||||
const _left = (elCrop.x) / scale;
|
||||
const _bottom = (elCrop.height + elCrop.y) / scale;
|
||||
const _right = (elCrop.width + elCrop.x) / scale;
|
||||
return `&rect=${Math.round(_top)},${Math.round(_left)},${Math.round(_bottom)},${Math.round(_right)}`;
|
||||
}
|
||||
if(rotate === 180) {
|
||||
const _right = (elCrop.naturalWidth-elCrop.x-elCrop.width) / scale;
|
||||
const _top = (elCrop.y) / scale;
|
||||
const _left = (elCrop.naturalWidth - elCrop.x) / scale;
|
||||
const _bottom = (elCrop.height + elCrop.y) / scale;
|
||||
return `&rect=${Math.round(_right)},${Math.round(_top)},${Math.round(_left)},${Math.round(_bottom)}`;
|
||||
|
||||
}
|
||||
if(rotate === 270) {
|
||||
const _bottom = (elCrop.naturalHeight - elCrop.height-elCrop.y) / scale;
|
||||
const _right = (elCrop.naturalWidth - elCrop.width - elCrop.x) / scale;
|
||||
const _top = (elCrop.naturalHeight - elCrop.y) / scale;
|
||||
const _left = (elCrop.naturalWidth - elCrop.x) / scale;
|
||||
return `&rect=${Math.round(_bottom)},${Math.round(_right)},${Math.round(_top)},${Math.round(_left)}`;
|
||||
}
|
||||
const _left = elCrop.x / scale + left;
|
||||
const _right = elCrop.width / scale + _left;
|
||||
const _top = bottom + (elCrop.naturalHeight - elCrop.y) / scale;
|
||||
const _bottom = _top - elCrop.height / scale;
|
||||
return `&rect=${Math.round(_left)},${Math.round(_bottom)},${Math.round(_right)},${Math.round(_top)}`;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { DEVICE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { getParentOfClass } from "./obsidianUtils";
|
||||
import { TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { App, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { getLinkParts } from "./utils";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
|
||||
@@ -55,4 +55,15 @@ export const generateEmbeddableLink = (src: string, theme: "light" | "dark"):str
|
||||
}
|
||||
}*/
|
||||
return src;
|
||||
}
|
||||
|
||||
export function setFileToLocalGraph(app: App, file: TFile) {
|
||||
let lgv;
|
||||
app.workspace.iterateAllLeaves((l) => {
|
||||
if (l.view?.getViewType() === "localgraph") lgv = l.view;
|
||||
});
|
||||
if (lgv) {
|
||||
//@ts-ignore
|
||||
lgv.loadFile(file);
|
||||
}
|
||||
}
|
||||
@@ -226,20 +226,25 @@ export const addFiles = async (
|
||||
.forEach((f:FileData) => {
|
||||
s.scene.elements
|
||||
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && (
|
||||
(el.crop && el.crop.naturalWidth !== f.size.width) || !el.customData?.pdfPageViewProps
|
||||
(el.crop && el.crop?.naturalWidth !== f.size.width) || !el.customData?.pdfPageViewProps
|
||||
))
|
||||
.forEach((el:Mutable<ExcalidrawImageElement>) => {
|
||||
s.dirty = true;
|
||||
const scale = f.size.width / el.crop.naturalWidth;
|
||||
el.crop = {
|
||||
x: el.crop.x * scale,
|
||||
y: el.crop.y * scale,
|
||||
width: el.crop.width * scale,
|
||||
height: el.crop.height * scale,
|
||||
naturalWidth: f.size.width,
|
||||
naturalHeight: f.size.height,
|
||||
};
|
||||
addAppendUpdateCustomData(el, { pdfPageViewProps: f.pdfPageViewProps});
|
||||
if(el.crop) {
|
||||
s.dirty = true;
|
||||
const scale = f.size.width / el.crop.naturalWidth;
|
||||
el.crop = {
|
||||
x: el.crop.x * scale,
|
||||
y: el.crop.y * scale,
|
||||
width: el.crop.width * scale,
|
||||
height: el.crop.height * scale,
|
||||
naturalWidth: f.size.width,
|
||||
naturalHeight: f.size.height,
|
||||
};
|
||||
}
|
||||
if(!el.customData?.pdfPageViewProps) {
|
||||
s.dirty = true;
|
||||
addAppendUpdateCustomData(el, { pdfPageViewProps: f.pdfPageViewProps});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -363,7 +368,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
|
||||
super(leaf);
|
||||
this._plugin = plugin;
|
||||
this.excalidrawData = new ExcalidrawData(plugin);
|
||||
this.excalidrawData = new ExcalidrawData(plugin, this);
|
||||
this.canvasNodeFactory = new CanvasNodeFactory(this);
|
||||
this.setHookServer();
|
||||
this.dropManager = new DropManager(this);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ConstructableWorkspaceSplit, getContainerForDocument, isObsidianThemeDa
|
||||
import { DEVICE, EXTENDED_EVENT_TYPES, KEYBOARD_EVENT_TYPES } from "src/constants/constants";
|
||||
import { ExcalidrawImperativeAPI, UIAppState } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { ObsidianCanvasNode } from "src/view/managers/CanvasNodeFactory";
|
||||
import { processLinkText, patchMobileView } from "src/utils/customEmbeddableUtils";
|
||||
import { processLinkText, patchMobileView, setFileToLocalGraph } from "src/utils/customEmbeddableUtils";
|
||||
import { EmbeddableMDCustomProps } from "src/shared/Dialogs/EmbeddableSettings";
|
||||
|
||||
declare module "obsidian" {
|
||||
@@ -154,6 +154,15 @@ function RenderObsidianView(
|
||||
}; //cleanup on unmount
|
||||
}, [isActiveRef.current, containerRef.current]);
|
||||
|
||||
//set local graph to view when deactivating embeddables
|
||||
React.useEffect(() => {
|
||||
if(file === view.file) {
|
||||
return;
|
||||
}
|
||||
if(!isActiveRef.current) {
|
||||
setFileToLocalGraph(view.app, view.file);
|
||||
}
|
||||
}, [isActiveRef.current]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Mount the workspace leaf or the canvas node depending on subpath
|
||||
@@ -408,6 +417,10 @@ function RenderObsidianView(
|
||||
return;
|
||||
}
|
||||
|
||||
if(file !== view.file) {
|
||||
setFileToLocalGraph(view.app, file);
|
||||
}
|
||||
|
||||
if(leafRef.current.leaf?.view?.getViewType() === "markdown") {
|
||||
//Handle markdown leaf
|
||||
//@ts-ignore
|
||||
|
||||
@@ -361,9 +361,16 @@ div.excalidraw-draginfo {
|
||||
}
|
||||
|
||||
.excalidraw [data-radix-popper-content-wrapper] {
|
||||
/*Overrides position:fixed in popover*/
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
.excalidraw .Island .App-mobile-menu,
|
||||
.excalidraw .Island.App-menu__left {
|
||||
/*Arrow Picker Popover Overflow*/
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.excalidraw__embeddable-container .view-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
BIN
test-data/PDFs/page-rotated-180.pdf
Normal file
BIN
test-data/PDFs/page-rotated-180.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-rotated-270.pdf
Normal file
BIN
test-data/PDFs/page-rotated-270.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-rotated-90.pdf
Normal file
BIN
test-data/PDFs/page-rotated-90.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-trimmed-rotated-180.pdf
Normal file
BIN
test-data/PDFs/page-trimmed-rotated-180.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-trimmed-rotated-270.pdf
Normal file
BIN
test-data/PDFs/page-trimmed-rotated-270.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-trimmed-rotated-90.pdf
Normal file
BIN
test-data/PDFs/page-trimmed-rotated-90.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page-trimmed.pdf
Normal file
BIN
test-data/PDFs/page-trimmed.pdf
Normal file
Binary file not shown.
BIN
test-data/PDFs/page.pdf
Normal file
BIN
test-data/PDFs/page.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user