mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
41 Commits
2.7.1-beta
...
2.7.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@ hot-reload.bat
|
||||
data.json
|
||||
lib
|
||||
dist
|
||||
tmp
|
||||
|
||||
#VSCode
|
||||
.vscode
|
||||
|
||||
6
docs/API/ExcalidrawAutomate.d.ts
vendored
6
docs/API/ExcalidrawAutomate.d.ts
vendored
@@ -1,16 +1,16 @@
|
||||
/// <reference types="react" />
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
|
||||
import ExcalidrawView, { ExportSettings } from "src/view/ExcalidrawView";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { ConnectionPoint, DeviceType } from "src/types/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
@@ -23,13 +23,17 @@ const {angle, backgroundColor, fillStyle, fontFamily, fontSize, height, width, o
|
||||
|
||||
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 +42,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 |
@@ -26,6 +26,10 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
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
|
||||
//-------------------------------
|
||||
@@ -57,8 +61,9 @@ const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").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;
|
||||
@@ -350,8 +355,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 +510,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.",
|
||||
@@ -730,6 +736,9 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
hostView.refreshCanvasOffset();
|
||||
excalidrawAPI.setActiveTool({type: "selection"});
|
||||
})
|
||||
if(!shouldSaveAfterThePresentation) {
|
||||
ea.targetView.clearDirty();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
@@ -755,6 +764,7 @@ const start = async () => {
|
||||
resetControlPanelElPosition();
|
||||
}
|
||||
if(presentationPathType === "line") await toggleArrowVisibility(isHidden);
|
||||
ea.targetView.clearDirty();
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
@@ -769,10 +779,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);
|
||||
}
|
||||
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.7.4",
|
||||
"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.7.4",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-22",
|
||||
"@zsviczian/excalidraw": "0.17.6-24",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
|
||||
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -16,6 +17,8 @@ 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 +35,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 +61,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 +108,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',
|
||||
@@ -141,7 +147,12 @@ const BUILD_CONFIG = {
|
||||
exports: 'default',
|
||||
},
|
||||
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 +177,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: (
|
||||
@@ -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;
|
||||
|
||||
@@ -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,26 @@ 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 { 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,
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "../ExcalidrawAutomate";
|
||||
import { templatePromt } from "../dialogs/Prompt";
|
||||
import { t } from "../lang/helpers";
|
||||
} from "../../shared/ExcalidrawAutomate";
|
||||
import { templatePromt } from "../../shared/Dialogs/Prompt";
|
||||
import { t } from "../../lang/helpers";
|
||||
import {
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
@@ -45,32 +46,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;
|
||||
|
||||
@@ -1044,6 +1046,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,38 @@ 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";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -71,6 +71,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;
|
||||
@@ -249,6 +250,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
renderingConcurrency: 3,
|
||||
allowImageCache: true,
|
||||
allowImageCacheInScene: true,
|
||||
displayExportedImageIfAvailable: false,
|
||||
@@ -1054,55 +1056,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 +1282,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 +1401,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 +1435,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 +1470,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 +1666,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 +1911,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 +2048,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", {
|
||||
@@ -2461,27 +2413,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");
|
||||
@@ -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",
|
||||
@@ -415,6 +417,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 +580,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",
|
||||
@@ -973,4 +980,30 @@ 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)",
|
||||
};
|
||||
|
||||
@@ -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 文件夹(區分大小寫!)",
|
||||
@@ -400,7 +407,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 +417,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 +577,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 +592,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 +636,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>'来打开该绘图",
|
||||
@@ -703,7 +715,7 @@ FILENAME_HEAD: "文件名",
|
||||
"文件浏览器等创建的绘图都将是旧格式(*.excalidraw)。" +
|
||||
"此外,您打开旧格式绘图文件时将不再收到警告消息。",
|
||||
MATHJAX_NAME: "MathJax (LaTeX) 的 javascript 库服务器",
|
||||
MATHJAX_DESC: "如果您在绘图中使用 LaTeX,插件需要从服务器获取并加载一个 javascript 库。" +
|
||||
MATHJAX_DESC: "如果您在绘图中使用 LaTeX,插件需要从服务器获取并加载一个 javascript 库。" +
|
||||
"如果您的网络无法访问某些库服务器,可以尝试通过此选项更换库服务器。"+
|
||||
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
|
||||
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
|
||||
@@ -722,7 +734,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 +797,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 +812,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 +980,30 @@ 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 文档为图片时适用)" ,
|
||||
};
|
||||
@@ -3,11 +3,11 @@ 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, cloneElement } from "src/shared/ExcalidrawAutomate";
|
||||
import { ExportSettings } from "src/view/ExcalidrawView";
|
||||
import { nanoid } from "src/constants/constants";
|
||||
import { svgToBase64 } from "./Utils";
|
||||
import { svgToBase64 } from "../utils/utils";
|
||||
|
||||
export class CropImage {
|
||||
private imageEA: ExcalidrawAutomate;
|
||||
@@ -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 = {
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Modal, Setting, TFile } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { getEA } from "src/core";
|
||||
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";
|
||||
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";
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
@@ -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";
|
||||
|
||||
@@ -17,6 +17,100 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
|
||||
`,
|
||||
"2.7.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":
|
||||
@@ -71,56 +165,4 @@ I misread a line in the Excalidraw package code... ended up breaking image loadi
|
||||
## 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)
|
||||
`,
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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 { 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> {
|
||||
|
||||
@@ -297,8 +297,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 +420,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;",
|
||||
@@ -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 { 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,15 @@ 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 { getNewUniqueFilepath } from "../utils/fileUtils";
|
||||
import { t } from "../lang/helpers";
|
||||
import { displayFontMessage } from "../utils/excalidrawViewUtils";
|
||||
import { getPDFRect } from "../utils/PDFUtils";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
@@ -2028,7 +2028,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,6 +1,6 @@
|
||||
// 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";
|
||||
@@ -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> {
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import { getTransformMatrix, transformPoints } from "./transform";
|
||||
import { pointsOnPath } from "points-on-path";
|
||||
import { randomId, getWindingOrder } from "./utils";
|
||||
import { ROUNDNESS } from "../constants/constants";
|
||||
import { ROUNDNESS } from "../../constants/constants";
|
||||
|
||||
const SUPPORTED_TAGS = [
|
||||
"svg",
|
||||
33
src/types/excalidrawAutomateTypes.ts
Normal file
33
src/types/excalidrawAutomateTypes.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { TFile } from "obsidian";
|
||||
import { FileId } from "src/core";
|
||||
import { ColorMap, MimeType } from "src/shared/EmbeddedFileLoader";
|
||||
|
||||
export type SVGColorInfo = Map<string, {
|
||||
mappedTo: string;
|
||||
fill: boolean;
|
||||
stroke: boolean;
|
||||
}>;
|
||||
|
||||
export type ImageInfo = {
|
||||
mimeType: MimeType,
|
||||
id: FileId,
|
||||
dataURL: DataURL,
|
||||
created: number,
|
||||
isHyperLink?: boolean,
|
||||
hyperlink?: string,
|
||||
file?:string | TFile,
|
||||
hasSVGwithBitmap: boolean,
|
||||
latex?: string,
|
||||
size?: {height: number, width: number},
|
||||
colorMap?: ColorMap,
|
||||
}
|
||||
|
||||
export interface AddImageOptions {
|
||||
topX: number;
|
||||
topY: number;
|
||||
imageFile: TFile | string;
|
||||
scale?: boolean;
|
||||
anchor?: boolean;
|
||||
colorMap?: ColorMap;
|
||||
}
|
||||
62
src/types/excalidrawViewTypes.ts
Normal file
62
src/types/excalidrawViewTypes.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// src/types/ExcalidrawViewTypes.ts
|
||||
|
||||
import { WorkspaceLeaf } from "obsidian";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ObsidianCanvasNode } from "../view/managers/CanvasNodeFactory";
|
||||
|
||||
export type Position = { x: number; y: number };
|
||||
|
||||
export interface SelectedElementWithLink {
|
||||
id: string | null;
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export interface SelectedImage {
|
||||
id: string | null;
|
||||
fileId: FileId | null;
|
||||
}
|
||||
|
||||
export interface EmbeddableLeafRef {
|
||||
leaf: WorkspaceLeaf;
|
||||
node?: ObsidianCanvasNode;
|
||||
editNode?: Function;
|
||||
}
|
||||
|
||||
export interface ViewSemaphores {
|
||||
warnAboutLinearElementLinkClick: boolean;
|
||||
//flag to prevent overwriting the changes the user makes in an embeddable view editing the back side of the drawing
|
||||
embeddableIsEditingSelf: boolean;
|
||||
popoutUnload: boolean; //the unloaded Excalidraw view was the last leaf in the popout window
|
||||
viewunload: boolean;
|
||||
//first time initialization of the view
|
||||
scriptsReady: boolean;
|
||||
|
||||
//The role of justLoaded is to capture the Excalidraw.onChange event that fires right after the canvas was loaded for the first time to
|
||||
//- prevent the first onChange event to mark the file as dirty and to consequently cause a save right after load, causing sync issues in turn
|
||||
//- trigger autozoom (in conjunction with preventAutozoomOnLoad)
|
||||
justLoaded: boolean;
|
||||
|
||||
//the modifyEventHandler in main.ts will fire when an Excalidraw file has changed (e.g. due to sync)
|
||||
//when a drawing that is currently open in a view receives a sync update, excalidraw reload() is triggered
|
||||
//the preventAutozoomOnLoad flag will prevent the open drawing from autozooming when it is reloaded
|
||||
preventAutozoom: boolean;
|
||||
|
||||
autosaving: boolean; //flags that autosaving is in progress. Autosave is an async timer, the flag prevents collision with force save
|
||||
forceSaving: boolean; //flags that forcesaving is in progress. The flag prevents collision with autosaving
|
||||
dirty: string; //null if there are no changes to be saved, the path of the file if the drawing has unsaved changes
|
||||
|
||||
//reload() is triggered by modifyEventHandler in main.ts. preventReload is a one time flag to abort reloading
|
||||
//to avoid interrupting the flow of drawing by the user.
|
||||
preventReload: boolean;
|
||||
|
||||
isEditingText: boolean; //https://stackoverflow.com/questions/27132796/is-there-any-javascript-event-fired-when-the-on-screen-keyboard-on-mobile-safari
|
||||
|
||||
//Save is triggered by multiple threads when an Excalidraw pane is terminated
|
||||
//- by the view itself
|
||||
//- by the activeLeafChangeEventHandler change event handler
|
||||
//- by monkeypatches on detach(next)
|
||||
//This semaphore helps avoid collision of saves
|
||||
saving: boolean;
|
||||
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
|
||||
wheelTimeout:number; //used to avoid hover preview while zooming
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user