Compare commits

...

12 Commits

Author SHA1 Message Date
zsviczian
88f256cd8f Added comments to EA, moved non-class function to EAUtils 2025-01-04 20:23:14 +01:00
zsviczian
1562600cd3 Image mask offset, PDF drift and offset, addAppendUpdateCustomData on EA, some type cleanup
Some checks are pending
CodeQL / Analyze (javascript) (push) Waiting to run
2025-01-04 19:06:43 +01:00
zsviczian
90533138e5 fixed embedding images into Excalidraw with areaRef links did not work as expected due to conflicting width and height values
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
2024-12-31 08:37:46 +01:00
zsviczian
80d8f0e5b6 image occlusion, full-year calendar 2024-12-29 10:52:54 +01:00
zsviczian
9829fab97c Merge pull request #2185 from simonperet/full-year-calendar-script
Add full year calendar generator script
2024-12-29 10:48:30 +01:00
Simon
a33c8b6eab Add a full year calendar generator script 2024-12-29 09:21:44 +01:00
zsviczian
f0921856c1 fix 2184 2024-12-29 07:15:47 +01:00
zsviczian
31e06ac0e0 updated add connector point and set stroke width 2024-12-28 22:19:47 +01:00
zsviczian
033d764b1c 2.7.4 2024-12-28 20:04:22 +01:00
zsviczian
00f98dd14e fix color picker in shade master 2024-12-28 11:14:27 +01:00
zsviczian
0f601d969a updated the script 2024-12-27 22:03:50 +01:00
zsviczian
8fa0fb37b2 shade master mobile friendly(er) 2024-12-27 21:35:57 +01:00
33 changed files with 7052 additions and 833 deletions

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ hot-reload.bat
data.json
lib
dist
tmp
#VSCode
.vscode

5782
docs/Release-notes.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -22,4 +22,4 @@ elements.forEach((el)=>{
);
ea.addToGroup([el.id,ellipseId]);
});
ea.addElementsToView(false,false);
await ea.addElementsToView(false,false,true);

View File

