mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
108 Commits
2.7.0-bugs
...
2.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b15ddef7fe | ||
|
|
ef785e5fb0 | ||
|
|
15ba4146ac | ||
|
|
9956fd1756 | ||
|
|
b6f2161f1c | ||
|
|
22b8b1f707 | ||
|
|
98f6871caa | ||
|
|
aae588249a | ||
|
|
ef890d51e3 | ||
|
|
b0bc03437a | ||
|
|
23b94da8f0 | ||
|
|
1f227ddd24 | ||
|
|
12152665af | ||
|
|
064e17b29d | ||
|
|
0aaba80c82 | ||
|
|
1744668fbd | ||
|
|
8e3e2ffb25 | ||
|
|
f5475bfde6 | ||
|
|
27fa270b42 | ||
|
|
15ece75b5d | ||
|
|
a796621f93 | ||
|
|
3c943c6685 | ||
|
|
4209774b4e | ||
|
|
b18637f7d0 | ||
|
|
af8a848d14 | ||
|
|
01e392158d | ||
|
|
fc47b7aa0d | ||
|
|
a0e0627a49 | ||
|
|
efcb0c0580 | ||
|
|
23d7105fb1 | ||
|
|
5d9565bd7c | ||
|
|
59785523ae | ||
|
|
2a21ed5fc7 | ||
|
|
3d3ce73fa1 | ||
|
|
c35bd385fe | ||
|
|
a790b04547 | ||
|
|
5171978c37 | ||
|
|
ea4a0c91e8 | ||
|
|
34af6dd447 | ||
|
|
ed2e700946 | ||
|
|
7eb23ab5e1 | ||
|
|
7cccf1d4e2 | ||
|
|
2a5545964c | ||
|
|
4ce22883cc | ||
|
|
272804afc8 | ||
|
|
dc0b50f717 | ||
|
|
a0eb625b8a | ||
|
|
524dc54d03 | ||
|
|
918718be90 | ||
|
|
78ee784be1 | ||
|
|
7e0e016bf9 | ||
|
|
4f875a03a0 | ||
|
|
63c56e0e98 | ||
|
|
46477208be | ||
|
|
3194c014c7 | ||
|
|
25ccb9dc43 | ||
|
|
fa46f8c39d | ||
|
|
8ffe5c3942 | ||
|
|
88f256cd8f | ||
|
|
1562600cd3 | ||
|
|
d759abbc47 | ||
|
|
90533138e5 | ||
|
|
80d8f0e5b6 | ||
|
|
9829fab97c | ||
|
|
a33c8b6eab | ||
|
|
f0921856c1 | ||
|
|
31e06ac0e0 | ||
|
|
033d764b1c | ||
|
|
00f98dd14e | ||
|
|
0f601d969a | ||
|
|
8fa0fb37b2 | ||
|
|
5a58d17d99 | ||
|
|
982958a4c6 | ||
|
|
d425884bb8 | ||
|
|
d3b61a0df1 | ||
|
|
4bab0162ba | ||
|
|
d3f4437478 | ||
|
|
a64586c3e6 | ||
|
|
7a92e78851 | ||
|
|
af0122b21a | ||
|
|
1f95f57e97 | ||
|
|
f384e95e44 | ||
|
|
a40521f07b | ||
|
|
9649b36175 | ||
|
|
6cb1394793 | ||
|
|
e5b2977c0c | ||
|
|
22d3f25dc4 | ||
|
|
d9534fcc4f | ||
|
|
fd1604c3a4 | ||
|
|
8f0f8d64df | ||
|
|
5a413ab910 | ||
|
|
d3133f055c | ||
|
|
fe05518e31 | ||
|
|
8adcb7d850 | ||
|
|
be383f2b48 | ||
|
|
682307b51d | ||
|
|
60328613ea | ||
|
|
4a2e054ac6 | ||
|
|
eebc428f1b | ||
|
|
ab8ba66eb5 | ||
|
|
97b3050270 | ||
|
|
6733f76fbf | ||
|
|
1dcc45585d | ||
|
|
0c5ceaa3f7 | ||
|
|
2e602d49a2 | ||
|
|
84bcdf8bee | ||
|
|
6d60bcf6eb | ||
|
|
b832a51a5b |
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:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,6 @@
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# build
|
||||
main.js
|
||||
@@ -14,6 +13,7 @@ hot-reload.bat
|
||||
data.json
|
||||
lib
|
||||
dist
|
||||
tmp
|
||||
|
||||
#VSCode
|
||||
.vscode
|
||||
|
||||
@@ -15,9 +15,15 @@ let html: any;
|
||||
let preamble: string;
|
||||
|
||||
function svgToBase64(svg: string): string {
|
||||
return `data:image/svg+xml;base64,${btoa(
|
||||
decodeURIComponent(encodeURIComponent(svg.replaceAll(" ", " "))),
|
||||
)}`;
|
||||
const cleanSvg = svg.replaceAll(" ", " ");
|
||||
|
||||
// Convert the string to UTF-8 and handle non-Latin1 characters
|
||||
const encodedData = encodeURIComponent(cleanSvg)
|
||||
.replace(/%([0-9A-F]{2})/g,
|
||||
(match, p1) => String.fromCharCode(parseInt(p1, 16))
|
||||
);
|
||||
|
||||
return `data:image/svg+xml;base64,${btoa(encodedData)}`;
|
||||
}
|
||||
|
||||
async function getImageSize(src: string): Promise<{ height: number; width: number }> {
|
||||
@@ -32,7 +38,7 @@ async function getImageSize(src: string): Promise<{ height: number; width: numbe
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app?: any
|
||||
plugin?: any
|
||||
): Promise<{
|
||||
mimeType: string;
|
||||
fileId: FileId;
|
||||
@@ -44,9 +50,9 @@ export async function tex2dataURL(
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
|
||||
if(!adaptor) {
|
||||
if (app) {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
preamble = file ? await app.vault.read(file) : null;
|
||||
if (plugin) {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(plugin.settings.latexPreambleLocation || "preamble.sty");
|
||||
preamble = file ? await plugin.app.vault.read(file) : null;
|
||||
}
|
||||
adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
@@ -63,10 +69,18 @@ export async function tex2dataURL(
|
||||
|
||||
try {
|
||||
const node = html.convert(
|
||||
preamble ? `${preamble}${tex}` : tex,
|
||||
preamble ? `${preamble}\n${tex}` : tex,
|
||||
{ display: true, scale }
|
||||
);
|
||||
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195
|
||||
//https://stackoverflow.com/a/77181931
|
||||
let styleNode = document.createElement('style');
|
||||
styleNode.setAttribute("type", "text/css");
|
||||
styleNode.appendChild(document.createTextNode(".mjx-solid { stroke-width: 80px; }"));
|
||||
svg.appendChild(styleNode);
|
||||
|
||||
if (svg) {
|
||||
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
|
||||
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
|
||||
|
||||
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:
|
||||
|
||||
1032
docs/API/ExcalidrawAutomate.d.ts
vendored
1032
docs/API/ExcalidrawAutomate.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
import { PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { PaneTarget } from "src/utils/modifierkeyHelper";
|
||||
export declare class ExcalidrawAutomate {
|
||||
/**
|
||||
* Utility function that returns the Obsidian Module object.
|
||||
|
||||
5782
docs/Release-notes.md
Normal file
5782
docs/Release-notes.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,4 +22,4 @@ elements.forEach((el)=>{
|
||||
);
|
||||
ea.addToGroup([el.id,ellipseId]);
|
||||
});
|
||||
ea.addElementsToView(false,false);
|
||||
await ea.addElementsToView(false,false,true);
|
||||
|
||||
@@ -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();
|
||||
@@ -9,7 +9,7 @@ Select some elements in the scene. The script will take these elements and move
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.3")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -79,15 +79,19 @@ 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) {
|
||||
const hyperlink = img?.hyperlink;
|
||||
if(img && (path || hyperlink)) {
|
||||
const colorMap = ea.getColorMapForImageElement(el);
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hyperlink,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
colorMap,
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
157
ea-scripts/Full-Year Calendar Generator.md
Normal file
157
ea-scripts/Full-Year Calendar Generator.md
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
|
||||
This script generates a complete calendar for a specified year, visually distinguishing weekends from weekdays through color coding.
|
||||
|
||||

|
||||
|
||||
## Customizable Colors
|
||||
|
||||
You can personalize the calendar’s appearance by defining your own colors:
|
||||
|
||||
1. Create two rectangles in your design.
|
||||
2. Select both rectangles before running the script:
|
||||
• The **fill and stroke colors of the first rectangle** will be applied to weekdays.
|
||||
• The **fill and stroke colors of the second rectangle** will be used for weekends.
|
||||
|
||||
If no rectangle are selected, the default color schema will be used (white and purple).
|
||||
|
||||

|
||||
|
||||
```javascript
|
||||
*/
|
||||
|
||||
ea.reset();
|
||||
|
||||
// -------------------------------------
|
||||
// Constants initiation
|
||||
// -------------------------------------
|
||||
|
||||
const RECT_WIDTH = 300; // day width
|
||||
const RECT_HEIGHT = 45; // day height
|
||||
const START_X = 0; // X start position
|
||||
const START_Y = 0; // PY start position
|
||||
const MONTH_SPACING = 30; // space between months
|
||||
const DAY_SPACING = 0; // space between days
|
||||
const DAY_NAME_SPACING = 45; // space between day number and day letters
|
||||
const DAY_NAME_AND_NUMBER_X_MARGIN = 5;
|
||||
const MONTH_NAME_SPACING = -40;
|
||||
const YEAR_X = (RECT_WIDTH + MONTH_SPACING) * 6 - 150;
|
||||
const YEAR_Y = -200;
|
||||
|
||||
let COLOR_WEEKEND = "#c3abf3";
|
||||
let COLOR_WEEKDAY = "#ffffff";
|
||||
const COLOR_DAY_STROKE = "none";
|
||||
let STROKE_DAY = 4;
|
||||
let FILLSTYLE_DAY = "solid";
|
||||
|
||||
const FONT_SIZE_MONTH = 60;
|
||||
const FONT_SIZE_DAY = 30;
|
||||
const FONT_SIZE_YEAR = 100;
|
||||
|
||||
const LINE_STROKE_SIZE = 4;
|
||||
let LINE_STROKE_COLOR_WEEKDAY = "black";
|
||||
let LINE_STROKE_COLOR_WEEKEND = "black";
|
||||
|
||||
const SATURDAY = 6;
|
||||
const SUNDAY = 0;
|
||||
const JANUARY = 0;
|
||||
const FIRST_DAY_OF_THE_MONTH = 1;
|
||||
|
||||
const DAY_NAME_AND_NUMBER_Y_MARGIN = (RECT_HEIGHT - FONT_SIZE_DAY) / 2;
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
// ask for requested Year
|
||||
// Default value is the current year
|
||||
let requestedYear = parseFloat(new Date().getFullYear());
|
||||
requestedYear = parseFloat(await utils.inputPrompt("Year ?", requestedYear, requestedYear));
|
||||
if(isNaN(requestedYear)) {
|
||||
new Notice("Invalid number");
|
||||
return;
|
||||
}
|
||||
|
||||
// -------------------------------------
|
||||
// Use selected element for the calendar style
|
||||
// -------------------------------------
|
||||
|
||||
let elements = ea.getViewSelectedElements();
|
||||
if (elements.length>=1){
|
||||
COLOR_WEEKDAY = elements[0].backgroundColor;
|
||||
FILLSTYLE_DAY = elements[0].fillStyle;
|
||||
STROKE_DAY = elements[0].strokeWidth;
|
||||
LINE_STROKE_COLOR_WEEKDAY = elements[0].strokeColor;
|
||||
|
||||
}
|
||||
if (elements.length>=2){
|
||||
COLOR_WEEKEND = elements[1].backgroundColor;
|
||||
LINE_STROKE_COLOR_WEEKEND = elements[1].strokeColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// get the first day of the current year (01/01)
|
||||
var firstDayOfYear = new Date(requestedYear, JANUARY, FIRST_DAY_OF_THE_MONTH);
|
||||
|
||||
var currentDay = firstDayOfYear
|
||||
|
||||
// write year number
|
||||
let calendarYear = firstDayOfYear.getFullYear();
|
||||
ea.style.fontSize = FONT_SIZE_YEAR;
|
||||
ea.addText(START_X + YEAR_X, START_Y + YEAR_Y, String(calendarYear));
|
||||
|
||||
|
||||
// while we do not reach the end of the year iterate on all the day of the current year
|
||||
do {
|
||||
|
||||
var curentDayOfTheMonth = currentDay.getDate();
|
||||
var currentMonth = currentDay.getMonth();
|
||||
var isWeekend = currentDay.getDay() == SATURDAY || currentDay.getDay() == SUNDAY;
|
||||
|
||||
// set background color if it's a weekend or weekday
|
||||
ea.style.backgroundColor = isWeekend ? COLOR_WEEKEND : COLOR_WEEKDAY ;
|
||||
|
||||
|
||||
ea.style.strokeColor = COLOR_DAY_STROKE;
|
||||
ea.style.fillStyle = FILLSTYLE_DAY;
|
||||
ea.style.strokeWidth = STROKE_DAY;
|
||||
|
||||
|
||||
let x = START_X + currentMonth * (RECT_WIDTH + MONTH_SPACING);
|
||||
let y = START_Y + curentDayOfTheMonth * (RECT_HEIGHT + DAY_SPACING);
|
||||
|
||||
// only one time per month
|
||||
if(curentDayOfTheMonth == FIRST_DAY_OF_THE_MONTH) {
|
||||
|
||||
// add month name
|
||||
ea.style.fontSize = FONT_SIZE_MONTH;
|
||||
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN, START_Y+MONTH_NAME_SPACING, currentDay.toLocaleString('default', { month: 'long' }));
|
||||
}
|
||||
|
||||
// Add day rectangle
|
||||
ea.style.fontSize = FONT_SIZE_DAY;
|
||||
ea.addRect(x, y, RECT_WIDTH, RECT_HEIGHT);
|
||||
|
||||
// set stroke color based on weekday
|
||||
ea.style.strokeColor = isWeekend ? LINE_STROKE_COLOR_WEEKEND : LINE_STROKE_COLOR_WEEKDAY;
|
||||
|
||||
// add line between days
|
||||
//ea.style.strokeColor = LINE_STROKE_COLOR_WEEKDAY;
|
||||
ea.style.strokeWidth = LINE_STROKE_SIZE;
|
||||
ea.addLine([[x,y],[x+RECT_WIDTH, y]]);
|
||||
|
||||
|
||||
// add day number
|
||||
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN, y + DAY_NAME_AND_NUMBER_Y_MARGIN, String(curentDayOfTheMonth));
|
||||
// add day name
|
||||
ea.addText(x + DAY_NAME_AND_NUMBER_X_MARGIN + DAY_NAME_SPACING, y + DAY_NAME_AND_NUMBER_Y_MARGIN, String(currentDay.toLocaleString('default', { weekday: 'narrow' })));
|
||||
|
||||
// go to the next day
|
||||
currentDay.setDate(currentDay.getDate() + 1);
|
||||
|
||||
} while (!(currentDay.getMonth() == JANUARY && currentDay.getDate() == FIRST_DAY_OF_THE_MONTH)) // stop if we reach the 01/01 of the next year
|
||||
|
||||
|
||||
await ea.addElementsToView(false, false, true);
|
||||
10
ea-scripts/Full-Year Calendar Generator.svg
Normal file
10
ea-scripts/Full-Year Calendar Generator.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50.097575068473816 56.100231877975375" width="50.097575068473816" height="56.100231877975375" class="excalidraw-svg">
|
||||
<!-- svg-source:excalidraw -->
|
||||
|
||||
<defs>
|
||||
<style class="style-fonts">
|
||||
@font-face { font-family: Nunito; src: url(data:font/woff2;base64,d09GMgABAAAAAAN4AA8AAAAABswAAAMeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgQwcLgZgP1NUQVREAEQRCAqCMIIHCwoAATYCJAMQBCAFhCQHIBuUBcguChxjano4VFxM2vQ1QySkcTXuPsXzT/vRzn0z64qIV7yJJ/EmGSp5Q0EalEiqlgiVyv/L7uv1Qpy/pAfsqfk9Cx44ly4UHRVC2VW+YH51qZj/wAD1eZu/7e/zfzNtkm0LNEs8kV4W4FwgUb7BEox+1s0hcpt7B9o7UD9zWyypoLCgyLAoF3TIGFjCt/9zgmlAiWgimHRUDsvrVQ0d4PV6RpPA690oUcCLFQz/C/KW1hSwQxBdGRfjymFWuCiNpQ7DZwDDscLwyUUTpYlomhJRStJMOi7BBt/PBqzAjvfKyhfW1JZFD4AbvUueQVDCsDDk3yTfBN7BFQ3t838A/UISdgP6BED+1nvsZim+dJYbtD/zgeUIAj7ZZBGCWGbFzydiAggCGUFfAAoNyywFy6ycBsbpAlxRrmEgQGPIthJUmvOuGydAT4H3YPkMSmTbaOxSp3F7M1DceuB47Z4/v7F+08G4H9CV65T/cP2u2BPnHPNo1Njby9l9lqCzm7uyMRu86fNiz8HY2fFxdzcm+/lrt24Fx+vVjuOWhqzPHlHBaqZuP3PXoc7G406+6sZfeO7ufn05DaoJRR5e5HzVrTsP79AzLra5Dm2pRJjYVsGw41e6W67j9sQLnUPzqc0Fimnx1xZHPf0V1+aX3CiUZM329mTN07WNyR3+eb++hXAzh+fUMLjgc9WH8Z3yyaPxrSryLvf0qvAGgKB9DTasXLeHXQv+jTfLj/DT99Yu4E/arcz/tgqsErobMKpAeLRyF0C2LRAeQ88XaXVqd92A/IrQCaF7wtobVgIAumDKFlhKtwe+w49BxmkfyDLrB9lcN1numByxaYAYdVJSCoFpewlG+CpsjU9+k4kD7sNkoxSNaBN4OlktYpSEN64bjcfiEE10Ch6BVZpGaEY1oGqiArsLTWOeiiko6ZJkSZEm3HxNmjWpzFMZbutn6SSjtL1mKvApcDlMNUNDv0uTIlUGSgcOjVL8TAsNJqCNIyildAQH05hRYnAIQmWWJ1kyFl8W6cYkGYfJCxXDbbqExsAUhFky5ZIfyxKLDU8LAgAA); }
|
||||
</style>
|
||||
|
||||
</defs>
|
||||
<g stroke-linecap="round"><g transform="translate(17.077535418245503 32.086027259866626) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.865850031647774 39.07185193521212) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.946763254178506 46.743160072731996) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(16.680839244467208 53.831041680294504) rotate(0 -6.0775204356467825 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.41441860670051 32.80666516147113) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.20273322010279 39.79248983681666) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g transform="translate(0 0) rotate(0 25.048787534236908 17.010119812063635)"><text x="0" y="25.30097820935094" font-family="Nunito, Segoe UI Emoji" font-size="25.200177499353526px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">CAL</text></g><g stroke-linecap="round"><g transform="translate(26.079917947816256 27.03803518092093) rotate(0 0 14.531098348527223)"><path d="M0 0 C0 4.84, 0 24.22, 0 29.06 M0 0 C0 4.84, 0 24.22, 0 29.06" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(47.199941306131535 47.15190785557627) rotate(0 -6.077520435646779 0)"><path d="M0 0 C-2.03 0, -10.13 0, -12.16 0 M0 0 C-2.03 0, -10.13 0, -12.16 0" stroke="#1e1e1e" stroke-width="4" fill="none"></path></g></g><mask></mask></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -7,29 +7,58 @@ 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();
|
||||
|
||||
const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerHTML = html));
|
||||
|
||||
function lc(x) {
|
||||
return x?.toLocaleLowerCase();
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// RUN
|
||||
//--------------------------
|
||||
const run = () => {
|
||||
selectedElements = elements.filter(el=>
|
||||
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
|
||||
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
|
||||
((typeof config.backgroundColor === "undefined") || (lc(el.backgroundColor) === lc(config.backgroundColor))) &&
|
||||
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
|
||||
((typeof config.fontFamily === "undefined") || (el.fontFamily === config.fontFamily)) &&
|
||||
((typeof config.fontSize === "undefined") || (el.fontSize === config.fontSize)) &&
|
||||
@@ -38,7 +67,7 @@ const run = () => {
|
||||
((typeof config.opacity === "undefined") || (el.opacity === config.opacity)) &&
|
||||
((typeof config.roughness === "undefined") || (el.roughness === config.roughness)) &&
|
||||
((typeof config.roundness === "undefined") || (el.roundness === config.roundness)) &&
|
||||
((typeof config.strokeColor === "undefined") || (el.strokeColor === config.strokeColor)) &&
|
||||
((typeof config.strokeColor === "undefined") || (lc(el.strokeColor) === lc(config.strokeColor))) &&
|
||||
((typeof config.strokeStyle === "undefined") || (el.strokeStyle === config.strokeStyle)) &&
|
||||
((typeof config.strokeWidth === "undefined") || (el.strokeWidth === config.strokeWidth)) &&
|
||||
((typeof config.type === "undefined") || (el.type === config.type)) &&
|
||||
|
||||
@@ -17,4 +17,5 @@ if(isNaN(width)) {
|
||||
const elements=ea.getViewSelectedElements();
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.getElements().forEach((el)=>el.strokeWidth=width);
|
||||
ea.addElementsToView(false,false);
|
||||
await ea.addElementsToView(false,false);
|
||||
ea.viewUpdateScene({appState: {currentItemStrokeWidth: width}});
|
||||
|
||||
729
ea-scripts/Shade Master.md
Normal file
729
ea-scripts/Shade Master.md
Normal file
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
This is an experimental script. If you find bugs, please consider debugging yourself then submitting a PR on github with the fix, instead of raising an issue. Thank you!
|
||||
|
||||
This script modifies the color lightness/hue/saturation/transparency of selected Excalidraw elements and SVG and nested Excalidraw drawings. Select eligible elements in the scene, then run the script.
|
||||
|
||||
- The color of Excalidraw elements (lines, ellipses, rectangles, etc.) will be changed by the script.
|
||||
- The color of SVG elements and nested Excalidraw drawings will only be mapped. When mapping colors, the original image remains unchanged, only a mapping table is created and the image is recolored during rendering of your Excalidraw screen. In case you want to make manual changes you can also edit the mapping in Markdown View Mode under `## Embedded Files`
|
||||
|
||||
If you select only a single SVG or nested Excalidraw element, then the script offers an additional feature. You can map colors one by one in the image.
|
||||
```js*/
|
||||
|
||||
const HELP_TEXT = `
|
||||
<ul>
|
||||
<li dir="auto">Select SVG images, nested Excalidraw drawings and/or regular Excalidraw elements</li>
|
||||
<li dir="auto">For a single selected image, you can map colors individually in the color mapping section</li>
|
||||
<li dir="auto">For Excalidraw elements: stroke and background colors are modified permanently</li>
|
||||
<li dir="auto">For SVG/nested drawings: original files stay unchanged, color mapping is stored under <code>## Embedded Files</code></li>
|
||||
<li dir="auto">Using color maps helps maintain links between drawings while allowing different color themes</li>
|
||||
<li dir="auto">Sliders work on relative scale - the amount of change is applied to current values</li>
|
||||
<li dir="auto">Unlike Excalidraw's opacity setting which affects the whole element:
|
||||
<ul>
|
||||
<li dir="auto">Shade Master can set different opacity for stroke vs background</li>
|
||||
<li dir="auto">Note: SVG/nested drawing colors are mapped at color name level, thus "black" is different from "#000000"</li>
|
||||
<li dir="auto">Additionally if the same color is used as fill and stroke the color can only be mapped once</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li dir="auto">This is an experimental script - contributions welcome on GitHub via PRs</li>
|
||||
</ul>
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/ISuORbVKyhQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.2")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
SVGColorInfo is returned by ea.getSVGColorInfoForImgElement. Color info will all the color strings in the SVG file plus "fill" which represents the default fill color for SVG icons set at the SVG root element level. Fill if not set defaults to black:
|
||||
|
||||
type SVGColorInfo = Map<string, {
|
||||
mappedTo: string;
|
||||
fill: boolean;
|
||||
stroke: boolean;
|
||||
}>;
|
||||
|
||||
In the Excalidraw file under `## Embedded Files` the color map is included after the file. That color map implements ColorMap. ea.updateViewSVGImageColorMap takes a ColorMap as input.
|
||||
interface ColorMap {
|
||||
[color: string]: string;
|
||||
};
|
||||
*/
|
||||
|
||||
// Main script execution
|
||||
const allElements = ea.getViewSelectedElements();
|
||||
const svgImageElements = allElements.filter(el => {
|
||||
if(el.type !== "image") return false;
|
||||
const file = ea.getViewFileForImageElement(el);
|
||||
if(!file) return false;
|
||||
return el.type === "image" && (
|
||||
file.extension === "svg" ||
|
||||
ea.isExcalidrawFile(file)
|
||||
);
|
||||
});
|
||||
|
||||
if(allElements.length === 0) {
|
||||
new Notice("Select at least one rectangle, ellipse, diamond, line, arrow, freedraw, text or SVG image elment");
|
||||
return;
|
||||
}
|
||||
|
||||
const originalColors = new Map();
|
||||
const currentColors = new Map();
|
||||
const colorInputs = new Map();
|
||||
const sliderResetters = [];
|
||||
let terminate = false;
|
||||
const FORMAT = "Color Format";
|
||||
const STROKE = "Modify Stroke Color";
|
||||
const BACKGROUND = "Modify Background Color"
|
||||
const ACTIONS = ["Hue", "Lightness", "Saturation", "Transparency"];
|
||||
const precision = [1,2,2,3];
|
||||
const minLigtness = 1/Math.pow(10,precision[2]);
|
||||
const maxLightness = 100 - minLigtness;
|
||||
const minSaturation = 1/Math.pow(10,precision[2]);
|
||||
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if(!settings[STROKE]) {
|
||||
settings = {};
|
||||
settings[FORMAT] = {
|
||||
value: "HEX",
|
||||
valueset: ["HSL", "RGB", "HEX"],
|
||||
description: "Output color format."
|
||||
};
|
||||
settings[STROKE] = { value: true }
|
||||
settings[BACKGROUND] = {value: true }
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
function getRegularElements() {
|
||||
ea.clear();
|
||||
//loading view elements again as element objects change when colors are updated
|
||||
const allElements = ea.getViewSelectedElements();
|
||||
return allElements.filter(el =>
|
||||
["rectangle", "ellipse", "diamond", "line", "arrow", "freedraw", "text"].includes(el.type)
|
||||
);
|
||||
}
|
||||
|
||||
const updatedImageElementColorMaps = new Map();
|
||||
let isWaitingForSVGUpdate = false;
|
||||
function updateViewImageColors() {
|
||||
if(terminate || isWaitingForSVGUpdate || updatedImageElementColorMaps.size === 0) {
|
||||
return;
|
||||
}
|
||||
isWaitingForSVGUpdate = true;
|
||||
elementArray = Array.from(updatedImageElementColorMaps.keys());
|
||||
colorMapArray = Array.from(updatedImageElementColorMaps.values());
|
||||
updatedImageElementColorMaps.clear();
|
||||
ea.updateViewSVGImageColorMap(elementArray, colorMapArray).then(()=>{
|
||||
isWaitingForSVGUpdate = false;
|
||||
updateViewImageColors();
|
||||
});
|
||||
}
|
||||
|
||||
async function storeOriginalColors() {
|
||||
// Store colors for regular elements
|
||||
for (const el of getRegularElements()) {
|
||||
const key = el.id;
|
||||
const colorData = {
|
||||
type: "regular",
|
||||
strokeColor: el.strokeColor,
|
||||
backgroundColor: el.backgroundColor
|
||||
};
|
||||
originalColors.set(key, colorData);
|
||||
}
|
||||
|
||||
// Store colors for SVG elements
|
||||
for (const el of svgImageElements) {
|
||||
const colorInfo = await ea.getSVGColorInfoForImgElement(el);
|
||||
const svgColors = new Map();
|
||||
for (const [color, info] of colorInfo.entries()) {
|
||||
svgColors.set(color, {...info});
|
||||
}
|
||||
|
||||
originalColors.set(el.id, {type: "svg",colors: svgColors});
|
||||
}
|
||||
copyOriginalsToCurrent();
|
||||
}
|
||||
|
||||
function copyOriginalsToCurrent() {
|
||||
for (const [key, value] of originalColors.entries()) {
|
||||
if(value.type === "regular") {
|
||||
currentColors.set(key, {...value});
|
||||
} else {
|
||||
const newColorMap = new Map();
|
||||
for (const [color, info] of value.colors.entries()) {
|
||||
newColorMap.set(color, {...info});
|
||||
}
|
||||
currentColors.set(key, {type: "svg", colors: newColorMap});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearSVGMapping() {
|
||||
for (const resetter of sliderResetters) {
|
||||
resetter();
|
||||
}
|
||||
// Reset SVG elements
|
||||
if (svgImageElements.length === 1) {
|
||||
const el = svgImageElements[0];
|
||||
const original = originalColors.get(el.id);
|
||||
const current = currentColors.get(el.id);
|
||||
if (original && original.type === "svg") {
|
||||
|
||||
for (const color of original.colors.keys()) {
|
||||
current.colors.get(color).mappedTo = color;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const el of svgImageElements) {
|
||||
const original = originalColors.get(el.id);
|
||||
const current = currentColors.get(el.id);
|
||||
if (original && original.type === "svg") {
|
||||
for (const color of original.colors.keys()) {
|
||||
current.colors.get(color).mappedTo = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
run("clear");
|
||||
}
|
||||
|
||||
// Set colors
|
||||
async function setColors(colors) {
|
||||
debounceColorPicker = true;
|
||||
const regularElements = getRegularElements();
|
||||
|
||||
if (regularElements.length > 0) {
|
||||
ea.copyViewElementsToEAforEditing(regularElements);
|
||||
for (const el of ea.getElements()) {
|
||||
const original = colors.get(el.id);
|
||||
if (original && original.type === "regular") {
|
||||
if (original.strokeColor) el.strokeColor = original.strokeColor;
|
||||
if (original.backgroundColor) el.backgroundColor = original.backgroundColor;
|
||||
}
|
||||
}
|
||||
await ea.addElementsToView(false, false);
|
||||
}
|
||||
|
||||
// Reset SVG elements
|
||||
if (svgImageElements.length === 1) {
|
||||
const el = svgImageElements[0];
|
||||
const original = colors.get(el.id);
|
||||
if (original && original.type === "svg") {
|
||||
const newColorMap = {};
|
||||
|
||||
for (const [color, info] of original.colors.entries()) {
|
||||
newColorMap[color] = info.mappedTo;
|
||||
// Update UI components
|
||||
const inputs = colorInputs.get(color);
|
||||
if (inputs) {
|
||||
if(info.mappedTo === "fill") {
|
||||
info.mappedTo = "black";
|
||||
//"fill" is a special value in case the SVG has no fill color defined (i.e black)
|
||||
inputs.textInput.setValue("black");
|
||||
inputs.colorPicker.setValue("#000000");
|
||||
} else {
|
||||
const cm = ea.getCM(info.mappedTo);
|
||||
inputs.textInput.setValue(info.mappedTo);
|
||||
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
updatedImageElementColorMaps.set(el, newColorMap);
|
||||
}
|
||||
} else {
|
||||
for (const el of svgImageElements) {
|
||||
const original = colors.get(el.id);
|
||||
if (original && original.type === "svg") {
|
||||
const newColorMap = {};
|
||||
|
||||
for (const [color, info] of original.colors.entries()) {
|
||||
newColorMap[color] = info.mappedTo;
|
||||
}
|
||||
updatedImageElementColorMaps.set(el, newColorMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
updateViewImageColors();
|
||||
}
|
||||
|
||||
function modifyColor(color, isDecrease, step, action) {
|
||||
if (!color) return null;
|
||||
|
||||
const cm = ea.getCM(color);
|
||||
if (!cm) return color;
|
||||
|
||||
let modified = cm;
|
||||
if (modified.lightness === 0) modified = modified.lightnessTo(minLigtness);
|
||||
if (modified.lightness === 100) modified = modified.lightnessTo(maxLightness);
|
||||
if (modified.saturation === 0) modified = modified.saturationTo(minSaturation);
|
||||
|
||||
switch(action) {
|
||||
case "Lightness":
|
||||
// handles edge cases where lightness is 0 or 100 would convert saturation and hue to 0
|
||||
let lightness = cm.lightness;
|
||||
const shouldRoundLight = (lightness === minLigtness || lightness === maxLightness);
|
||||
if (shouldRoundLight) lightness = Math.round(lightness);
|
||||
lightness += isDecrease ? -step : step;
|
||||
if (lightness <= 0) lightness = minLigtness;
|
||||
if (lightness >= 100) lightness = maxLightness;
|
||||
modified = modified.lightnessTo(lightness);
|
||||
break;
|
||||
case "Hue":
|
||||
modified = isDecrease ? modified.hueBy(-step) : modified.hueBy(step);
|
||||
break;
|
||||
case "Transparency":
|
||||
modified = isDecrease ? modified.alphaBy(-step) : modified.alphaBy(step);
|
||||
break;
|
||||
default:
|
||||
let saturation = cm.saturation;
|
||||
const shouldRoundSat = saturation === minSaturation;
|
||||
if (shouldRoundSat) saturation = Math.round(saturation);
|
||||
saturation += isDecrease ? -step : step;
|
||||
if (saturation <= 0) saturation = minSaturation;
|
||||
modified = modified.saturationTo(saturation);
|
||||
}
|
||||
|
||||
const hasAlpha = modified.alpha < 1;
|
||||
const opts = { alpha: hasAlpha, precision };
|
||||
|
||||
const format = settings[FORMAT].value;
|
||||
switch(format) {
|
||||
case "RGB": return modified.stringRGB(opts).toLowerCase();
|
||||
case "HEX": return modified.stringHEX(opts).toLowerCase();
|
||||
default: return modified.stringHSL(opts).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function slider(contentEl, action, min, max, step, invert) {
|
||||
let prevValue = (max-min)/2;
|
||||
let debounce = false;
|
||||
let sliderControl;
|
||||
new ea.obsidian.Setting(contentEl)
|
||||
.setName(action)
|
||||
.addSlider(slider => {
|
||||
sliderControl = slider;
|
||||
slider
|
||||
.setLimits(min, max, step)
|
||||
.setValue(prevValue)
|
||||
.onChange(async (value) => {
|
||||
if (debounce) return;
|
||||
const isDecrease = invert ? value > prevValue : value < prevValue;
|
||||
const step = Math.abs(value-prevValue);
|
||||
prevValue = value;
|
||||
if(step>0) {
|
||||
run(action, isDecrease, step);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
debounce = true;
|
||||
prevValue = (max-min)/2;
|
||||
sliderControl.setValue(prevValue);
|
||||
debounce = false;
|
||||
}
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
let debounceColorPicker = true;
|
||||
const modal = new ea.obsidian.Modal(app);
|
||||
let dirty = false;
|
||||
|
||||
modal.onOpen = async () => {
|
||||
const { contentEl, modalEl } = modal;
|
||||
const { width, height } = ea.getExcalidrawAPI().getAppState();
|
||||
modal.bgOpacity = 0;
|
||||
contentEl.createEl('h2', { text: 'Shade Master' });
|
||||
|
||||
const helpDiv = contentEl.createEl("details", {
|
||||
attr: { style: "margin-bottom: 1em;background: var(--background-secondary); padding: 1em; border-radius: 4px;" }});
|
||||
helpDiv.createEl("summary", { text: "Help & Usage Guide", attr: { style: "cursor: pointer; color: var(--text-accent);" } });
|
||||
const helpDetailsDiv = helpDiv.createEl("div", {
|
||||
attr: { style: "margin-top: 0em; " }
|
||||
});
|
||||
helpDetailsDiv.innerHTML = HELP_TEXT;
|
||||
|
||||
const component = new ea.obsidian.Setting(contentEl)
|
||||
.setName(FORMAT)
|
||||
.setDesc("Output color format")
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOptions({
|
||||
"HSL": "HSL",
|
||||
"RGB": "RGB",
|
||||
"HEX": "HEX"
|
||||
})
|
||||
.setValue(settings[FORMAT].value)
|
||||
.onChange(value => {
|
||||
settings[FORMAT].value = value;
|
||||
run();
|
||||
dirty = true;
|
||||
})
|
||||
);
|
||||
|
||||
new ea.obsidian.Setting(contentEl)
|
||||
.setName(STROKE)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings[STROKE].value)
|
||||
.onChange(value => {
|
||||
settings[STROKE].value = value;
|
||||
dirty = true;
|
||||
})
|
||||
);
|
||||
|
||||
new ea.obsidian.Setting(contentEl)
|
||||
.setName(BACKGROUND)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings[BACKGROUND].value)
|
||||
.onChange(value => {
|
||||
settings[BACKGROUND].value = value;
|
||||
dirty = true;
|
||||
})
|
||||
);
|
||||
|
||||
// lightness and saturation are on a scale of 0%-100%
|
||||
// Hue is in degrees, 360 for the full circle
|
||||
// transparency is on a range between 0 and 1 (equivalent to 0%-100%)
|
||||
// The range for lightness, saturation and transparency are double since
|
||||
// the input could be at either end of the scale
|
||||
// The range for Hue is 360 since regarless of the position on the circle moving
|
||||
// the slider to the two extremes will travel the entire circle
|
||||
// To modify blacks and whites, lightness first needs to be changed to value between 1% and 99%
|
||||
sliderResetters.push(slider(contentEl, "Hue", 0, 360, 1, false));
|
||||
sliderResetters.push(slider(contentEl, "Saturation", 0, 200, 1, false));
|
||||
sliderResetters.push(slider(contentEl, "Lightness", 0, 200, 1, false));
|
||||
sliderResetters.push(slider(contentEl, "Transparency", 0, 2, 0.05, true));
|
||||
|
||||
// Add color pickers if a single SVG image is selected
|
||||
if (svgImageElements.length === 1) {
|
||||
const svgElement = svgImageElements[0];
|
||||
//note that the objects in currentColors might get replaced when
|
||||
//colors are reset, thus in the onChange functions I will always
|
||||
//read currentColorInfo from currentColors based on svgElement.id
|
||||
const initialColorInfo = currentColors.get(svgElement.id).colors;
|
||||
const colorSection = contentEl.createDiv();
|
||||
colorSection.createEl('h3', { text: 'SVG Colors' });
|
||||
|
||||
for (const [color, info] of initialColorInfo.entries()) {
|
||||
const row = new ea.obsidian.Setting(colorSection)
|
||||
.setName(color === "fill" ? "SVG default" : color)
|
||||
.setDesc(`${info.fill ? "Fill" : ""}${info.fill && info.stroke ? " & " : ""}${info.stroke ? "Stroke" : ""}`);
|
||||
row.descEl.style.width = "100px";
|
||||
row.nameEl.style.width = "100px";
|
||||
|
||||
// Create color preview div
|
||||
const previewDiv = row.controlEl.createDiv();
|
||||
previewDiv.style.width = "50px";
|
||||
previewDiv.style.height = "20px";
|
||||
previewDiv.style.border = "1px solid var(--background-modifier-border)";
|
||||
if (color === "transparent") {
|
||||
previewDiv.style.backgroundImage = "linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%)";
|
||||
previewDiv.style.backgroundSize = "10px 10px";
|
||||
previewDiv.style.backgroundPosition = "0 0, 0 5px, 5px -5px, -5px 0px";
|
||||
} else {
|
||||
previewDiv.style.backgroundColor = ea.getCM(color).stringHEX({alpha: false}).toLowerCase();
|
||||
}
|
||||
|
||||
const resetButton = new ea.obsidian.Setting(row.controlEl)
|
||||
.addButton(button => button
|
||||
.setButtonText(">>")
|
||||
.setClass("reset-color-button")
|
||||
.onClick(async () => {
|
||||
const original = originalColors.get(svgElement.id);
|
||||
const current = currentColors.get(svgElement.id);
|
||||
if (original?.type === "svg") {
|
||||
const originalInfo = original.colors.get(color);
|
||||
const currentInfo = current.colors.get(color);
|
||||
if (originalInfo) {
|
||||
currentInfo.mappedTo = color;
|
||||
run("reset single color");
|
||||
}
|
||||
}
|
||||
}))
|
||||
resetButton.settingEl.style.padding = "0";
|
||||
resetButton.settingEl.style.border = "0";
|
||||
|
||||
// Add text input for color value
|
||||
const textInput = new ea.obsidian.TextComponent(row.controlEl)
|
||||
.setValue(info.mappedTo)
|
||||
.setPlaceholder("Color value");
|
||||
textInput.inputEl.style.width = "100%";
|
||||
textInput.onChange(value => {
|
||||
const lower = value.toLowerCase();
|
||||
if (lower === color) return;
|
||||
textInput.setValue(lower);
|
||||
})
|
||||
|
||||
const applyButtonComponent = new ea.obsidian.Setting(row.controlEl)
|
||||
.addButton(button => button
|
||||
.setIcon("check")
|
||||
.setTooltip("Apply")
|
||||
.onClick(async () => {
|
||||
const value = textInput.getValue();
|
||||
try {
|
||||
if(!CSS.supports("color",value)) {
|
||||
new Notice (`${value} is not a valid color string`);
|
||||
return;
|
||||
}
|
||||
const cm = ea.getCM(value);
|
||||
if (cm) {
|
||||
const format = settings[FORMAT].value;
|
||||
const alpha = cm.alpha < 1 ? true : false;
|
||||
const newColor = format === "RGB"
|
||||
? cm.stringRGB({alpha , precision }).toLowerCase()
|
||||
: format === "HEX"
|
||||
? cm.stringHEX({alpha}).toLowerCase()
|
||||
: cm.stringHSL({alpha, precision }).toLowerCase();
|
||||
|
||||
textInput.setValue(newColor);
|
||||
const currentInfo = currentColors.get(svgElement.id).colors;
|
||||
currentInfo.get(color).mappedTo = newColor;
|
||||
run("Update SVG color");
|
||||
debounceColorPicker = true;
|
||||
colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Invalid color value:", e);
|
||||
}
|
||||
}));
|
||||
applyButtonComponent.settingEl.style.padding = "0";
|
||||
applyButtonComponent.settingEl.style.border = "0";
|
||||
|
||||
// Add color picker
|
||||
const colorPicker = new ea.obsidian.ColorComponent(row.controlEl)
|
||||
.setValue(ea.getCM(info.mappedTo).stringHEX({alpha: false}).toLowerCase());
|
||||
|
||||
colorPicker.colorPickerEl.style.maxWidth = "2.5rem";
|
||||
|
||||
// Store references to the components
|
||||
colorInputs.set(color, {
|
||||
textInput,
|
||||
colorPicker,
|
||||
previewDiv,
|
||||
resetButton
|
||||
});
|
||||
|
||||
colorPicker.colorPickerEl.addEventListener('click', () => {
|
||||
debounceColorPicker = false;
|
||||
});
|
||||
|
||||
colorPicker.onChange(async (value) => {
|
||||
try {
|
||||
if(!debounceColorPicker) {
|
||||
const currentInfo = currentColors.get(svgElement.id).colors.get(color);
|
||||
// Preserve alpha from original color
|
||||
const originalAlpha = ea.getCM(currentInfo.mappedTo).alpha;
|
||||
const cm = ea.getCM(value);
|
||||
cm.alphaTo(originalAlpha);
|
||||
const alpha = originalAlpha < 1 ? true : false;
|
||||
const format = settings[FORMAT].value;
|
||||
const newColor = format === "RGB"
|
||||
? cm.stringRGB({alpha, precision }).toLowerCase()
|
||||
: format === "HEX"
|
||||
? cm.stringHEX({alpha}).toLowerCase()
|
||||
: cm.stringHSL({alpha, precision }).toLowerCase();
|
||||
|
||||
// Update text input
|
||||
textInput.setValue(newColor);
|
||||
|
||||
// Update SVG
|
||||
currentInfo.mappedTo = newColor;
|
||||
run("Update SVG color");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Invalid color value:", e);
|
||||
} finally {
|
||||
debounceColorPicker = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const buttons = new ea.obsidian.Setting(contentEl);
|
||||
if(svgImageElements.length > 0) {
|
||||
buttons.addButton(button => button
|
||||
.setButtonText("Initialize SVG Colors")
|
||||
.onClick(() => {
|
||||
debounceColorPicker = true;
|
||||
clearSVGMapping();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
buttons
|
||||
.addButton(button => button
|
||||
.setButtonText("Reset")
|
||||
.onClick(() => {
|
||||
for (const resetter of sliderResetters) {
|
||||
resetter();
|
||||
}
|
||||
copyOriginalsToCurrent();
|
||||
setColors(originalColors);
|
||||
}))
|
||||
.addButton(button => button
|
||||
.setButtonText("Close")
|
||||
.setCta(true)
|
||||
.onClick(() => modal.close()));
|
||||
|
||||
makeModalDraggable(modalEl);
|
||||
|
||||
const maxHeight = Math.round(height * 0.6);
|
||||
const maxWidth = Math.round(width * 0.9);
|
||||
modalEl.style.maxHeight = `${maxHeight}px`;
|
||||
modalEl.style.maxWidth = `${maxWidth}px`;
|
||||
};
|
||||
|
||||
modal.onClose = () => {
|
||||
terminate = true;
|
||||
if (dirty) {
|
||||
ea.setScriptSettings(settings);
|
||||
}
|
||||
if(ea.targetView.isDirty()) {
|
||||
ea.targetView.save(false);
|
||||
}
|
||||
};
|
||||
|
||||
modal.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add draggable functionality to the modal element.
|
||||
* @param {HTMLElement} modalEl - The modal element to make draggable.
|
||||
*/
|
||||
function makeModalDraggable(modalEl) {
|
||||
let isDragging = false;
|
||||
let startX, startY, initialX, initialY;
|
||||
|
||||
const header = modalEl.querySelector('.modal-titlebar') || modalEl; // Default to modalEl if no titlebar
|
||||
header.style.cursor = 'move';
|
||||
|
||||
const onPointerDown = (e) => {
|
||||
// Ensure the event target isn't an interactive element like slider, button, or input
|
||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
|
||||
|
||||
isDragging = true;
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
const rect = modalEl.getBoundingClientRect();
|
||||
initialX = rect.left;
|
||||
initialY = rect.top;
|
||||
|
||||
modalEl.style.position = 'absolute';
|
||||
modalEl.style.margin = '0';
|
||||
modalEl.style.left = `${initialX}px`;
|
||||
modalEl.style.top = `${initialY}px`;
|
||||
};
|
||||
|
||||
const onPointerMove = (e) => {
|
||||
if (!isDragging) return;
|
||||
|
||||
const dx = e.clientX - startX;
|
||||
const dy = e.clientY - startY;
|
||||
|
||||
modalEl.style.left = `${initialX + dx}px`;
|
||||
modalEl.style.top = `${initialY + dy}px`;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
header.addEventListener('pointerdown', onPointerDown);
|
||||
document.addEventListener('pointermove', onPointerMove);
|
||||
document.addEventListener('pointerup', onPointerUp);
|
||||
|
||||
// Clean up event listeners on modal close
|
||||
modalEl.addEventListener('remove', () => {
|
||||
header.removeEventListener('pointerdown', onPointerDown);
|
||||
document.removeEventListener('pointermove', onPointerMove);
|
||||
document.removeEventListener('pointerup', onPointerUp);
|
||||
});
|
||||
}
|
||||
|
||||
function executeChange(isDecrease, step, action) {
|
||||
const modifyStroke = settings[STROKE].value;
|
||||
const modifyBackground = settings[BACKGROUND].value;
|
||||
const regularElements = getRegularElements();
|
||||
|
||||
// Process regular elements
|
||||
if (regularElements.length > 0) {
|
||||
for (const el of regularElements) {
|
||||
const currentColor = currentColors.get(el.id);
|
||||
|
||||
if (modifyStroke && currentColor.strokeColor) {
|
||||
currentColor.strokeColor = modifyColor(el.strokeColor, isDecrease, step, action);
|
||||
}
|
||||
|
||||
if (modifyBackground && currentColor.backgroundColor) {
|
||||
currentColor.backgroundColor = modifyColor(el.backgroundColor, isDecrease, step, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process SVG image elements
|
||||
if (svgImageElements.length === 1) { // Only update UI for single SVG
|
||||
const el = svgImageElements[0];
|
||||
colorInfo = currentColors.get(el.id).colors;
|
||||
|
||||
// Process each color in the SVG
|
||||
for (const [color, info] of colorInfo.entries()) {
|
||||
let shouldModify = (modifyBackground && info.fill) || (modifyStroke && info.stroke);
|
||||
|
||||
if (shouldModify) {
|
||||
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
|
||||
colorInfo.get(color).mappedTo = modifiedColor;
|
||||
// Update UI components if they exist
|
||||
const inputs = colorInputs.get(color);
|
||||
if (inputs) {
|
||||
const cm = ea.getCM(modifiedColor);
|
||||
inputs.textInput.setValue(modifiedColor);
|
||||
inputs.colorPicker.setValue(cm.stringHEX({alpha: false}).toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (svgImageElements.length > 0) {
|
||||
for (const el of svgImageElements) {
|
||||
const colorInfo = currentColors.get(el.id).colors;
|
||||
|
||||
// Process each color in the SVG
|
||||
for (const [color, info] of colorInfo.entries()) {
|
||||
let shouldModify = (modifyBackground && info.fill) || (modifyStroke && info.stroke);
|
||||
|
||||
if (shouldModify) {
|
||||
const modifiedColor = modifyColor(info.mappedTo, isDecrease, step, action);
|
||||
colorInfo.get(color).mappedTo = modifiedColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let isRunning = false;
|
||||
let queue = false;
|
||||
function processQueue() {
|
||||
if (!terminate && !isRunning && queue) {
|
||||
queue = false;
|
||||
isRunning = true;
|
||||
setColors(currentColors).then(() => {
|
||||
isRunning = false;
|
||||
if (queue) processQueue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function run(action="Hue", isDecrease=true, step=0) {
|
||||
// passing invalid action (such as "clear") will bypass rewriting of colors using CM
|
||||
// this is useful when resetting colors to original values
|
||||
if(ACTIONS.includes(action)) {
|
||||
executeChange(isDecrease, step, action);
|
||||
}
|
||||
queue = true;
|
||||
if (!isRunning) processQueue();
|
||||
}
|
||||
|
||||
await storeOriginalColors();
|
||||
showModal();
|
||||
processQueue();
|
||||
1
ea-scripts/Shade Master.svg
Normal file
1
ea-scripts/Shade Master.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg class="skip" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 17a4 4 0 0 1-8 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2Z"/><path d="M16.7 13H19a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2H7"/><path d="M 7 17h.01"/><path d="m11 8 2.3-2.3a2.4 2.4 0 0 1 3.404.004L18.6 7.6a2.4 2.4 0 0 1 .026 3.434L9.9 19.8"/></svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -21,11 +21,15 @@ The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.8.0")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(ea.targetView.isDirty()) {
|
||||
ea.targetView.forceSave(true);
|
||||
}
|
||||
|
||||
const hostLeaf = ea.targetView.leaf;
|
||||
const hostView = hostLeaf.view;
|
||||
const statusBarElement = document.querySelector("div.status-bar");
|
||||
@@ -33,7 +37,7 @@ const ctrlKey = ea.targetView.modifierKeyDown.ctrlKey || ea.targetView.modifierK
|
||||
const altKey = ea.targetView.modifierKeyDown.altKey || ctrlKey;
|
||||
const shiftKey = ea.targetView.modifierKeyDown.shiftKey;
|
||||
const shouldStartWithLastSlide = shiftKey && window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide === "number")
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] === "number")
|
||||
//-------------------------------
|
||||
//constants
|
||||
//-------------------------------
|
||||
@@ -42,6 +46,8 @@ const TRANSITION_DELAY = 1000; //maximum time for transition between slides in m
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
const PRINT_SLIDE_WIDTH = 1920;
|
||||
const PRINT_SLIDE_HEIGHT = 1080;
|
||||
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
|
||||
//but excalidraw might be open in a popout window which has a different document object
|
||||
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
|
||||
@@ -53,12 +59,14 @@ const SVG_MAXIMIZE = ea.obsidian.getIcon("lucide-maximize").outerHTML;
|
||||
const SVG_MINIMIZE = ea.obsidian.getIcon("lucide-minimize").outerHTML;
|
||||
const SVG_LASER_ON = ea.obsidian.getIcon("lucide-hand").outerHTML;
|
||||
const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
|
||||
const SVG_PRINTER = ea.obsidian.getIcon("lucide-printer").outerHTML;
|
||||
|
||||
//-------------------------------
|
||||
//utility & convenience functions
|
||||
//-------------------------------
|
||||
let shouldSaveAfterThePresentation = false;
|
||||
let isLaserOn = false;
|
||||
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide : 0;
|
||||
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] : 0;
|
||||
let isFullscreen = false;
|
||||
const ownerDocument = ea.targetView.ownerDocument;
|
||||
const startFullscreen = !altKey;
|
||||
@@ -266,8 +274,8 @@ if(presentationPathType==="line") {
|
||||
//-----------------------------
|
||||
// scroll-to-location functions
|
||||
//-----------------------------
|
||||
const getNavigationRect = ({ x1, y1, x2, y2 }) => {
|
||||
const { width, height } = excalidrawAPI.getAppState();
|
||||
const getNavigationRect = ({ x1, y1, x2, y2, printDimensions }) => {
|
||||
const { width, height } = printDimensions ? printDimensions : excalidrawAPI.getAppState();
|
||||
const ratioX = width / Math.abs(x1 - x2);
|
||||
const ratioY = height / Math.abs(y1 - y2);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
|
||||
@@ -350,8 +358,8 @@ const navigate = async (dir) => {
|
||||
}
|
||||
if(selectSlideDropdown) selectSlideDropdown.value = slide+1;
|
||||
await scrollToNextRect(nextRect);
|
||||
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide === "number")) {
|
||||
window.ExcalidrawSlideshow.slide = slide;
|
||||
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide?.[ea.targetView.file.path] === "number")) {
|
||||
window.ExcalidrawSlideshow.slide[ea.targetView.file.path] = slide;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,6 +513,7 @@ const createPresentationNavigationPanel = () => {
|
||||
new ea.obsidian.ToggleComponent(el)
|
||||
.setValue(isHidden)
|
||||
.onChange(value => {
|
||||
shouldSaveAfterThePresentation = true;
|
||||
if(value) {
|
||||
excalidrawAPI.setToast({
|
||||
message:"The presentation path remain hidden after the presentation. No need to select the line again. Just click the slideshow button to start the next presentation.",
|
||||
@@ -528,6 +537,20 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
if(ea.DEVICE.isDesktop) {
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
margin-right: calc(var(--default-button-size)*0.25);`,
|
||||
title: `Print to PDF\nClick to print slides at ${PRINT_SLIDE_WIDTH}x${
|
||||
PRINT_SLIDE_HEIGHT}\nHold SHIFT to print the presentation as displayed`
|
||||
//${!presentationPathLineEl ? "\nHold ALT/OPT to clip frames":""}`
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_PRINTER;
|
||||
button.onclick = (e) => printToPDF(e);
|
||||
});
|
||||
}
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
@@ -536,7 +559,7 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_FINISH;
|
||||
button.onclick = () => exitPresentation()
|
||||
button.onclick = () => exitPresentation();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -730,6 +753,99 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
hostView.refreshCanvasOffset();
|
||||
excalidrawAPI.setActiveTool({type: "selection"});
|
||||
})
|
||||
if(!shouldSaveAfterThePresentation) {
|
||||
ea.targetView.clearDirty();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Print to PDF
|
||||
//--------------------------
|
||||
let notice;
|
||||
let noticeEl;
|
||||
function setSingleNotice(message) {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.setMessage(message);
|
||||
return;
|
||||
}
|
||||
notice = new Notice(message, 0);
|
||||
noticeEl = notice.containerEl ?? n.noticeEl;
|
||||
}
|
||||
|
||||
function hideSingleNotice() {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.hide();
|
||||
}
|
||||
}
|
||||
|
||||
const translateToZero = ({ top, left, bottom, right }, padding) => {
|
||||
const {topX, topY, width, height} = ea.getBoundingBox(ea.getViewElements());
|
||||
const newTop = top - (topY - padding);
|
||||
const newLeft = left - (topX - padding);
|
||||
const newBottom = bottom - (topY - padding);
|
||||
const newRight = right - (topX - padding);
|
||||
|
||||
return {
|
||||
top: newTop,
|
||||
left: newLeft,
|
||||
bottom: newBottom,
|
||||
right: newRight,
|
||||
};
|
||||
}
|
||||
|
||||
const printToPDF = async (e) => {
|
||||
const slideWidth = e.shiftKey ? excalidrawAPI.getAppState().width : PRINT_SLIDE_WIDTH;
|
||||
const slideHeight = e.shiftKey ? excalidrawAPI.getAppState().height : PRINT_SLIDE_HEIGHT;
|
||||
//const shouldClipFrames = !presentationPathLineEl && e.altKey;
|
||||
const shouldClipFrames = false;
|
||||
//huge padding to ensure the HD window always fits the width
|
||||
//no padding if frames are clipped
|
||||
const padding = shouldClipFrames ? 0 : Math.round(Math.max(slideWidth,slideHeight)/2)+10;
|
||||
const st = ea.getExcalidrawAPI().getAppState();
|
||||
setSingleNotice("Generating image. This can take a longer time depending on the size of the image and speed of your device");
|
||||
const svg = await ea.createViewSVG({
|
||||
withBackground: true,
|
||||
theme: st.theme,
|
||||
frameRendering: { enabled: shouldClipFrames, name: false, outline: false, clip: shouldClipFrames },
|
||||
padding,
|
||||
selectedOnly: false,
|
||||
skipInliningFonts: false,
|
||||
embedScene: false,
|
||||
});
|
||||
const pages = [];
|
||||
for(i=0;i<slides.length;i++) {
|
||||
setSingleNotice(`Generating slide ${i+1}`);
|
||||
const s = slides[i];
|
||||
const { top, left, bottom, right } = translateToZero(
|
||||
getNavigationRect({
|
||||
...s,
|
||||
printDimensions: {width: slideWidth, height: slideHeight}
|
||||
}), padding
|
||||
);
|
||||
//always create the new SVG in the main Obsidian workspace (not the popout window, if present)
|
||||
const host = window.createDiv();
|
||||
host.innerHTML = svg.outerHTML;
|
||||
const clonedSVG = host.firstElementChild;
|
||||
const width = Math.abs(left-right);
|
||||
const height = Math.abs(top-bottom);
|
||||
clonedSVG.setAttribute("viewBox", `${left} ${top} ${width} ${height}`);
|
||||
clonedSVG.setAttribute("width", `${width}`);
|
||||
clonedSVG.setAttribute("height", `${height}`);
|
||||
pages.push(clonedSVG);
|
||||
}
|
||||
const bgColor = ea.getExcalidrawAPI().getAppState().viewBackgroundColor;
|
||||
setSingleNotice("Creating PDF Document");
|
||||
ea.createPDF({
|
||||
SVG: pages,
|
||||
scale: { fitToPage: true },
|
||||
pageProps: {
|
||||
dimensions: { width: slideWidth, height: slideHeight },
|
||||
backgroundColor: bgColor,
|
||||
margin: { left: 0, right: 0, top: 0, bottom: 0 },
|
||||
alignment: "center"
|
||||
},
|
||||
filename: ea.targetView.file.basename + ".pdf",
|
||||
}).then(()=>hideSingleNotice());
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
@@ -755,10 +871,15 @@ const start = async () => {
|
||||
resetControlPanelElPosition();
|
||||
}
|
||||
if(presentationPathType === "line") await toggleArrowVisibility(isHidden);
|
||||
ea.targetView.clearDirty();
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
|
||||
if(
|
||||
window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) &&
|
||||
(timestamp - window.ExcalidrawSlideshow.timestamp <400)
|
||||
) {
|
||||
if(window.ExcalidrawSlideshowStartTimer) {
|
||||
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
@@ -769,10 +890,14 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
}
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
timestamp,
|
||||
slide: 0
|
||||
};
|
||||
if(!window.ExcalidrawSlideshow) {
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
slide: {},
|
||||
};
|
||||
}
|
||||
window.ExcalidrawSlideshow.timestamp = timestamp;
|
||||
window.ExcalidrawSlideshow.slide[ea.targetView.file.path] = 0;
|
||||
|
||||
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -94,6 +94,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/Set%20Dimensions.svg"/></div>|[[#Set Dimensions]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Grid.svg"/></div>|[[#Set Grid]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Set%20Stroke%20Width%20of%20Selected%20Elements.svg"/></div>|[[#Set Stroke Width of Selected Elements]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Shade%20Master.svg"/></div>|[[#Shade Master]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Toggle%20Grid.svg"/></div>|[[#Toggle Grid]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Uniform%20size.svg"/></div>|[[#Uniform Size]]|
|
||||
|
||||
@@ -147,6 +148,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/Add%20Next%20Step%20in%20Process.svg"/></div>|[[#Add Next Step in Process]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Convert%20freedraw%20to%20line.svg"/></div>|[[#Convert freedraw to line]]|
|
||||
|<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/Full-Year%20Calendar%20Generator.svg"/></div>|[[#Full-Year Calendar Generator]]|
|
||||
|
||||
## Masking and cropping
|
||||
**Keywords**: Crop, Mask, Transform images
|
||||
@@ -393,6 +395,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/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/Excalidraw%20Writing%20Machine.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Creates a hierarchical Markdown document out of a visual layout of an article that can be fed to Templater and converted into an article using AI for Templater.<br>Watch this video to understand how the script is intended to work:<br><iframe width="400" height="225" src="https://www.youtube.com/embed/zvRpCOZAUSs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><br>You can download the sample Obsidian Templater file from <a href="https://gist.github.com/zsviczian/bf49d4b2d401f5749aaf8c2fa8a513d9">here</a>. You can download the demo PDF document showcased in the video from <a href="https://zsviczian.github.io/DemoArticle-AtomicHabits.pdf">here</a>.</td></tr></table>
|
||||
|
||||
## Full-Year Calendar Generator
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Full-Year%20Calendar%20Generator.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/simonperet'>@simonperet</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/Full-Year%20Calendar%20Generator.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Generates a complete calendar for a specified year.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-exemple.excalidraw.png'></td></tr></table>
|
||||
|
||||
## GPT Draw-a-UI
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/GPT-Draw-a-UI.md
|
||||
@@ -563,6 +571,13 @@ 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/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/Set%20Text%20Alignment.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Sets text alignment of text block (cetner, right, left). Useful if you want to set a keyboard shortcut for selecting text alignment.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-text-align.jpg'></td></tr></table>
|
||||
|
||||
## Shade Master
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Shade%20Master.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/Shade%20Master.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">You can modify the colors of SVG images, embedded files, and Excalidraw elements in a drawing by changing Hue, Saturation, Lightness and Transparency; and if only a single SVG or nested Excalidraw drawing is selected, then you can remap image colors.<br><iframe width="560" height="315" src="https://www.youtube.com/embed/ISuORbVKyhQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></td></tr></table>
|
||||
|
||||
|
||||
## Slideshow
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.md
|
||||
|
||||
BIN
images/scripts-full-year-calendar-customize.excalidraw.png
Normal file
BIN
images/scripts-full-year-calendar-customize.excalidraw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
images/scripts-full-year-calendar-exemple.excalidraw.png
Normal file
BIN
images/scripts-full-year-calendar-exemple.excalidraw.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.0",
|
||||
"version": "2.8.0-rc-2",
|
||||
"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.0",
|
||||
"version": "2.8.0",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
9284
package-lock.json
generated
Normal file
9284
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -24,7 +24,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-22",
|
||||
"@zsviczian/excalidraw": "0.17.6-29",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
@@ -58,7 +58,8 @@
|
||||
"@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",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/js-beautify": "^1.14.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
@@ -79,10 +80,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"
|
||||
},
|
||||
|
||||
@@ -5,17 +5,21 @@ import { terser } from "rollup-plugin-terser";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import typescript2 from "rollup-plugin-typescript2";
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import LZString from 'lz-string';
|
||||
import postprocess from '@zsviczian/rollup-plugin-postprocess';
|
||||
import cssnano from 'cssnano';
|
||||
import jsesc from 'jsesc';
|
||||
import { minify } from 'uglify-js';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
// Load environment variables
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const DIST_FOLDER = 'dist';
|
||||
const absolutePath = path.resolve(DIST_FOLDER);
|
||||
fs.mkdirSync(absolutePath, { recursive: true });
|
||||
const isProd = (process.env.NODE_ENV === "production");
|
||||
const isLib = (process.env.NODE_ENV === "lib");
|
||||
console.log(`Running: ${process.env.NODE_ENV}; isProd: ${isProd}; isLib: ${isLib}`);
|
||||
@@ -32,13 +36,16 @@ function trimLastSemicolon(input) {
|
||||
}
|
||||
|
||||
function minifyCode(code) {
|
||||
const minified = minify(code,{
|
||||
compress: true,
|
||||
const minified = minify(code, {
|
||||
compress: {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2170
|
||||
reduce_vars: false,
|
||||
},
|
||||
mangle: true,
|
||||
output: {
|
||||
comments: false,
|
||||
beautify: false,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
if (minified.error) {
|
||||
@@ -55,7 +62,7 @@ function compressLanguageFile(lang) {
|
||||
return LZString.compressToBase64(minifyCode(`x = ${content};`));
|
||||
}
|
||||
|
||||
const excalidraw_pkg = isLib ? "" : minifyCode( isProd
|
||||
const excalidraw_pkg = isLib ? "" : minifyCode(isProd
|
||||
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
|
||||
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8"));
|
||||
const react_pkg = isLib ? "" : minifyCode(isProd
|
||||
@@ -102,7 +109,7 @@ const packageString = isLib
|
||||
'const PLUGIN_VERSION="' + manifest.version + '";';
|
||||
|
||||
const BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
input: 'src/core/main.ts',
|
||||
external: [
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
@@ -124,6 +131,7 @@ const BASE_CONFIG = {
|
||||
|
||||
const getRollupPlugins = (tsconfig, ...plugins) => [
|
||||
typescript2(tsconfig),
|
||||
json(),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
@@ -139,9 +147,15 @@ const BUILD_CONFIG = {
|
||||
entryFileNames: 'main.js',
|
||||
format: 'cjs',
|
||||
exports: 'default',
|
||||
inlineDynamicImports: true, // Add this line only
|
||||
},
|
||||
plugins: getRollupPlugins(
|
||||
{tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json"},
|
||||
{
|
||||
tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json",
|
||||
sourcemap: !isProd,
|
||||
clean: true,
|
||||
//verbosity: isProd ? 1 : 2,
|
||||
},
|
||||
...(isProd ? [
|
||||
terser({
|
||||
toplevel: false,
|
||||
@@ -166,10 +180,10 @@ const BUILD_CONFIG = {
|
||||
|
||||
const LIB_CONFIG = {
|
||||
...BASE_CONFIG,
|
||||
input: "src/index.ts",
|
||||
input: "src/core/index.ts",
|
||||
output: {
|
||||
dir: "lib",
|
||||
sourcemap: true,
|
||||
sourcemap: false,
|
||||
format: "cjs",
|
||||
name: "Excalidraw (Library)",
|
||||
},
|
||||
|
||||
368
src/OneOffs.ts
368
src/OneOffs.ts
@@ -1,368 +0,0 @@
|
||||
import ExcalidrawPlugin from "./main";
|
||||
|
||||
export class OneOffs {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/*
|
||||
public patchCommentBlock() {
|
||||
//This is a once off cleanup process to remediate incorrectly placed comment %% before # Text Elements
|
||||
if (!this.plugin.settings.patchCommentBlock) {
|
||||
return;
|
||||
}
|
||||
const plugin = this.plugin;
|
||||
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format("HH:mm:ss")}: Excalidraw will patch drawings in 5 minutes`,
|
||||
);
|
||||
setTimeout(async () => {
|
||||
await plugin.loadSettings();
|
||||
if (!plugin.settings.patchCommentBlock) {
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format(
|
||||
"HH:mm:ss",
|
||||
)}: Excalidraw patching aborted because synched data.json is already patched`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format("HH:mm:ss")}: Excalidraw is starting the patching process`,
|
||||
);
|
||||
let i = 0;
|
||||
const excalidrawFiles = plugin.app.vault.getFiles();
|
||||
for (const f of (excalidrawFiles || []).filter((f: TFile) =>
|
||||
plugin.isExcalidrawFile(f),
|
||||
)) {
|
||||
if (
|
||||
f.extension !== "excalidraw" && //legacy files do not need to be touched
|
||||
plugin.app.workspace.getActiveFile() !== f
|
||||
) {
|
||||
//file is currently being edited
|
||||
let drawing = await plugin.app.vault.read(f);
|
||||
const orig_drawing = drawing;
|
||||
drawing = drawing.replaceAll("\r\n", "\n").replaceAll("\r", "\n"); //Win, Mac, Linux compatibility
|
||||
drawing = drawing.replace(
|
||||
"\n%%\n# Text Elements\n",
|
||||
"\n# Text Elements\n",
|
||||
);
|
||||
if (drawing.search("\n%%\n# Drawing\n") === -1) {
|
||||
const sceneJSONandPOS = getJSON(drawing);
|
||||
drawing = `${drawing.substr(
|
||||
0,
|
||||
sceneJSONandPOS.pos,
|
||||
)}\n%%\n# Drawing\n\`\`\`json\n${sceneJSONandPOS.scene}\n\`\`\`%%`;
|
||||
}
|
||||
if (drawing !== orig_drawing) {
|
||||
i++;
|
||||
log(`Excalidraw patched: ${f.path}`);
|
||||
await plugin.app.vault.modify(f, drawing);
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.settings.patchCommentBlock = false;
|
||||
plugin.saveSettings();
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format("HH:mm:ss")}: Excalidraw patched in total ${i} files`,
|
||||
);
|
||||
}, 300000); //5 minutes
|
||||
}
|
||||
|
||||
public migrationNotice() {
|
||||
if (this.plugin.settings.loadCount > 0) {
|
||||
return;
|
||||
}
|
||||
const plugin = this.plugin;
|
||||
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
plugin.settings.loadCount++;
|
||||
plugin.saveSettings();
|
||||
const files = plugin.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => f.extension === "excalidraw");
|
||||
if (files.length > 0) {
|
||||
const prompt = new MigrationPrompt(plugin.app, plugin);
|
||||
prompt.open();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public imageElementLaunchNotice() {
|
||||
if (!this.plugin.settings.imageElementNotice) {
|
||||
return;
|
||||
}
|
||||
const plugin = this.plugin;
|
||||
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
const prompt = new ImageElementNotice(plugin.app, plugin);
|
||||
prompt.open();
|
||||
});
|
||||
}
|
||||
|
||||
public wysiwygPatch() {
|
||||
if (this.plugin.settings.patchCommentBlock) {
|
||||
return;
|
||||
} //the comment block patch needs to happen first (unlikely that someone has waited this long with the update...)
|
||||
//This is a once off process to patch excalidraw files remediate incorrectly placed comment %% before # Text Elements
|
||||
if (
|
||||
!(
|
||||
this.plugin.settings.runWYSIWYGpatch ||
|
||||
this.plugin.settings.fixInfinitePreviewLoop
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const plugin = this.plugin;
|
||||
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format(
|
||||
"HH:mm:ss",
|
||||
)}: Excalidraw will patch drawings to support WYSIWYG in 7 minutes`,
|
||||
);
|
||||
setTimeout(async () => {
|
||||
await plugin.loadSettings();
|
||||
if (
|
||||
!(
|
||||
this.plugin.settings.runWYSIWYGpatch ||
|
||||
this.plugin.settings.fixInfinitePreviewLoop
|
||||
)
|
||||
) {
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format(
|
||||
"HH:mm:ss",
|
||||
)}: Excalidraw patching aborted because synched data.json is already patched`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format("HH:mm:ss")}: Excalidraw is starting the patching process`,
|
||||
);
|
||||
let i = 0;
|
||||
const excalidrawFiles = plugin.app.vault.getFiles();
|
||||
for (const f of (excalidrawFiles || []).filter((f: TFile) =>
|
||||
plugin.isExcalidrawFile(f),
|
||||
)) {
|
||||
if (
|
||||
f.extension !== "excalidraw" && //legacy files do not need to be touched
|
||||
plugin.app.workspace.getActiveFile() !== f
|
||||
) {
|
||||
//file is currently being edited
|
||||
try {
|
||||
const excalidrawData = new ExcalidrawData(plugin);
|
||||
const data = await plugin.app.vault.read(f);
|
||||
const textMode = getTextMode(data);
|
||||
await excalidrawData.loadData(data, f, textMode);
|
||||
|
||||
let trimLocation = data.search(/(^%%\n)?# Text Elements\n/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(/(%%\n)?# Drawing\n/);
|
||||
}
|
||||
if (trimLocation > -1) {
|
||||
let header = data
|
||||
.substring(0, trimLocation)
|
||||
.replace(
|
||||
/excalidraw-plugin:\s.*\n/,
|
||||
`${FRONTMATTER_KEY}: ${
|
||||
textMode == TextMode.raw ? "raw\n" : "parsed\n"
|
||||
}`,
|
||||
);
|
||||
|
||||
header = header.replace(
|
||||
/cssclass:[\s]*excalidraw-hide-preview-text[\s]*\n/,
|
||||
"",
|
||||
);
|
||||
|
||||
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
if (header.match(REG_IMG)) {
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
const newData = header + excalidrawData.generateMD();
|
||||
|
||||
if (data !== newData) {
|
||||
i++;
|
||||
log(`Excalidraw patched: ${f.path}`);
|
||||
await plugin.app.vault.modify(f, newData);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
errorlog({
|
||||
where: "OneOffs.wysiwygPatch",
|
||||
message: `Unable to process: ${f.path}`,
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.settings.runWYSIWYGpatch = false;
|
||||
plugin.settings.fixInfinitePreviewLoop = false;
|
||||
plugin.saveSettings();
|
||||
log(
|
||||
`${window
|
||||
.moment()
|
||||
.format("HH:mm:ss")}: Excalidraw patched in total ${i} files`,
|
||||
);
|
||||
}, 420000); //7 minutes
|
||||
}
|
||||
}
|
||||
|
||||
class MigrationPrompt extends Modal {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.titleEl.setText("Welcome to Excalidraw 1.2");
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
createForm(): void {
|
||||
const div = this.contentEl.createDiv();
|
||||
// div.addClass("excalidraw-prompt-div");
|
||||
// div.style.maxWidth = "600px";
|
||||
div.createEl("p", {
|
||||
text: "This version comes with tons of new features and possibilities. Please read the description in Community Plugins to find out more.",
|
||||
});
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
el.innerHTML =
|
||||
"Drawings you've created with version 1.1.x need to be converted to take advantage of the new features. You can also continue to use them in compatibility mode. " +
|
||||
"During conversion your old *.excalidraw files will be replaced with new *.excalidraw.md files.";
|
||||
});
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
//files manually follow one of two options:
|
||||
el.innerHTML =
|
||||
"To convert your drawings you have the following options:<br><ul>" +
|
||||
"<li>Click <code>CONVERT FILES</code> now to convert all of your *.excalidraw files, or if you prefer to make a backup first, then click <code>CANCEL</code>.</li>" +
|
||||
"<li>In the Command Palette select <code>Excalidraw: Convert *.excalidraw files to *.excalidraw.md files</code></li>" +
|
||||
"<li>Right click an <code>*.excalidraw</code> file in File Explorer and select one of the following options to convert files one by one: <ul>" +
|
||||
"<li><code>*.excalidraw => *.excalidraw.md</code></li>" +
|
||||
"<li><code>*.excalidraw => *.md (Logseq compatibility)</code>. This option will retain the original *.excalidraw file next to the new Obsidian format. " +
|
||||
"Make sure you also enable <code>Compatibility features</code> in Settings for a full solution.</li></ul></li>" +
|
||||
"<li>Open a drawing in compatibility mode and select <code>Convert to new format</code> from the <code>Options Menu</code></li></ul>";
|
||||
});
|
||||
div.createEl("p", {
|
||||
text: "This message will only appear maximum 3 times in case you have *.excalidraw files in your Vault.",
|
||||
});
|
||||
const bConvert = div.createEl("button", { text: "CONVERT FILES" });
|
||||
bConvert.onclick = () => {
|
||||
this.plugin.convertExcalidrawToMD();
|
||||
this.close();
|
||||
};
|
||||
const bCancel = div.createEl("button", { text: "CANCEL" });
|
||||
bCancel.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ImageElementNotice extends Modal {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private saveChanges: boolean = false;
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.titleEl.setText("Image Elements have arrived!");
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.contentEl.empty();
|
||||
if (!this.saveChanges) {
|
||||
return;
|
||||
}
|
||||
await this.plugin.loadSettings();
|
||||
this.plugin.settings.imageElementNotice = false;
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
createForm(): void {
|
||||
const div = this.contentEl.createDiv();
|
||||
//div.addClass("excalidraw-prompt-div");
|
||||
//div.style.maxWidth = "600px";
|
||||
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
el.innerHTML =
|
||||
"Welcome to Obsidian-Excalidraw 1.4! I've added Image Elements. " +
|
||||
"Please watch the video below to learn how to use this new feature.";
|
||||
});
|
||||
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
el.innerHTML =
|
||||
"<u>⚠ WARNING:</u> Opening new drawings with an older version of the plugin will lead to loss of images. " +
|
||||
"Update the plugin on all your devices.";
|
||||
});
|
||||
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
el.innerHTML =
|
||||
"Since March, I have spent most of my free time building this plugin. Close to 75 workdays worth of my time (assuming 8-hour days). " +
|
||||
"Some of you have already bought me a coffee. THANK YOU! Your support really means a lot to me! If you have not yet done so, please consider clicking the button below.";
|
||||
});
|
||||
|
||||
const coffeeDiv = div.createDiv("coffee");
|
||||
coffeeDiv.addClass("ex-coffee-div");
|
||||
const coffeeLink = coffeeDiv.createEl("a", {
|
||||
href: "https://ko-fi.com/zsolt",
|
||||
});
|
||||
const coffeeImg = coffeeLink.createEl("img", {
|
||||
attr: {
|
||||
src: "https://cdn.ko-fi.com/cdn/kofi3.png?v=3",
|
||||
},
|
||||
});
|
||||
coffeeImg.height = 45;
|
||||
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
//files manually follow one of two options:
|
||||
el.style.textAlign = "center";
|
||||
el.innerHTML =
|
||||
'<iframe width="560" height="315" src="https://www.youtube.com/embed/_c_0zpBJ4Xc?start=20" title="YouTube video player" ' +
|
||||
'frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" ' +
|
||||
"allowfullscreen></iframe>";
|
||||
});
|
||||
|
||||
div.createEl("p", { text: "" }, (el) => {
|
||||
//files manually follow one of two options:
|
||||
el.style.textAlign = "right";
|
||||
|
||||
const bOk = el.createEl("button", { text: "OK - Don't show this again" });
|
||||
bOk.onclick = () => {
|
||||
this.saveChanges = true;
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bCancel = el.createEl("button", {
|
||||
text: "CANCEL - Read next time",
|
||||
});
|
||||
bCancel.onclick = () => {
|
||||
this.saveChanges = false;
|
||||
this.close();
|
||||
};
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings, TextSelect } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { PenStyle } from "src/types/PenTypes";
|
||||
import { PenStyle } from "src/types/penTypes";
|
||||
|
||||
export const ICONS = {
|
||||
ExportImage: (
|
||||
@@ -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.onImageFilePathHook = (data) => {};
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
* onCanvasColorChangeHook: (
|
||||
@@ -1,8 +1,8 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { DeviceType } from "../types/types";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { ExcalidrawLib } from "../types/excalidrawLib";
|
||||
import { moment } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { DeviceType } from "src/types/types";
|
||||
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
|
||||
declare const PLUGIN_VERSION:string;
|
||||
export let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
|
||||
@@ -26,7 +26,7 @@ export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
declare const excalidrawLib: typeof ExcalidrawLib;
|
||||
|
||||
export const LOCALE = moment.locale();
|
||||
export const LOCALE = localStorage.getItem("language")?.toLowerCase() || "en";
|
||||
export const CJK_FONTS = "CJK Fonts";
|
||||
|
||||
export const obsidianToExcalidrawMap: { [key: string]: string } = {
|
||||
@@ -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;
|
||||
@@ -446,4 +446,4 @@ export const SCRIPTENGINE_ICON = `<g transform="translate(-8,-8)"><path d="M24.3
|
||||
export const DISK_ICON_NAME = "save";
|
||||
export const EXPORT_IMG_ICON = ` <g transform="scale(4.166)" strokeWidth="1.25" fill="none" stroke="currentColor"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M15 8h.01"></path><path d="M12 20h-5a3 3 0 0 1 -3 -3v-10a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v5"></path><path d="M4 15l4 -4c.928 -.893 2.072 -.893 3 0l4 4"></path><path d="M14 14l1 -1c.617 -.593 1.328 -.793 2.009 -.598"></path><path d="M19 16v6"></path><path d="M22 19l-3 3l-3 -3"></path></g>`;
|
||||
export const EXPORT_IMG_ICON_NAME = `export-img`;
|
||||
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;
|
||||
export const EXCALIDRAW_ICON = `<path d="M24 17h121v121H24z" style="fill:none" transform="matrix(.8843 0 0 .83471 -21.223 -14.19)"/><path d="M119.81 105.98a.549.549 0 0 0-.53-.12c-4.19-6.19-9.52-12.06-14.68-17.73l-.85-.93c0-.11-.05-.21-.12-.3a.548.548 0 0 0-.34-.2l-.17-.18-.12-.09c-.15-.32-.53-.56-.95-.35-1.58.81-3 1.97-4.4 3.04-1.87 1.43-3.7 2.92-5.42 4.52-.7.65-1.39 1.33-1.97 2.09-.28.37-.07.72.27.87-1.22 1.2-2.45 2.45-3.68 3.74-.11.12-.17.28-.16.44.01.16.09.31.22.41l2.16 1.65s.01.03.03.04c3.09 3.05 8.51 7.28 14.25 11.76.85.67 1.71 1.34 2.57 2.01.39.47.76.94 1.12 1.4.19.25.55.3.8.11.13.1.26.21.39.31a.57.57 0 0 0 .8-.1c.07-.09.1-.2.11-.31.04 0 .07.03.1.03.15 0 .31-.06.42-.18l10.18-11.12a.56.56 0 0 0-.04-.8l.01-.01Zm-29.23-3.85c.07.09.14.17.21.25 1.16.98 2.4 2.04 3.66 3.12l-5.12-3.91s-.32-.22-.52-.36c-.11-.08-.21-.16-.31-.24l-.38-.32s.07-.07.1-.11l.35-.35c1.72-1.74 4.67-4.64 6.19-6.06-1.61 1.62-4.87 6.37-4.17 7.98h-.01Zm17.53 13.81-4.22-3.22c-1.65-1.71-3.43-3.4-5.24-5.03 2.28 1.76 4.23 3.25 4.52 3.51 2.21 1.97 2.11 1.61 3.63 2.91l1.83 1.33c-.18.16-.36.33-.53.49l.01.01Zm1.06.81-.08-.06c.16-.13.33-.25.49-.38l-.4.44h-.01Zm-66.93-65.3c.14.72.27 1.43.4 2.11.69 3.7 1.33 7.03 2.55 9.56l.48 1.92c.19.73.46 1.64.71 1.83 2.85 2.52 7.22 6.28 11.89 9.82.21.16.5.15.7-.01.01.02.03.03.04.04.11.1.24.15.38.15.16 0 .31-.06.42-.19 5.98-6.65 10.43-12.12 13.6-16.7.2-.25.3-.54.29-.84.2-.24.41-.48.6-.68a.558.558 0 0 0-.1-.86.578.578 0 0 0-.17-.36c-1.39-1.34-2.42-2.31-3.46-3.28-1.84-1.72-3.74-3.5-7.77-7.51-.02-.02-.05-.04-.07-.06a.555.555 0 0 0-.22-.14c-1.11-.39-3.39-.78-6.26-1.28-4.22-.72-10-1.72-15.2-3.27h-.04v-.01s-.02 0-.03.02h-.01l.04-.02s-.31.01-.37.04c-.08.04-.14.09-.19.15-.05.06-.09.12-.47.2-.38.08.08 0 .11 0h-.11v.03c.07.34.05.58.16.97-.02.1.21 1.02.24 1.11l1.83 7.26h.03Zm30.95 6.54s-.03.04-.04.05l-.64-.71c.22.21.44.42.68.66Zm-7.09 9.39s-.07.08-.1.12l-.02-.02c.04-.03.08-.07.13-.1h-.01Zm-7.07 8.47Zm3.02-28.57c.35.35 1.74 1.65 2.06 1.97-1.45-.66-5.06-2.34-6.74-2.88 1.65.29 3.93.66 4.68.91Zm-19.18-2.77c.84 1.44 1.5 6.49 2.16 11.4-.37-1.58-.69-3.12-.99-4.6-.52-2.56-1-4.85-1.67-6.88.14.01.31.03.49.05 0 .01 0 .02.02.03h-.01Zm-.29-1.21c-.23-.02-.44-.04-.62-.05-.02-.04-.03-.08-.04-.12l.66.18v-.01Zm-2.22.45v-.02.02Zm78.54-1.18c.04-.23-1.1-1.24-.74-1.26.85-.04.86-1.35 0-1.31-1.13.06-2.27.32-3.37.53-1.98.37-3.95.78-5.92 1.21-4.39.94-8.77 1.93-13.1 3.11-1.36.37-2.86.7-4.11 1.36-.42.22-.4.67-.17.95-.09.05-.18.08-.28.09-.37.07-.74.13-1.11.19a.566.566 0 0 0-.39.86c-2.32 3.1-4.96 6.44-7.82 9.95-2.81 3.21-5.73 6.63-8.72 10.14-9.41 11.06-20.08 23.6-31.9 34.64-.23.21-.24.57-.03.8.05.06.12.1.19.13-.16.15-.32.3-.48.44-.1.09-.14.2-.16.32-.08.08-.16.17-.23.25-.21.23-.2.59.03.8.23.21.59.2.8-.03.04-.04.08-.09.12-.13a.84.84 0 0 1 1.22 0c.69.74 1.34 1.44 1.95 2.09l-1.38-1.15a.57.57 0 0 0-.8.07c-.2.24-.17.6.07.8l14.82 12.43c.11.09.24.13.37.13.15 0 .29-.06.4-.17l.36-.36a.56.56 0 0 0 .63-.12c20.09-20.18 36.27-35.43 54.8-49.06.17-.12.25-.32.23-.51a.57.57 0 0 0 .48-.39c3.42-10.46 4.08-19.72 4.28-24.27 0-.03.01-.05.02-.07.02-.05.03-.1.04-.14.03-.11.05-.19.05-.19.26-.78.17-1.53-.15-2.15v.02ZM82.98 58.94c.9-1.03 1.79-2.04 2.67-3.02-5.76 7.58-15.3 19.26-28.81 33.14 9.2-10.18 18.47-20.73 26.14-30.12Zm-32.55 52.81-.03-.03c.11.02.19.04.2.04a.47.47 0 0 0-.17 0v-.01Zm6.9 6.42-.05-.04.03-.03c.02 0 .03.02.04.02 0 .02-.02.03-.03.05h.01Zm8.36-7.21 1.38-1.44c.01.01.02.03.03.05-.47.46-.94.93-1.42 1.39h.01Zm2.24-2.21c.26-.3.56-.65.87-1.02.01-.01.02-.03.04-.04 3.29-3.39 6.68-6.82 10.18-10.25.02-.02.05-.04.07-.06.86-.66 1.82-1.39 2.72-2.08-4.52 4.32-9.11 8.78-13.88 13.46v-.01Zm21.65-55.88c-1.86 2.42-3.9 5.56-5.63 8.07-5.46 7.91-23.04 27.28-23.43 27.65-2.71 2.62-10.88 10.46-16.09 15.37-.14.13-.25.24-.34.35a.794.794 0 0 1 .03-1.13c24.82-23.4 39.88-42.89 46-51.38-.13.33-.24.69-.55 1.09l.01-.02Zm16.51 7.1-.01.02c0-.02-.02-.07.01-.02Zm-.91-5.13Zm-5.89 9.45c-2.26-1.31-3.32-3.27-2.71-5.25l.19-.66c.08-.19.17-.38.28-.57.59-.98 1.49-1.85 2.52-2.36.05-.02.1-.03.15-.04a.795.795 0 0 1-.04-.43c.05-.31.25-.58.66-.58.67 0 2.75.62 3.54 1.3.24.19.47.4.68.63.3.35.74.92.96 1.33.13.06.23.62.38.91.14.46.2.93.18 1.4 0 .02 0 .02.01.03-.03.07 0 .37-.04.4-.1.72-.36 1.43-.75 2.05-.04.05-.07.11-.11.16 0 .01-.02.02-.03.04-.3.43-.65.83-1.08 1.13-1.26.89-2.73 1.16-4.2.79a6.33 6.33 0 0 1-.57-.25l-.02-.03Zm16.27-1.63c-.49 2.05-1.09 4.19-1.8 6.38-.03.08-.03.16-.03.23-.1.01-.19.05-.27.11-4.44 3.26-8.73 6.62-12.98 10.11 3.67-3.32 7.39-6.62 11.23-9.95a6.409 6.409 0 0 0 2.11-3.74l.56-3.37.03-.1c.25-.71 1.34-.4 1.17.33h-.02Z" style="fill:currentColor;fill-rule:nonzero" transform="translate(-26.41 -29.49)"/>`;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
import { Extension } from "@codemirror/state";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
||||
import { debug, DEBUGGING } from "src/utils/DebugHelper";
|
||||
import { debug, DEBUGGING } from "src/utils/debugHelper";
|
||||
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
||||
|
||||
const editorExtensions: {[key:string]:Extension}= {
|
||||
@@ -1,14 +1,14 @@
|
||||
import "obsidian";
|
||||
//import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
//export ExcalidrawAutomate from "./ExcalidrawAutomate";
|
||||
//export {ExcalidrawAutomate} from "./ExcaildrawAutomate";
|
||||
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeRoundness, StrokeStyle } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
export type { Point } from "src/types/types";
|
||||
export const getEA = (view?:any): any => {
|
||||
try {
|
||||
return window.ExcalidrawAutomate.getAPI(view);
|
||||
} catch(e) {
|
||||
console.log({message: "Excalidraw not available", fn: getEA});
|
||||
return null;
|
||||
}
|
||||
import "obsidian";
|
||||
//import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
//export ExcalidrawAutomate from "./ExcalidrawAutomate";
|
||||
//export {ExcalidrawAutomate} from "./ExcaildrawAutomate";
|
||||
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeRoundness, StrokeStyle } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
export type { Point } from "src/types/types";
|
||||
export const getEA = (view?:any): any => {
|
||||
try {
|
||||
return window.ExcalidrawAutomate.getAPI(view);
|
||||
} catch(e) {
|
||||
console.log({message: "Excalidraw not available", fn: getEA});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,25 +16,23 @@ import {
|
||||
IMAGE_TYPES,
|
||||
DEVICE,
|
||||
sceneCoordsToViewportCoords,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
|
||||
fileid,
|
||||
} from "../../constants/constants";
|
||||
import ExcalidrawView, { TextMode } from "../../view/ExcalidrawView";
|
||||
import {
|
||||
REGEX_LINK,
|
||||
} from "../ExcalidrawData";
|
||||
} from "../../shared/ExcalidrawData";
|
||||
import { ExcalidrawSettings } from "../settings";
|
||||
import { openDialogAction, OpenFileDialog } from "../dialogs/OpenDrawing";
|
||||
import { InsertLinkDialog } from "../dialogs/InsertLinkDialog";
|
||||
import { InsertCommandDialog } from "../dialogs/InsertCommandDialog";
|
||||
import { InsertImageDialog } from "../dialogs/InsertImageDialog";
|
||||
import { ImportSVGDialog } from "../dialogs/ImportSVGDialog";
|
||||
import { InsertMDDialog } from "../dialogs/InsertMDDialog";
|
||||
import {
|
||||
ExcalidrawAutomate,
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "../ExcalidrawAutomate";
|
||||
import { templatePromt } from "../dialogs/Prompt";
|
||||
import { t } from "../lang/helpers";
|
||||
import { openDialogAction, OpenFileDialog } from "../../shared/Dialogs/OpenDrawing";
|
||||
import { InsertLinkDialog } from "../../shared/Dialogs/InsertLinkDialog";
|
||||
import { InsertCommandDialog } from "../../shared/Dialogs/InsertCommandDialog";
|
||||
import { InsertImageDialog } from "../../shared/Dialogs/InsertImageDialog";
|
||||
import { ImportSVGDialog } from "../../shared/Dialogs/ImportSVGDialog";
|
||||
import { InsertMDDialog } from "../../shared/Dialogs/InsertMDDialog";
|
||||
import { ExcalidrawAutomate } from "../../shared/ExcalidrawAutomate";
|
||||
import { insertLaTeXToView, search } from "src/utils/excalidrawAutomateUtils";
|
||||
import { templatePromt } from "../../shared/Dialogs/Prompt";
|
||||
import { t } from "../../lang/helpers";
|
||||
import {
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
@@ -45,32 +43,33 @@ import {
|
||||
getLink,
|
||||
getListOfTemplateFiles,
|
||||
getURLImageExtension,
|
||||
} from "../utils/FileUtils";
|
||||
} from "../../utils/fileUtils";
|
||||
import {
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "../utils/Utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, isObsidianThemeDark, mergeMarkdownFiles, setExcalidrawView } from "../utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ReleaseNotes } from "../dialogs/ReleaseNotes";
|
||||
import { ScriptInstallPrompt } from "../dialogs/ScriptInstallPrompt";
|
||||
import Taskbone from "../ocr/Taskbone";
|
||||
import { emulateCTRLClickForLinks, linkClickModifierType, PaneTarget } from "../utils/ModifierkeyHelper";
|
||||
import { InsertPDFModal } from "../dialogs/InsertPDFModal";
|
||||
import { ExportDialog } from "../dialogs/ExportDialog";
|
||||
import { UniversalInsertFileModal } from "../dialogs/UniversalInsertFileModal";
|
||||
import { PublishOutOfDateFilesDialog } from "../dialogs/PublishOutOfDateFiles";
|
||||
import { EmbeddableSettings } from "../dialogs/EmbeddableSettings";
|
||||
import { processLinkText } from "../utils/CustomEmbeddableUtils";
|
||||
import { getEA } from "src";
|
||||
} from "../../utils/utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, isObsidianThemeDark, mergeMarkdownFiles, setExcalidrawView } from "../../utils/obsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ReleaseNotes } from "../../shared/Dialogs/ReleaseNotes";
|
||||
import { ScriptInstallPrompt } from "../../shared/Dialogs/ScriptInstallPrompt";
|
||||
import Taskbone from "../../shared/OCR/Taskbone";
|
||||
import { emulateCTRLClickForLinks, linkClickModifierType, PaneTarget } from "../../utils/modifierkeyHelper";
|
||||
import { InsertPDFModal } from "../../shared/Dialogs/InsertPDFModal";
|
||||
import { ExportDialog } from "../../shared/Dialogs/ExportDialog";
|
||||
import { UniversalInsertFileModal } from "../../shared/Dialogs/UniversalInsertFileModal";
|
||||
import { PublishOutOfDateFilesDialog } from "../../shared/Dialogs/PublishOutOfDateFiles";
|
||||
import { EmbeddableSettings } from "../../shared/Dialogs/EmbeddableSettings";
|
||||
import { processLinkText } from "../../utils/customEmbeddableUtils";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile } from "../utils/CarveOut";
|
||||
import { showFrameSettings } from "../dialogs/FrameSettings";
|
||||
import { insertImageToView } from "../utils/ExcalidrawViewUtils";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile } from "../../utils/carveout";
|
||||
import { showFrameSettings } from "../../shared/Dialogs/FrameSettings";
|
||||
import { insertImageToView } from "../../utils/excalidrawViewUtils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { get } from "http";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -757,7 +756,6 @@ export class CommandManager {
|
||||
if (view) {
|
||||
if(!view.exportDialog) {
|
||||
view.exportDialog = new ExportDialog(this.plugin, view,view.file);
|
||||
view.exportDialog.createForm();
|
||||
}
|
||||
view.exportDialog.open();
|
||||
return true;
|
||||
@@ -1044,6 +1042,43 @@ export class CommandManager {
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "duplicate-image",
|
||||
name: t("DUPLICATE_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
if(els.length !== 1) {
|
||||
if(checking) return false;
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
const ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef?.file) return false;
|
||||
if(checking) return true;
|
||||
(async()=>{
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const isAnchored = Boolean(el.customData?.isAnchored);
|
||||
const imgId = await ea.addImage(el.x+el.width/5, el.y+el.height/5, ef.file,!isAnchored, isAnchored);
|
||||
const img = ea.getElement(imgId) as Mutable<ExcalidrawImageElement>;
|
||||
img.width = el.width;
|
||||
img.height = el.height;
|
||||
if(el.crop) img.crop = {...el.crop};
|
||||
const newFileId = fileid() as FileId;
|
||||
ea.imagesDict[newFileId] = ea.imagesDict[img.fileId];
|
||||
ea.imagesDict[newFileId].id = newFileId;
|
||||
delete ea.imagesDict[img.fileId]
|
||||
img.fileId = newFileId;
|
||||
await ea.addElementsToView(false, false, true);
|
||||
ea.selectElementsInView([imgId]);
|
||||
ea.destroy();
|
||||
})();
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -1,12 +1,12 @@
|
||||
import { WorkspaceLeaf, TFile, Editor, MarkdownView, MarkdownFileInfo, MetadataCache, App, EventRef, Menu, FileView } from "obsidian";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLink } from "../utils/FileUtils";
|
||||
import { editorInsertText, getParentOfClass, setExcalidrawView } from "../utils/ObsidianUtils";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { DEBUGGING, debug } from "src/utils/DebugHelper";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getLink } from "../../utils/fileUtils";
|
||||
import { editorInsertText, getExcalidrawViews, getParentOfClass, setExcalidrawView } from "../../utils/obsidianUtils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { DEBUGGING, debug } from "src/utils/debugHelper";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { DEVICE, FRONTMATTER_KEYS, ICON_NAME, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
/**
|
||||
@@ -81,6 +81,8 @@ export class EventManager {
|
||||
//save Excalidraw leaf and update embeds when switching to another leaf
|
||||
this.registerEvent(this.plugin.app.workspace.on("active-leaf-change", this.onActiveLeafChangeHandler.bind(this)));
|
||||
|
||||
this.registerEvent(this.app.workspace.on("layout-change", this.onLayoutChangeHandler.bind(this)));
|
||||
|
||||
//File Save Trigger Handlers
|
||||
//Save the drawing if the user clicks outside the Excalidraw Canvas
|
||||
const onClickEventSaveActiveDrawing = this.onClickSaveActiveDrawing.bind(this);
|
||||
@@ -101,6 +103,10 @@ export class EventManager {
|
||||
this.plugin.registerEvent(this.plugin.app.workspace.on("editor-menu", this.onEditorMenuHandler.bind(this)));
|
||||
}
|
||||
|
||||
private onLayoutChangeHandler() {
|
||||
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.refresh());
|
||||
}
|
||||
|
||||
private onPasteHandler (evt: ClipboardEvent, editor: Editor, info: MarkdownView | MarkdownFileInfo ) {
|
||||
if(evt.defaultPrevented) return
|
||||
const data = evt.clipboardData.getData("text/plain");
|
||||
@@ -1,15 +1,15 @@
|
||||
import { debug } from "src/utils/DebugHelper";
|
||||
import { debug } from "src/utils/debugHelper";
|
||||
import { App, FrontMatterCache, MarkdownView, MetadataCache, normalizePath, Notice, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { BLANK_DRAWING, DARK_BLANK_DRAWING, DEVICE, EXPORT_TYPES, FRONTMATTER, FRONTMATTER_KEYS, JSON_parse, nanoid, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import { Prompt, templatePromt } from "src/dialogs/Prompt";
|
||||
import { changeThemeOfExcalidrawMD, ExcalidrawData, getMarkdownDrawingSection } from "src/ExcalidrawData";
|
||||
import ExcalidrawView, { getTextMode } from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { DEBUGGING } from "src/utils/DebugHelper";
|
||||
import { checkAndCreateFolder, download, getIMGFilename, getLink, getListOfTemplateFiles, getNewUniqueFilepath } from "src/utils/FileUtils";
|
||||
import { PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { getExcalidrawViews, getNewOrAdjacentLeaf, isObsidianThemeDark, openLeaf } from "src/utils/ObsidianUtils";
|
||||
import { errorlog, getExportTheme } from "src/utils/Utils";
|
||||
import { Prompt, templatePromt } from "src/shared/Dialogs/Prompt";
|
||||
import { changeThemeOfExcalidrawMD, ExcalidrawData, getMarkdownDrawingSection } from "../../shared/ExcalidrawData";
|
||||
import ExcalidrawView, { getTextMode } from "src/view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { DEBUGGING } from "src/utils/debugHelper";
|
||||
import { checkAndCreateFolder, download, getIMGFilename, getLink, getListOfTemplateFiles, getNewUniqueFilepath } from "src/utils/fileUtils";
|
||||
import { PaneTarget } from "src/utils/modifierkeyHelper";
|
||||
import { getExcalidrawViews, getNewOrAdjacentLeaf, isObsidianThemeDark, openLeaf } from "src/utils/obsidianUtils";
|
||||
import { errorlog, getExportTheme } from "src/utils/utils";
|
||||
|
||||
export class PluginFileManager {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
@@ -324,7 +324,7 @@ export class PluginFileManager {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let outString = `# Excalidraw Data\n\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
import { debug, DEBUGGING } from "src/utils/DebugHelper";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { CustomMutationObserver } from "src/utils/DebugHelper";
|
||||
import { getExcalidrawViews, isObsidianThemeDark } from "src/utils/ObsidianUtils";
|
||||
import { debug, DEBUGGING } from "src/utils/debugHelper";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { CustomMutationObserver } from "src/utils/debugHelper";
|
||||
import { getExcalidrawViews, isObsidianThemeDark } from "src/utils/obsidianUtils";
|
||||
import { App, Notice, TFile } from "obsidian";
|
||||
|
||||
export class ObserverManager {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { updateExcalidrawLib } from "src/constants/constants";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { Packages } from "../types/types";
|
||||
import { debug, DEBUGGING } from "../utils/DebugHelper";
|
||||
import { ExcalidrawLib } from "../../types/excalidrawLib";
|
||||
import { Packages } from "../../types/types";
|
||||
import { debug, DEBUGGING } from "../../utils/debugHelper";
|
||||
import { Notice } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
declare let REACT_PACKAGES:string;
|
||||
declare let react:any;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WorkspaceWindow } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getAllWindowDocuments } from "./ObsidianUtils";
|
||||
import { DEBUGGING, debug } from "./DebugHelper";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { getAllWindowDocuments } from "../../utils/obsidianUtils";
|
||||
import { DEBUGGING, debug } from "../../utils/debugHelper";
|
||||
|
||||
export let REM_VALUE = 16;
|
||||
|
||||
@@ -10,38 +10,39 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { GITHUB_RELEASES, setRootElementSize } from "./constants/constants";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
import { PenStyle } from "./types/PenTypes";
|
||||
import { DynamicStyle, GridSettings } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import { GITHUB_RELEASES, setRootElementSize } from "src/constants/constants";
|
||||
import { t } from "src/lang/helpers";
|
||||
import type ExcalidrawPlugin from "src/core/main";
|
||||
import { PenStyle } from "src/types/penTypes";
|
||||
import { DynamicStyle, GridSettings } from "src/types/types";
|
||||
import { PreviewImageType } from "src/types/utilTypes";
|
||||
import { setDynamicStyle } from "src/utils/dynamicStyling";
|
||||
import {
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
} from "./utils/FileUtils";
|
||||
import { PENS } from "./utils/Pens";
|
||||
} from "src/utils/fileUtils";
|
||||
import { PENS } from "src/utils/pens";
|
||||
import {
|
||||
addIframe,
|
||||
fragWithHTML,
|
||||
setLeftHandedMode,
|
||||
} from "./utils/Utils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/EmbeddableMDFileCustomDataSettingsComponent";
|
||||
import { startupScript } from "./constants/starutpscript";
|
||||
import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
|
||||
import { setDebugging } from "./utils/DebugHelper";
|
||||
import { Rank } from "./menu/ActionIcons";
|
||||
} from "src/utils/utils";
|
||||
import { imageCache } from "src/shared/ImageCache";
|
||||
import { ConfirmationPrompt } from "src/shared/Dialogs/Prompt";
|
||||
import { EmbeddableMDCustomProps } from "src/shared/Dialogs/EmbeddableSettings";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "src/shared/Dialogs/EmbeddableMDFileCustomDataSettingsComponent";
|
||||
import { startupScript } from "src/constants/starutpscript";
|
||||
import { ModifierKeySet, ModifierSetType } from "src/utils/modifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "src/shared/Dialogs/ModifierKeySettings";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "src/utils/carveout";
|
||||
import { EDITOR_FADEOUT } from "src/core/editor/EditorHandler";
|
||||
import { setDebugging } from "src/utils/debugHelper";
|
||||
import { Rank } from "src/constants/actionIcons";
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { HotkeyEditor } from "./dialogs/HotkeyEditor";
|
||||
import { getExcalidrawViews } from "./utils/ObsidianUtils";
|
||||
import de from "./lang/locale/de";
|
||||
import { HotkeyEditor } from "src/shared/Dialogs/HotkeyEditor";
|
||||
import { getExcalidrawViews } from "src/utils/obsidianUtils";
|
||||
import { createSliderWithText } from "src/utils/sliderUtils";
|
||||
import { PDFExportSettingsComponent, PDFExportSettings } from "src/shared/Dialogs/PDFExportSettingsComponent";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -71,6 +72,7 @@ export interface ExcalidrawSettings {
|
||||
annotatePreserveSize: boolean;
|
||||
displaySVGInPreview: boolean; //No longer used since 1.9.13
|
||||
previewImageType: PreviewImageType; //Introduced with 1.9.13
|
||||
renderingConcurrency: number;
|
||||
allowImageCache: boolean;
|
||||
allowImageCacheInScene: boolean;
|
||||
displayExportedImageIfAvailable: boolean;
|
||||
@@ -171,6 +173,7 @@ export interface ExcalidrawSettings {
|
||||
showNewVersionNotification: boolean;
|
||||
//mathjaxSourceURL: string;
|
||||
latexBoilerplate: string;
|
||||
latexPreambleLocation: string;
|
||||
taskboneEnabled: boolean;
|
||||
taskboneAPIkey: string;
|
||||
pinnedScripts: string[];
|
||||
@@ -217,6 +220,7 @@ export interface ExcalidrawSettings {
|
||||
rank: Rank;
|
||||
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
|
||||
showSplashscreen: boolean;
|
||||
pdfSettings: PDFExportSettings;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -249,6 +253,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
renderingConcurrency: 3,
|
||||
allowImageCache: true,
|
||||
allowImageCacheInScene: true,
|
||||
displayExportedImageIfAvailable: false,
|
||||
@@ -344,6 +349,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
showNewVersionNotification: true,
|
||||
//mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
|
||||
latexBoilerplate: "\\color{blue}",
|
||||
latexPreambleLocation: "preamble.sty",
|
||||
taskboneEnabled: false,
|
||||
taskboneAPIkey: "",
|
||||
pinnedScripts: [],
|
||||
@@ -495,6 +501,15 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
{modifiers: ["Mod"], key:"G"},
|
||||
],
|
||||
showSplashscreen: true,
|
||||
pdfSettings: {
|
||||
pageSize: "A4",
|
||||
pageOrientation: "portrait",
|
||||
fitToPage: 1,
|
||||
paperColor: "white",
|
||||
customPaperColor: "#ffffff",
|
||||
alignment: "center",
|
||||
margin: "normal",
|
||||
},
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -1054,55 +1069,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEFAULT_PEN_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEFAULT_PEN_MODE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("never", "Never")
|
||||
.addOption("mobile", "On Obsidian Mobile")
|
||||
.addOption("always", "Always")
|
||||
.setValue(this.plugin.settings.defaultPenMode)
|
||||
.onChange(async (value: "never" | "always" | "mobile") => {
|
||||
this.plugin.settings.defaultPenMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DISABLE_DOUBLE_TAP_ERASER_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeDoubleTapEraser)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeDoubleTapEraser = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DISABLE_SINGLE_FINGER_PANNING_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeSingleFingerPanning)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeSingleFingerPanning = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeCrosshairVisible)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeCrosshairVisible = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
const readingModeEl = new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC")))
|
||||
@@ -1329,27 +1295,77 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
let zoomText: HTMLDivElement;
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ZOOM_TO_FIT_MAX_LEVEL_NAME"))
|
||||
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_MAX_LEVEL_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0.5, 10, 0.5)
|
||||
.setValue(this.plugin.settings.zoomToFitMaxLevel)
|
||||
.onChange(async (value) => {
|
||||
zoomText.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.zoomToFitMaxLevel = value;
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("ZOOM_TO_FIT_MAX_LEVEL_NAME"),
|
||||
desc: t("ZOOM_TO_FIT_MAX_LEVEL_DESC"),
|
||||
value: this.plugin.settings.zoomToFitMaxLevel,
|
||||
min: 0.5,
|
||||
max: 10,
|
||||
step: 0.5,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.zoomToFitMaxLevel = value;
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
})
|
||||
|
||||
// ------------------------------------------------
|
||||
// Pen
|
||||
// ------------------------------------------------
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("PEN_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEFAULT_PEN_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEFAULT_PEN_MODE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("never", "Never")
|
||||
.addOption("mobile", "On Obsidian Mobile")
|
||||
.addOption("always", "Always")
|
||||
.setValue(this.plugin.settings.defaultPenMode)
|
||||
.onChange(async (value: "never" | "always" | "mobile") => {
|
||||
this.plugin.settings.defaultPenMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
zoomText = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.zoomToFitMaxLevel.toString()}`;
|
||||
});
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DISABLE_DOUBLE_TAP_ERASER_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeDoubleTapEraser)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeDoubleTapEraser = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DISABLE_SINGLE_FINGER_PANNING_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeSingleFingerPanning)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeSingleFingerPanning = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeCrosshairVisible)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeCrosshairVisible = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
// ------------------------------------------------
|
||||
// Grid
|
||||
@@ -1398,28 +1414,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
// Grid opacity slider (hex value between 00 and FF)
|
||||
let opacityValue: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("GRID_OPACITY_NAME"))
|
||||
.setDesc(fragWithHTML(t("GRID_OPACITY_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0, 100, 1) // 0 to 100 in decimal
|
||||
.setValue(this.plugin.settings.gridSettings.OPACITY)
|
||||
.onChange(async (value) => {
|
||||
opacityValue.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.gridSettings.OPACITY = value;
|
||||
this.applySettingsUpdate();
|
||||
updateGridColor();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
opacityValue = el;
|
||||
el.style.minWidth = "3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.gridSettings.OPACITY}`;
|
||||
});
|
||||
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("GRID_OPACITY_NAME"),
|
||||
desc: t("GRID_OPACITY_DESC"),
|
||||
value: this.plugin.settings.gridSettings.OPACITY,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.gridSettings.OPACITY = value;
|
||||
this.applySettingsUpdate();
|
||||
updateGridColor();
|
||||
},
|
||||
minWidth: "3em",
|
||||
})
|
||||
|
||||
// ------------------------------------------------
|
||||
// Laser Pointer
|
||||
@@ -1440,47 +1448,33 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
let decayTime: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LASER_DECAY_TIME_NAME"))
|
||||
.setDesc(fragWithHTML(t("LASER_DECAY_TIME_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(500, 20000, 500)
|
||||
.setValue(this.plugin.settings.laserSettings.DECAY_TIME)
|
||||
.onChange(async (value) => {
|
||||
decayTime.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.laserSettings.DECAY_TIME = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
decayTime = el;
|
||||
el.style.minWidth = "3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_TIME.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("LASER_DECAY_TIME_NAME"),
|
||||
desc: t("LASER_DECAY_TIME_DESC"),
|
||||
value: this.plugin.settings.laserSettings.DECAY_TIME,
|
||||
min: 500,
|
||||
max: 20000,
|
||||
step: 500,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.laserSettings.DECAY_TIME = value;
|
||||
this.applySettingsUpdate();
|
||||
},
|
||||
minWidth: "3em",
|
||||
})
|
||||
|
||||
let decayLength: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LASER_DECAY_LENGTH_NAME"))
|
||||
.setDesc(fragWithHTML(t("LASER_DECAY_LENGTH_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(25, 2000, 25)
|
||||
.setValue(this.plugin.settings.laserSettings.DECAY_LENGTH)
|
||||
.onChange(async (value) => {
|
||||
decayLength.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.laserSettings.DECAY_LENGTH = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
decayLength = el;
|
||||
el.style.minWidth = "3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.laserSettings.DECAY_LENGTH.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("LASER_DECAY_LENGTH_NAME"),
|
||||
desc: t("LASER_DECAY_LENGTH_DESC"),
|
||||
value: this.plugin.settings.laserSettings.DECAY_LENGTH,
|
||||
min: 25,
|
||||
max: 2000,
|
||||
step: 25,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.laserSettings.DECAY_LENGTH = value;
|
||||
this.applySettingsUpdate();
|
||||
},
|
||||
minWidth: "3em",
|
||||
})
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
@@ -1489,47 +1483,31 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
});
|
||||
detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" });
|
||||
|
||||
let longPressDesktop: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_DESKTOP_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressDesktop)
|
||||
.onChange(async (value) => {
|
||||
longPressDesktop.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressDesktop = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressDesktop = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressDesktop.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("LONG_PRESS_DESKTOP_NAME"),
|
||||
desc: t("LONG_PRESS_DESKTOP_DESC"),
|
||||
value: this.plugin.settings.longPressDesktop,
|
||||
min: 300,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.longPressDesktop = value;
|
||||
this.applySettingsUpdate(true);
|
||||
},
|
||||
})
|
||||
|
||||
let longPressMobile: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_MOBILE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_MOBILE_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressMobile)
|
||||
.onChange(async (value) => {
|
||||
longPressMobile.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressMobile = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressMobile = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("LONG_PRESS_MOBILE_NAME"),
|
||||
desc: t("LONG_PRESS_MOBILE_DESC"),
|
||||
value: this.plugin.settings.longPressMobile,
|
||||
min: 300,
|
||||
max: 3000,
|
||||
step: 100,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.longPressMobile = value;
|
||||
this.applySettingsUpdate(true);
|
||||
},
|
||||
})
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DOUBLE_CLICK_LINK_OPEN_VIEW_MODE"))
|
||||
@@ -1701,26 +1679,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
);
|
||||
donePrefixSetting.setDisabled(!this.plugin.settings.parseTODO);
|
||||
|
||||
let opacityText: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LINKOPACITY_NAME"))
|
||||
.setDesc(fragWithHTML(t("LINKOPACITY_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0, 1, 0.05)
|
||||
.setValue(this.plugin.settings.linkOpacity)
|
||||
.onChange(async (value) => {
|
||||
opacityText.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.linkOpacity = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
opacityText = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.linkOpacity.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("LINKOPACITY_NAME"),
|
||||
desc: t("LINKOPACITY_DESC"),
|
||||
value: this.plugin.settings.linkOpacity,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.linkOpacity = value;
|
||||
this.applySettingsUpdate(true);
|
||||
},
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("HOVERPREVIEW_NAME"))
|
||||
@@ -1954,6 +1924,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("RENDERING_CONCURRENCY_NAME"),
|
||||
desc: t("RENDERING_CONCURRENCY_DESC"),
|
||||
min: 1,
|
||||
max: 5,
|
||||
step: 1,
|
||||
value: this.plugin.settings.renderingConcurrency,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.renderingConcurrency = value;
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_IMAGE_CACHE_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_IMAGE_CACHE_DESC")))
|
||||
@@ -2078,49 +2061,31 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
let scaleText: HTMLDivElement;
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("EXPORT_PNG_SCALE_NAME"),
|
||||
desc: t("EXPORT_PNG_SCALE_DESC"),
|
||||
value: this.plugin.settings.pngExportScale,
|
||||
min: 1,
|
||||
max: 5,
|
||||
step: 0.5,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.pngExportScale = value;
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXPORT_PNG_SCALE_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_PNG_SCALE_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(1, 5, 0.5)
|
||||
.setValue(this.plugin.settings.pngExportScale)
|
||||
.onChange(async (value) => {
|
||||
scaleText.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.pngExportScale = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
scaleText = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.pngExportScale.toString()}`;
|
||||
});
|
||||
|
||||
let exportPadding: HTMLDivElement;
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXPORT_PADDING_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_PADDING_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(0, 50, 5)
|
||||
.setValue(this.plugin.settings.exportPaddingSVG)
|
||||
.onChange(async (value) => {
|
||||
exportPadding.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.exportPaddingSVG = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
exportPadding = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.exportPaddingSVG.toString()}`;
|
||||
});
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("EXPORT_PADDING_NAME"),
|
||||
desc: fragWithHTML(t("EXPORT_PADDING_DESC")),
|
||||
value: this.plugin.settings.exportPaddingSVG,
|
||||
min: 0,
|
||||
max: 50,
|
||||
step: 5,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.exportPaddingSVG = value;
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
@@ -2166,6 +2131,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("PDF_EXPORT_SETTINGS"),
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
|
||||
new PDFExportSettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.pdfSettings,
|
||||
() => {
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
).render();
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EXPORT_HEAD"),
|
||||
@@ -2310,9 +2289,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
el.innerHTML = t("MD_EMBED_CUSTOMDATA_HEAD_DESC");
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.embeddableMarkdownDefaults,
|
||||
@@ -2461,27 +2437,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
let areaZoomText: HTMLDivElement;
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MAX_IMAGE_ZOOM_IN_NAME"))
|
||||
.setDesc(fragWithHTML(t("MAX_IMAGE_ZOOM_IN_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(1, 10, 0.5)
|
||||
.setValue(this.plugin.settings.areaZoomLimit)
|
||||
.onChange(async (value) => {
|
||||
areaZoomText.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.areaZoomLimit = value;
|
||||
this.applySettingsUpdate();
|
||||
this.plugin.excalidrawConfig.updateValues(this.plugin);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
areaZoomText = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.areaZoomLimit.toString()}`;
|
||||
createSliderWithText(detailsEl, {
|
||||
name: t("MAX_IMAGE_ZOOM_IN_NAME"),
|
||||
desc: fragWithHTML(t("MAX_IMAGE_ZOOM_IN_DESC")),
|
||||
value: this.plugin.settings.areaZoomLimit,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 0.5,
|
||||
onChange: (value) => {
|
||||
this.plugin.settings.areaZoomLimit = value;
|
||||
this.applySettingsUpdate();
|
||||
this.plugin.excalidrawConfig.updateValues(this.plugin);
|
||||
},
|
||||
});
|
||||
|
||||
detailsEl = nonstandardDetailsEl.createEl("details");
|
||||
@@ -2542,6 +2510,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.requestReloadDrawings = true;
|
||||
this.plugin.settings.experimentalEnableFourthFont = value;
|
||||
this.applySettingsUpdate();
|
||||
if(value) {
|
||||
this.plugin.initializeFonts();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2647,6 +2618,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LATEX_PREAMBLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LATEX_PREAMBLE_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: preamble.sty")
|
||||
.setValue(this.plugin.settings.latexPreambleLocation)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.latexPreambleLocation = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FILETYPE_NAME"))
|
||||
.setDesc(fragWithHTML(t("FILETYPE_DESC")))
|
||||
@@ -1,224 +0,0 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Modal, Setting, TFile } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/Utils";
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
private api: ExcalidrawImperativeAPI;
|
||||
public padding: number;
|
||||
public scale: number;
|
||||
public theme: string;
|
||||
public transparent: boolean;
|
||||
public saveSettings: boolean;
|
||||
public dirty: boolean = false;
|
||||
private selectedOnlySetting: Setting;
|
||||
private hasSelectedElements: boolean = false;
|
||||
private boundingBox: {
|
||||
topX: number;
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
public embedScene: boolean;
|
||||
public exportSelectedOnly: boolean;
|
||||
public saveToVault: boolean;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
private file: TFile,
|
||||
) {
|
||||
super(plugin.app);
|
||||
this.ea = getEA(this.view);
|
||||
this.api = this.ea.getExcalidrawAPI() as ExcalidrawImperativeAPI;
|
||||
this.padding = getExportPadding(this.plugin,this.file);
|
||||
this.scale = getPNGScale(this.plugin,this.file)
|
||||
this.theme = getExportTheme(this.plugin, this.file, (this.api).getAppState().theme)
|
||||
this.boundingBox = this.ea.getBoundingBox(this.ea.getViewElements());
|
||||
this.embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
this.exportSelectedOnly = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
this.saveSettings = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.ea.destroy();
|
||||
this.ea = null;
|
||||
this.view = null;
|
||||
this.file = null;
|
||||
this.api = null;
|
||||
this.theme = null;
|
||||
this.selectedOnlySetting = null;
|
||||
this.containerEl.remove();
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Export Image`);
|
||||
this.hasSelectedElements = this.view.getViewSelectedElements().length > 0;
|
||||
//@ts-ignore
|
||||
this.selectedOnlySetting.setVisibility(this.hasSelectedElements);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.dirty = this.saveSettings;
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
let scaleSetting:Setting;
|
||||
let paddingSetting: Setting;
|
||||
|
||||
this.contentEl.createEl("h1",{text: "Image settings"});
|
||||
this.contentEl.createEl("p",{text: "Transparency only affects PNGs. Excalidraw files can only be exported outside the Vault. PNGs copied to clipboard may not include the scene."})
|
||||
|
||||
const size = ():DocumentFragment => {
|
||||
const width = Math.round(this.scale*this.boundingBox.width + this.padding*2);
|
||||
const height = Math.round(this.scale*this.boundingBox.height + this.padding*2);
|
||||
return fragWithHTML(`The lager the scale, the larger the image.<br>Scale: <b>${this.scale}</b><br>Image size: <b>${width}x${height}</b>`);
|
||||
}
|
||||
|
||||
const padding = ():DocumentFragment => {
|
||||
return fragWithHTML(`Current image padding is <b>${this.padding}</b>`);
|
||||
}
|
||||
|
||||
paddingSetting = new Setting(this.contentEl)
|
||||
.setName("Image padding")
|
||||
.setDesc(padding())
|
||||
.addSlider(slider => {
|
||||
slider
|
||||
.setLimits(0,50,1)
|
||||
.setValue(this.padding)
|
||||
.onChange(value => {
|
||||
this.padding = value;
|
||||
scaleSetting.setDesc(size());
|
||||
paddingSetting.setDesc(padding());
|
||||
})
|
||||
})
|
||||
|
||||
scaleSetting = new Setting(this.contentEl)
|
||||
.setName("PNG Scale")
|
||||
.setDesc(size())
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0.5,5,0.5)
|
||||
.setValue(this.scale)
|
||||
.onChange(value => {
|
||||
this.scale = value;
|
||||
scaleSetting.setDesc(size());
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Export theme")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("light","Light")
|
||||
.addOption("dark","Dark")
|
||||
.setValue(this.theme)
|
||||
.onChange(value => {
|
||||
this.theme = value;
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Background color")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("transparent","Transparent")
|
||||
.addOption("with-color","Use scene background color")
|
||||
.setValue(this.transparent?"transparent":"with-color")
|
||||
.onChange(value => {
|
||||
this.transparent = value === "transparent";
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Save or one-time settings?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("save","Save these settings as the preset for this image")
|
||||
.addOption("one-time","These are one-time settings")
|
||||
.setValue(this.saveSettings?"save":"one-time")
|
||||
.onChange(value => {
|
||||
this.saveSettings = value === "save";
|
||||
})
|
||||
)
|
||||
|
||||
this.contentEl.createEl("h1",{text:"Export settings"});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Embed the Excalidraw scene in the exported file?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("embed","Embed scene")
|
||||
.addOption("no-embed","Do not embed scene")
|
||||
.setValue(this.embedScene?"embed":"no-embed")
|
||||
.onChange(value => {
|
||||
this.embedScene = value === "embed";
|
||||
})
|
||||
)
|
||||
|
||||
if(DEVICE.isDesktop) {
|
||||
new Setting(this.contentEl)
|
||||
.setName("Where to save the image?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("vault","Save image to your Vault")
|
||||
.addOption("outside","Export image outside your Vault")
|
||||
.setValue(this.saveToVault?"vault":"outside")
|
||||
.onChange(value => {
|
||||
this.saveToVault = value === "vault";
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
this.selectedOnlySetting = new Setting(this.contentEl)
|
||||
.setName("Export entire scene or just selected elements?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("all","Export entire scene")
|
||||
.addOption("selected","Export selected elements")
|
||||
.setValue(this.exportSelectedOnly?"selected":"all")
|
||||
.onChange(value => {
|
||||
this.exportSelectedOnly = value === "selected";
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bPNG = div.createEl("button", { text: "PNG to File", cls: "excalidraw-prompt-button"});
|
||||
bPNG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly))
|
||||
: this.view.exportPNG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
const bSVG = div.createEl("button", { text: "SVG to File", cls: "excalidraw-prompt-button" });
|
||||
bSVG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.saveSVG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly))
|
||||
: this.view.exportSVG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
const bExcalidraw = div.createEl("button", { text: "Excalidraw", cls: "excalidraw-prompt-button" });
|
||||
bExcalidraw.onclick = () => {
|
||||
this.view.exportExcalidraw(this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
if(DEVICE.isDesktop) {
|
||||
const bPNGClipboard = div.createEl("button", { text: "PNG to Clipboard", cls: "excalidraw-prompt-button" });
|
||||
bPNGClipboard.onclick = () => {
|
||||
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
export const FIRST_RUN = `
|
||||
The Excalidraw Obsidian plugin is much more than "just" a drawing tool. To help you get started here's a showcase of the key Excalidraw plugin features.
|
||||
|
||||
If you'd like to learn more, please subscribe to my YouTube channel: [Visual PKM](https://www.youtube.com/channel/UCC0gns4a9fhVkGkngvSumAQ) where I regularly share videos about Obsidian-Excalidraw and about tools and techniques for Visual Personal Knowledge Management.
|
||||
|
||||
Thank you & Enjoy!
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
export const RELEASE_NOTES: { [k: string]: string } = {
|
||||
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 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://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.0":`
|
||||
## Fixed
|
||||
- Various Markdown embeddable "fuzziness":
|
||||
- Fixed issues with appearance settings and edit mode toggling when single-click editing is enabled.
|
||||
- Ensured embeddable file editing no longer gets interrupted unexpectedly.
|
||||
- **Hover Preview**: Disabled hover preview for back-of-the-note cards to reduce distractions.
|
||||
- **Settings Save**: Fixed an issue where plugin settings unnecessarily saved on every startup.
|
||||
|
||||
## New Features
|
||||
- **Image Cropping Snaps to Objects**: When snapping is enabled in the scene, image cropping now aligns to nearby objects.
|
||||
- **Session Persistence for Pen Mode**: Excalidraw remembers the last pen mode when switching between drawings within the same session.
|
||||
|
||||
## Refactoring
|
||||
- **Mermaid Diagrams**: Excalidraw now uses its own Mermaid package, breaking future dependencies on Obsidian's Mermaid updates. This ensures stability and includes all fixes and improvements made to Excalidraw Mermaid since February 2024. The plugin file size has increased slightly, but this change significantly improves maintainability while remaining invisible to users.
|
||||
- **MathJax Optimization**: MathJax (LaTeX equation SVG image generation) now loads only on demand, with the package compressed to minimize the startup and file size impact caused by the inclusion of Mermaid.
|
||||
- **On-Demand Language Loading**: Non-English language files are now compressed and load only when needed, counterbalancing the increase in file size due to Mermaid and improving load speeds.
|
||||
- **Codebase Restructuring**: Improved type safety by removing many ${String.fromCharCode(96)}//@ts-ignore${String.fromCharCode(96)} commands and enhancing modularity. Introduced new management classes: **CommandManager**, **EventManager**, **PluginFileManager**, **ObserverManager**, and **PackageManager**. Further restructuring is planned for upcoming releases to improve maintainability and stability.
|
||||
`,
|
||||
"2.6.8":`
|
||||
## New
|
||||
- **QoL improvements**:
|
||||
- Obsidian-link search button in Element Link Editor.
|
||||
- Add Any File now searches file aliases as well.
|
||||
- Cosmetic changes to file search modals (display path, show file type icon).
|
||||
- Text Element cursor-color matches the text color.
|
||||
- New script in script store: [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) by [@TrillStones](https://github.com/TrillStones) 🙏
|
||||
|
||||
## Fixed
|
||||
- Excalidraw icon on the **ribbon menu kept reappearing** every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115)
|
||||
- In pen mode, when **single-finger panning** is enabled, Excalidraw should still **allow actions with the mouse**.
|
||||
- When **editing a drawing in split mode** (drawing is on one side, markdown view is on the other), editing the markdown note sometimes causes the drawing to re-zoom and jump away from the selected area.
|
||||
- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041)
|
||||
- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**.
|
||||
|
||||
`,
|
||||
"2.6.7":`
|
||||
Hoping to finally move on to 2.7.0... but still have one last bug to fix in 2.6.x!
|
||||
|
||||
## Fixed
|
||||
I misread a line in the Excalidraw package code... ended up breaking image loading in 2.6.6. The icon library script didn't work right, and updating nested drawings caused all images in the scene to be dropped from memory. This led to image-placeholders in exports and broke copy-paste to Excalidraw.com and between drawings. I am surprised no one reported it! 😳
|
||||
`,
|
||||
"2.6.6":`
|
||||
## Fixed
|
||||
- Images and LaTeX formulas did not update in the scene when the source was changed until the Excalidraw drawing was closed and reopened. [#2105](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2105)
|
||||
`,
|
||||
"2.6.5":`
|
||||
## Fixed
|
||||
- Text sizing issue in the drawing that is first loaded after Obsidian restarts [#2086](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2086)
|
||||
- Excalidraw didn't load if there was a file in the Excalidraw folder with a name that starts the same way as the Scripts folder name. [#2095](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2095)
|
||||
- **OVERSIZED EXCALIDRAW TOOLBAR**: Added a new setting under "Excalidraw Appearance and Behavior > Theme and Styling" called "Limit Obsidian Font Size to Editor Text." This setting is off by default. When enabled, it restricts Obsidian's custom font size adjustments to editor text only, preventing unintended scaling of Excalidraw UI elements and other themes that rely on the default interface font size. Feel free to experiment with this setting to improve Excalidraw UI consistency. However, because this change affects the broader Obsidian UI, it's recommended to turn it off if any layout issues arise. [#2087](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2087)`,
|
||||
"2.6.4":`
|
||||
## Fixed
|
||||
- Error saving when cropping images embedded from a URL (not from a file in the Vault) [#2096](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2096)
|
||||
`,
|
||||
"2.6.3":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/OfUWAvCgbXk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- **Cropping PDF Pages**
|
||||
- Improved PDF++ cropping: You can now double-click cropped images in Excalidraw to adjust the crop area, which will also appear as a highlight in PDF++. This feature applies to PDF cut-outs created in version 2.6.3 and beyond.
|
||||
- **Insert Last Active PDF Page as Image**
|
||||
- New command palette action lets you insert the currently active PDF page into Excalidraw. Ideal for setups with PDF and Excalidraw side-by-side. You can assign a hotkey for quicker access. Cropped areas in Excalidraw will show as highlights in PDF++.
|
||||
|
||||
## Fixed
|
||||
- Fixed **Close Settings** button toggle behavior [#2085](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2085)
|
||||
- Resolved text wrapping issues causing layout shifts due to trailing whitespaces [#8714](https://github.com/excalidraw/excalidraw/pull/8714)
|
||||
- **Aspect Ratio and Size Reset** commands now function correctly with cropped images.
|
||||
- **Cropped Drawings**: Adjustments to cropped Excalidraw drawings are now supported. However, for nested Excalidraw drawings, it's recommended to use area, group, and frame references instead of cropping.
|
||||
|
||||
## Refactoring
|
||||
- Further font loading optimizations on Excalidraw.com; no impact expected in Obsidian [#8693](https://github.com/excalidraw/excalidraw/pull/8693)
|
||||
- Text wrapping improvements [#8715](https://github.com/excalidraw/excalidraw/pull/8715)
|
||||
- Plugin initiation and error handling
|
||||
`,
|
||||
"2.6.2":`
|
||||
## Fixed
|
||||
- Image scaling issue with SVGs that miss the width and height property. [#8729](https://github.com/excalidraw/excalidraw/issues/8729)
|
||||
`,
|
||||
"2.6.1":`
|
||||
## New
|
||||
- Pen-mode single-finger panning enabled also for the "Selection" tool.
|
||||
- You can disable pen-mode single-finger panning in Plugin Settings under Excalidraw Appearance and Behavior [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
|
||||
|
||||
## Fixed
|
||||
- Text tool did not work in pen-mode using finger [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
|
||||
- Pasting images to Excalidraw from the web resulted in filenames of "image_1.png", "image_2.png" instead of "Pasted Image TIMESTAMP" [#2081](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2081)
|
||||
`,
|
||||
"2.6.0":`
|
||||
## Performance
|
||||
- Much faster plugin initialization. Down from 1000-3000ms to 100-300ms. According to my testing speed varies on a wide spectrum depending on device, size of Vault and other plugins being loaded. I measured values ranging from 84ms up to 782ms [#2068](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2068)
|
||||
- Faster loading of scenes with many embedded illustrations or PDF pages.
|
||||
- SVG export results in even smaller files by further optimizing which characters are included in the embedded fonts. [#8641](https://github.com/excalidraw/excalidraw/pull/8641)
|
||||
|
||||
## New
|
||||
- Image cropping tool. Double click the image to crop it. [#8613](https://github.com/excalidraw/excalidraw/pull/8613)
|
||||
- Single finger panning in pen mode.
|
||||
- Native handwritten CJK Font support [8530](https://github.com/excalidraw/excalidraw/pull/8530)
|
||||
- Created a new **Fonts** section in settings. This includes configuration of the "Local Font" and downloading of the CJK fonts in case you need them offline.
|
||||
- Option under **Appearance and Behavior / Link Click** to disable double-click link navigation in view mode. [#2075](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2075)
|
||||
- New RU translation 🙏[@tovBender](https://github.com/tovBender)
|
||||
|
||||
## Updated
|
||||
- CN translation 🙏[@dmscode](https://github.com/dmscode)
|
||||
`,
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CJK_FONTS,
|
||||
} from "src/constants/constants";
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
"Script is up to date - Click to reinstall",
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
DUPLICATE_IMAGE: "Duplicate selected image with a different image ID",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert markdown note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW: "Create new drawing",
|
||||
@@ -110,6 +111,7 @@ export default {
|
||||
SELECT_LINK_TO_OPEN: "Select a link to open",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
ERROR_CANT_READ_FILEPATH: "Error, can't read file path. Importing file instead",
|
||||
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
||||
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
|
||||
LINKLIST_SECOND_ORDER_LINK: "Second Order Link",
|
||||
@@ -384,13 +386,14 @@ FILENAME_HEAD: "Filename",
|
||||
"This setting will not affect the display of the drawing when you are in Excalidraw mode or when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
|
||||
"<li>See other related setting for <a href='#"+TAG_PDFEXPORT+"'>PDF Export</a> under 'Embedding and Exporting' further below.</li></ul><br>" +
|
||||
"You must close the active excalidraw/markdown file and reopen it for this change to take effect.",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render the file as an image when exporting an Excalidraw file to PDF",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render Excalidraw as Image in Obsidian PDF Export",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"This setting controls the behavior of Excalidraw when exporting an Excalidraw file to PDF in markdown view mode using Obsidian's <b>Export to PDF</b> feature.<br>" +
|
||||
"<ul><li>When <b>enabled</b> the PDF will show the Excalidraw drawing only;</li>" +
|
||||
"<li>When <b>disabled</b> the PDF will show the markdown side of the document.</li></ul>" +
|
||||
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearnace and Behavior' further above.<br>" +
|
||||
"⚠️ Note, you must close the active excalidraw/markdown file and reopen for this change to take effect. ⚠️",
|
||||
"This setting controls how Excalidraw files are exported to PDF using Obsidian's built-in <b>Export to PDF</b> feature.<br>" +
|
||||
"<ul><li><b>Enabled:</b> The PDF will include the Excalidraw drawing as an image.</li>" +
|
||||
"<li><b>Disabled:</b> The PDF will include the markdown content as text.</li></ul>" +
|
||||
"Note: This setting does not affect the PDF export feature within Excalidraw itself.<br>" +
|
||||
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearance and Behavior' further above.<br>" +
|
||||
"⚠️ You must close and reopen the Excalidraw/markdown file for changes to take effect. ⚠️",
|
||||
HOTKEY_OVERRIDE_HEAD: "Hotkey overrides",
|
||||
HOTKEY_OVERRIDE_DESC: `Some of the Excalidraw hotkeys such as <code>${labelCTRL()}+Enter</code> to edit text or <code>${labelCTRL()}+K</code> to create an element link ` +
|
||||
"conflict with Obsidian hotkey settings. The hotkey combinations you add below will override Obsidian's hotkey settings while useing Excalidraw, thus " +
|
||||
@@ -415,6 +418,7 @@ FILENAME_HEAD: "Filename",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Zoom to fit max ZOOM level",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_DESC:
|
||||
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
|
||||
PEN_HEAD: "Pen",
|
||||
GRID_HEAD: "Grid",
|
||||
GRID_DYNAMIC_COLOR_NAME: "Dynamic grid color",
|
||||
GRID_DYNAMIC_COLOR_DESC:
|
||||
@@ -577,7 +581,11 @@ FILENAME_HEAD: "Filename",
|
||||
EMBED_CANVAS_DESC:
|
||||
"Hide canvas node border and background when embedding an Excalidraw drawing to Canvas. " +
|
||||
"Note that for a full transparent background for your image, you will still need to configure Excalidraw to export images with transparent background.",
|
||||
EMBED_CACHING: "Image caching",
|
||||
EMBED_CACHING: "Image caching and rendering optimization",
|
||||
RENDERING_CONCURRENCY_NAME: "Image rendering concurrency",
|
||||
RENDERING_CONCURRENCY_DESC:
|
||||
"Number of parallel workers to use for image rendering. Increasing this number will speed up the rendering process, but may slow down the rest of the system. " +
|
||||
"The default value is 3. You can increase this number if you have a powerful system.",
|
||||
EXPORT_SUBHEAD: "Export Settings",
|
||||
EMBED_SIZING: "Image sizing",
|
||||
EMBED_THEME_BACKGROUND: "Image theme and background color",
|
||||
@@ -654,6 +662,7 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"Embed Excalidraw scene in exported image. Can be overridden at a file level by adding the <code>excalidraw-export-embed-scene: true/false<code> frontmatter key. " +
|
||||
"The setting only takes effect the next time you (re)open drawings.",
|
||||
PDF_EXPORT_SETTINGS: "PDF Export Settings",
|
||||
EXPORT_HEAD: "Auto-export Settings",
|
||||
EXPORT_SYNC_NAME:
|
||||
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
@@ -713,6 +722,8 @@ FILENAME_HEAD: "Filename",
|
||||
"restart Obsidian after closing settings, for this change to take effect.",
|
||||
LATEX_DEFAULT_NAME: "Default LaTeX formula for new equations",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
LATEX_PREAMBLE_NAME: "LaTeX preamble file (CasE SEnSiTivE!)",
|
||||
LATEX_PREAMBLE_DESC: "Full filepath to the preamble file, leave empty for default. If the file doesn't exist this option will be ignored.<br><strong>Important:</strong> Requires obsidian reload after change to take effect!",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
|
||||
@@ -973,4 +984,115 @@ FILENAME_HEAD: "Filename",
|
||||
//Utils.ts
|
||||
UPDATE_AVAILABLE: `A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is`,
|
||||
ERROR_PNG_TOO_LARGE: "Error exporting PNG - PNG too large, try a smaller resolution",
|
||||
|
||||
//modifierkeyHelper.ts
|
||||
// WebBrowserDragAction
|
||||
WEB_DRAG_IMPORT_IMAGE: "Import Image to Vault",
|
||||
WEB_DRAG_IMAGE_URL: "Insert Image or YouTube Thumbnail with URL",
|
||||
WEB_DRAG_LINK: "Insert Link",
|
||||
WEB_DRAG_EMBEDDABLE: "Insert Interactive-Frame",
|
||||
|
||||
// LocalFileDragAction
|
||||
LOCAL_DRAG_IMPORT: "Import external file or reuse existing file if path is from the Vault",
|
||||
LOCAL_DRAG_IMAGE: "Insert Image: with local URI or internal-link if from Vault",
|
||||
LOCAL_DRAG_LINK: "Insert Link: local URI or internal-link if from Vault",
|
||||
LOCAL_DRAG_EMBEDDABLE: "Insert Interactive-Frame: local URI or internal-link if from Vault",
|
||||
|
||||
// InternalDragAction
|
||||
INTERNAL_DRAG_IMAGE: "Insert Image",
|
||||
INTERNAL_DRAG_IMAGE_FULL: "Insert Image @100%",
|
||||
INTERNAL_DRAG_LINK: "Insert Link",
|
||||
INTERNAL_DRAG_EMBEDDABLE: "Insert Interactive-Frame",
|
||||
|
||||
// LinkClickAction
|
||||
LINK_CLICK_ACTIVE: "Open in current active window",
|
||||
LINK_CLICK_NEW_PANE: "Open in a new adjacent window",
|
||||
LINK_CLICK_POPOUT: "Open in a popout window",
|
||||
LINK_CLICK_NEW_TAB: "Open in a new tab",
|
||||
LINK_CLICK_MD_PROPS: "Show the Markdown image-properties dialog (only relevant if you have embedded a markdown document as an image)",
|
||||
|
||||
//ExportDialog
|
||||
// Dialog and tabs
|
||||
EXPORTDIALOG_TITLE: "Export Drawing",
|
||||
EXPORTDIALOG_TAB_IMAGE: "Image",
|
||||
EXPORTDIALOG_TAB_PDF: "PDF",
|
||||
// Settings persistence
|
||||
EXPORTDIALOG_SAVE_SETTINGS: "Save image settings to file doc.properties?",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_SAVE: "Save as preset",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_ONETIME: "One-time use",
|
||||
// Image settings
|
||||
EXPORTDIALOG_IMAGE_SETTINGS: "Image",
|
||||
EXPORTDIALOG_IMAGE_DESC: "PNG supports transparency. External files can include Excalidraw scene data.",
|
||||
EXPORTDIALOG_PADDING: "Padding",
|
||||
EXPORTDIALOG_SCALE: "Scale",
|
||||
EXPORTDIALOG_CURRENT_PADDING: "Current padding:",
|
||||
EXPORTDIALOG_SIZE_DESC: "Scale affects output size",
|
||||
EXPORTDIALOG_SCALE_VALUE: "Scale:",
|
||||
EXPORTDIALOG_IMAGE_SIZE: "Size:",
|
||||
// Theme and background
|
||||
EXPORTDIALOG_EXPORT_THEME: "Theme",
|
||||
EXPORTDIALOG_THEME_LIGHT: "Light",
|
||||
EXPORTDIALOG_THEME_DARK: "Dark",
|
||||
EXPORTDIALOG_BACKGROUND: "Background",
|
||||
EXPORTDIALOG_BACKGROUND_TRANSPARENT: "Transparent",
|
||||
EXPORTDIALOG_BACKGROUND_USE_COLOR: "Use scene color",
|
||||
// Selection
|
||||
EXPORTDIALOG_SELECTED_ELEMENTS: "Export",
|
||||
EXPORTDIALOG_SELECTED_ALL: "Entire scene",
|
||||
EXPORTDIALOG_SELECTED_SELECTED: "Selection only",
|
||||
// Export options
|
||||
EXPORTDIALOG_EMBED_SCENE: "Include scene data?",
|
||||
EXPORTDIALOG_EMBED_YES: "Yes",
|
||||
EXPORTDIALOG_EMBED_NO: "No",
|
||||
// PDF settings
|
||||
EXPORTDIALOG_PDF_SETTINGS: "PDF",
|
||||
EXPORTDIALOG_PAGE_SIZE: "Size",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION: "Orientation",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT: "Portrait",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE: "Page Fitting",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION: "Fit to page",
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION: "Fit to max 2-pages",
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION: "Fit to max 4-pages",
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION: "Fit to max 6-pages",
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION: "Fit to max 8-pages",
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION: "Fit to max 12-pages",
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION: "Fit to max 16-pages",
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION: "Use image scale (may span multiple pages)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
|
||||
EXPORTDIALOG_PDF_PAPER_SCENE: "Use scene color",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT: "Center Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT: "Center Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT: "Bottom Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER: "Bottom Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT: "Bottom Right",
|
||||
EXPORTDIALOG_PDF_MARGIN: "Margin",
|
||||
EXPORTDIALOG_PDF_MARGIN_NONE: "None",
|
||||
EXPORTDIALOG_PDF_MARGIN_TINY: "Small",
|
||||
EXPORTDIALOG_PDF_MARGIN_NORMAL: "Normal",
|
||||
EXPORTDIALOG_SAVE_PDF_SETTINGS: "Save PDF settings",
|
||||
EXPORTDIALOG_SAVE_CONFIRMATION: "PDF config saved to plugin settings as default",
|
||||
// Buttons
|
||||
EXPORTDIALOG_PNGTOFILE : "Export PNG",
|
||||
EXPORTDIALOG_SVGTOFILE : "Export SVG",
|
||||
EXPORTDIALOG_PNGTOVAULT : "PNG to Vault",
|
||||
EXPORTDIALOG_SVGTOVAULT : "SVG to Vault",
|
||||
EXPORTDIALOG_EXCALIDRAW: "Excalidraw",
|
||||
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG to Clipboard",
|
||||
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG to Clipboard",
|
||||
EXPORTDIALOG_PDF: "Export PDF",
|
||||
|
||||
EXPORTDIALOG_PDF_PROGRESS_NOTICE: "Exporting PDF. If this image is large, it may take a while.",
|
||||
EXPORTDIALOG_PDF_PROGRESS_DONE: "Export complete",
|
||||
EXPORTDIALOG_PDF_PROGRESS_ERROR: "Error exporting PDF, check developer console for details",
|
||||
|
||||
//exportUtils.ts
|
||||
PDF_EXPORT_DESKTOP_ONLY: "PDF export is only available on desktop",
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DEVICE, FRONTMATTER_KEYS, CJK_FONTS } from "src/constants/constants";
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
|
||||
|
||||
// русский
|
||||
export default {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { DEVICE, FRONTMATTER_KEYS, CJK_FONTS } from "src/constants/constants";
|
||||
import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEYS,
|
||||
CJK_FONTS
|
||||
} from "src/constants/constants";
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/modifierkeyHelper";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
// 简体中文
|
||||
@@ -25,6 +30,7 @@ export default {
|
||||
"脚本已是最新 - 点击重新安装",
|
||||
OPEN_AS_EXCALIDRAW: "打开为 Excalidraw 绘图",
|
||||
TOGGLE_MODE: "在 Excalidraw 和 Markdown 模式之间切换",
|
||||
DUPLICATE_IMAGE : "复制选定的图像,并分配一个不同的图像 ID",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "转换:空白 Markdown 文档 => Excalidraw 绘图文件",
|
||||
CONVERT_EXCALIDRAW: "转换: *.excalidraw => *.md",
|
||||
CREATE_NEW: "新建绘图文件",
|
||||
@@ -105,6 +111,7 @@ export default {
|
||||
SELECT_LINK_TO_OPEN: "选择要打开的链接",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
ERROR_CANT_READ_FILEPATH : "错误,无法读取文件路径。正在改为导入文件",
|
||||
NO_SEARCH_RESULT: "在绘图中未找到匹配的元素",
|
||||
FORCE_SAVE_ABORTED: "自动保存被中止,因为文件正在保存中",
|
||||
LINKLIST_SECOND_ORDER_LINK: "二级链接",
|
||||
@@ -190,7 +197,7 @@ export default {
|
||||
NEWVERSION_NOTIFICATION_DESC:
|
||||
"<b>开启:</b>当本插件存在可用更新时,显示通知。<br>" +
|
||||
"<b>关闭:</b>您需要手动检查本插件的更新(设置 - 第三方插件 - 检查更新)。",
|
||||
|
||||
|
||||
BASIC_HEAD: "基本",
|
||||
BASIC_DESC: `包括:更新说明,更新提示,新绘图文件、模板文件、脚本文件的存储路径等的设置。`,
|
||||
FOLDER_NAME: "Excalidraw 文件夹(區分大小寫!)",
|
||||
@@ -379,13 +386,14 @@ FILENAME_HEAD: "文件名",
|
||||
"此设置不会影响您在 Excalidraw 模式下的绘图显示,或者在将绘图嵌入 Markdown 文档时,或在渲染悬停预览时。<br><ul>" +
|
||||
"<li>请参阅下面‘嵌入和导出’部分的 <a href='#"+TAG_PDFEXPORT+"'>PDF 导出</a> 相关设置。</li></ul><br>" +
|
||||
"您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在将 Excalidraw 文件导出为 PDF 时将文件渲染为图像",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME : "在 Obsidian 中导出为 PDF 格式时将 Excalidraw 渲染为图像" ,
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"处于 Markdown 视图模式时,此设置控制 Excalidraw 在使用 Obsidian 的 <b>导出为 PDF</b> 功能时,将 Excalidraw 文件导出为 PDF 的行为。<br>" +
|
||||
"<ul><li>当 <b>启用</b> 时,PDF 将仅显示 Excalidraw 绘图;</li>" +
|
||||
"<li>当 <b>禁用</b> 时,PDF 将显示文档的 Markdown 部分(背景笔记)。</li></ul>" +
|
||||
"请参阅上面‘外观和行为’部分的 <<a href='#"+TAG_MDREADINGMODE+"'>>Markdown 阅读模式</a> 相关设置。" +
|
||||
"⚠️ 注意,您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。⚠️",
|
||||
"此设置控制在使用 Obsidian 内置的<b>导出为 PDF</b>功能,如何将 Excalidraw 文件导出为 PDF。<br>" +
|
||||
"<ul><li><b>启用:</b>PDF 将包含图像格式的 Excalidraw 绘图。</li>" +
|
||||
"<li><b>禁用:</b>PDF 将包含作为文本的 Markdown 内容。</li></ul>" +
|
||||
"注意:此设置不会影响 Excalidraw 本身的 PDF 导出功能。<br>" +
|
||||
"请参阅上方“外观和行为”部分中与<a href='#" + TAG_MDREADINGMODE + "'>Markdown 阅读模式</a>相关的其他设置。<br>" +
|
||||
"⚠️ 您必须关闭并重新打开 Excalidraw/Markdown 文件,设置更改才会生效。⚠️",
|
||||
HOTKEY_OVERRIDE_HEAD: "热键覆盖",
|
||||
HOTKEY_OVERRIDE_DESC: `一些 Excalidraw 的热键,例如 ${labelCTRL()}+Enter 用于编辑文本,或 ${labelCTRL()}+K 用于创建元素链接。` +
|
||||
"与 Obsidian 的热键设置发生冲突。您在下面添加的热键组合将在使用 Excalidraw 时覆盖 Obsidian 的热键设置," +
|
||||
@@ -400,7 +408,7 @@ FILENAME_HEAD: "文件名",
|
||||
DEFAULT_WHEELZOOM_NAME: "鼠标滚轮缩放页面",
|
||||
DEFAULT_WHEELZOOM_DESC:
|
||||
`<b>开启:</b>鼠标滚轮为缩放页面,${labelCTRL()}+鼠标滚轮为滚动页面</br><b>关闭:</b>鼠标滚轮为滚动页面,${labelCTRL()}+鼠标滚轮为缩放页面`,
|
||||
|
||||
|
||||
ZOOM_TO_FIT_NAME: "调节面板尺寸后自动缩放页面",
|
||||
ZOOM_TO_FIT_DESC: "调节面板尺寸后,自适应地缩放页面" +
|
||||
"<br><b>开启:</b>自动缩放。<br><b>关闭:</b>禁用自动缩放。",
|
||||
@@ -410,6 +418,7 @@ FILENAME_HEAD: "文件名",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
|
||||
ZOOM_TO_FIT_MAX_LEVEL_DESC:
|
||||
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.5(50%)且不能超过 10(1000%)。",
|
||||
PEN_HEAD: "手写笔",
|
||||
GRID_HEAD: "网格",
|
||||
GRID_DYNAMIC_COLOR_NAME: "动态网格颜色",
|
||||
GRID_DYNAMIC_COLOR_DESC:
|
||||
@@ -569,10 +578,14 @@ FILENAME_HEAD: "文件名",
|
||||
此外,还有自动导出 SVG 或 PNG 文件并保持与绘图文件状态同步的设置。`,
|
||||
EMBED_CANVAS: "Obsidian 白板支持",
|
||||
EMBED_CANVAS_NAME: "沉浸式嵌入",
|
||||
EMBED_CANVAS_DESC:
|
||||
EMBED_CANVAS_DESC:
|
||||
"当嵌入绘图到 Obsidian 白板中时,隐藏元素的边界和背景。" +
|
||||
"注意:如果想要背景完全透明,您依然需要在 Excalidraw 中设置“导出的图像不包含背景”。",
|
||||
EMBED_CACHING: "预览图缓存",
|
||||
EMBED_CACHING : "图像缓存和渲染优化" ,
|
||||
RENDERING_CONCURRENCY_NAME : "图像渲染并发性" ,
|
||||
RENDERING_CONCURRENCY_DESC :
|
||||
"用于图像渲染的并行工作线程数。增加此数值可以加快渲染速度,但可能会减慢系统的其他部分运行速度。" +
|
||||
"默认值为 3。如果您的系统性能强大,可以增加此数值。" ,
|
||||
EXPORT_SUBHEAD: "导出",
|
||||
EMBED_SIZING: "图像尺寸",
|
||||
EMBED_THEME_BACKGROUND: "图像的主题和背景色",
|
||||
@@ -580,7 +593,7 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_IMAGE_CACHE_DESC: "可提高下次嵌入的速度。" +
|
||||
"但如果绘图中又嵌入了子绘图,当子绘图改变时,您需要打开子绘图并手动保存,才能够更新父绘图的预览图。",
|
||||
SCENE_IMAGE_CACHE_NAME: "缓存场景中嵌套的 Excalidraw",
|
||||
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
|
||||
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
|
||||
"Excalidraw 将智能地尝试识别嵌套 Excalidraw 的子元素是否发生变化,并更新缓存。 " +
|
||||
"如果您怀疑缓存未能正确更新,您可能需要关闭此功能。",
|
||||
EMBED_IMAGE_CACHE_CLEAR: "清除缓存",
|
||||
@@ -624,7 +637,7 @@ FILENAME_HEAD: "文件名",
|
||||
"如果您选择了 PNG 或 SVG 副本,当副本不存在时,该命令将会插入一条损坏的链接,您需要打开绘图文件并手动导出副本才能修复 —— " +
|
||||
"也就是说,该选项不会自动帮您生成 PNG/SVG 副本,而只会引用已有的 PNG/SVG 副本。",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "将链接作为注释嵌入",
|
||||
EMBED_MARKDOWN_COMMENT_DESC:
|
||||
EMBED_MARKDOWN_COMMENT_DESC:
|
||||
"在图像下方以 Markdown 链接的形式嵌入原始 Excalidraw 文件的链接,例如:<code>%%[[drawing.excalidraw]]%%</code>。<br>" +
|
||||
"除了添加 Markdown 注释之外,您还可以选择嵌入的 SVG 或 PNG,并使用命令面板:" +
|
||||
"'<code>Excalidraw: 打开 Excalidraw 绘图</code>'来打开该绘图",
|
||||
@@ -649,6 +662,7 @@ FILENAME_HEAD: "文件名",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"在导出的图像中嵌入 Excalidraw 场景。可以通过在文件级别添加 <code>excalidraw-export-embed-scene: true/false</code> frontmatter 元数据键来覆盖此设置。" +
|
||||
"此设置仅在您下次(重新)打开绘图时生效。",
|
||||
PDF_EXPORT_SETTINGS : "PDF 导出设置",
|
||||
EXPORT_HEAD: "导出设置",
|
||||
EXPORT_SYNC_NAME:
|
||||
"保持 SVG/PNG 文件名与绘图文件同步",
|
||||
@@ -703,7 +717,7 @@ FILENAME_HEAD: "文件名",
|
||||
"文件浏览器等创建的绘图都将是旧格式(*.excalidraw)。" +
|
||||
"此外,您打开旧格式绘图文件时将不再收到警告消息。",
|
||||
MATHJAX_NAME: "MathJax (LaTeX) 的 javascript 库服务器",
|
||||
MATHJAX_DESC: "如果您在绘图中使用 LaTeX,插件需要从服务器获取并加载一个 javascript 库。" +
|
||||
MATHJAX_DESC: "如果您在绘图中使用 LaTeX,插件需要从服务器获取并加载一个 javascript 库。" +
|
||||
"如果您的网络无法访问某些库服务器,可以尝试通过此选项更换库服务器。"+
|
||||
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
|
||||
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
|
||||
@@ -722,7 +736,7 @@ FILENAME_HEAD: "文件名",
|
||||
EXPERIMENTAL_HEAD: "杂项",
|
||||
EXPERIMENTAL_DESC: `包括:默认的 LaTeX 公式,字段建议,绘图文件的类型标识符,OCR 等设置。`,
|
||||
EA_HEAD: "Excalidraw 自动化",
|
||||
EA_DESC:
|
||||
EA_DESC:
|
||||
"ExcalidrawAutomate 是用于 Excalidraw 自动化脚本的 API,但是目前说明文档还不够完善," +
|
||||
"建议阅读 <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/docs/API/ExcalidrawAutomate.d.ts'>ExcalidrawAutomate.d.ts</a> 文件源码," +
|
||||
"参考 <a href='https://zsviczian.github.io/obsidian-excalidraw-plugin/'>ExcalidrawAutomate How-to</a> 网页(不过该网页" +
|
||||
@@ -785,7 +799,7 @@ FILENAME_HEAD: "文件名",
|
||||
CJK_ASSETS_FOLDER_NAME: "CJK 字体文件夹(區分大小寫!)",
|
||||
CJK_ASSETS_FOLDER_DESC: `您可以在此设置 CJK 字体文件夹的位置。例如,您可以选择将其放置在 <code>Excalidraw/CJK Fonts</code> 下。<br><br>
|
||||
<strong>重要:</strong> 请勿将此文件夹设置为 Vault 根目录!请勿在此文件夹中放置其他字体。<br><br>
|
||||
<strong>注意:</strong> 如果您使用 Obsidian Sync 并希望在设备之间同步这些字体文件,请确保 Obsidian Sync 设置为同步“所有其他文件类型”。`,
|
||||
<strong>注意:</strong> 如果您使用 Obsidian Sync 并希望在设备之间同步这些字体文件,请确保 Obsidian Sync 设置为同步“所有其他文件类型”。`,
|
||||
LOAD_CHINESE_FONTS_NAME: "启动时从文件加载中文字体",
|
||||
LOAD_JAPANESE_FONTS_NAME: "启动时从文件加载日文字体",
|
||||
LOAD_KOREAN_FONTS_NAME: "启动时从文件加载韩文字体",
|
||||
@@ -800,7 +814,7 @@ FILENAME_HEAD: "文件名",
|
||||
TASKBONE_ENABLE_DESC: "启用意味着您同意 Taskbone <a href='https://www.taskbone.com/legal/terms/' target='_blank'>条款及细则</a> 以及 " +
|
||||
"<a href='https://www.taskbone.com/legal/privacy/' target='_blank'>隐私政策</a>。",
|
||||
TASKBONE_APIKEY_NAME: "Taskbone API Key",
|
||||
TASKBONE_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
|
||||
TASKBONE_APIKEY_DESC: "Taskbone 的免费 API key 提供了一定数量的每月识别次数。如果您非常频繁地使用此功能,或者想要支持 " +
|
||||
"Taskbone 的开发者(您懂的,没有人能用爱发电,Taskbone 开发者也需要投入资金来维持这项 OCR 服务)您可以" +
|
||||
"到 <a href='https://www.taskbone.com/' target='_blank'>taskbone.com</a> 购买一个商用 API key。购买后请将它填写到旁边这个文本框里,替换掉原本自动生成的免费 API key。",
|
||||
|
||||
@@ -968,4 +982,112 @@ FILENAME_HEAD: "文件名",
|
||||
//Utils.ts
|
||||
UPDATE_AVAILABLE: `Excalidraw 的新版本已在社区插件中可用。\n\n您正在使用 ${PLUGIN_VERSION}。\n最新版本是`,
|
||||
ERROR_PNG_TOO_LARGE: "导出 PNG 时出错 - PNG 文件过大,请尝试较小的分辨率",
|
||||
};
|
||||
|
||||
// ModifierkeyHelper.ts
|
||||
// WebBrowserDragAction
|
||||
WEB_DRAG_IMPORT_IMAGE : "导入图片到 Vault" ,
|
||||
WEB_DRAG_IMAGE_URL : "通过 URL 插入图片或 YouTube 缩略图" ,
|
||||
WEB_DRAG_LINK : "插入链接" ,
|
||||
WEB_DRAG_EMBEDDABLE : "插入交互框架" ,
|
||||
|
||||
// LocalFileDragAction
|
||||
LOCAL_DRAG_IMPORT : "导入外部文件,或在路径来自 Vault 时复用现有文件" ,
|
||||
LOCAL_DRAG_IMAGE : "插入图片:使用本地 URI,或在路径来自 Vault 时使用内部链接" ,
|
||||
LOCAL_DRAG_LINK : "插入链接:使用本地 URI,或在路径来自 Vault 时使用内部链接" ,
|
||||
LOCAL_DRAG_EMBEDDABLE : "插入交互框架:使用本地 URI,或在路径来自 Vault 时使用内部链接" ,
|
||||
|
||||
// InternalDragAction
|
||||
INTERNAL_DRAG_IMAGE : "插入图片" ,
|
||||
INTERNAL_DRAG_IMAGE_FULL : "插入图片(100% 尺寸)" ,
|
||||
INTERNAL_DRAG_LINK : "插入链接" ,
|
||||
INTERNAL_DRAG_EMBEDDABLE : "插入交互框架" ,
|
||||
|
||||
// LinkClickAction
|
||||
LINK_CLICK_ACTIVE : "在当前活动窗口中打开" ,
|
||||
LINK_CLICK_NEW_PANE : "在相邻的新窗口中打开" ,
|
||||
LINK_CLICK_POPOUT : "在弹出窗口中打开" ,
|
||||
LINK_CLICK_NEW_TAB : "在新标签页中打开" ,
|
||||
LINK_CLICK_MD_PROPS : "显示 Markdown 图片属性对话框(仅在嵌入 Markdown 文档为图片时适用)" ,
|
||||
|
||||
// 导出对话框
|
||||
// 对话框和标签页
|
||||
EXPORTDIALOG_TITLE : "导出图形",
|
||||
EXPORTDIALOG_TAB_IMAGE : "图像",
|
||||
EXPORTDIALOG_TAB_PDF : "PDF",
|
||||
// 设置持久化
|
||||
EXPORTDIALOG_SAVE_SETTINGS : "将图像设置保存到文件 doc.properties 吗?",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_SAVE : "保存为预设",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_ONETIME : "仅本次使用",
|
||||
// 图像设置
|
||||
EXPORTDIALOG_IMAGE_SETTINGS : "图像",
|
||||
EXPORTDIALOG_IMAGE_DESC : "PNG 支持透明。外部文件可以包含 Excalidraw 场景数据。",
|
||||
EXPORTDIALOG_PADDING : "边距",
|
||||
EXPORTDIALOG_SCALE : "缩放",
|
||||
EXPORTDIALOG_CURRENT_PADDING : "当前边距:",
|
||||
EXPORTDIALOG_SIZE_DESC : "缩放会影响输出大小",
|
||||
EXPORTDIALOG_SCALE_VALUE : "缩放:",
|
||||
EXPORTDIALOG_IMAGE_SIZE : "大小:",
|
||||
// 主题和背景
|
||||
EXPORTDIALOG_EXPORT_THEME : "主题",
|
||||
EXPORTDIALOG_THEME_LIGHT : "浅色",
|
||||
EXPORTDIALOG_THEME_DARK : "深色",
|
||||
EXPORTDIALOG_BACKGROUND : "背景",
|
||||
EXPORTDIALOG_BACKGROUND_TRANSPARENT : "透明",
|
||||
EXPORTDIALOG_BACKGROUND_USE_COLOR : "使用场景颜色",
|
||||
// 选择
|
||||
EXPORTDIALOG_SELECTED_ELEMENTS : "导出",
|
||||
EXPORTDIALOG_SELECTED_ALL : "整个场景",
|
||||
EXPORTDIALOG_SELECTED_SELECTED : "仅选中部分",
|
||||
// 导出选项
|
||||
EXPORTDIALOG_EMBED_SCENE : "包含场景数据吗?",
|
||||
EXPORTDIALOG_EMBED_YES : "是",
|
||||
EXPORTDIALOG_EMBED_NO : "否",
|
||||
// PDF 设置
|
||||
EXPORTDIALOG_PDF_SETTINGS : "PDF",
|
||||
EXPORTDIALOG_PAGE_SIZE : "页面大小",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION : "方向",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT : "纵向",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE : "横向",
|
||||
EXPORTDIALOG_PDF_DPI : "图像质量 [DPI]",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE : "页面适配",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION : "适配页面",
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION : "适配至 2 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION : "适配至 4 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION : "适配至 6 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION : "适配至 8 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION : "适配至 12 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION : "适配至 16 页" ,
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION : "使用图像缩放(可能跨多页)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR : "纸张颜色",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE : "白色",
|
||||
EXPORTDIALOG_PDF_PAPER_SCENE : "使用场景颜色",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM : "自定义颜色",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT : "页面位置",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER : "居中",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT : "左上角",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER : "顶部居中",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT : "右上角",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT : "左下角",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER : "底部居中",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT : "右下角",
|
||||
EXPORTDIALOG_PDF_MARGIN : "边距",
|
||||
EXPORTDIALOG_PDF_MARGIN_NONE : "无",
|
||||
EXPORTDIALOG_PDF_MARGIN_TINY : "小",
|
||||
EXPORTDIALOG_PDF_MARGIN_NORMAL : "正常",
|
||||
EXPORTDIALOG_SAVE_PDF_SETTINGS : "保存 PDF 设置",
|
||||
EXPORTDIALOG_SAVE_CONFIRMATION : "PDF 配置已保存为插件默认设置",
|
||||
// 按钮
|
||||
EXPORTDIALOG_PNGTOFILE : "导出 PNG 文件",
|
||||
EXPORTDIALOG_SVGTOFILE : "导出 SVG 文件",
|
||||
EXPORTDIALOG_PNGTOVAULT : "PNG 保存到 Vault",
|
||||
EXPORTDIALOG_SVGTOVAULT : "SVG 保存到 Vault",
|
||||
EXPORTDIALOG_EXCALIDRAW : "Excalidraw",
|
||||
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG 复制到剪贴板",
|
||||
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG 复制到剪贴板",
|
||||
EXPORTDIALOG_PDF : "导出 PDF 文件",
|
||||
EXPORTDIALOG_PDFTOVAULT : "PDF 保存到 Vault",
|
||||
|
||||
EXPORTDIALOG_PDF_PROGRESS_NOTICE : "正在导出页面" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_IMAGE : "的图像" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_DONE : "导出完成" ,
|
||||
};
|
||||
@@ -3,11 +3,37 @@ import { BinaryFileData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate, cloneElement } from "src/ExcalidrawAutomate";
|
||||
import { ExportSettings } from "src/ExcalidrawView";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { cloneElement } from "src/utils/excalidrawAutomateUtils";
|
||||
import { ExportSettings } from "src/view/ExcalidrawView";
|
||||
import { nanoid } from "src/constants/constants";
|
||||
import { svgToBase64 } from "../utils/utils";
|
||||
|
||||
/**
|
||||
* Creates a masked image from an Excalidraw scene.
|
||||
*
|
||||
* The scene must contain:
|
||||
* - One element.type="frame" element that defines the crop area
|
||||
* - One or more element.type="image" elements
|
||||
* - Zero or more non-image shape elements (rectangles, ellipses etc) that define the mask
|
||||
*
|
||||
* The class splits the scene into two parts:
|
||||
* 1. Images (managed in imageEA)
|
||||
* 2. Mask shapes (managed in maskEA)
|
||||
*
|
||||
* A transparent rectangle matching the combined bounding box is added to both
|
||||
* imageEA and maskEA to ensure consistent sizing between image and mask.
|
||||
*
|
||||
* For performance, if there is only one image, it is not rotated, and
|
||||
* its size matches the bounding box,
|
||||
* the image data is used directly from cache rather than regenerating.
|
||||
*
|
||||
* @example
|
||||
* const cropper = new CropImage(elements, files);
|
||||
* const pngBlob = await cropper.getCroppedPNG();
|
||||
* cropper.destroy();
|
||||
*/
|
||||
export class CropImage {
|
||||
private imageEA: ExcalidrawAutomate;
|
||||
private maskEA: ExcalidrawAutomate;
|
||||
@@ -105,10 +131,15 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
const images = this.imageEA.getElements().filter(el=>el.type === "image" && el.isDeleted === false);
|
||||
const isRotated = images.some(el=>el.angle !== 0);
|
||||
const imageDataURLs = Object.values(this.imageEA.imagesDict);
|
||||
if(!isRotated && images.length === 1 && imageDataURLs.length === 1) {
|
||||
const { width, height } = this.bbox;
|
||||
if(images[0].width === width && images[0].height === height) {
|
||||
//get image from the cache if mask is not bigger than the image, and if there is a single image element
|
||||
return imageDataURLs[0].dataURL;
|
||||
}
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
}
|
||||
@@ -170,7 +201,7 @@ export class CropImage {
|
||||
1 // image quality (0 - 1)
|
||||
);
|
||||
};
|
||||
image.src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
|
||||
image.src = svgToBase64(svgData);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Setting, ToggleComponent } from "obsidian";
|
||||
import { EmbeddableMDCustomProps } from "./EmbeddableSettings";
|
||||
import { fragWithHTML } from "src/utils/Utils";
|
||||
import { fragWithHTML } from "src/utils/utils";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ExcalidrawEmbeddableElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { Modal, Notice, Setting, TFile, ToggleComponent } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import { t } from "src/lang/helpers";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/Utils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/fileUtils";
|
||||
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/utils";
|
||||
import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent";
|
||||
import { isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper";
|
||||
import { isWinCTRLorMacCMD } from "src/utils/modifierkeyHelper";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
|
||||
export type EmbeddableMDCustomProps = {
|
||||
411
src/shared/Dialogs/ExportDialog.ts
Normal file
411
src/shared/Dialogs/ExportDialog.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Modal, Notice, Setting, TFile, ButtonComponent } from "obsidian";
|
||||
import { getEA } from "src/core";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/utils";
|
||||
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, exportSVGToClipboard } from "src/utils/exportUtils";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { PDFExportSettings, PDFExportSettingsComponent } from "./PDFExportSettingsComponent";
|
||||
|
||||
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
private api: ExcalidrawImperativeAPI;
|
||||
public padding: number;
|
||||
public scale: number;
|
||||
public theme: string;
|
||||
public transparent: boolean;
|
||||
public saveSettings: boolean;
|
||||
public dirty: boolean = false;
|
||||
private selectedOnlySetting: Setting;
|
||||
private hasSelectedElements: boolean = false;
|
||||
private boundingBox: {
|
||||
topX: number;
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
public embedScene: boolean;
|
||||
public exportSelectedOnly: boolean;
|
||||
public saveToVault: boolean;
|
||||
public pageSize: PageSize = "A4";
|
||||
public pageOrientation: PageOrientation = "portrait";
|
||||
private activeTab: "image" | "pdf" = "image";
|
||||
private contentContainer: HTMLDivElement;
|
||||
private buttonContainerRow1: HTMLDivElement;
|
||||
private buttonContainerRow2: HTMLDivElement;
|
||||
public fitToPage: number = 1;
|
||||
public paperColor: "white" | "scene" | "custom" = "white";
|
||||
public customPaperColor: string = "#ffffff";
|
||||
public alignment: PDFPageAlignment = "center";
|
||||
public margin: PDFPageMarginString = "normal";
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
private file: TFile,
|
||||
) {
|
||||
super(plugin.app);
|
||||
this.ea = getEA(this.view);
|
||||
this.api = this.ea.getExcalidrawAPI() as ExcalidrawImperativeAPI;
|
||||
this.padding = getExportPadding(this.plugin,this.file);
|
||||
this.scale = getPNGScale(this.plugin,this.file)
|
||||
this.theme = getExportTheme(this.plugin, this.file, (this.api).getAppState().theme)
|
||||
this.boundingBox = this.ea.getBoundingBox(this.ea.getViewElements());
|
||||
this.embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
this.exportSelectedOnly = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
|
||||
this.pageSize = plugin.settings.pdfSettings.pageSize;
|
||||
this.pageOrientation = plugin.settings.pdfSettings.pageOrientation;
|
||||
this.fitToPage = plugin.settings.pdfSettings.fitToPage;
|
||||
this.paperColor = plugin.settings.pdfSettings.paperColor;
|
||||
this.customPaperColor = plugin.settings.pdfSettings.customPaperColor;
|
||||
this.alignment = plugin.settings.pdfSettings.alignment;
|
||||
this.margin = plugin.settings.pdfSettings.margin;
|
||||
|
||||
this.saveSettings = false;
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.app = null;
|
||||
this.plugin = null;
|
||||
this.ea.destroy();
|
||||
this.ea = null;
|
||||
this.view = null;
|
||||
this.file = null;
|
||||
this.api = null;
|
||||
this.theme = null;
|
||||
this.selectedOnlySetting = null;
|
||||
this.containerEl.remove();
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(t("EXPORTDIALOG_TITLE"));
|
||||
this.hasSelectedElements = this.view.getViewSelectedElements().length > 0;
|
||||
//@ts-ignore
|
||||
this.selectedOnlySetting.setVisibility(this.hasSelectedElements);
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
this.dirty = this.saveSettings;
|
||||
}
|
||||
|
||||
createForm() {
|
||||
if(DEVICE.isDesktop) {
|
||||
// Create tab container
|
||||
const tabContainer = this.contentEl.createDiv("nav-buttons-container");
|
||||
const imageTab = tabContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_TAB_IMAGE"),
|
||||
cls: `nav-button ${this.activeTab === "image" ? "is-active" : ""}`
|
||||
});
|
||||
|
||||
|
||||
const pdfTab = tabContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_TAB_PDF"),
|
||||
cls: `nav-button ${this.activeTab === "pdf" ? "is-active" : ""}`
|
||||
});
|
||||
|
||||
// Tab click handlers
|
||||
imageTab.onclick = () => {
|
||||
this.activeTab = "image";
|
||||
imageTab.addClass("is-active");
|
||||
pdfTab.removeClass("is-active");
|
||||
this.renderContent();
|
||||
};
|
||||
|
||||
pdfTab.onclick = () => {
|
||||
this.activeTab = "pdf";
|
||||
pdfTab.addClass("is-active");
|
||||
imageTab.removeClass("is-active");
|
||||
this.renderContent();
|
||||
};
|
||||
}
|
||||
|
||||
// Create content container
|
||||
this.contentContainer = this.contentEl.createDiv();
|
||||
this.buttonContainerRow1 = this.contentEl.createDiv({cls: "excalidraw-export-buttons-div"});
|
||||
this.buttonContainerRow2 = this.contentEl.createDiv({cls: "excalidraw-export-buttons-div"});
|
||||
this.buttonContainerRow2.style.marginTop = "10px";
|
||||
|
||||
this.renderContent();
|
||||
}
|
||||
|
||||
private createSaveSettingsDropdown() {
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SAVE_SETTINGS"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("save", t("EXPORTDIALOG_SAVE_SETTINGS_SAVE"))
|
||||
.addOption("one-time", t("EXPORTDIALOG_SAVE_SETTINGS_ONETIME"))
|
||||
.setValue(this.saveSettings ? "save" : "one-time")
|
||||
.onChange(value => {
|
||||
this.saveSettings = value === "save";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
this.contentContainer.empty();
|
||||
this.buttonContainerRow1.empty();
|
||||
this.buttonContainerRow2.empty();
|
||||
|
||||
if (this.activeTab === "image") {
|
||||
this.createImageSettings();
|
||||
this.createExportSettings();
|
||||
this.createImageButtons();
|
||||
} else {
|
||||
this.createImageSettings();
|
||||
this.createPDFSettings();
|
||||
this.createPDFButton();
|
||||
}
|
||||
}
|
||||
|
||||
private createImageSettings() {
|
||||
let scaleSetting:Setting;
|
||||
let paddingSetting: Setting;
|
||||
|
||||
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_IMAGE_SETTINGS")});
|
||||
this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_IMAGE_DESC")})
|
||||
|
||||
this.createSaveSettingsDropdown();
|
||||
|
||||
const size = ():DocumentFragment => {
|
||||
const width = Math.round(this.scale*this.boundingBox.width + this.padding*2);
|
||||
const height = Math.round(this.scale*this.boundingBox.height + this.padding*2);
|
||||
return fragWithHTML(`${t("EXPORTDIALOG_SIZE_DESC")}<br>${t("EXPORTDIALOG_SCALE_VALUE")} <b>${this.scale}</b><br>${t("EXPORTDIALOG_IMAGE_SIZE")} <b>${width}x${height}</b>`);
|
||||
}
|
||||
|
||||
const padding = ():DocumentFragment => {
|
||||
return fragWithHTML(`${t("EXPORTDIALOG_CURRENT_PADDING")} <b>${this.padding}</b>`);
|
||||
}
|
||||
|
||||
paddingSetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PADDING"))
|
||||
.setDesc(padding())
|
||||
.addSlider(slider => {
|
||||
slider
|
||||
.setLimits(0,100,1)
|
||||
.setValue(this.padding)
|
||||
.onChange(value => {
|
||||
this.padding = value;
|
||||
scaleSetting.setDesc(size());
|
||||
paddingSetting.setDesc(padding());
|
||||
})
|
||||
})
|
||||
|
||||
scaleSetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SCALE"))
|
||||
.setDesc(size())
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0.2,7,0.1)
|
||||
.setValue(this.scale)
|
||||
.onChange(value => {
|
||||
this.scale = value;
|
||||
scaleSetting.setDesc(size());
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_EXPORT_THEME"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("light", t("EXPORTDIALOG_THEME_LIGHT"))
|
||||
.addOption("dark", t("EXPORTDIALOG_THEME_DARK"))
|
||||
.setValue(this.theme)
|
||||
.onChange(value => {
|
||||
this.theme = value;
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_BACKGROUND"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("transparent", t("EXPORTDIALOG_BACKGROUND_TRANSPARENT"))
|
||||
.addOption("with-color", t("EXPORTDIALOG_BACKGROUND_USE_COLOR"))
|
||||
.setValue(this.transparent?"transparent":"with-color")
|
||||
.onChange(value => {
|
||||
this.transparent = value === "transparent";
|
||||
})
|
||||
)
|
||||
|
||||
this.selectedOnlySetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SELECTED_ELEMENTS"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("all", t("EXPORTDIALOG_SELECTED_ALL"))
|
||||
.addOption("selected", t("EXPORTDIALOG_SELECTED_SELECTED"))
|
||||
.setValue(this.exportSelectedOnly?"selected":"all")
|
||||
.onChange(value => {
|
||||
this.exportSelectedOnly = value === "selected";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createExportSettings() {
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_EMBED_SCENE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("embed",t("EXPORTDIALOG_EMBED_YES"))
|
||||
.addOption("no-embed",t("EXPORTDIALOG_EMBED_NO"))
|
||||
.setValue(this.embedScene?"embed":"no-embed")
|
||||
.onChange(value => {
|
||||
this.embedScene = value === "embed";
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private createPDFSettings() {
|
||||
if (!DEVICE.isDesktop) return;
|
||||
|
||||
this.contentContainer.createEl("h1", { text: t("EXPORTDIALOG_PDF_SETTINGS") });
|
||||
|
||||
const pdfSettings: PDFExportSettings = {
|
||||
pageSize: this.pageSize,
|
||||
pageOrientation: this.pageOrientation,
|
||||
fitToPage: this.fitToPage,
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin,
|
||||
};
|
||||
|
||||
new PDFExportSettingsComponent(
|
||||
this.contentContainer,
|
||||
pdfSettings,
|
||||
() => {
|
||||
this.pageSize = pdfSettings.pageSize;
|
||||
this.pageOrientation = pdfSettings.pageOrientation;
|
||||
this.fitToPage = pdfSettings.fitToPage;
|
||||
this.paperColor = pdfSettings.paperColor;
|
||||
this.customPaperColor = pdfSettings.customPaperColor;
|
||||
this.alignment = pdfSettings.alignment;
|
||||
this.margin = pdfSettings.margin;
|
||||
}
|
||||
).render();
|
||||
}
|
||||
|
||||
private createImageButtons() {
|
||||
if(DEVICE.isDesktop) {
|
||||
const bPNG = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOFILE"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNG.onclick = () => {
|
||||
this.view.exportPNG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
const bPNGVault = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOVAULT"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNGVault.onclick = () => {
|
||||
this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bPNGClipboard = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOCLIPBOARD"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNGClipboard.onclick = async () => {
|
||||
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
|
||||
if(DEVICE.isDesktop) {
|
||||
const bExcalidraw = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_EXCALIDRAW"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bExcalidraw.onclick = () => {
|
||||
this.view.exportExcalidraw();
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bSVG = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOFILE"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVG.onclick = () => {
|
||||
this.view.exportSVG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
const bSVGVault = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOVAULT"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVGVault.onclick = () => {
|
||||
this.view.saveSVG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bSVGClipboard = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOCLIPBOARD"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVGClipboard.onclick = async () => {
|
||||
const svg = await this.view.getSVG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
exportSVGToClipboard(svg);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
private createPDFButton() {
|
||||
const bSavePDFSettings = this.buttonContainerRow1.createEl("button",
|
||||
{ text: t("EXPORTDIALOG_SAVE_PDF_SETTINGS"), cls: "excalidraw-export-button" }
|
||||
);
|
||||
bSavePDFSettings.onclick = async () => {
|
||||
//in case sync loaded a new version of settings in the mean time
|
||||
await this.plugin.loadSettings();
|
||||
this.plugin.settings.pdfSettings = {
|
||||
pageSize: this.pageSize,
|
||||
pageOrientation: this.pageOrientation,
|
||||
fitToPage: this.fitToPage,
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin,
|
||||
};
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(t("EXPORTDIALOG_SAVE_CONFIRMATION"));
|
||||
};
|
||||
|
||||
if (!DEVICE.isDesktop) return;
|
||||
const bPDFExport = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PDF"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPDFExport.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
public getPaperColor(): string {
|
||||
switch (this.paperColor) {
|
||||
case "white": return this.theme === "light" ? "#ffffff" : "#000000";
|
||||
case "scene": return this.api.getAppState().viewBackgroundColor;
|
||||
case "custom": return this.customPaperColor;
|
||||
default: return "#ffffff";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export const showFrameSettings = (ea: ExcalidrawAutomate) => {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { BaseComponent, Setting, Modifier } from 'obsidian';
|
||||
import { DEVICE } from 'src/constants/constants';
|
||||
import { t } from 'src/lang/helpers';
|
||||
import { ExcalidrawSettings } from 'src/settings';
|
||||
import { modifierLabel } from 'src/utils/ModifierkeyHelper';
|
||||
import { fragWithHTML } from 'src/utils/Utils';
|
||||
import { ExcalidrawSettings } from 'src/core/settings';
|
||||
import { modifierLabel } from 'src/utils/modifierkeyHelper';
|
||||
import { fragWithHTML } from 'src/utils/utils';
|
||||
|
||||
export class HotkeyEditor extends BaseComponent {
|
||||
private settings: ExcalidrawSettings;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../../constants/constants";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
|
||||
export class ImportSVGDialog extends FuzzySuggestModal<TFile> {
|
||||
public plugin: ExcalidrawPlugin;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../../constants/constants";
|
||||
import { t } from "../../lang/helpers";
|
||||
|
||||
export class InsertCommandDialog extends FuzzySuggestModal<TFile> {
|
||||
private addText: Function;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { scaleToFullsizeModifier } from "src/utils/ModifierkeyHelper";
|
||||
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getEA } from "src";
|
||||
import { FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { scaleToFullsizeModifier } from "src/utils/modifierkeyHelper";
|
||||
import { DEVICE, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS } from "../../constants/constants";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { getEA } from "src/core";
|
||||
|
||||
export class InsertImageDialog extends FuzzySuggestModal<TFile> {
|
||||
public plugin: ExcalidrawPlugin;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { FuzzyMatch, FuzzySuggestModal, setIcon } from "obsidian";
|
||||
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getLink } from "src/utils/FileUtils";
|
||||
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../../constants/constants";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { getLink } from "src/utils/fileUtils";
|
||||
import { LinkSuggestion } from "src/types/types";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getEA } from "src";
|
||||
import { FuzzySuggestModal, TFile } from "obsidian";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { getEA } from "src/core";
|
||||
|
||||
export class InsertMDDialog extends FuzzySuggestModal<TFile> {
|
||||
public plugin: ExcalidrawPlugin;
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ButtonComponent, TFile, ToggleComponent } from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getPDFDoc } from "src/utils/FileUtils";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { getPDFDoc } from "src/utils/fileUtils";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { FileSuggestionModal } from "../Suggesters/FileSuggestionModal";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
279
src/shared/Dialogs/Messages.ts
Normal file
279
src/shared/Dialogs/Messages.ts
Normal file
@@ -0,0 +1,279 @@
|
||||
export const FIRST_RUN = `
|
||||
The Excalidraw Obsidian plugin is much more than "just" a drawing tool. To help you get started here's a showcase of the key Excalidraw plugin features.
|
||||
|
||||
If you'd like to learn more, please subscribe to my YouTube channel: [Visual PKM](https://www.youtube.com/channel/UCC0gns4a9fhVkGkngvSumAQ) where I regularly share videos about Obsidian-Excalidraw and about tools and techniques for Visual Personal Knowledge Management.
|
||||
|
||||
Thank you & Enjoy!
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
export const RELEASE_NOTES: { [k: string]: string } = {
|
||||
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 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://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.8.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/tWi5xTUTz7E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Updated "Export Image" dialog
|
||||
- 🚀 PDF Export option including tiling of images over multiple pages. Only available on desktop :(
|
||||
- SVG to clipboard
|
||||
- More granular setting for padding and scale
|
||||
- Slideshow script can now print slides to PDF (update script from script store)
|
||||
- Set local graph to show the links in the embeddable when it is activated/deactivated [#2200](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2200)
|
||||
|
||||
## Fixed
|
||||
- Fixed several LaTeX issues. 🙏 @Sintuz [#1631](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1631), [#2195](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195), [#1842](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1842)
|
||||
- Fixed support for *.jfif and *.avif images [#2212](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2212)
|
||||
- PDF++ selection is not correctly showing after embedded into a drawing (for some specific files) [#2213](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2213)
|
||||
- iOS 18 can't upload image and library [#2182](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2182)
|
||||
- Image block references are broken in hover previews [#2218](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2218)
|
||||
- ⚠️ Note there is a known issue in Obsidian 1.8.2 ⚠️ affecting preview windows in Excalidraw. I received confirmation that this will be fixed in 1.8.3. For now, if hover previews are important to you, you can downgrade to Obsidian 1.8.1 [#2228](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2225)
|
||||
- Mobile elements panel and context menu are not scrollable [#2216](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2216)
|
||||
- "Local Font" menu disappears when opening a drawing in an Obsidian popout-window [#2205](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2205)
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Pressing delete on a frame will only delete the children [#9011](https://github.com/excalidraw/excalidraw/pull/9011)
|
||||
- New crowfoot arrowheads and a new arrowhead picker [#8942](https://github.com/excalidraw/excalidraw/pull/8942)
|
||||
- Fixed some of the arrow binding issues [#9010](https://github.com/excalidraw/excalidraw/pull/9010), [#2209](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2209)
|
||||
- New context menu action: "Wrap selection in frame" [#9005](https://github.com/excalidraw/excalidraw/pull/9005)
|
||||
- Elbow arrow segment fixing and positioning [#8952](https://github.com/excalidraw/excalidraw/pull/8952)
|
||||
- When drag creating a new frame, do not add a partial group to it. When wrapping a selected partial group in a frame however, do add it to the wrapping frame. But such that it should be separated from the previous containing group. [#9014](https://github.com/excalidraw/excalidraw/pull/9014)
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- New hook: ${String.fromCharCode(96)}onImageFileNameHook${String.fromCharCode(96)}. When set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
- PDF export functions, paving the way for slideshow to export slides to PDF
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Returns the dimensions of a standard page size in pixels.
|
||||
*/
|
||||
function getPagePDFDimensions(
|
||||
pageSize: PageSize,
|
||||
orientation: PageOrientation
|
||||
): PageDimensions;
|
||||
|
||||
/**
|
||||
* Creates a PDF from the provided SVG elements with specified scaling and page properties.
|
||||
*/
|
||||
function createPDF(props: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale?: PDFExportScale;
|
||||
pageProps?: PDFPageProperties;
|
||||
filename: string;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates an SVG representation of the current view.
|
||||
*/
|
||||
function createViewSVG(props : {
|
||||
withBackground?: boolean;
|
||||
theme?: "light" | "dark";
|
||||
frameRendering?: FrameRenderingOptions;
|
||||
padding?: number;
|
||||
selectedOnly?: boolean;
|
||||
skipInliningFonts?: boolean;
|
||||
embedScene?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function onImageFilePathHook: (data: {
|
||||
currentImageName: string;
|
||||
drawingFilePath: string;
|
||||
}) => string = null;
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"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
|
||||
- White flash when opening a dark drawing [#2178](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2178)
|
||||
`,
|
||||
"2.7.3":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/ISuORbVKyhQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Toggling image size anchoring on and off by modifying the image link did not update the image in the view until the user forced saved it or closed and opened the drawing again. This was a side-effect of the less frequent view save introduced in 2.7.1
|
||||
|
||||
## New
|
||||
- **Shade Master Script**: A new script that allows you to modify the color lightness, hue, saturation, and transparency of selected Excalidraw elements, SVG images, and nested Excalidraw drawings. When a single image is selected, you can map colors individually. The original image remains unchanged, and a mapping table is added under ${String.fromCharCode(96)}## Embedded Files${String.fromCharCode(96)} for SVG and nested drawings. This helps maintain links between drawings while allowing different color themes.
|
||||
- New Command Palette Command: "Duplicate selected image with a different image ID". Creates a copy of the selected image with a new image ID. This allows you to add multiple color mappings to the same image. In the scene, the image will be treated as if a different image, but loaded from the same file in the Vault.
|
||||
|
||||
## QoL Improvements
|
||||
- New setting under ${String.fromCharCode(96)}Embedding Excalidraw into your notes and Exporting${String.fromCharCode(96)} > ${String.fromCharCode(96)}Image Caching and rendering optimization${String.fromCharCode(96)}. You can now set the number of concurrent workers that render your embedded images. Increasing the number will increase the speed but temporarily reduce the responsiveness of your system in case of large drawings.
|
||||
- Moved pen-related settings under ${String.fromCharCode(96)}Excalidraw appearance and behavior${String.fromCharCode(96)} to their sub-heading called ${String.fromCharCode(96)}Pen${String.fromCharCode(96)}.
|
||||
- Minor error fixing and performance optimizations when loading and updating embedded images.
|
||||
- Color maps in ${String.fromCharCode(96)}## Embedded Files${String.fromCharCode(96)} may now include color keys "stroke" and "fill". If set, these will change the fill and stroke attributes of the SVG root element of the relevant file.
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
// Updates the color map of an SVG image element in the view. If a ColorMap is provided, it will be used directly.
|
||||
// If an SVGColorInfo is provided, it will be converted to a ColorMap.
|
||||
// The view will be marked as dirty and the image will be reset using the color map.
|
||||
updateViewSVGImageColorMap(
|
||||
elements: ExcalidrawImageElement | ExcalidrawImageElement[],
|
||||
colors: ColorMap | SVGColorInfo | ColorMap[] | SVGColorInfo[]
|
||||
): Promise<void>;
|
||||
|
||||
// Retrieves the color map for an image element.
|
||||
// The color map contains information about the mapping of colors used in the image.
|
||||
// If the element already has a color map, it will be returned.
|
||||
getColorMapForImageElement(el: ExcalidrawElement): ColorMap;
|
||||
|
||||
// Retrieves the color map for an SVG image element.
|
||||
// The color map contains information about the fill and stroke colors used in the SVG.
|
||||
// If the element already has a color map, it will be merged with the colors extracted from the SVG.
|
||||
getColorMapForImgElement(el: ExcalidrawElement): Promise<SVGColorInfo>;
|
||||
|
||||
// Extracts the fill (background) and stroke colors from an Excalidraw file and returns them as an SVGColorInfo.
|
||||
getColosFromExcalidrawFile(file:TFile, img: ExcalidrawImageElement): Promise<SVGColorInfo>;
|
||||
|
||||
// Extracts the fill and stroke colors from an SVG string and returns them as an SVGColorInfo.
|
||||
getColorsFromSVGString(svgString: string): SVGColorInfo;
|
||||
|
||||
// upgraded the addImage function.
|
||||
// 1. It now accepts an object as the input parameter, making your scripts more readable
|
||||
// 2. AddImageOptions now includes colorMap as an optional parameter, this will only have an effect in case of SVGs and nested Excalidraws
|
||||
// 3. The API function is backwards compatible, but I recommend new implementations to use the object based input
|
||||
addImage(opts: AddImageOptions}): Promise<string>;
|
||||
|
||||
interface AddImageOptions {
|
||||
topX: number;
|
||||
topY: number;
|
||||
imageFile: TFile | string;
|
||||
scale?: boolean;
|
||||
anchor?: boolean;
|
||||
colorMap?: ColorMap;
|
||||
}
|
||||
|
||||
type SVGColorInfo = Map<string, {
|
||||
mappedTo: string;
|
||||
fill: boolean;
|
||||
stroke: boolean;
|
||||
}>;
|
||||
|
||||
interface ColorMap {
|
||||
[color: string]: string;
|
||||
};
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.7.2":`
|
||||
## Fixed
|
||||
- The plugin did not load on **iOS 16 and older**. [#2170](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2170)
|
||||
- Added empty line between ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)} and ${String.fromCharCode(96)}## Text Elements${String.fromCharCode(96)}. This will now follow **correct markdown linting**. [#2168](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2168)
|
||||
- Adding an **embeddable** to view did not **honor the element background and element stroke colors**, even if it was configured in plugin settings. [#2172](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2172)
|
||||
- **Deconstruct selected elements script** did not copy URLs and URIs for images embedded from outside Obsidian. Please update your script from the script library.
|
||||
- When **rearranging tabs in Obsidian**, e.g. having two tabs side by side, and moving one of them to another location, if the tab was an Excalidraw tab, it appeared as non-responsive after the move, until the tab was resized.
|
||||
|
||||
## Source Code Refactoring
|
||||
- Updated filenames, file locations, and file name letter-casing across the project
|
||||
- Extracted onDrop, onDragover, etc. handlers to DropManger in ExcalidrawView
|
||||
`,
|
||||
"2.7.1":`
|
||||
## Fixed
|
||||
- Deleting excalidraw file from file system while it is open in fullscreen mode in Obsidian causes Obsidian to be stuck in full-screen view [#2161](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2161)
|
||||
- Chinese fonts are not rendered in LaTeX statements [#2162](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2162)
|
||||
- Since Electron 32 (newer Obsidian Desktop installers) drag and drop links from Finder or OS File Explorer did not work. [Electron breaking change](https://www.electronjs.org/docs/latest/breaking-changes#removed-filepath). This is now fixed
|
||||
- Addressed unnecessary image reloads when changing windows in Obsidian
|
||||
`,
|
||||
"2.7.0":`
|
||||
## Fixed
|
||||
- Various Markdown embeddable "fuzziness":
|
||||
- Fixed issues with appearance settings and edit mode toggling when single-click editing is enabled.
|
||||
- Ensured embeddable file editing no longer gets interrupted unexpectedly.
|
||||
- **Hover Preview**: Disabled hover preview for back-of-the-note cards to reduce distractions.
|
||||
- **Settings Save**: Fixed an issue where plugin settings unnecessarily saved on every startup.
|
||||
|
||||
## New Features
|
||||
- **Image Cropping Snaps to Objects**: When snapping is enabled in the scene, image cropping now aligns to nearby objects.
|
||||
- **Session Persistence for Pen Mode**: Excalidraw remembers the last pen mode when switching between drawings within the same session.
|
||||
|
||||
## Refactoring
|
||||
- **Mermaid Diagrams**: Excalidraw now uses its own Mermaid package, breaking future dependencies on Obsidian's Mermaid updates. This ensures stability and includes all fixes and improvements made to Excalidraw Mermaid since February 2024. The plugin file size has increased slightly, but this change significantly improves maintainability while remaining invisible to users.
|
||||
- **MathJax Optimization**: MathJax (LaTeX equation SVG image generation) now loads only on demand, with the package compressed to minimize the startup and file size impact caused by the inclusion of Mermaid.
|
||||
- **On-Demand Language Loading**: Non-English language files are now compressed and load only when needed, counterbalancing the increase in file size due to Mermaid and improving load speeds.
|
||||
- **Codebase Restructuring**: Improved type safety by removing many ${String.fromCharCode(96)}//@ts-ignore${String.fromCharCode(96)} commands and enhancing modularity. Introduced new management classes: **CommandManager**, **EventManager**, **PluginFileManager**, **ObserverManager**, and **PackageManager**. Further restructuring is planned for upcoming releases to improve maintainability and stability.
|
||||
`,
|
||||
"2.6.8":`
|
||||
## New
|
||||
- **QoL improvements**:
|
||||
- Obsidian-link search button in Element Link Editor.
|
||||
- Add Any File now searches file aliases as well.
|
||||
- Cosmetic changes to file search modals (display path, show file type icon).
|
||||
- Text Element cursor-color matches the text color.
|
||||
- New script in script store: [Image Occlusion](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md) by [@TrillStones](https://github.com/TrillStones) 🙏
|
||||
|
||||
## Fixed
|
||||
- Excalidraw icon on the **ribbon menu kept reappearing** every time you reopen Obsidian [#2115](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2115)
|
||||
- In pen mode, when **single-finger panning** is enabled, Excalidraw should still **allow actions with the mouse**.
|
||||
- When **editing a drawing in split mode** (drawing is on one side, markdown view is on the other), editing the markdown note sometimes causes the drawing to re-zoom and jump away from the selected area.
|
||||
- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041)
|
||||
- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**.
|
||||
|
||||
`,
|
||||
"2.6.7":`
|
||||
Hoping to finally move on to 2.7.0... but still have one last bug to fix in 2.6.x!
|
||||
|
||||
## Fixed
|
||||
I misread a line in the Excalidraw package code... ended up breaking image loading in 2.6.6. The icon library script didn't work right, and updating nested drawings caused all images in the scene to be dropped from memory. This led to image-placeholders in exports and broke copy-paste to Excalidraw.com and between drawings. I am surprised no one reported it! 😳
|
||||
`,
|
||||
"2.6.6":`
|
||||
## Fixed
|
||||
- Images and LaTeX formulas did not update in the scene when the source was changed until the Excalidraw drawing was closed and reopened. [#2105](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2105)
|
||||
`,
|
||||
"2.6.5":`
|
||||
## Fixed
|
||||
- Text sizing issue in the drawing that is first loaded after Obsidian restarts [#2086](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2086)
|
||||
- Excalidraw didn't load if there was a file in the Excalidraw folder with a name that starts the same way as the Scripts folder name. [#2095](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2095)
|
||||
- **OVERSIZED EXCALIDRAW TOOLBAR**: Added a new setting under "Excalidraw Appearance and Behavior > Theme and Styling" called "Limit Obsidian Font Size to Editor Text." This setting is off by default. When enabled, it restricts Obsidian's custom font size adjustments to editor text only, preventing unintended scaling of Excalidraw UI elements and other themes that rely on the default interface font size. Feel free to experiment with this setting to improve Excalidraw UI consistency. However, because this change affects the broader Obsidian UI, it's recommended to turn it off if any layout issues arise. [#2087](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2087)`,
|
||||
"2.6.4":`
|
||||
## Fixed
|
||||
- Error saving when cropping images embedded from a URL (not from a file in the Vault) [#2096](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2096)
|
||||
`,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Setting } from "obsidian";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ModifierKeySet, ModifierSetType, modifierKeyTooltipMessages } from "src/utils/ModifierkeyHelper";
|
||||
import { ModifierKeySet, ModifierSetType, modifierKeyTooltipMessages } from "src/utils/modifierkeyHelper";
|
||||
|
||||
type ModifierKeyCategories = Partial<{
|
||||
[modifierSetType in ModifierSetType]: string;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { EMPTY_MESSAGE } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { EMPTY_MESSAGE } from "../../constants/constants";
|
||||
import { t } from "../../lang/helpers";
|
||||
|
||||
export enum openDialogAction {
|
||||
openFile,
|
||||
148
src/shared/Dialogs/PDFExportSettingsComponent.ts
Normal file
148
src/shared/Dialogs/PDFExportSettingsComponent.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Setting } from "obsidian";
|
||||
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, STANDARD_PAGE_SIZES } from "src/utils/exportUtils";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export interface PDFExportSettings {
|
||||
pageSize: PageSize;
|
||||
pageOrientation: PageOrientation;
|
||||
fitToPage: number;
|
||||
paperColor: "white" | "scene" | "custom";
|
||||
customPaperColor: string;
|
||||
alignment: PDFPageAlignment;
|
||||
margin: PDFPageMarginString;
|
||||
}
|
||||
|
||||
export class PDFExportSettingsComponent {
|
||||
constructor(
|
||||
private contentEl: HTMLElement,
|
||||
private settings: PDFExportSettings,
|
||||
private update?: Function,
|
||||
) {
|
||||
if (!update) this.update = () => {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const pageSizeOptions: Record<string, string> = Object.keys(STANDARD_PAGE_SIZES)
|
||||
.reduce((acc, key) => ({
|
||||
...acc,
|
||||
[key]: key
|
||||
}), {});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PAGE_SIZE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(pageSizeOptions)
|
||||
.setValue(this.settings.pageSize)
|
||||
.onChange(value => {
|
||||
this.settings.pageSize = value as PageSize;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PAGE_ORIENTATION"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"portrait": t("EXPORTDIALOG_ORIENTATION_PORTRAIT"),
|
||||
"landscape": t("EXPORTDIALOG_ORIENTATION_LANDSCAPE")
|
||||
})
|
||||
.setValue(this.settings.pageOrientation)
|
||||
.onChange(value => {
|
||||
this.settings.pageOrientation = value as PageOrientation;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_FIT_TO_PAGE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"scale": t("EXPORTDIALOG_PDF_SCALE_OPTION"),
|
||||
"fit": t("EXPORTDIALOG_PDF_FIT_OPTION"),
|
||||
"fit-2": t("EXPORTDIALOG_PDF_FIT_2_OPTION"),
|
||||
"fit-4": t("EXPORTDIALOG_PDF_FIT_4_OPTION"),
|
||||
"fit-6": t("EXPORTDIALOG_PDF_FIT_6_OPTION"),
|
||||
"fit-8": t("EXPORTDIALOG_PDF_FIT_8_OPTION"),
|
||||
"fit-12": t("EXPORTDIALOG_PDF_FIT_12_OPTION"),
|
||||
"fit-16": t("EXPORTDIALOG_PDF_FIT_16_OPTION")
|
||||
})
|
||||
.setValue(this.settings.fitToPage === 1 ? "fit" :
|
||||
(typeof this.settings.fitToPage === "number" ? `fit-${this.settings.fitToPage}` : "scale"))
|
||||
.onChange(value => {
|
||||
this.settings.fitToPage = value === "scale" ? 0 :
|
||||
(value === "fit" ? 1 : parseInt(value.split("-")[1]));
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_MARGIN"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"none": t("EXPORTDIALOG_PDF_MARGIN_NONE"),
|
||||
"tiny": t("EXPORTDIALOG_PDF_MARGIN_TINY"),
|
||||
"normal": t("EXPORTDIALOG_PDF_MARGIN_NORMAL")
|
||||
})
|
||||
.setValue(this.settings.margin)
|
||||
.onChange(value => {
|
||||
this.settings.margin = value as PDFPageMarginString;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
const paperColorSetting = new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_PAPER_COLOR"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"white": t("EXPORTDIALOG_PDF_PAPER_WHITE"),
|
||||
"scene": t("EXPORTDIALOG_PDF_PAPER_SCENE"),
|
||||
"custom": t("EXPORTDIALOG_PDF_PAPER_CUSTOM")
|
||||
})
|
||||
.setValue(this.settings.paperColor)
|
||||
.onChange(value => {
|
||||
this.settings.paperColor = value as typeof this.settings.paperColor;
|
||||
colorInput.style.display = (value === "custom") ? "block" : "none";
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
const colorInput = paperColorSetting.controlEl.createEl("input", {
|
||||
type: "color",
|
||||
value: this.settings.customPaperColor
|
||||
});
|
||||
colorInput.style.width = "50px";
|
||||
colorInput.style.marginLeft = "10px";
|
||||
colorInput.style.display = this.settings.paperColor === "custom" ? "block" : "none";
|
||||
colorInput.addEventListener("change", (e) => {
|
||||
this.settings.customPaperColor = (e.target as HTMLInputElement).value;
|
||||
this.update();
|
||||
});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_ALIGNMENT"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"center": t("EXPORTDIALOG_PDF_ALIGN_CENTER"),
|
||||
"center-left": t("EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT"),
|
||||
"center-right": t("EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT"),
|
||||
"top-left": t("EXPORTDIALOG_PDF_ALIGN_TOP_LEFT"),
|
||||
"top-center": t("EXPORTDIALOG_PDF_ALIGN_TOP_CENTER"),
|
||||
"top-right": t("EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT"),
|
||||
"bottom-left": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT"),
|
||||
"bottom-center": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER"),
|
||||
"bottom-right": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT")
|
||||
})
|
||||
.setValue(this.settings.alignment)
|
||||
.onChange(value => {
|
||||
this.settings.alignment = value as PDFPageAlignment;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { ColorComponent, Modal, Setting, TextComponent, ToggleComponent } from "obsidian";
|
||||
import { COLOR_NAMES } from "src/constants/constants";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { setPen } from "src/menu/ObsidianMenu";
|
||||
import { ExtendedFillStyle, PenType } from "src/types/PenTypes";
|
||||
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
|
||||
import { PENS } from "src/utils/Pens";
|
||||
import { fragWithHTML } from "src/utils/Utils";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { setPen } from "src/view/components/menu/ObsidianMenu";
|
||||
import { ExtendedFillStyle, PenType } from "src/types/penTypes";
|
||||
import { getExcalidrawViews } from "src/utils/obsidianUtils";
|
||||
import { PENS } from "src/utils/pens";
|
||||
import { fragWithHTML } from "src/utils/utils";
|
||||
import { __values } from "tslib";
|
||||
|
||||
const EASINGFUNCTIONS: Record<string,string> = {
|
||||
@@ -8,21 +8,20 @@ import {
|
||||
TFile,
|
||||
Notice,
|
||||
TextAreaComponent,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { escapeRegExp, getLinkParts, sleep } from "../utils/Utils";
|
||||
import { getLeaf, openLeaf } from "../utils/ObsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { KeyEvent, isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { escapeRegExp, getLinkParts, sleep } from "../../utils/utils";
|
||||
import { getLeaf, openLeaf } from "../../utils/obsidianUtils";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/fileUtils";
|
||||
import { KeyEvent, isWinCTRLorMacCMD } from "src/utils/modifierkeyHelper";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ExcalidrawElement, getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawElement, getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { MAX_IMAGE_SIZE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { REGEX_LINK, REGEX_TAGS } from "src/ExcalidrawData";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { openExternalLink, openTagSearch, parseObsidianLink } from "src/utils/ExcalidrawViewUtils";
|
||||
import { REGEX_LINK, REGEX_TAGS } from "../ExcalidrawData";
|
||||
import { ScriptEngine } from "../Scripts";
|
||||
import { openExternalLink, openTagSearch, parseObsidianLink } from "src/utils/excalidrawViewUtils";
|
||||
|
||||
export type ButtonDefinition = { caption: string; tooltip?:string; action: Function };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal, Setting, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getIMGFilename } from "src/utils/FileUtils";
|
||||
import { addIframe } from "src/utils/Utils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { getIMGFilename } from "src/utils/fileUtils";
|
||||
import { addIframe } from "src/utils/utils";
|
||||
|
||||
const haveLinkedFilesChanged = (depth: number, mtime: number, path: string, sourceList: Set<string>, plugin: ExcalidrawPlugin):boolean => {
|
||||
if(depth++ > 5) return false;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { App, MarkdownRenderer, Modal } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { Rank, SwordColors } from "src/menu/ActionIcons";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { Rank, SwordColors } from "src/constants/actionIcons";
|
||||
|
||||
export class RankMessage extends Modal {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { App, MarkdownRenderer, Modal } from "obsidian";
|
||||
import { isVersionNewerThanOther } from "src/utils/Utils";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { isVersionNewerThanOther } from "src/utils/utils";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { FIRST_RUN, RELEASE_NOTES } from "./Messages";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { errorlog, escapeRegExp } from "../utils/Utils";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { errorlog, escapeRegExp } from "../../utils/utils";
|
||||
import { log } from "src/utils/debugHelper";
|
||||
|
||||
const URL =
|
||||
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
|
||||
@@ -1,10 +1,10 @@
|
||||
import { App, FuzzySuggestModal, Notice, TFile } from "obsidian";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { App, FuzzySuggestModal, Notice } from "obsidian";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { addBackOfTheNoteCard } from "src/utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard } from "src/utils/excalidrawViewUtils";
|
||||
|
||||
export class SelectCard extends FuzzySuggestModal<string> {
|
||||
|
||||
@@ -157,6 +157,15 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Set ea.style.roundness. 0: is the legacy value, 3: is the current default value, null is sharp",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addAppendUpdateCustomData",
|
||||
code: "addAppendUpdateCustomData(id: string, newData: Partial<Record<string, unknown>>)",
|
||||
desc: "Add, modify keys in element customData and preserve existing keys.\n" +
|
||||
"Creates customData={} if it does not exist.\n" +
|
||||
"Takes the element ID for an element in the elementsDict and the new data to add or modify.\n" +
|
||||
"To delete keys set key value in newData to undefined. so {keyToBeDeleted:undefined} will be deleted.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addToGroup",
|
||||
code: "addToGroup(objectIds: []): string;",
|
||||
@@ -215,6 +224,87 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "createPDF",
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties, filename: string}): Promise<void>",
|
||||
desc: "Creates a PDF from the provided SVG elements with specified scaling and page properties.\n" +
|
||||
"\n" +
|
||||
"@param {Object} params - The parameters for creating the PDF.\n" +
|
||||
"@param {SVGSVGElement[]} params.SVG - An array of SVG elements to be included in the PDF. If multiple SVGs are provided, each will be added to a new page.\n" +
|
||||
"@param {PDFExportScale} [params.scale={ fitToPage: true, zoom: 1 }] - The scaling options for the SVG elements.\n" +
|
||||
"@param {PDFPageProperties} [params.pageProps] - The properties for the PDF pages.\n" +
|
||||
"@param {string} params.filename - The name of the PDF file to be created.\n" +
|
||||
"@returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFExportScale\n" +
|
||||
"@property {boolean} fitToPage - Whether to fit the SVG to the page.\n" +
|
||||
"@property {number} [zoom=1] - The zoom level for the SVG. Used only if fitToPage is false. If the SVG does not fit the page, it will be tiled over multiple pages.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFPageProperties\n" +
|
||||
"@property {{width: number, height: number}} [dimensions] - The dimensions of the PDF pages in pixels. Use getPageDimensions to get standard page sizes.\n" +
|
||||
"@property {string} [backgroundColor] - The background color of the PDF pages.\n" +
|
||||
"@property {PDFMargin} margin - The margins of the PDF pages in pixels.\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.",
|
||||
after: "({\n" +
|
||||
" SVG: [svgElement1, svgElement2],\n" +
|
||||
" scale: { fitToPage: true },\n" +
|
||||
" pageProps: {\n" +
|
||||
" dimensions: { width: 595.28, height: 841.89 },\n" +
|
||||
" backgroundColor: \"#ffffff\",\n" +
|
||||
" margin: { left: 20, right: 20, top: 20, bottom: 20 },\n" +
|
||||
" alignment: \"center\"\n" +
|
||||
" filename: \"myPDF.pdf\"\n" +
|
||||
" }\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "createViewSVG",
|
||||
code: "async createViewSVG({withBackground?: boolean, theme?: 'light' | 'dark', frameRendering?: FrameRenderingOptions, padding?: number, selectedOnly?: boolean, skipInliningFonts?: boolean, embedScene?: boolean}): Promise<SVGSVGElement>",
|
||||
desc: "Creates an SVG representation of the current view with specified options.\n" +
|
||||
"\n" +
|
||||
"@param {Object} options - The options for creating the SVG.\n" +
|
||||
"@param {boolean} [options.withBackground=true] - Whether to include the background in the SVG.\n" +
|
||||
"@param {\"light\" | \"dark\"} [options.theme] - The theme to use for the SVG.\n" +
|
||||
"@param {FrameRenderingOptions} [options.frameRendering={enabled: true, name: true, outline: true, clip: true}] - The frame rendering options.\n" +
|
||||
"@param {number} [options.padding] - The padding to apply around the SVG.\n" +
|
||||
"@param {boolean} [options.selectedOnly=false] - Whether to include only the selected elements in the SVG.\n" +
|
||||
"@param {boolean} [options.skipInliningFonts=false] - Whether to skip inlining fonts in the SVG.\n" +
|
||||
"@param {boolean} [options.embedScene=false] - Whether to embed the scene in the SVG.\n" +
|
||||
"@returns {Promise<SVGSVGElement>} A promise that resolves to the SVG element.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} FrameRenderingOptions\n" +
|
||||
"@property {boolean} enabled - Whether frame rendering is enabled.\n" +
|
||||
"@property {boolean} name - Whether to include the name in the frame rendering.\n" +
|
||||
"@property {boolean} outline - Whether to include the outline in the frame rendering.\n" +
|
||||
"@property {boolean} clip - Whether to clip the frame rendering.\n",
|
||||
after: "({\n" +
|
||||
" withBackground: true,\n" +
|
||||
" theme: 'light',\n" +
|
||||
" frameRendering: { enabled: true, name: true, outline: true, clip: true },\n" +
|
||||
" padding: 10,\n" +
|
||||
" selectedOnly: false,\n" +
|
||||
" skipInliningFonts: false,\n" +
|
||||
" embedScene: false,\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "getPagePDFDimensions",
|
||||
code: "getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions",
|
||||
desc: "Returns the dimensions of a standard page size in pixels.\n" +
|
||||
"\n" +
|
||||
"@param {PageSize} pageSize - The standard page size. Possible values are \"A0\", \"A1\", \"A2\", \"A3\", \"A4\", \"A5\", \"Letter\", \"Legal\", \"Tabloid\".\n" +
|
||||
"@param {PageOrientation} orientation - The orientation of the page. Possible values are \"portrait\" and \"landscape\".\n" +
|
||||
"@returns {PageDimensions} - An object containing the width and height of the page in pixels.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PageDimensions\n" +
|
||||
"@property {number} width - The width of the page in pixels.\n" +
|
||||
"@property {number} height - The height of the page in pixels.\n" +
|
||||
"\n" +
|
||||
"@typedef {\"A0\" | \"A1\" | \"A2\" | \"A3\" | \"A4\" | \"A5\" | \"Letter\" | \"Legal\" | \"Tabloid\"} PageSize\n" +
|
||||
"\n" +
|
||||
"@typedef {\"portrait\" | \"landscape\"} PageOrientation",
|
||||
after: "(\"A4\", \"portrait\");",
|
||||
},
|
||||
{
|
||||
field: "createPNG",
|
||||
code: "async createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,padding?: number): Promise<any>;",
|
||||
@@ -297,8 +387,13 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addImage",
|
||||
code: "async addImage(topX: number, topY: number, imageFile: TFile|string, scale?: boolean, anchor?: boolean): Promise<string>;",
|
||||
desc: "imageFile may be a TFile or a string that contains a hyperlink. imageFile may also be an obsidian filepath including a reference eg.: 'path/my.pdf#page=3'\nSet 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.\nanchor will only be evaluated if scale is false. anchor true will add |100% to the end of the filename, resulting in an image that will always pop back to 100% when the source file is updated or when the Excalidraw file is reopened.",
|
||||
code: "async addImage(opts: {topX: number, topY: number, imageFile: TFile|string, scale?: boolean, anchor?: boolean, colorMap?: ColorMap}): Promise<string>;",
|
||||
desc: "imageFile may be a TFile or a string that contains a hyperlink.\n"+
|
||||
"imageFile may also be an obsidian filepath including a reference eg.: 'path/my.pdf#page=3'\n"+
|
||||
"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.\n"+
|
||||
"anchor will only be evaluated if scale is false. anchor true will add |100% to the end of the filename, resulting in an image that will always pop back to 100% when the source file is updated or when the Excalidraw file is reopened.\n"+
|
||||
"colorMap is only used for SVG images and nested Excalidraw images. See the Shade Master script and the Deconstruct Selected Elements script for examples using colorMap.\n"+
|
||||
"type ColorMap = { [color: string]: string; }",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -415,6 +510,47 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Returns the TFile file handle for the image element",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "updateViewSVGImageColorMap",
|
||||
code: "async updateViewSVGImageColorMap(elements: ExcalidrawImageElement | ExcalidrawImageElement[], colors: ColorMap | SVGColorInfo | ColorMap[] | SVGColorInfo[]): Promise<void>;",
|
||||
desc: 'Updates the color map of an SVG image element in the view. If a ColorMap is provided, it will be used directly. If an SVGColorInfo is provided, it will be converted to a ColorMap. The view will be marked as dirty (i.e. will be saved at next scheduled time) and the image will be reset using the color map.\n'+
|
||||
'See "Shade Master" scritp in Script Library for an example of using this function.\n\n' +
|
||||
'type SVGColorInfo = Map<string, { mappedTo: string; fill: boolean; stroke: boolean; }>\n' +
|
||||
'type ColorMap = { [color: string]: string; }',
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getColorMapForImageElement",
|
||||
code: "getColorMapForImageElement(el: ExcalidrawElement): ColorMap",
|
||||
desc: 'Retrieves the color map for an image element. The color map contains information about the mapping of colors used in the image. If the element already has a color map, it will be returned. The colorMap does not include all colors in the image, only those that have been mapped.\n' +
|
||||
'See "Shade Master" scritp in Script Library for an example of using this function.\n\n' +
|
||||
'type ColorMap = { [color: string]: string; }',
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getSVGColorInfoForImgElement",
|
||||
code: "async getColorMapForImgElement(el: ExcalidrawElement): Promise<SVGColorInfo>",
|
||||
desc: 'This function must be awaited. Retrieves the color map for an SVG image element. The color map contains information about the fill and stroke colors used in the SVG. If the element already has a color map, it will be merged with the colors extracted from the SVG.\n' +
|
||||
'See "Shade Master" scritp in Script Library for an example of using this function.\n\n' +
|
||||
'type SVGColorInfo = Map<string, { mappedTo: string; fill: boolean; stroke: boolean; }>',
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getColosFromExcalidrawFile",
|
||||
code: "async getColosFromExcalidrawFile(file:TFile, img: ExcalidrawImageElement): Promise<SVGColorInfo>",
|
||||
desc: 'Must be awaited. Extracts the fill (background) and stroke colors from an excalidraw file and returns them as an SVGColorInfo. The SVGColorInfo is a map where the keys are the colors used in the SVG and the values contain information about whether the color is used for fill, stroke, or both.\n' +
|
||||
'See "Shade Master" scritp in Script Library for an example of using this function.\n\n' +
|
||||
'type SVGColorInfo = Map<string, { mappedTo: string; fill: boolean; stroke: boolean; }>',
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getColorsFromSVGString",
|
||||
code: "getColorsFromSVGString(svgString: string): SVGColorInfo",
|
||||
desc: 'Extracts the fill and stroke colors from an SVG string and returns them as an SVGColorInfo. The SVGColorInfo is a map where the keys are the colors used in the SVG and the values contain information about whether the color is used for fill, stroke, or both.\n' +
|
||||
'See "Shade Master" scritp in Script Library for an example of using this function.\n\n' +
|
||||
'type SVGColorInfo = Map<string, { mappedTo: string; fill: boolean; stroke: boolean; }>',
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "copyViewElementsToEAforEditing",
|
||||
code: "copyViewElementsToEAforEditing(elements: ExcalidrawElement[], copyImages: boolean = false): void;",
|
||||
@@ -445,6 +581,21 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "If set Excalidraw will call this function onDrop events.\nA return of true will stop the default onDrop processing in Excalidraw.\n\ndraggable is the Obsidian draggable object\nfiles is the array of dropped files\nexcalidrawFile is the file receiving the drop event\nview is the excalidraw view receiving the drop.\npointerPosition is the pointer position on canvas at the time of drop.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "onImageFilePathHook",
|
||||
code: `onImageFilePathHook: (data: {currentImageName: string; drawingFilePath: string;}): string;`,
|
||||
desc: "If set, this callback is triggered when an image is being saved in Excalidraw.\n"
|
||||
+ "You can use this callback to customize the naming and path of pasted images to avoid\n"
|
||||
+ 'default names like "Pasted image 123147170.png" being saved in the attachments folder,\n'
|
||||
+ "and instead use more meaningful names based on the Excalidraw file or other criteria,\n"
|
||||
+ "plus save the image in a different folder.\n\n"
|
||||
+ "If the function returns null or undefined, the normal Excalidraw operation will continue\n"
|
||||
+ "with the excalidraw generated name and default path.\n"
|
||||
+ "If a filepath is returned, that will be used. Include the full Vault filepath and filename\n"
|
||||
+ "with the file extension.\n"
|
||||
+ "The currentImageName is the name of the image generated by excalidraw or provided during paste.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "mostRecentMarkdownSVG",
|
||||
code: "mostRecentMarkdownSVG: SVGSVGElement;",
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import ExcalidrawView from "../../view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../../core/main";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
|
||||
import { FileSuggestionModal } from "../Suggesters/FileSuggestionModal";
|
||||
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
import { insertEmbeddableToView, insertImageToView } from "src/utils/excalidrawViewUtils";
|
||||
import { getEA } from "src/core";
|
||||
import { InsertPDFModal } from "./InsertPDFModal";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { cleanSectionHeading } from "src/utils/obsidianUtils";
|
||||
|
||||
export class UniversalInsertFileModal extends Modal {
|
||||
private center: { x: number, y: number } = { x: 0, y: 0 };
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
export class ExcalidrawConfig {
|
||||
public areaLimit: number = 16777216;
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
loadSceneFonts,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { TextMode } from "./ExcalidrawView";
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import ExcalidrawView, { TextMode } from "../view/ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
@@ -37,8 +37,8 @@ import {
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
compressAsync,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
} from "../utils/utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "../utils/obsidianUtils";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
@@ -47,15 +47,16 @@ import {
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { DEBUGGING, debug } from "./utils/DebugHelper";
|
||||
import { ConfirmationPrompt } from "./Dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "../utils/mermaidUtils";
|
||||
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 { t } from "./lang/helpers";
|
||||
import { displayFontMessage } from "./utils/ExcalidrawViewUtils";
|
||||
import { getPDFRect } from "./utils/PDFUtils";
|
||||
import { updateElementIdsInScene } from "../utils/excalidrawSceneUtils";
|
||||
import { checkAndCreateFolder, getNewUniqueFilepath, splitFolderAndFilename } from "../utils/fileUtils";
|
||||
import { t } from "../lang/helpers";
|
||||
import { displayFontMessage } from "../utils/excalidrawViewUtils";
|
||||
import { getPDFRect } from "../utils/PDFUtils";
|
||||
import { create } from "domain";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -480,7 +481,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>();
|
||||
@@ -518,7 +519,7 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
const saveVersion = this.scene.source.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
|
||||
const saveVersion = this.scene.source?.split("https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/")[1]??"1.8.16";
|
||||
|
||||
const elements = this.scene.elements;
|
||||
for (const el of elements) {
|
||||
@@ -649,7 +650,6 @@ export class ExcalidrawData {
|
||||
containers.forEach((container: any) => {
|
||||
if(ellipseAndRhombusContainerWrapping && !container.customData?.legacyTextWrap) {
|
||||
addAppendUpdateCustomData(container, {legacyTextWrap: true});
|
||||
//container.customData = {...container.customData, legacyTextWrap: true};
|
||||
}
|
||||
const filteredBoundElements = container.boundElements.filter(
|
||||
(boundEl: any) => elements.some((el: any) => el.id === boundEl.id),
|
||||
@@ -859,7 +859,7 @@ export class ExcalidrawData {
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(/^((%%\n*)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
const normalMatch = data.match(/^((%%\n*)?# Excalidraw Data\n\n?## Text Elements(?:\n|$))/m)
|
||||
?? data.match(/^((%%\n*)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
const textElementsMatch = normalMatch
|
||||
@@ -1427,7 +1427,7 @@ export class ExcalidrawData {
|
||||
disableCompression: boolean = false;
|
||||
generateMDBase(deletedElements: ExcalidrawElement[] = []) {
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
outString += `# Excalidraw Data\n\n## Text Elements\n`;
|
||||
if (this.plugin.settings.addDummyTextElement) {
|
||||
outString += `\n^_dummy!_\n\n`;
|
||||
}
|
||||
@@ -1462,7 +1462,7 @@ export class ExcalidrawData {
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
outString += `${key}: $$${this.equations.get(key).latex}$$\n\n`;
|
||||
outString += `${key}: $$${this.equations.get(key).latex.trim()}$$\n\n`;
|
||||
}
|
||||
}
|
||||
if (this.files.size > 0) {
|
||||
@@ -1547,13 +1547,24 @@ 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);
|
||||
await checkAndCreateFolder(folderpath);
|
||||
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;
|
||||
@@ -1569,13 +1580,13 @@ export class ExcalidrawData {
|
||||
filepath,
|
||||
);
|
||||
|
||||
embeddedFile.setImage(
|
||||
dataURL,
|
||||
embeddedFile.setImage({
|
||||
imgBase64: dataURL,
|
||||
mimeType,
|
||||
{ height: 0, width: 0 },
|
||||
scene.appState?.theme === "dark",
|
||||
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
);
|
||||
size: { height: 0, width: 0 },
|
||||
isDark: scene.appState?.theme === "dark",
|
||||
isSVGwithBitmap: mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
|
||||
});
|
||||
this.setFile(key as FileId, embeddedFile);
|
||||
return file;
|
||||
}
|
||||
@@ -1593,7 +1604,9 @@ export class ExcalidrawData {
|
||||
const pageRef = ef.linkParts.original.split("#")?.[1];
|
||||
if(!pageRef || !pageRef.startsWith("page=") || pageRef.includes("rect")) return;
|
||||
const restOfLink = el.link ? el.link.match(/&rect=\d*,\d*,\d*,\d*(.*)/)?.[1] : "";
|
||||
const link = ef.linkParts.original + getPDFRect(el.crop, pdfScale) + (restOfLink ? restOfLink : "]]");
|
||||
const link = ef.linkParts.original +
|
||||
getPDFRect({elCrop: el.crop, scale: pdfScale, customData: el.customData}) +
|
||||
(restOfLink ? restOfLink : "]]");
|
||||
el.link = `[[${link}`;
|
||||
this.elementLinks.set(el.id, el.link);
|
||||
dirty = true;
|
||||
@@ -1992,7 +2005,7 @@ export class ExcalidrawData {
|
||||
isLocalLink: data.isLocalLink,
|
||||
path: data.hyperlink,
|
||||
blockrefData: null,
|
||||
hasSVGwithBitmap: data.isSVGwithBitmap
|
||||
hasSVGwithBitmap: data.isSVGwithBitmap,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -2028,7 +2041,8 @@ export class ExcalidrawData {
|
||||
this.file.path,
|
||||
masterFile.blockrefData
|
||||
? masterFile.path + "#" + masterFile.blockrefData
|
||||
: masterFile.path
|
||||
: masterFile.path,
|
||||
masterFile.colorMapJSON
|
||||
);
|
||||
this.files.set(fileId,embeddedFile);
|
||||
return embeddedFile;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { App, Notice, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { convertSVGStringToElement } from "./Utils";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./UtilTypes";
|
||||
import { hasExcalidrawEmbeddedImagesTreeChanged } from "./FileUtils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { convertSVGStringToElement } from "../utils/utils";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "../types/utilTypes";
|
||||
import { hasExcalidrawEmbeddedImagesTreeChanged } from "../utils/fileUtils";
|
||||
|
||||
//@ts-ignore
|
||||
const DB_NAME = "Excalidraw " + app.appId;
|
||||
@@ -1,9 +1,9 @@
|
||||
// LaTeX.ts
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import ExcalidrawView from "../view/ExcalidrawView";
|
||||
import { FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { App } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
declare const loadMathjaxToSVG: Function;
|
||||
let mathjaxLoaded = false;
|
||||
@@ -52,7 +52,7 @@ export const updateEquation = async (
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app: App,
|
||||
plugin: ExcalidrawPlugin,
|
||||
): Promise<{
|
||||
mimeType: MimeType;
|
||||
fileId: FileId;
|
||||
@@ -61,7 +61,7 @@ export async function tex2dataURL(
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
await loadMathJax();
|
||||
return tex2dataURLExternal(tex, scale, app);
|
||||
return tex2dataURLExternal(tex, scale, plugin);
|
||||
}
|
||||
|
||||
export const clearMathJaxVariables = () => {
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
import FrontmatterEditor from "src/utils/Frontmatter";
|
||||
import ExcalidrawPlugin from "../../core/main"
|
||||
import ExcalidrawView, { ExportSettings } from "../../view/ExcalidrawView"
|
||||
import FrontmatterEditor from "src/shared/Frontmatter";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { blobToBase64 } from "src/utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
import { EmbeddedFilesLoader } from "../EmbeddedFileLoader";
|
||||
import { blobToBase64 } from "src/utils/fileUtils";
|
||||
import { getEA } from "src/core";
|
||||
import { log } from "src/utils/debugHelper";
|
||||
|
||||
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
|
||||
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
|
||||
@@ -4,18 +4,17 @@ import {
|
||||
normalizePath,
|
||||
TAbstractFile,
|
||||
TFile,
|
||||
WorkspaceLeaf,
|
||||
} from "obsidian";
|
||||
import { PLUGIN_ID } from "./constants/constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./dialogs/Prompt";
|
||||
import { getIMGFilename } from "./utils/FileUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
import { WeakArray } from "./utils/WeakArray";
|
||||
import { getExcalidrawViews } from "./utils/ObsidianUtils";
|
||||
import { PLUGIN_ID } from "../constants/constants";
|
||||
import ExcalidrawView from "../view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./Dialogs/Prompt";
|
||||
import { getIMGFilename } from "../utils/fileUtils";
|
||||
import { splitFolderAndFilename } from "../utils/fileUtils";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
|
||||
import { WeakArray } from "./WeakArray";
|
||||
import { getExcalidrawViews } from "../utils/obsidianUtils";
|
||||
|
||||
export type ScriptIconMap = {
|
||||
[key: string]: { name: string; group: string; svgString: string };
|
||||
@@ -254,6 +253,12 @@ export class ScriptEngine {
|
||||
if (!view || !script || !title) {
|
||||
return;
|
||||
}
|
||||
//addresses the situation when after paste text element IDs are not updated to 8 characters
|
||||
//linked to onPaste save issue with the false parameter
|
||||
if(view.getScene().elements.some(el=>!el.isDeleted && el.type === "text" && el.id.length > 8)) {
|
||||
await view.save(false, true);
|
||||
}
|
||||
|
||||
script = script.replace(/^---.*?---\n/gs, "");
|
||||
const ea = getEA(view);
|
||||
this.eaInstances.push(ea);
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
EditorSuggestTriggerInfo,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { FRONTMATTER_KEYS_INFO } from "../../dialogs/SuggesterInfo";
|
||||
import { FRONTMATTER_KEYS_INFO } from "../Dialogs/SuggesterInfo";
|
||||
import {
|
||||
EXCALIDRAW_AUTOMATE_INFO,
|
||||
EXCALIDRAW_SCRIPTENGINE_INFO,
|
||||
} from "../../dialogs/SuggesterInfo";
|
||||
import type ExcalidrawPlugin from "../../main";
|
||||
} from "../Dialogs/SuggesterInfo";
|
||||
import type ExcalidrawPlugin from "../../core/main";
|
||||
|
||||
/**
|
||||
* The field suggester recommends document properties in source mode, ea and utils function and attribute names.
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { SuggestionModal } from "./SuggestionModal";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { LinkSuggestion } from "src/types/types";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, VIDEO_TYPES } from "src/constants/constants";
|
||||
|
||||
export class FileSuggestionModal extends SuggestionModal<LinkSuggestion> {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user