@@ -0,0 +1,157 @@
/*
This script generates a complete calendar for a specified year, visually distinguishing weekends from weekdays through color coding.
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-exemple.excalidraw.png)
## Customizable Colors
You can personalize the calendars 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).
![](https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-full-year-calendar-customize.excalidraw.png)
```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);

View 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

View File

@@ -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}});

View File

@@ -31,7 +31,7 @@ const HELP_TEXT = `
</div></div>
`;
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.3")) {
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.7.2")) {
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
return;
}
@@ -332,7 +332,8 @@ function showModal() {
let dirty = false;
modal.onOpen = async () => {
const { contentEl } = modal;
const { contentEl, modalEl } = modal;
const { width, height } = ea.getExcalidrawAPI().getAppState();
modal.bgOpacity = 0;
contentEl.createEl('h2', { text: 'Shade Master' });
@@ -397,12 +398,14 @@ function showModal() {
// Add color pickers if a single SVG image is selected
if (svgImageElements.length === 1) {
const svgElement = svgImageElements[0];
const colorInfo = await ea.getSVGColorInfoForImgElement(svgElement);
//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 colorInfo.entries()) {
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" : ""}`);
@@ -424,7 +427,7 @@ function showModal() {
const resetButton = new ea.obsidian.Setting(row.controlEl)
.addButton(button => button
.setButtonText(">>>")
.setButtonText(">>")
.setClass("reset-color-button")
.onClick(async () => {
const original = originalColors.get(svgElement.id);
@@ -474,9 +477,9 @@ function showModal() {
: cm.stringHSL({alpha, precision }).toLowerCase();
textInput.setValue(newColor);
const colorInfo = currentColors.get(svgElement.id).colors;
colorInfo.get(color).mappedTo = newColor;
run("no action");
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());
}
@@ -490,6 +493,8 @@ function showModal() {
// 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, {
@@ -506,8 +511,9 @@ function showModal() {
colorPicker.onChange(async (value) => {
try {
if(!debounceColorPicker) {
// Preserve alpha from original color
const originalAlpha = ea.getCM(info.mappedTo).alpha;
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;
@@ -522,14 +528,8 @@ function showModal() {
textInput.setValue(newColor);
// Update SVG
const newColorMap = await ea.getColorMapForImageElement(svgElement);
if(color === newColor) {
delete newColorMap[color];
} else {
newColorMap[color] = newColor;
}
updatedImageElementColorMaps.set(svgElement, newColorMap);
updateViewImageColors();
currentInfo.mappedTo = newColor;
run("Update SVG color");
}
} catch (e) {
console.error("Invalid color value:", e);
@@ -566,7 +566,12 @@ function showModal() {
.setCta(true)
.onClick(() => modal.close()));
makeModalDraggable(modal.modalEl);
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 = () => {

File diff suppressed because one or more lines are too long

View File

@@ -148,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
@@ -394,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

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

View File

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

View File

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

View File

@@ -39,7 +39,8 @@ import {
setRootElementSize,
} from "../constants/constants";
import { ExcalidrawSettings, DEFAULT_SETTINGS, ExcalidrawSettingTab } from "./settings";
import { initExcalidrawAutomate, ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
import { ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
import { initExcalidrawAutomate } from "src/utils/excalidrawAutomateUtils";
import { around, dedupe } from "monkey-around";
import { t } from "../lang/helpers";
import {
@@ -91,6 +92,15 @@ import { EventManager } from "./managers/EventManager";
declare const PLUGIN_VERSION:string;
declare const INITIAL_TIMESTAMP: number;
type FileMasterInfo = {
isHyperLink: boolean;
isLocalLink: boolean;
path: string;
hasSVGwithBitmap: boolean;
blockrefData: string,
colorMapJSON?: string
}
export default class ExcalidrawPlugin extends Plugin {
private fileManager: PluginFileManager;
private observerManager: ObserverManager;
@@ -113,7 +123,7 @@ export default class ExcalidrawPlugin extends Plugin {
public opencount: number = 0;
public ea: ExcalidrawAutomate;
//A master list of fileIds to facilitate copy / paste
public filesMaster: Map<FileId, { isHyperLink: boolean; isLocalLink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string, colorMapJSON?: string}> =
public filesMaster: Map<FileId, FileMasterInfo> =
null; //fileId, path
public equationsMaster: Map<FileId, string> = null; //fileId, formula
public mermaidsMaster: Map<FileId, string> = null; //fileId, mermaidText

View File

@@ -29,11 +29,8 @@ 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 "../../shared/ExcalidrawAutomate";
import { ExcalidrawAutomate } from "../../shared/ExcalidrawAutomate";
import { insertLaTeXToView, search } from "src/utils/excalidrawAutomateUtils";
import { templatePromt } from "../../shared/Dialogs/Prompt";
import { t } from "../../lang/helpers";
import {

View File

@@ -8,7 +8,7 @@ import {
} from "obsidian";
import { DEVICE, RERENDER_EVENT } from "../../constants/constants";
import { EmbeddedFilesLoader } from "../../shared/EmbeddedFileLoader";
import { createPNG, createSVG } from "../../shared/ExcalidrawAutomate";
import { createPNG, createSVG } from "../../utils/excalidrawAutomateUtils";
import { ExportSettings } from "../../view/ExcalidrawView";
import ExcalidrawPlugin from "../main";
import {getIMGFilename,} from "../../utils/fileUtils";
@@ -893,7 +893,12 @@ export const markdownPostProcessor = async (
el.firstElementChild?.hasClass("frontmatter") ||
el.firstElementChild?.hasClass("block-language-yaml");
if(isPrinting && isFrontmatter) {
return;
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2184
const file = app.vault.getAbstractFileByPath(ctx.sourcePath);
const isDrawing = file && file instanceof TFile && plugin.isExcalidrawFile(file);
if(isDrawing) {
return;
}
}
//@ts-ignore

View File

@@ -4,11 +4,36 @@ import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { Notice } from "obsidian";
import { getEA } from "src/core";
import { ExcalidrawAutomate, cloneElement } from "src/shared/ExcalidrawAutomate";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import { cloneElement } from "src/utils/excalidrawAutomateUtils";
import { ExportSettings } from "src/view/ExcalidrawView";
import { nanoid } from "src/constants/constants";
import { svgToBase64 } from "../utils/utils";
/**
* Creates a masked image from an Excalidraw scene.
*
* The scene must contain:
* - One element.type="frame" element that defines the crop area
* - One or more element.type="image" elements
* - Zero or more non-image shape elements (rectangles, ellipses etc) that define the mask
*
* The class splits the scene into two parts:
* 1. Images (managed in imageEA)
* 2. Mask shapes (managed in maskEA)
*
* A transparent rectangle matching the combined bounding box is added to both
* imageEA and maskEA to ensure consistent sizing between image and mask.
*
* For performance, if there is only one image, it is not rotated, and
* its size matches the bounding box,
* the image data is used directly from cache rather than regenerating.
*
* @example
* const cropper = new CropImage(elements, files);
* const pngBlob = await cropper.getCroppedPNG();
* cropper.destroy();
*/
export class CropImage {
private imageEA: ExcalidrawAutomate;
private maskEA: ExcalidrawAutomate;
@@ -106,10 +131,15 @@ export class CropImage {
withTheme: false,
isMask: false,
}
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
const images = Object.values(this.imageEA.imagesDict);
if(!isRotated && (images.length === 1)) {
return images[0].dataURL;
const images = this.imageEA.getElements().filter(el=>el.type === "image" && el.isDeleted === false);
const isRotated = images.some(el=>el.angle !== 0);
const imageDataURLs = Object.values(this.imageEA.imagesDict);
if(!isRotated && images.length === 1 && imageDataURLs.length === 1) {
const { width, height } = this.bbox;
if(images[0].width === width && images[0].height === height) {
//get image from the cache if mask is not bigger than the image, and if there is a single image element
return imageDataURLs[0].dataURL;
}
}
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
}

View File

@@ -17,6 +17,11 @@ 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>

View File

@@ -1,4 +1,4 @@
import { App, FuzzySuggestModal, Notice, TFile } from "obsidian";
import { App, FuzzySuggestModal, Notice } from "obsidian";
import { t } from "../../lang/helpers";
import ExcalidrawView from "src/view/ExcalidrawView";
import { getEA } from "src/core";

View File

@@ -157,6 +157,15 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
desc: "Set ea.style.roundness. 0: is the legacy value, 3: is the current default value, null is sharp",
after: "",
},
{
field: "addAppendUpdateCustomData",
code: "addAppendUpdateCustomData(id: string, newData: Partial<Record<string, unknown>>)",
desc: "Add, modify keys in element customData and preserve existing keys.\n" +
"Creates customData={} if it does not exist.\n" +
"Takes the element ID for an element in the elementsDict and the new data to add or modify.\n" +
"To delete keys set key value in newData to undefined. so {keyToBeDeleted:undefined} will be deleted.",
after: "",
},
{
field: "addToGroup",
code: "addToGroup(objectIds: []): string;",

View File

@@ -13,7 +13,7 @@ import {
FRONTMATTER_KEYS,
getCSSFontDefinition,
} from "../constants/constants";
import { createSVG } from "./ExcalidrawAutomate";
import { createSVG } from "src/utils/excalidrawAutomateUtils";
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
import { ExportSettings } from "../view/ExcalidrawView";
import { t } from "../lang/helpers";
@@ -73,7 +73,8 @@ type ImgData = {
dataURL: DataURL;
created: number;
hasSVGwithBitmap: boolean;
size: { height: number; width: number };
size: Size;
pdfPageViewProps?: PDFPageViewProps;
};
export declare type MimeType = ValueOf<typeof IMAGE_MIME_TYPES> | "application/octet-stream";
@@ -82,8 +83,16 @@ export type FileData = BinaryFileData & {
size: Size;
hasSVGwithBitmap: boolean;
shouldScale: boolean; //true if image should maintain its area, false if image should display at 100% its size
pdfPageViewProps?: PDFPageViewProps;
};
export type PDFPageViewProps = {
left: number;
bottom: number;
right: number;
top: number;
}
export type Size = {
height: number;
width: number;
@@ -177,6 +186,7 @@ export class EmbeddedFile {
public isLocalLink: boolean = false;
public hyperlink:DataURL;
public colorMap: ColorMap | null = null;
public pdfPageViewProps: PDFPageViewProps;
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string, colorMapJSON?: string) {
this.plugin = plugin;
@@ -252,12 +262,14 @@ export class EmbeddedFile {
return this.mtime !== this.file.stat.mtime;
}
public setImage(
imgBase64: string,
mimeType: MimeType,
size: Size,
isDark: boolean,
isSVGwithBitmap: boolean,
public setImage({ imgBase64, mimeType, size, isDark, isSVGwithBitmap, pdfPageViewProps } : {
imgBase64: string;
mimeType: MimeType;
size: Size;
isDark: boolean;
isSVGwithBitmap: boolean;
pdfPageViewProps?: PDFPageViewProps;
}
) {
if (!this.file && !this.isHyperLink && !this.isLocalLink) {
return;
@@ -266,6 +278,7 @@ export class EmbeddedFile {
this.imgInverted = this.img = "";
}
this.mtime = this.isHyperLink || this.isLocalLink ? 0 : this.file.stat.mtime;
this.pdfPageViewProps = pdfPageViewProps;
this.size = size;
this.mimeType = mimeType;
switch (isDark && isSVGwithBitmap) {
@@ -345,6 +358,7 @@ export class EmbeddedFilesLoader {
created: number;
hasSVGwithBitmap: boolean;
size: { height: number; width: number };
pdfPageViewProps?: PDFPageViewProps;
}> {
const result = await this._getObsidianImage(inFile, depth);
this.emptyPDFDocsMap();
@@ -552,9 +566,9 @@ export class EmbeddedFilesLoader {
const excalidrawSVG = isExcalidrawFile ? dURL : null;
const [pdfDataURL, pdfSize] = isPDF
const [pdfDataURL, pdfSize, pdfPageViewProps] = isPDF
? await this.pdfToDataURL(file,linkParts)
: [null, null];
: [null, null, null];
let mimeType: MimeType = isPDF
? "image/png"
@@ -593,13 +607,14 @@ export class EmbeddedFilesLoader {
return {
mimeType,
fileId: await generateIdFromFile(
isHyperLink || isPDF ? (new TextEncoder()).encode(dataURL as string) : ab,
isHyperLink || isPDF || isExcalidrawFile ? (new TextEncoder()).encode(dataURL as string) : ab,
inFile instanceof EmbeddedFile ? inFile.filenameparts?.linkpartReference : undefined
),
dataURL,
created: isHyperLink || isLocalLink ? 0 : file.stat.mtime,
hasSVGwithBitmap,
size,
pdfPageViewProps,
};
} catch(e) {
return null;
@@ -634,7 +649,7 @@ export class EmbeddedFilesLoader {
files.push([]);
let batch = 0;
function* loadIterator():Generator<Promise<void>> {
function* loadIterator(this: EmbeddedFilesLoader):Generator<Promise<void>> {
while (!(entry = entries.next()).done) {
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
const embeddedFile: EmbeddedFile = entry.value[1];
@@ -654,20 +669,22 @@ export class EmbeddedFilesLoader {
created: data.created,
size: data.size,
hasSVGwithBitmap: data.hasSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
shouldScale: embeddedFile.shouldScale(),
pdfPageViewProps: data.pdfPageViewProps,
};
files[batch].push(fileData);
}
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
//this will reload the image in light/dark mode when switching themes
const fileData = {
const fileData: FileData = {
mimeType: embeddedFile.mimeType,
id: id,
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
created: embeddedFile.mtime,
size: embeddedFile.size,
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
shouldScale: embeddedFile.shouldScale()
shouldScale: embeddedFile.shouldScale(),
pdfPageViewProps: embeddedFile.pdfPageViewProps,
};
files[batch].push(fileData);
}
@@ -803,7 +820,7 @@ export class EmbeddedFilesLoader {
private async pdfToDataURL(
file: TFile,
linkParts: LinkParts,
): Promise<[DataURL,{width:number, height:number}]> {
): Promise<[DataURL,Size, PDFPageViewProps]> {
try {
let width = 0, height = 0;
const pdfDoc = this.pdfDocsMap.get(file.path) ?? await getPDFDoc(file);
@@ -814,6 +831,7 @@ export class EmbeddedFilesLoader {
const scale = this.plugin.settings.pdfScale;
const cropRect = linkParts.ref.split("rect=")[1]?.split(",").map(x=>parseInt(x));
const validRect = cropRect && cropRect.length === 4 && cropRect.every(x=>!isNaN(x));
let viewProps: PDFPageViewProps;
// Render the page
const renderPage = async (num:number) => {
@@ -824,8 +842,8 @@ export class EmbeddedFilesLoader {
const page = await pdfDoc.getPage(num);
// Set scale
const viewport = page.getViewport({ scale });
height = canvas.height = viewport.height;
width = canvas.width = viewport.width;
height = canvas.height = Math.round(viewport.height);
width = canvas.width = Math.round(viewport.width);
const renderCtx = {
canvasContext: ctx,
@@ -846,9 +864,10 @@ export class EmbeddedFilesLoader {
continue;
}
}
const [left, bottom, right, top] = page.view;
viewProps = {left, bottom, right, top};
if(validRect) {
const [left, bottom, _, top] = page.view;
const pageHeight = top - bottom;
width = (cropRect[2] - cropRect[0]) * scale;
height = (cropRect[3] - cropRect[1]) * scale;
@@ -868,19 +887,19 @@ export class EmbeddedFilesLoader {
const canvas = await renderPage(pageNum);
if(canvas) {
const result: [DataURL,{width:number, height:number}] = [`data:image/png;base64,${await new Promise((resolve, reject) => {
const result: [DataURL,Size, PDFPageViewProps] = [`data:image/png;base64,${await new Promise((resolve, reject) => {
canvas.toBlob(async (blob) => {
const dataURL = await blobToBase64(blob);
resolve(dataURL);
});
})}` as DataURL, {width, height}];
})}` as DataURL, {width, height}, viewProps];
canvas.width = 0; //free memory iOS bug
canvas.height = 0;
return result;
}
} catch(e) {
console.log(e);
return [null,null];
return [null, null, null];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -649,7 +649,6 @@ export class ExcalidrawData {
containers.forEach((container: any) => {
if(ellipseAndRhombusContainerWrapping && !container.customData?.legacyTextWrap) {
addAppendUpdateCustomData(container, {legacyTextWrap: true});
//container.customData = {...container.customData, legacyTextWrap: true};
}
const filteredBoundElements = container.boundElements.filter(
(boundEl: any) => elements.some((el: any) => el.id === boundEl.id),
@@ -1569,13 +1568,13 @@ export class ExcalidrawData {
filepath,
);
embeddedFile.setImage(
dataURL,
embeddedFile.setImage({
imgBase64: dataURL,
mimeType,
{ height: 0, width: 0 },
scene.appState?.theme === "dark",
mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
);
size: { height: 0, width: 0 },
isDark: scene.appState?.theme === "dark",
isSVGwithBitmap: mimeType === "image/svg+xml", //this treat all SVGs as if they had embedded images REF:addIMAGE
});
this.setFile(key as FileId, embeddedFile);
return file;
}
@@ -1593,7 +1592,9 @@ export class ExcalidrawData {
const pageRef = ef.linkParts.original.split("#")?.[1];
if(!pageRef || !pageRef.startsWith("page=") || pageRef.includes("rect")) return;
const restOfLink = el.link ? el.link.match(/&rect=\d*,\d*,\d*,\d*(.*)/)?.[1] : "";
const link = ef.linkParts.original + getPDFRect(el.crop, pdfScale) + (restOfLink ? restOfLink : "]]");
const link = ef.linkParts.original +
getPDFRect({elCrop: el.crop, scale: pdfScale, customData: el.customData}) +
(restOfLink ? restOfLink : "]]");
el.link = `[[${link}`;
this.elementLinks.set(el.id, el.link);
dirty = true;
@@ -1992,7 +1993,7 @@ export class ExcalidrawData {
isLocalLink: data.isLocalLink,
path: data.hyperlink,
blockrefData: null,
hasSVGwithBitmap: data.isSVGwithBitmap
hasSVGwithBitmap: data.isSVGwithBitmap,
});
return;
}

View File

@@ -1,7 +1,7 @@
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
import { TFile } from "obsidian";
import { FileId } from "src/core";
import { ColorMap, MimeType } from "src/shared/EmbeddedFileLoader";
import { ColorMap, MimeType, PDFPageViewProps, Size } from "src/shared/EmbeddedFileLoader";
export type SVGColorInfo = Map<string, {
mappedTo: string;
@@ -19,8 +19,9 @@ export type ImageInfo = {
file?:string | TFile,
hasSVGwithBitmap: boolean,
latex?: string,
size?: {height: number, width: number},
size?: Size,
colorMap?: ColorMap,
pdfPageViewProps?: PDFPageViewProps,
}
export interface AddImageOptions {

View File

@@ -1,4 +1,4 @@
import { Notice, RequestUrlResponse, requestUrl } from "obsidian";
import { Notice, RequestUrlResponse } from "obsidian";
import ExcalidrawPlugin from "src/core/main";
type MessageContent =

View File

@@ -1,26 +1,29 @@
//for future use, not used currently
import { ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { PDFPageViewProps } from "src/shared/EmbeddedFileLoader";
export function getPDFCropRect (props: {
scale: number,
link: string,
naturalHeight: number,
naturalWidth: number,
pdfPageViewProps: PDFPageViewProps,
}) : ImageCrop | null {
const rectVal = props.link.match(/&rect=(\d*),(\d*),(\d*),(\d*)/);
if (!rectVal || rectVal.length !== 5) {
return null;
}
const { left, bottom } = props.pdfPageViewProps;
const R0 = parseInt(rectVal[1]);
const R1 = parseInt(rectVal[2]);
const R2 = parseInt(rectVal[3]);
const R3 = parseInt(rectVal[4]);
return {
x: R0 * props.scale,
y: (props.naturalHeight/props.scale - R3) * props.scale,
x: (R0 - left) * props.scale,
y: (bottom + props.naturalHeight/props.scale - R3) * props.scale,
width: (R2 - R0) * props.scale,
height: (R3 - R1) * props.scale,
naturalWidth: props.naturalWidth,
@@ -28,10 +31,16 @@ export function getPDFCropRect (props: {
}
}
export function getPDFRect(elCrop: ImageCrop, scale: number): string {
const R0 = elCrop.x / scale;
export function getPDFRect({elCrop, scale, customData}:{
elCrop: ImageCrop, scale: number, customData: Record<string, unknown>
}): string {
const { left, bottom } = (customData && customData.pdfPageViewProps)
? customData.pdfPageViewProps as PDFPageViewProps
: { left: 0, bottom: 0 };
const R0 = elCrop.x / scale + left;
const R2 = elCrop.width / scale + R0;
const R3 = (elCrop.naturalHeight - elCrop.y) / scale;
const R3 = bottom + (elCrop.naturalHeight - elCrop.y) / scale;
const R1 = R3 - elCrop.height / scale;
return `&rect=${Math.round(R0)},${Math.round(R1)},${Math.round(R2)},${Math.round(R3)}`;
}

View File

@@ -3,7 +3,7 @@ import { ColorMaster } from "@zsviczian/colormaster";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import ExcalidrawView from "src/view/ExcalidrawView";
import { DynamicStyle } from "src/types/types";
import { cloneElement } from "src/shared/ExcalidrawAutomate";
import { cloneElement } from "./excalidrawAutomateUtils";
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { addAppendUpdateCustomData } from "./utils";
import { mutateElement } from "src/constants/constants";

View File

@@ -1,8 +1,49 @@
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import { errorlog } from "./utils";
import { ColorMap } from "src/shared/EmbeddedFileLoader";
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
import ExcalidrawPlugin from "src/core/main";
import {
ExcalidrawElement,
ExcalidrawImageElement,
FileId,
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { normalizePath, TFile } from "obsidian";
import ExcalidrawView, { ExportSettings, getTextMode } from "src/view/ExcalidrawView";
import {
GITHUB_RELEASES,
getCommonBoundingBox,
restore,
REG_LINKINDEX_INVALIDCHARS,
THEME_FILTER,
EXCALIDRAW_PLUGIN,
getFontFamilyString,
getLineHeight,
measureText,
} from "src/constants/constants";
import {
//debug,
errorlog,
getEmbeddedFilenameParts,
getLinkParts,
getPNG,
getSVG,
isVersionNewerThanOther,
scaleLoadedImage,
} from "src/utils/utils";
import { GenericInputPrompt, NewFileActions } from "src/shared/Dialogs/Prompt";
import { t } from "src/lang/helpers";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import {
postOpenAI as _postOpenAI,
extractCodeBlocks as _extractCodeBlocks,
} from "../utils/AIUtils";
import { ColorMap, EmbeddedFilesLoader, FileData } from "src/shared/EmbeddedFileLoader";
import { SVGColorInfo } from "src/types/excalidrawAutomateTypes";
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, REGEX_LINK } from "src/shared/ExcalidrawData";
import { getFrameBasedOnFrameNameOrId } from "./excalidrawViewUtils";
import { ScriptEngine } from "src/shared/Scripts";
declare const PLUGIN_VERSION:string;
export function isSVGColorInfo(obj: ColorMap | SVGColorInfo): boolean {
return (
@@ -112,3 +153,668 @@ export function isColorStringTransparent(color: string): boolean {
return rgbaHslaTransparentRegex.test(color) || hexTransparentRegex.test(color);
}
export function initExcalidrawAutomate(
plugin: ExcalidrawPlugin,
): ExcalidrawAutomate {
const ea = new ExcalidrawAutomate(plugin);
//@ts-ignore
window.ExcalidrawAutomate = ea;
return ea;
}
export function normalizeLinePoints(
points: [x: number, y: number][],
//box: { x: number; y: number; w: number; h: number },
): number[][] {
const p = [];
const [x, y] = points[0];
for (let i = 0; i < points.length; i++) {
p.push([points[i][0] - x, points[i][1] - y]);
}
return p;
}
export function getLineBox(
points: [x: number, y: number][]
):{x:number, y:number, w: number, h:number} {
const [x1, y1, x2, y2] = estimateLineBound(points);
return {
x: x1,
y: y1,
w: x2 - x1, //Math.abs(points[points.length-1][0]-points[0][0]),
h: y2 - y1, //Math.abs(points[points.length-1][1]-points[0][1])
};
}
export function getFontFamily(id: number):string {
return getFontFamilyString({fontFamily:id})
}
export function _measureText(
newText: string,
fontSize: number,
fontFamily: number,
lineHeight: number,
): {w: number, h:number} {
//following odd error with mindmap on iPad while synchornizing with desktop.
if (!fontSize) {
fontSize = 20;
}
if (!fontFamily) {
fontFamily = 1;
lineHeight = getLineHeight(fontFamily);
}
const metrics = measureText(
newText,
`${fontSize.toString()}px ${getFontFamily(fontFamily)}` as any,
lineHeight
);
return { w: metrics.width, h: metrics.height };
}
export async function getTemplate(
plugin: ExcalidrawPlugin,
fileWithPath: string,
loadFiles: boolean = false,
loader: EmbeddedFilesLoader,
depth: number,
convertMarkdownLinksToObsidianURLs: boolean = false,
): Promise<{
elements: any;
appState: any;
frontmatter: string;
files: any;
hasSVGwithBitmap: boolean;
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
}> {
const app = plugin.app;
const vault = app.vault;
const filenameParts = getEmbeddedFilenameParts(fileWithPath);
const templatePath = normalizePath(filenameParts.filepath);
const file = app.metadataCache.getFirstLinkpathDest(templatePath, "");
let hasSVGwithBitmap = false;
if (file && file instanceof TFile) {
const data = (await vault.read(file))
.replaceAll("\r\n", "\n")
.replaceAll("\r", "\n");
const excalidrawData: ExcalidrawData = new ExcalidrawData(plugin);
if (file.extension === "excalidraw") {
await excalidrawData.loadLegacyData(data, file);
return {
elements: convertMarkdownLinksToObsidianURLs
? updateElementLinksToObsidianLinks({
elements: excalidrawData.scene.elements,
hostFile: file,
}) : excalidrawData.scene.elements,
appState: excalidrawData.scene.appState,
frontmatter: "",
files: excalidrawData.scene.files,
hasSVGwithBitmap,
plaintext: "",
};
}
const textMode = getTextMode(data);
await excalidrawData.loadData(
data,
file,
textMode,
);
let trimLocation = data.search(/^##? Text Elements$/m);
if (trimLocation == -1) {
trimLocation = data.search(/##? Drawing\n/);
}
let scene = excalidrawData.scene;
let groupElements:ExcalidrawElement[] = scene.elements;
if(filenameParts.hasGroupref) {
const el = filenameParts.hasSectionref
? getTextElementsMatchingQuery(scene.elements,["# "+filenameParts.sectionref],true)
: scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
if(el.length > 0) {
groupElements = plugin.ea.getElementsInTheSameGroupWithElement(el[0],scene.elements,true)
}
}
if(filenameParts.hasFrameref || filenameParts.hasClippedFrameref) {
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
if(el) {
groupElements = plugin.ea.getElementsInFrame(el,scene.elements, filenameParts.hasClippedFrameref);
}
}
if(filenameParts.hasTaskbone) {
groupElements = groupElements.filter( el =>
el.type==="freedraw" ||
( el.type==="image" &&
!plugin.isExcalidrawFile(excalidrawData.getFile(el.fileId)?.file)
));
}
let fileIDWhiteList:Set<FileId>;
if(groupElements.length < scene.elements.length) {
fileIDWhiteList = new Set<FileId>();
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
}
if (loadFiles) {
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
await loader.loadSceneFiles({
excalidrawData,
addFiles: (fileArray: FileData[]) => {
//, isDark: boolean) => {
if (!fileArray || fileArray.length === 0) {
return;
}
for (const f of fileArray) {
if (f.hasSVGwithBitmap) {
hasSVGwithBitmap = true;
}
excalidrawData.scene.files[f.id] = {
mimeType: f.mimeType,
id: f.id,
dataURL: f.dataURL,
created: f.created,
};
}
scene = scaleLoadedImage(excalidrawData.scene, fileArray).scene;
},
depth,
fileIDWhiteList
});
}
excalidrawData.destroy();
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
let files:any = {};
const sceneFilesSize = Object.values(scene.files).length;
if (sceneFilesSize > 0) {
if(fileIDWhiteList && (sceneFilesSize > fileIDWhiteList.size)) {
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
files[f.id] = f;
});
} else {
files = scene.files;
}
}
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
return {
elements: convertMarkdownLinksToObsidianURLs
? updateElementLinksToObsidianLinks({
elements: groupElements,
hostFile: file,
}) : groupElements,
appState: scene.appState,
frontmatter,
plaintext: frontmatter !== filehead
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
: "",
files,
hasSVGwithBitmap,
};
}
return {
elements: [],
appState: {},
frontmatter: null,
files: [],
hasSVGwithBitmap,
plaintext: "",
};
}
export const generatePlaceholderDataURL = (width: number, height: number): DataURL => {
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="#E7E7E7" /><text x="${width / 2}" y="${height / 2}" dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="${Math.min(width, height) / 5}" fill="#888">Placeholder</text></svg>`;
return `data:image/svg+xml;base64,${btoa(svgString)}` as DataURL;
};
export async function createPNG(
templatePath: string = undefined,
scale: number = 1,
exportSettings: ExportSettings,
loader: EmbeddedFilesLoader,
forceTheme: string = undefined,
canvasTheme: string = undefined,
canvasBackgroundColor: string = undefined,
automateElements: ExcalidrawElement[] = [],
plugin: ExcalidrawPlugin,
depth: number,
padding?: number,
imagesDict?: any,
): Promise<Blob> {
if (!loader) {
loader = new EmbeddedFilesLoader(plugin);
}
padding = padding ?? plugin.settings.exportPaddingSVG;
const template = templatePath
? await getTemplate(plugin, templatePath, true, loader, depth)
: null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
const files = imagesDict ?? {};
if(template?.files) {
Object.values(template.files).forEach((f:any)=>{
if(!f.dataURL.startsWith("http")) {
files[f.id]=f;
};
});
}
return await getPNG(
{
type: "excalidraw",
version: 2,
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements,
appState: {
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
viewBackgroundColor:
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
},
files,
},
{
withBackground:
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme,
isMask: exportSettings?.isMask ?? false,
},
padding,
scale,
);
}
export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
elements: ExcalidrawElement[];
hostFile: TFile;
}): ExcalidrawElement[] => {
return elements.map((el)=>{
if(el.link && el.link.startsWith("[")) {
const partsArray = REGEX_LINK.getResList(el.link)[0];
if(!partsArray?.value) return el;
let linkText = REGEX_LINK.getLink(partsArray);
if (linkText.search("#") > -1) {
const linkParts = getLinkParts(linkText, hostFile);
linkText = linkParts.path;
}
if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) {
return el;
}
const file = EXCALIDRAW_PLUGIN.app.metadataCache.getFirstLinkpathDest(
linkText,
hostFile.path,
);
if(!file) {
return el;
}
let link = EXCALIDRAW_PLUGIN.app.getObsidianUrl(file);
if(window.ExcalidrawAutomate?.onUpdateElementLinkForExportHook) {
link = window.ExcalidrawAutomate.onUpdateElementLinkForExportHook({
originalLink: el.link,
obsidianLink: link,
linkedFile: file,
hostFile: hostFile
});
}
const newElement: Mutable<ExcalidrawElement> = cloneElement(el);
newElement.link = link;
return newElement;
}
return el;
})
}
function addFilterToForeignObjects(svg:SVGSVGElement):void {
const foreignObjects = svg.querySelectorAll("foreignObject");
foreignObjects.forEach((foreignObject) => {
foreignObject.setAttribute("filter", THEME_FILTER);
});
}
export async function createSVG(
templatePath: string = undefined,
embedFont: boolean = false,
exportSettings: ExportSettings,
loader: EmbeddedFilesLoader,
forceTheme: string = undefined,
canvasTheme: string = undefined,
canvasBackgroundColor: string = undefined,
automateElements: ExcalidrawElement[] = [],
plugin: ExcalidrawPlugin,
depth: number,
padding?: number,
imagesDict?: any,
convertMarkdownLinksToObsidianURLs: boolean = false,
): Promise<SVGSVGElement> {
if (!loader) {
loader = new EmbeddedFilesLoader(plugin);
}
if(typeof exportSettings.skipInliningFonts === "undefined") {
exportSettings.skipInliningFonts = !embedFont;
}
const template = templatePath
? await getTemplate(plugin, templatePath, true, loader, depth, convertMarkdownLinksToObsidianURLs)
: null;
let elements = template?.elements ?? [];
elements = elements.concat(automateElements);
padding = padding ?? plugin.settings.exportPaddingSVG;
const files = imagesDict ?? {};
if(template?.files) {
Object.values(template.files).forEach((f:any)=>{
files[f.id]=f;
});
}
const theme = forceTheme ?? template?.appState?.theme ?? canvasTheme;
const withTheme = exportSettings?.withTheme ?? plugin.settings.exportWithTheme;
const filenameParts = getEmbeddedFilenameParts(templatePath);
const svg = await getSVG(
{
//createAndOpenDrawing
type: "excalidraw",
version: 2,
source: GITHUB_RELEASES+PLUGIN_VERSION,
elements,
appState: {
theme,
viewBackgroundColor:
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
},
files,
},
{
withBackground:
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
withTheme,
isMask: exportSettings?.isMask ?? false,
...filenameParts?.hasClippedFrameref
? {frameRendering: {enabled: true, name: false, outline: false, clip: true}}
: {},
},
padding,
null,
);
if (withTheme && theme === "dark") addFilterToForeignObjects(svg);
if(
!(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref) &&
(filenameParts.hasBlockref || filenameParts.hasSectionref)
) {
let el = filenameParts.hasSectionref
? getTextElementsMatchingQuery(elements,["# "+filenameParts.sectionref],true)
: elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
if(el.length>0) {
const containerId = el[0].containerId;
if(containerId) {
el = el.concat(elements.filter((el: ExcalidrawElement)=>el.id === containerId));
}
const elBB = plugin.ea.getBoundingBox(el);
const drawingBB = plugin.ea.getBoundingBox(elements);
svg.viewBox.baseVal.x = elBB.topX - drawingBB.topX;
svg.viewBox.baseVal.y = elBB.topY - drawingBB.topY;
const width = elBB.width + 2*padding;
svg.viewBox.baseVal.width = width;
const height = elBB.height + 2*padding;
svg.viewBox.baseVal.height = height;
svg.setAttribute("width", `${width}`);
svg.setAttribute("height", `${height}`);
}
}
if (template?.hasSVGwithBitmap) {
svg.setAttribute("hasbitmap", "true");
}
return svg;
}
function estimateLineBound(points: any): [number, number, number, number] {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const [x, y] of points) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
return [minX, minY, maxX, maxY];
}
export function estimateBounds(
elements: ExcalidrawElement[],
): [number, number, number, number] {
const bb = getCommonBoundingBox(elements);
return [bb.minX, bb.minY, bb.maxX, bb.maxY];
}
export function repositionElementsToCursor(
elements: ExcalidrawElement[],
newPosition: { x: number; y: number },
center: boolean = false,
): ExcalidrawElement[] {
const [x1, y1, x2, y2] = estimateBounds(elements);
let [offsetX, offsetY] = [0, 0];
if (center) {
[offsetX, offsetY] = [
newPosition.x - (x1 + x2) / 2,
newPosition.y - (y1 + y2) / 2,
];
} else {
[offsetX, offsetY] = [newPosition.x - x1, newPosition.y - y1];
}
elements.forEach((element: any) => {
//using any so I can write read-only propery x & y
element.x = element.x + offsetX;
element.y = element.y + offsetY;
});
return restore({elements}, null, null).elements;
}
export const insertLaTeXToView = (view: ExcalidrawView) => {
const app = view.plugin.app;
const ea = view.plugin.ea;
GenericInputPrompt.Prompt(
view,
view.plugin,
app,
t("ENTER_LATEX"),
"\\color{red}\\oint_S {E_n dA = \\frac{1}{{\\varepsilon _0 }}} Q_{inside}",
view.plugin.settings.latexBoilerplate,
undefined,
3
).then(async (formula: string) => {
if (!formula) {
return;
}
ea.reset();
await ea.addLaTex(0, 0, formula);
ea.setView(view);
ea.addElementsToView(true, false, true);
});
};
export const search = async (view: ExcalidrawView) => {
const ea = view.plugin.ea;
ea.reset();
ea.setView(view);
const elements = ea.getViewElements().filter((el) => el.type === "text" || el.type === "frame" || el.link || el.type === "image");
if (elements.length === 0) {
return;
}
let text = await ScriptEngine.inputPrompt(
view,
view.plugin,
view.plugin.app,
"Search for",
"use quotation marks for exact match",
"",
);
if (!text) {
return;
}
const res = text.matchAll(/"(.*?)"/g);
let query: string[] = [];
let parts;
while (!(parts = res.next()).done) {
query.push(parts.value[1]);
}
text = text.replaceAll(/"(.*?)"/g, "");
query = query.concat(text.split(" ").filter((s:string) => s.length !== 0));
ea.targetView.selectElementsMatchingQuery(elements, query);
};
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export const getTextElementsMatchingQuery = (
elements: ExcalidrawElement[],
query: string[],
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
): ExcalidrawElement[] => {
if (!elements || elements.length === 0 || !query || query.length === 0) {
return [];
}
return elements.filter((el: any) =>
el.type === "text" &&
query.some((q) => {
if (exactMatch) {
const text = el.rawText.toLowerCase().split("\n")[0].trim();
const m = text.match(/^#*(# .*)/);
if (!m || m.length !== 2) {
return false;
}
return m[1] === q.toLowerCase();
}
const text = el.rawText.toLowerCase().replaceAll("\n", " ").trim();
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
}));
}
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export const getFrameElementsMatchingQuery = (
elements: ExcalidrawElement[],
query: string[],
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
): ExcalidrawElement[] => {
if (!elements || elements.length === 0 || !query || query.length === 0) {
return [];
}
return elements.filter((el: any) =>
el.type === "frame" &&
query.some((q) => {
if (exactMatch) {
const text = el.name?.toLowerCase().split("\n")[0].trim() ?? "";
const m = text.match(/^#*(# .*)/);
if (!m || m.length !== 2) {
return false;
}
return m[1] === q.toLowerCase();
}
const text = el.name
? el.name.toLowerCase().replaceAll("\n", " ").trim()
: "";
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
}));
}
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export const getElementsWithLinkMatchingQuery = (
elements: ExcalidrawElement[],
query: string[],
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
): ExcalidrawElement[] => {
if (!elements || elements.length === 0 || !query || query.length === 0) {
return [];
}
return elements.filter((el: any) =>
el.link &&
query.some((q) => {
const text = el.link.toLowerCase().trim();
return exactMatch
? (text === q.toLowerCase())
: text.match(q.toLowerCase());
}));
}
/**
*
* @param elements
* @param query
* @param exactMatch - when searching for section header exactMatch should be set to true
* @returns the elements matching the query
*/
export const getImagesMatchingQuery = (
elements: ExcalidrawElement[],
query: string[],
excalidrawData: ExcalidrawData,
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
): ExcalidrawElement[] => {
if (!elements || elements.length === 0 || !query || query.length === 0) {
return [];
}
return elements.filter((el: ExcalidrawElement) =>
el.type === "image" &&
query.some((q) => {
const filename = excalidrawData.getFile(el.fileId)?.file?.basename.toLowerCase().trim();
const equation = excalidrawData.getEquation(el.fileId)?.latex?.toLocaleLowerCase().trim();
const text = filename ?? equation;
if(!text) return false;
return exactMatch
? (text === q.toLowerCase())
: text.match(q.toLowerCase());
}));
}
export const cloneElement = (el: ExcalidrawElement):any => {
const newEl = JSON.parse(JSON.stringify(el));
newEl.version = el.version + 1;
newEl.updated = Date.now();
newEl.versionNonce = Math.floor(Math.random() * 1000000000);
return newEl;
}
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
}
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
return container?.boundElements?.length
? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
: null;
};

View File

@@ -2,7 +2,7 @@ import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement } from
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "../shared/ExcalidrawData";
import ExcalidrawView, { TextMode } from "src/view/ExcalidrawView";
import { rotatedDimensions } from "./utils";
import { getBoundTextElementId } from "src/shared/ExcalidrawAutomate";
import { getBoundTextElementId } from "src/utils/excalidrawAutomateUtils";
export const getElementsAtPointer = (
pointer: any,

View File

@@ -18,20 +18,21 @@ import {
getContainerElement,
} from "../constants/constants";
import ExcalidrawPlugin from "../core/main";
import { ExcalidrawElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ExportSettings } from "../view/ExcalidrawView";
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./fileUtils";
import { generateEmbeddableLink } from "./customEmbeddableUtils";
import { FILENAMEPARTS } from "../types/utilTypes";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./obsidianUtils";
import { updateElementLinksToObsidianLinks } from "src/shared/ExcalidrawAutomate";
import { updateElementLinksToObsidianLinks } from "./excalidrawAutomateUtils";
import { CropImage } from "../shared/CropImage";
import opentype from 'opentype.js';
import { runCompressionWorker } from "src/shared/Workers/compression-worker";
import Pool from "es6-promise-pool";
import { FileData } from "../shared/EmbeddedFileLoader";
import { t } from "src/lang/helpers";
import ExcalidrawScene from "src/shared/svgToExcalidraw/elements/ExcalidrawScene";
declare const PLUGIN_VERSION:string;
declare var LZString: any;
@@ -415,11 +416,17 @@ export async function getImageSize (
});
};
export function addAppendUpdateCustomData (el: Mutable<ExcalidrawElement>, newData: any): ExcalidrawElement {
export function addAppendUpdateCustomData (
el: Mutable<ExcalidrawElement>,
newData: Partial<Record<string, unknown>>
): ExcalidrawElement {
if(!newData) return el;
if(!el.customData) el.customData = {};
for (const key in newData) {
if(typeof newData[key] === "undefined") continue;
if(typeof newData[key] === "undefined") {
delete el.customData[key];
continue;
}
el.customData[key] = newData[key];
}
return el;
@@ -447,7 +454,7 @@ export function scaleLoadedImage (
scene.elements
.filter((e: any) => e.type === "image" && e.fileId === img.id)
.forEach((el: any) => {
.forEach((el: Mutable<ExcalidrawImageElement>) => {
const [elWidth, elHeight] = [el.width, el.height];
const maintainArea = img.shouldScale; //true if image should maintain its area, false if image should display at 100% its size
const elCrop: ImageCrop = el.crop;

View File

@@ -57,16 +57,16 @@ import {
getContainerElement,
} from "../constants/constants";
import ExcalidrawPlugin from "../core/main";
import { ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
import {
repositionElementsToCursor,
ExcalidrawAutomate,
getTextElementsMatchingQuery,
cloneElement,
getFrameElementsMatchingQuery,
getElementsWithLinkMatchingQuery,
getImagesMatchingQuery,
getBoundTextElementId
} from "../shared/ExcalidrawAutomate";
} from "../utils/excalidrawAutomateUtils";
import { t } from "../lang/helpers";
import {
ExcalidrawData,
@@ -105,8 +105,9 @@ import {
shouldEmbedScene,
_getContainerElement,
arrayToMap,
addAppendUpdateCustomData,
} from "../utils/utils";
import { cleanBlockRef, cleanSectionHeading, closeLeafView, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf, setExcalidrawView } from "../utils/obsidianUtils";
import { cleanBlockRef, cleanSectionHeading, closeLeafView, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf, setExcalidrawView } from "../utils/obsidianUtils";
import { splitFolderAndFilename } from "../utils/fileUtils";
import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "../shared/Dialogs/Prompt";
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
@@ -147,6 +148,7 @@ import { IS_WORKER_SUPPORTED } from "../shared/Workers/compression-worker";
import { getPDFCropRect } from "../utils/PDFUtils";
import { Position, ViewSemaphores } from "../types/excalidrawViewTypes";
import { DropManager } from "./managers/DropManager";
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
const PREVENT_RELOAD_TIMEOUT = 2000;
@@ -223,7 +225,9 @@ export const addFiles = async (
.filter((f:FileData) => view.excalidrawData.getFile(f.id)?.file?.extension === "pdf")
.forEach((f:FileData) => {
s.scene.elements
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && el.crop && el.crop.naturalWidth !== f.size.width)
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && (
(el.crop && el.crop.naturalWidth !== f.size.width) || !el.customData?.pdfPageViewProps
))
.forEach((el:Mutable<ExcalidrawImageElement>) => {
s.dirty = true;
const scale = f.size.width / el.crop.naturalWidth;
@@ -235,6 +239,7 @@ export const addFiles = async (
naturalWidth: f.size.width,
naturalHeight: f.size.height,
};
addAppendUpdateCustomData(el, { pdfPageViewProps: f.pdfPageViewProps});
});
});
@@ -250,13 +255,14 @@ export const addFiles = async (
if (view.excalidrawData.hasFile(f.id)) {
const embeddedFile = view.excalidrawData.getFile(f.id);
embeddedFile.setImage(
f.dataURL,
f.mimeType,
f.size,
embeddedFile.setImage({
imgBase64: f.dataURL,
mimeType: f.mimeType,
size: f.size,
isDark,
f.hasSVGwithBitmap,
);
isSVGwithBitmap: f.hasSVGwithBitmap,
pdfPageViewProps: f.pdfPageViewProps,
});
}
if (view.excalidrawData.hasEquation(f.id)) {
const latex = view.excalidrawData.getEquation(f.id).latex;
@@ -1087,7 +1093,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
isFullscreen(): boolean {
//(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.isFullscreen, "ExcalidrawView.isFullscreen");
return Boolean(document.body.querySelector(".excalidraw-hidden"));
return Boolean(this.ownerDocument.body.querySelector(".excalidraw-hidden"));
}
exitFullscreen() {
@@ -3322,19 +3328,31 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
const isPointerOutsideVisibleArea = top.x>this.currentPosition.x || bottom.x<this.currentPosition.x || top.y>this.currentPosition.y || bottom.y<this.currentPosition.y;
const id = ea.addText(this.currentPosition.x, this.currentPosition.y, text);
await this.addElements(ea.getElements(), isPointerOutsideVisibleArea, save, undefined, true);
await this.addElements({
newElements: ea.getElements(),
repositionToCursor: isPointerOutsideVisibleArea,
save: save,
newElementsOnTop: true
});
ea.destroy();
return id;
};
public async addElements(
newElements: ExcalidrawElement[],
repositionToCursor: boolean = false,
save: boolean = false,
images: any,
newElementsOnTop: boolean = false,
shouldRestoreElements: boolean = false,
): Promise<boolean> {
public async addElements({
newElements,
repositionToCursor = false,
save = false,
images,
newElementsOnTop = false,
shouldRestoreElements = false,
}: {
newElements: ExcalidrawElement[];
repositionToCursor?: boolean;
save?: boolean;
images?: {[key: FileId]: ImageInfo};
newElementsOnTop?: boolean;
shouldRestoreElements?: boolean;
}): Promise<boolean> {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addElements, "ExcalidrawView.addElements", newElements, repositionToCursor, save, images, newElementsOnTop, shouldRestoreElements);
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
if (!api) {
@@ -3391,40 +3409,38 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
? el.concat(newElements.filter((e) => !removeList.includes(e.id)))
: newElements.filter((e) => !removeList.includes(e.id)).concat(el);
this.updateScene(
{
elements,
storeAction: "capture",
},
shouldRestoreElements,
);
if (images && Object.keys(images).length >0) {
const files: BinaryFileData[] = [];
Object.keys(images).forEach((k) => {
const files: BinaryFileData[] = [];
if (images && Object.keys(images).length >0) {
Object.keys(images).forEach((k: FileId) => {
files.push({
mimeType: images[k].mimeType,
id: images[k].id,
dataURL: images[k].dataURL,
created: images[k].created,
});
if (images[k].file || images[k].isHyperLink || images[k].isLocalLink) {
if (images[k].file || images[k].isHyperLink) { //|| images[k].isLocalLink but isLocalLink was never passed
const embeddedFile = new EmbeddedFile(
this.plugin,
this.file.path,
images[k].isHyperLink && !images[k].isLocalLink
images[k].isHyperLink //&& !images[k].isLocalLink local link is never passed to addElements
? images[k].hyperlink
: images[k].file,
: (typeof images[k].file === "string" ? images[k].file : images[k].file.path),
);
const st: AppState = api.getAppState();
embeddedFile.setImage(
images[k].dataURL,
images[k].mimeType,
images[k].size,
st.theme === "dark",
images[k].hasSVGwithBitmap,
);
embeddedFile.setImage({
imgBase64: images[k].dataURL,
mimeType: images[k].mimeType,
size: images[k].size,
isDark: st.theme === "dark",
isSVGwithBitmap: images[k].hasSVGwithBitmap,
pdfPageViewProps: images[k].pdfPageViewProps,
});
this.excalidrawData.setFile(images[k].id, embeddedFile);
if(images[k].pdfPageViewProps) {
elements.filter((e) => e.type === "image" && e.fileId === images[k].id).forEach((e) => {
addAppendUpdateCustomData(e, {pdfPageViewProps: images[k].pdfPageViewProps});
});
}
}
if (images[k].latex) {
this.excalidrawData.setEquation(images[k].id, {
@@ -3433,8 +3449,20 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
});
}
});
}
this.updateScene(
{
elements,
storeAction: "capture",
},
shouldRestoreElements,
);
if(files.length > 0) {
api.addFiles(files);
}
api.updateContainerSize(api.getSceneElements().filter(el => newIds.includes(el.id)).filter(isContainer));
if (save) {
await this.save(false); //preventReload=false will ensure that markdown links are paresed and displayed correctly
@@ -3993,7 +4021,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
link,
naturalHeight: fd.size.height,
naturalWidth: fd.size.width,
pdfPageViewProps: fd.pdfPageViewProps,
});
addAppendUpdateCustomData(el, {pdfPageViewProps: fd.pdfPageViewProps});
if(el.crop) {
el.width = el.crop.width/this.plugin.settings.pdfScale;
el.height = el.crop.height/this.plugin.settings.pdfScale;

View File

@@ -4,7 +4,7 @@ import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, saveIcon, stringToSVG } from "../../../constants/actionIcons";
import { DEVICE, SCRIPT_INSTALL_FOLDER } from "../../../constants/constants";
import { insertLaTeXToView, search } from "../../../shared/ExcalidrawAutomate";
import { insertLaTeXToView, search } from "../../../utils/excalidrawAutomateUtils";
import ExcalidrawView, { TextMode } from "../../ExcalidrawView";
import { t } from "../../../lang/helpers";
import { ReleaseNotes } from "../../../shared/Dialogs/ReleaseNotes";

View File

@@ -6,7 +6,7 @@
.excalidraw-wrapper {
height: 100%;
margin: 0px;
background-color: white;
background-color: var(--background-primary);
position:relative;
}
@@ -664,4 +664,4 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
.excalidraw-setting-desc:hover {
background-color: var(--background-modifier-hover);
color: var(--text-accent);
}
}