mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
30 Commits
printToPDF
...
2.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e793526cb2 | ||
|
|
d1eb4cae57 | ||
|
|
f33fa33a08 | ||
|
|
858ae11c8b | ||
|
|
99a7e74825 | ||
|
|
304cef4d7d | ||
|
|
7dba9b88dc | ||
|
|
bfb5de1525 | ||
|
|
8071a2888b | ||
|
|
8b9abb13d0 | ||
|
|
6dbae61212 | ||
|
|
75c65d61c5 | ||
|
|
37e0de41af | ||
|
|
57f9e43508 | ||
|
|
3b7f931f28 | ||
|
|
b37a7aad4f | ||
|
|
667ab31ed9 | ||
|
|
b15ddef7fe | ||
|
|
ef785e5fb0 | ||
|
|
15ba4146ac | ||
|
|
9956fd1756 | ||
|
|
b6f2161f1c | ||
|
|
e6fca1a2d0 | ||
|
|
a9cad8c9f1 | ||
|
|
22b8b1f707 | ||
|
|
98f6871caa | ||
|
|
aae588249a | ||
|
|
ef890d51e3 | ||
|
|
b0bc03437a | ||
|
|
01a88a25a2 |
@@ -38,7 +38,7 @@ async function getImageSize(src: string): Promise<{ height: number; width: numbe
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app?: any
|
||||
plugin?: any
|
||||
): Promise<{
|
||||
mimeType: string;
|
||||
fileId: FileId;
|
||||
@@ -50,9 +50,9 @@ export async function tex2dataURL(
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
|
||||
if(!adaptor) {
|
||||
if (app) {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
preamble = file ? await app.vault.read(file) : null;
|
||||
if (plugin) {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(plugin.settings.latexPreambleLocation || "preamble.sty");
|
||||
preamble = file ? await plugin.app.vault.read(file) : null;
|
||||
}
|
||||
adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
@@ -69,10 +69,18 @@ export async function tex2dataURL(
|
||||
|
||||
try {
|
||||
const node = html.convert(
|
||||
preamble ? `${preamble}${tex}` : tex,
|
||||
preamble ? `${preamble}\n${tex}` : tex,
|
||||
{ display: true, scale }
|
||||
);
|
||||
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195
|
||||
//https://stackoverflow.com/a/77181931
|
||||
let styleNode = document.createElement('style');
|
||||
styleNode.setAttribute("type", "text/css");
|
||||
styleNode.appendChild(document.createTextNode(".mjx-solid { stroke-width: 80px; }"));
|
||||
svg.appendChild(styleNode);
|
||||
|
||||
if (svg) {
|
||||
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
|
||||
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
|
||||
|
||||
@@ -89,8 +89,8 @@ Plugin settings are grouped into the following sections:
|
||||
- **Basic settings**: such as default folders to use.
|
||||
- **Saving**: compression and autosave timer.
|
||||
- **Filename**: configure the automatically created Excalidraw filename.
|
||||
- **Display**: settings that effect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings).
|
||||
- **Links and transclusions**: Settings that effect how links and embedded items behave on the Excalidraw canvas.
|
||||
- **Display**: settings that affect the handling of Excalidraw (e.g.: left-handed mode, theme settings, mouse wheel and pinch zoom settings, zoom to fit settings).
|
||||
- **Links and transclusions**: Settings that affect how links and embedded items behave on the Excalidraw canvas.
|
||||
- **Markdown-embed settings**: These settings control how markdown documents from your Vault embedded into Excalidraw drawings will behave.
|
||||
- **Embed & Export**: Settings that control how Excalidraw images are displayed when embedding them into markdown documents.
|
||||
- **Auto-export Settings**: You can configure Excalidraw to create a PNG or SVG copy of your drawing each time it gets saved.
|
||||
|
||||
@@ -21,7 +21,7 @@ The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.8.0")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -46,6 +46,8 @@ const TRANSITION_DELAY = 1000; //maximum time for transition between slides in m
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
const PRINT_SLIDE_WIDTH = 1920;
|
||||
const PRINT_SLIDE_HEIGHT = 1080;
|
||||
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
|
||||
//but excalidraw might be open in a popout window which has a different document object
|
||||
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
|
||||
@@ -57,6 +59,7 @@ const SVG_MAXIMIZE = ea.obsidian.getIcon("lucide-maximize").outerHTML;
|
||||
const SVG_MINIMIZE = ea.obsidian.getIcon("lucide-minimize").outerHTML;
|
||||
const SVG_LASER_ON = ea.obsidian.getIcon("lucide-hand").outerHTML;
|
||||
const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
|
||||
const SVG_PRINTER = ea.obsidian.getIcon("lucide-printer").outerHTML;
|
||||
|
||||
//-------------------------------
|
||||
//utility & convenience functions
|
||||
@@ -202,7 +205,7 @@ const gotoFullscreen = async () => {
|
||||
}
|
||||
await waitForExcalidrawResize();
|
||||
const layerUIWrapper = contentEl.querySelector(".layer-ui__wrapper");
|
||||
if(!layerUIWrapper.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
|
||||
if(!layerUIWrapper?.hasClass("excalidraw-hidden")) layerUIWrapper.addClass("excalidraw-hidden");
|
||||
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MINIMIZE;
|
||||
resetControlPanelElPosition();
|
||||
isFullscreen = true;
|
||||
@@ -271,8 +274,8 @@ if(presentationPathType==="line") {
|
||||
//-----------------------------
|
||||
// scroll-to-location functions
|
||||
//-----------------------------
|
||||
const getNavigationRect = ({ x1, y1, x2, y2 }) => {
|
||||
const { width, height } = excalidrawAPI.getAppState();
|
||||
const getNavigationRect = ({ x1, y1, x2, y2, printDimensions }) => {
|
||||
const { width, height } = printDimensions ? printDimensions : excalidrawAPI.getAppState();
|
||||
const ratioX = width / Math.abs(x1 - x2);
|
||||
const ratioY = height / Math.abs(y1 - y2);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
|
||||
@@ -534,6 +537,20 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
if(ea.DEVICE.isDesktop) {
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
margin-right: calc(var(--default-button-size)*0.25);`,
|
||||
title: `Print to PDF\nClick to print slides at ${PRINT_SLIDE_WIDTH}x${
|
||||
PRINT_SLIDE_HEIGHT}\nHold SHIFT to print the presentation as displayed`
|
||||
//${!presentationPathLineEl ? "\nHold ALT/OPT to clip frames":""}`
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_PRINTER;
|
||||
button.onclick = (e) => printToPDF(e);
|
||||
});
|
||||
}
|
||||
el.createEl("button",{
|
||||
attr: {
|
||||
style: `
|
||||
@@ -542,7 +559,7 @@ const createPresentationNavigationPanel = () => {
|
||||
}
|
||||
}, button => {
|
||||
button.innerHTML = SVG_FINISH;
|
||||
button.onclick = () => exitPresentation()
|
||||
button.onclick = () => exitPresentation();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -741,6 +758,96 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Print to PDF
|
||||
//--------------------------
|
||||
let notice;
|
||||
let noticeEl;
|
||||
function setSingleNotice(message) {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.setMessage(message);
|
||||
return;
|
||||
}
|
||||
notice = new Notice(message, 0);
|
||||
noticeEl = notice.containerEl ?? notice.noticeEl;
|
||||
}
|
||||
|
||||
function hideSingleNotice() {
|
||||
if(noticeEl?.parentElement) {
|
||||
notice.hide();
|
||||
}
|
||||
}
|
||||
|
||||
const translateToZero = ({ top, left, bottom, right }, padding) => {
|
||||
const {topX, topY, width, height} = ea.getBoundingBox(ea.getViewElements());
|
||||
const newTop = top - (topY - padding);
|
||||
const newLeft = left - (topX - padding);
|
||||
const newBottom = bottom - (topY - padding);
|
||||
const newRight = right - (topX - padding);
|
||||
|
||||
return {
|
||||
top: newTop,
|
||||
left: newLeft,
|
||||
bottom: newBottom,
|
||||
right: newRight,
|
||||
};
|
||||
}
|
||||
|
||||
const printToPDF = async (e) => {
|
||||
const slideWidth = e.shiftKey ? excalidrawAPI.getAppState().width : PRINT_SLIDE_WIDTH;
|
||||
const slideHeight = e.shiftKey ? excalidrawAPI.getAppState().height : PRINT_SLIDE_HEIGHT;
|
||||
//const shouldClipFrames = !presentationPathLineEl && e.altKey;
|
||||
const shouldClipFrames = false;
|
||||
//huge padding to ensure the HD window always fits the width
|
||||
//no padding if frames are clipped
|
||||
const padding = shouldClipFrames ? 0 : Math.round(Math.max(slideWidth,slideHeight)/2)+10;
|
||||
const st = ea.getExcalidrawAPI().getAppState();
|
||||
setSingleNotice("Generating image. This can take a longer time depending on the size of the image and speed of your device");
|
||||
const svg = await ea.createViewSVG({
|
||||
withBackground: true,
|
||||
theme: st.theme,
|
||||
frameRendering: { enabled: shouldClipFrames, name: false, outline: false, clip: shouldClipFrames },
|
||||
padding,
|
||||
selectedOnly: false,
|
||||
skipInliningFonts: false,
|
||||
embedScene: false,
|
||||
});
|
||||
const pages = [];
|
||||
for(i=0;i<slides.length;i++) {
|
||||
setSingleNotice(`Generating slide ${i+1}`);
|
||||
const s = slides[i];
|
||||
const { top, left, bottom, right } = translateToZero(
|
||||
getNavigationRect({
|
||||
...s,
|
||||
printDimensions: {width: slideWidth, height: slideHeight}
|
||||
}), padding
|
||||
);
|
||||
//always create the new SVG in the main Obsidian workspace (not the popout window, if present)
|
||||
const host = window.createDiv();
|
||||
host.innerHTML = svg.outerHTML;
|
||||
const clonedSVG = host.firstElementChild;
|
||||
const width = Math.abs(left-right);
|
||||
const height = Math.abs(top-bottom);
|
||||
clonedSVG.setAttribute("viewBox", `${left} ${top} ${width} ${height}`);
|
||||
clonedSVG.setAttribute("width", `${width}`);
|
||||
clonedSVG.setAttribute("height", `${height}`);
|
||||
pages.push(clonedSVG);
|
||||
}
|
||||
const bgColor = ea.getExcalidrawAPI().getAppState().viewBackgroundColor;
|
||||
setSingleNotice("Creating PDF Document");
|
||||
ea.createPDF({
|
||||
SVG: pages,
|
||||
scale: { fitToPage: true },
|
||||
pageProps: {
|
||||
dimensions: { width: slideWidth, height: slideHeight },
|
||||
backgroundColor: bgColor,
|
||||
margin: { left: 0, right: 0, top: 0, bottom: 0 },
|
||||
alignment: "center"
|
||||
},
|
||||
filename: ea.targetView.file.basename + ".pdf",
|
||||
}).then(()=>hideSingleNotice());
|
||||
}
|
||||
|
||||
//--------------------------
|
||||
// Start presentation or open presentation settings on double click
|
||||
//--------------------------
|
||||
@@ -768,7 +875,11 @@ const start = async () => {
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (timestamp - window.ExcalidrawSlideshow.timestamp <400) ) {
|
||||
if(
|
||||
window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) &&
|
||||
(timestamp - window.ExcalidrawSlideshow.timestamp <400)
|
||||
) {
|
||||
if(window.ExcalidrawSlideshowStartTimer) {
|
||||
window.clearTimeout(window.ExcalidrawSlideshowStartTimer);
|
||||
delete window.ExcalidrawSlideshowStartTimer;
|
||||
@@ -789,4 +900,4 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
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
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.8.0-rc-1",
|
||||
"version": "2.9.2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.5",
|
||||
"version": "2.9.2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
2680
package-lock.json
generated
2680
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,21 +16,20 @@
|
||||
"build:mathjax": "cd MathjaxToSVG && npm run build",
|
||||
"build:all": "npm run build:mathjax && npm run build",
|
||||
"dev:mathjax": "cd MathjaxToSVG && npm run dev",
|
||||
"dev:all": "npm run dev:mathjax && npm run dev",
|
||||
"build:lang": "node ./scripts/compressLanguages.js"
|
||||
"dev:all": "npm run dev:mathjax && npm run dev"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-27",
|
||||
"@zsviczian/excalidraw": "0.18.0-3",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lucide-react": "^0.263.1",
|
||||
"lucide-react": "^0.479.0",
|
||||
"mathjax-full": "^3.2.2",
|
||||
"monkey-around": "^2.3.0",
|
||||
"nanoid": "^4.0.2",
|
||||
|
||||
@@ -24,6 +24,29 @@ 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}`);
|
||||
|
||||
|
||||
// Excalidraw React 19 compatiblity shim
|
||||
// Create JSX runtime compatibility layer
|
||||
const jsxRuntimeShim = `
|
||||
const jsx = (type, props, key) => {
|
||||
return React.createElement(type, props);
|
||||
};
|
||||
const jsxs = (type, props, key) => {
|
||||
return React.createElement(type, props);
|
||||
};
|
||||
const Fragment = React.Fragment;
|
||||
React.jsx = jsx;
|
||||
React.jsxs = jsxs;
|
||||
React.Fragment = Fragment;
|
||||
React.jsxRuntime = { jsx, jsxs, Fragment };
|
||||
window.__WEBPACK_EXTERNAL_MODULE_react_jsx_runtime__ = { jsx, jsxs, Fragment };
|
||||
window.__WEBPACK_EXTERNAL_MODULE_react_jsx_dev_runtime__ = { jsx, jsxs, Fragment, jsxDEV: jsx };
|
||||
window['react/jsx-runtime'] = { jsx, jsxs, Fragment };
|
||||
window['react/jsx-dev-runtime'] = { jsx, jsxs, Fragment, jsxDEV: jsx };
|
||||
`;
|
||||
|
||||
|
||||
|
||||
const mathjaxtosvg_pkg = isLib ? "" : fs.readFileSync("./MathjaxToSVG/dist/index.js", "utf8");
|
||||
|
||||
const LANGUAGES = ['ru', 'zh-cn']; //english is not compressed as it is always loaded by default
|
||||
@@ -99,7 +122,7 @@ const packageString = isLib
|
||||
? ""
|
||||
: ';const INITIAL_TIMESTAMP=Date.now();' + lzstring_pkg +
|
||||
'\nlet REACT_PACKAGES = `' +
|
||||
jsesc(react_pkg + reactdom_pkg, { quotes: 'backtick' }) +
|
||||
jsesc(react_pkg + reactdom_pkg + jsxRuntimeShim, { quotes: 'backtick' }) +
|
||||
'`;\n' +
|
||||
'const unpackExcalidraw = () => LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");\n' +
|
||||
'let {react, reactDOM } = new Function(`${REACT_PACKAGES}; return {react: React, reactDOM: ReactDOM};`)();\n' +
|
||||
|
||||
@@ -232,6 +232,39 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
export const CaptureUpdateAction = {
|
||||
/**
|
||||
* Immediately undoable.
|
||||
*
|
||||
* Use for updates which should be captured.
|
||||
* Should be used for most of the local updates.
|
||||
*
|
||||
* These updates will _immediately_ make it to the local undo / redo stacks.
|
||||
*/
|
||||
IMMEDIATELY: "IMMEDIATELY",
|
||||
/**
|
||||
* Never undoable.
|
||||
*
|
||||
* Use for updates which should never be recorded, such as remote updates
|
||||
* or scene initialization.
|
||||
*
|
||||
* These updates will _never_ make it to the local undo / redo stacks.
|
||||
*/
|
||||
NEVER: "NEVER",
|
||||
/**
|
||||
* Eventually undoable.
|
||||
*
|
||||
* Use for updates which should not be captured immediately - likely
|
||||
* exceptions which are part of some async multi-step process. Otherwise, all
|
||||
* such updates would end up being captured with the next
|
||||
* `CaptureUpdateAction.IMMEDIATELY` - triggered either by the next `updateScene`
|
||||
* or internally by the editor.
|
||||
*
|
||||
* These updates will _eventually_ make it to the local undo / redo stacks.
|
||||
*/
|
||||
EVENTUALLY: "EVENTUALLY",
|
||||
} as const;
|
||||
|
||||
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||
export const VIEW_TYPE_EXCALIDRAW_LOADING = "excalidraw-loading";
|
||||
|
||||
@@ -412,7 +412,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
try {
|
||||
this.isReady = true;
|
||||
switchToExcalidraw(this.app);
|
||||
await switchToExcalidraw(this.app);
|
||||
this.switchToExcalidarwAfterLoad();
|
||||
} catch (e) {
|
||||
new Notice("Error switching views to Excalidraw", 6000);
|
||||
|
||||
@@ -166,6 +166,7 @@ export class CommandManager {
|
||||
const fileMenuHandlerCreateNew = (menu: Menu, file: TFile) => {
|
||||
menu.addItem((item: MenuItem) => {
|
||||
item
|
||||
.setSection('action-primary')
|
||||
.setTitle(t("CREATE_NEW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick((e) => {createNewAction(e, file)});
|
||||
@@ -756,7 +757,6 @@ export class CommandManager {
|
||||
if (view) {
|
||||
if(!view.exportDialog) {
|
||||
view.exportDialog = new ExportDialog(this.plugin, view,view.file);
|
||||
view.exportDialog.createForm();
|
||||
}
|
||||
view.exportDialog.open();
|
||||
return true;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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, getExcalidrawViews, getParentOfClass, setExcalidrawView } from "../../utils/obsidianUtils";
|
||||
import { editorInsertText, getExcalidrawViews, getParentOfClass, isUnwantedLeaf, setExcalidrawView } from "../../utils/obsidianUtils";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { DEBUGGING, debug } from "src/utils/debugHelper";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
@@ -164,6 +164,13 @@ export class EventManager {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onActiveLeafChangeHandler,`onActiveLeafChangeEventHandler`, leaf);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723
|
||||
|
||||
//In Obsidian 1.8.x the active excalidraw leaf is obscured by an empty leaf without a parent
|
||||
//This hack resolves it
|
||||
if(this.app.workspace.activeLeaf === leaf && isUnwantedLeaf(leaf)) {
|
||||
leaf.detach();
|
||||
return;
|
||||
}
|
||||
|
||||
if (leaf.view && leaf.view.getViewType() === "pdf") {
|
||||
this.plugin.lastPDFLeafID = leaf.id;
|
||||
}
|
||||
|
||||
@@ -802,10 +802,14 @@ const tmpObsidianWYSIWYG = async (
|
||||
el.empty();
|
||||
} else {
|
||||
//Obsidian changed this at some point from h3 to h5 and also the text...
|
||||
const warningEl = el.querySelector("div>*[data-heading^='Unable to find ");
|
||||
let warningEl = el.querySelector("div>*[data-heading^='Unable to find ");
|
||||
if(!warningEl) {
|
||||
//changed in Obsidian 1.8.9
|
||||
warningEl = el.querySelector("div > *[data-heading]");
|
||||
}
|
||||
if(warningEl) {
|
||||
const dataHeading = warningEl.getAttr("data-heading");
|
||||
const ref = warningEl.getAttr("data-heading").match(/Unable to find[^^]+(\^(?:group=|area=|frame=|clippedframe=)[^ ”]+)/)?.[1];
|
||||
const ref = dataHeading.match(/.+(\^(?:group=|area=|frame=|clippedframe=)[A-Za-z0-9_-]{8,21})/)?.[1];
|
||||
if(ref) {
|
||||
attr.fname = file.path + "#" +ref;
|
||||
areaPreview = true;
|
||||
@@ -926,7 +930,7 @@ export const markdownPostProcessor = async (
|
||||
if(docIDs.has(ctx.docId) && !el.hasChildNodes()) {
|
||||
docIDs.delete(ctx.docId);
|
||||
}
|
||||
const isAreaGroupFrameRef = el.querySelectorAll('[data-heading^="Unable to find"]').length === 1;
|
||||
const isAreaGroupFrameRef = el.querySelectorAll('div > *[data-heading]').length === 1;
|
||||
if(!isAreaGroupFrameRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ export interface ExcalidrawSettings {
|
||||
showNewVersionNotification: boolean;
|
||||
//mathjaxSourceURL: string;
|
||||
latexBoilerplate: string;
|
||||
latexPreambleLocation: string;
|
||||
taskboneEnabled: boolean;
|
||||
taskboneAPIkey: string;
|
||||
pinnedScripts: string[];
|
||||
@@ -348,6 +349,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
showNewVersionNotification: true,
|
||||
//mathjaxSourceURL: "https://cdn.jsdelivr.net/npm/mathjax@3.2.1/es5/tex-svg.js",
|
||||
latexBoilerplate: "\\color{blue}",
|
||||
latexPreambleLocation: "preamble.sty",
|
||||
taskboneEnabled: false,
|
||||
taskboneAPIkey: "",
|
||||
pinnedScripts: [],
|
||||
@@ -2616,6 +2618,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LATEX_PREAMBLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LATEX_PREAMBLE_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: preamble.sty")
|
||||
.setValue(this.plugin.settings.latexPreambleLocation)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.latexPreambleLocation = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FILETYPE_NAME"))
|
||||
.setDesc(fragWithHTML(t("FILETYPE_DESC")))
|
||||
|
||||
@@ -9,6 +9,7 @@ declare var LZString: any;
|
||||
let locale: Partial<typeof en> | null = null;
|
||||
|
||||
function loadLocale(lang: string): Partial<typeof en> {
|
||||
if(lang === "zh") lang = "zh-cn"; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2247
|
||||
if (Object.keys(PLUGIN_LANGUAGES).includes(lang)) {
|
||||
const decompressed = LZString.decompressFromBase64(PLUGIN_LANGUAGES[lang]);
|
||||
let x = {};
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
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",
|
||||
CREATE_NEW: "New drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
CONVERT_FILE_REPLACE_EXT: "*.excalidraw => *.md (Logseq compatibility)",
|
||||
DOWNLOAD_LIBRARY: "Export stencil library as an *.excalidrawlib file",
|
||||
@@ -722,6 +722,8 @@ FILENAME_HEAD: "Filename",
|
||||
"restart Obsidian after closing settings, for this change to take effect.",
|
||||
LATEX_DEFAULT_NAME: "Default LaTeX formula for new equations",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
LATEX_PREAMBLE_NAME: "LaTeX preamble file (CasE SEnSiTivE!)",
|
||||
LATEX_PREAMBLE_DESC: "Full filepath to the preamble file, leave empty for default. If the file doesn't exist this option will be ignored.<br><strong>Important:</strong> Requires obsidian reload after change to take effect!",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
|
||||
@@ -1063,6 +1065,8 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT: "Center Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT: "Center Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
|
||||
|
||||
@@ -236,7 +236,7 @@ export default {
|
||||
"在这种情况下,创建新绘图时将提示您选择使用哪个模板。<br>" +
|
||||
"<b>专业提示:</b> 如果您正在使用 Obsidian Templater 插件,您可以将 Templater 代码添加到不同的" +
|
||||
"Excalidraw 模板中,以自动配置您的绘图",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感!)",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(區分大小寫!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"此文件夹用于存放 Excalidraw 自动化脚本。" +
|
||||
"您可以在 Obsidian 命令面板中执行这些脚本," +
|
||||
@@ -722,6 +722,8 @@ FILENAME_HEAD: "文件名",
|
||||
"更改此选项后,您可能需要重启 Obsidian 来使其生效。",
|
||||
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
|
||||
LATEX_DEFAULT_DESC: "允许留空。允许使用类似 <code>\\color{white}</code> 的格式化表达式。",
|
||||
LATEX_PREAMBLE_NAME : "LaTeX 前言文件(區分大小寫!)" ,
|
||||
LATEX_PREAMBLE_DESC : "前言文件的完整路径,留空则使用默认值。如果文件不存在,此选项将被忽略。<br><strong>重要:</strong>更改后需要重新加载 Obsidian 才能生效!" ,
|
||||
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果在 Excalidraw.com 导入绘图,这些特性将会发生不可预知的变化。
|
||||
包括:自定义画笔工具的数量,自定义字体等。`,
|
||||
@@ -1048,15 +1050,14 @@ EXPORTDIALOG_PAGE_SIZE : "页面大小",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION : "方向",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT : "纵向",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE : "横向",
|
||||
EXPORTDIALOG_PDF_DPI : "图像质量 [DPI]",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE : "页面适配",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION : "适配页面",
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION : "适配至 2 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION : "适配至 4 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION : "适配至 6 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION : "适配至 8 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION : "适配至 12 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION : "适配至 16 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_2_OPTION : "适配至最多 2 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_4_OPTION : "适配至最多 4 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_6_OPTION : "适配至最多 6 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_8_OPTION : "适配至最多 8 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_12_OPTION : "适配至最多 12 页" ,
|
||||
EXPORTDIALOG_PDF_FIT_16_OPTION : "适配至最多 16 页" ,
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION : "使用图像缩放(可能跨多页)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR : "纸张颜色",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE : "白色",
|
||||
@@ -1064,6 +1065,8 @@ EXPORTDIALOG_PDF_PAPER_SCENE : "使用场景颜色",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM : "自定义颜色",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT : "页面位置",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER : "居中",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT : "左对齐居中" ,
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT : "右对齐居中" ,
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT : "左上角",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER : "顶部居中",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT : "右上角",
|
||||
@@ -1085,9 +1088,11 @@ EXPORTDIALOG_EXCALIDRAW : "Excalidraw",
|
||||
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG 复制到剪贴板",
|
||||
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG 复制到剪贴板",
|
||||
EXPORTDIALOG_PDF : "导出 PDF 文件",
|
||||
EXPORTDIALOG_PDFTOVAULT : "PDF 保存到 Vault",
|
||||
|
||||
EXPORTDIALOG_PDF_PROGRESS_NOTICE : "正在导出页面" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_IMAGE : "的图像" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_NOTICE : "正在导出 PDF。如果图像较大,可能需要一些时间。" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_DONE : "导出完成" ,
|
||||
EXPORTDIALOG_PDF_PROGRESS_ERROR : "导出 PDF 时出错,请检查开发者控制台以获取详细信息" ,
|
||||
|
||||
// exportUtils.ts
|
||||
PDF_EXPORT_DESKTOP_ONLY : "PDF 导出功能仅限桌面端使用" ,
|
||||
};
|
||||
@@ -226,7 +226,7 @@ export class EmbeddableSettings extends Modal {
|
||||
(async() => {
|
||||
await this.ea.addElementsToView();
|
||||
//@ts-ignore
|
||||
this.ea.viewUpdateScene({appState: {}, storeAction: "update"});
|
||||
this.ea.viewUpdateScene({appState: {}, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
this.close(); //close should only run once update scene is done
|
||||
})();
|
||||
} else {
|
||||
|
||||
@@ -70,6 +70,7 @@ export class ExportDialog extends Modal {
|
||||
this.margin = plugin.settings.pdfSettings.margin;
|
||||
|
||||
this.saveSettings = false;
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -97,7 +98,7 @@ export class ExportDialog extends Modal {
|
||||
this.dirty = this.saveSettings;
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
createForm() {
|
||||
if(DEVICE.isDesktop) {
|
||||
// Create tab container
|
||||
const tabContainer = this.contentEl.createDiv("nav-buttons-container");
|
||||
@@ -391,7 +392,6 @@ export class ExportDialog extends Modal {
|
||||
});
|
||||
bPDFExport.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
false,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
@@ -402,7 +402,7 @@ export class ExportDialog extends Modal {
|
||||
|
||||
public getPaperColor(): string {
|
||||
switch (this.paperColor) {
|
||||
case "white": return "#ffffff";
|
||||
case "white": return this.theme === "light" ? "#ffffff" : "#000000";
|
||||
case "scene": return this.api.getAppState().viewBackgroundColor;
|
||||
case "custom": return this.customPaperColor;
|
||||
default: return "#ffffff";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
export const showFrameSettings = (ea: ExcalidrawAutomate) => {
|
||||
const {enabled, clip, name, outline} = ea.getExcalidrawAPI().getAppState().frameRendering;
|
||||
@@ -64,7 +65,7 @@ export const showFrameSettings = (ea: ExcalidrawAutomate) => {
|
||||
appState: {
|
||||
frameRendering: settings
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
frameSettingsModal.close();
|
||||
})
|
||||
|
||||
@@ -17,6 +17,134 @@ 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.9.2":`
|
||||
- More minor fix. Toolbars are not responsive when dynamic styling is turned off. [#2287](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2287)
|
||||
`,
|
||||
"2.9.1":`
|
||||
- Minor emergency fix. Reverting: [#9203](https://github.com/excalidraw/excalidraw/pull/9203)
|
||||
`,
|
||||
"2.9.0":`
|
||||
## New
|
||||
- QoL improvement: The context menu requires a longer press and hold for it to be displayed on mobile devices. When you want to precision adjust an element it happens that you linger on the point for just a little longer and the context menu appears unwantedly.
|
||||
- Elbow arrow improvements [#9236](https://github.com/excalidraw/excalidraw/pull/9236), [#8593](https://github.com/excalidraw/excalidraw/pull/8593), [#9197](https://github.com/excalidraw/excalidraw/pull/9197), [#9191](https://github.com/excalidraw/excalidraw/pull/9191), [#9236](https://github.com/excalidraw/excalidraw/pull/9236)
|
||||
|
||||
## Fixed Obsidian 1.8.9 regressions
|
||||
- Custom references (like #^group) broken in Live Preview in Obsidian 1.8.9 due to translation update [#2279](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2279)
|
||||
- Excalidraw tabs only show on a second click on Obsidian Mobile
|
||||
|
||||
## Refactoring
|
||||
- The Excalidraw component moved to React 19. Obsidian for now remains on React 18. This refactoring ensures that Excalidraw continues to work in Obsidian and Obsidian will receive future Excalidraw updates. [#9182](https://github.com/excalidraw/excalidraw/pull/9182)
|
||||
`,
|
||||
"2.8.3":`
|
||||
## Fixed
|
||||
- Chinese translation not available since 2.8.0. [#2247](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2247)
|
||||
- Since the most recent Samsung Android update, adding images from the gallery returns an Unsupported Image Type error. [#2245](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2245)
|
||||
- Duplicating/removing frame while children selected [#9079](https://github.com/excalidraw/excalidraw/pull/9079)
|
||||
`,
|
||||
"2.8.2":`
|
||||
## New
|
||||
- Moved "Create new drawing" option up in the context menu [#2243](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2243)
|
||||
|
||||
## Fixed
|
||||
- In rare cases drawing content gets overwritten with another drawing [#2152](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2152)
|
||||
- "Wrap selection in frame" sets dark mode to light mode [#2240](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2240)
|
||||
- Multiple bug fixes from Excalidraw.com
|
||||
- Elbow arrows within boxes [#9077](https://github.com/excalidraw/excalidraw/issues/9077)
|
||||
- Elbow arrow orthogonality [#9073](https://github.com/excalidraw/excalidraw/pull/9073)
|
||||
- Improve library sidebar performance [#9060](https://github.com/excalidraw/excalidraw/pull/9060)
|
||||
- Opacity slider now displays numerical value [#9009](https://github.com/excalidraw/excalidraw/pull/9009)
|
||||
- Resize a frame and its children together when box selecting the frame and its children together [#9031](https://github.com/excalidraw/excalidraw/pull/9031)
|
||||
- Excalidraw screen flickering in dark mode [#9057](https://github.com/excalidraw/excalidraw/pull/9057)
|
||||
`,
|
||||
"2.8.1":`
|
||||
## Fixed
|
||||
- Unable to open Excalidraw files after the 2.8.0 update. [#2235](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2235)
|
||||
`,
|
||||
"2.8.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/tWi5xTUTz7E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Updated "Export Image" dialog
|
||||
- 🚀 PDF Export option including tiling of images over multiple pages. Only available on desktop :(
|
||||
- SVG to clipboard
|
||||
- More granular setting for padding and scale
|
||||
- Slideshow script can now print slides to PDF (update script from script store)
|
||||
- Set local graph to show the links in the embeddable when it is activated/deactivated [#2200](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2200)
|
||||
|
||||
## Fixed
|
||||
- Fixed several LaTeX issues. 🙏 @Sintuz [#1631](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1631), [#2195](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2195), [#1842](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1842)
|
||||
- Fixed support for *.jfif and *.avif images [#2212](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2212)
|
||||
- PDF++ selection is not correctly showing after embedded into a drawing (for some specific files) [#2213](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2213)
|
||||
- iOS 18 can't upload image and library [#2182](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2182)
|
||||
- Image block references are broken in hover previews [#2218](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2218)
|
||||
- ⚠️ Note there is a known issue in Obsidian 1.8.2 ⚠️ affecting preview windows in Excalidraw. I received confirmation that this will be fixed in 1.8.3. For now, if hover previews are important to you, you can downgrade to Obsidian 1.8.1 [#2228](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2225)
|
||||
- Mobile elements panel and context menu are not scrollable [#2216](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2216)
|
||||
- "Local Font" menu disappears when opening a drawing in an Obsidian popout-window [#2205](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2205)
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Pressing delete on a frame will only delete the children [#9011](https://github.com/excalidraw/excalidraw/pull/9011)
|
||||
- New crowfoot arrowheads and a new arrowhead picker [#8942](https://github.com/excalidraw/excalidraw/pull/8942)
|
||||
- Fixed some of the arrow binding issues [#9010](https://github.com/excalidraw/excalidraw/pull/9010), [#2209](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2209)
|
||||
- New context menu action: "Wrap selection in frame" [#9005](https://github.com/excalidraw/excalidraw/pull/9005)
|
||||
- Elbow arrow segment fixing and positioning [#8952](https://github.com/excalidraw/excalidraw/pull/8952)
|
||||
- When drag creating a new frame, do not add a partial group to it. When wrapping a selected partial group in a frame however, do add it to the wrapping frame. But such that it should be separated from the previous containing group. [#9014](https://github.com/excalidraw/excalidraw/pull/9014)
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- New hook: ${String.fromCharCode(96)}onImageFileNameHook${String.fromCharCode(96)}. When set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
- PDF export functions, paving the way for slideshow to export slides to PDF
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Returns the dimensions of a standard page size in pixels.
|
||||
*/
|
||||
function getPagePDFDimensions(
|
||||
pageSize: PageSize,
|
||||
orientation: PageOrientation
|
||||
): PageDimensions;
|
||||
|
||||
/**
|
||||
* Creates a PDF from the provided SVG elements with specified scaling and page properties.
|
||||
*/
|
||||
function createPDF(props: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale?: PDFExportScale;
|
||||
pageProps?: PDFPageProperties;
|
||||
filename: string;
|
||||
}): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates an SVG representation of the current view.
|
||||
*/
|
||||
function createViewSVG(props : {
|
||||
withBackground?: boolean;
|
||||
theme?: "light" | "dark";
|
||||
frameRendering?: FrameRenderingOptions;
|
||||
padding?: number;
|
||||
selectedOnly?: boolean;
|
||||
skipInliningFonts?: boolean;
|
||||
embedScene?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
* You can use this callback to customize the naming and path of pasted images to avoid
|
||||
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
|
||||
* and instead use more meaningful names based on the Excalidraw file or other criteria,
|
||||
* plus save the image in a different folder.
|
||||
*
|
||||
* If the function returns null or undefined, the normal Excalidraw operation will continue
|
||||
* with the excalidraw generated name and default path.
|
||||
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
|
||||
* with the file extension.
|
||||
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
|
||||
*/
|
||||
function onImageFilePathHook: (data: {
|
||||
currentImageName: string;
|
||||
drawingFilePath: string;
|
||||
}) => string = null;
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.7.5":`
|
||||
## Fixed
|
||||
- PDF export scenario described in [#2184](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2184)
|
||||
@@ -171,24 +299,5 @@ ${String.fromCharCode(96,96,96)}
|
||||
- Hover-Editor compatibility resolved [2041](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2041)
|
||||
- ${String.fromCharCode(96)}ExcalidrawAutomate.create() ${String.fromCharCode(96)} will now correctly include the markdown text in templates above Excalidraw Data and below YAML front matter. This also fixes the same issue with the **Deconstruct Selected Element script**.
|
||||
|
||||
`,
|
||||
"2.6.7":`
|
||||
Hoping to finally move on to 2.7.0... but still have one last bug to fix in 2.6.x!
|
||||
|
||||
## Fixed
|
||||
I misread a line in the Excalidraw package code... ended up breaking image loading in 2.6.6. The icon library script didn't work right, and updating nested drawings caused all images in the scene to be dropped from memory. This led to image-placeholders in exports and broke copy-paste to Excalidraw.com and between drawings. I am surprised no one reported it! 😳
|
||||
`,
|
||||
"2.6.6":`
|
||||
## Fixed
|
||||
- Images and LaTeX formulas did not update in the scene when the source was changed until the Excalidraw drawing was closed and reopened. [#2105](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2105)
|
||||
`,
|
||||
"2.6.5":`
|
||||
## Fixed
|
||||
- Text sizing issue in the drawing that is first loaded after Obsidian restarts [#2086](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2086)
|
||||
- Excalidraw didn't load if there was a file in the Excalidraw folder with a name that starts the same way as the Scripts folder name. [#2095](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2095)
|
||||
- **OVERSIZED EXCALIDRAW TOOLBAR**: Added a new setting under "Excalidraw Appearance and Behavior > Theme and Styling" called "Limit Obsidian Font Size to Editor Text." This setting is off by default. When enabled, it restricts Obsidian's custom font size adjustments to editor text only, preventing unintended scaling of Excalidraw UI elements and other themes that rely on the default interface font size. Feel free to experiment with this setting to improve Excalidraw UI consistency. However, because this change affects the broader Obsidian UI, it's recommended to turn it off if any layout issues arise. [#2087](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2087)`,
|
||||
"2.6.4":`
|
||||
## Fixed
|
||||
- Error saving when cropping images embedded from a URL (not from a file in the Vault) [#2096](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2096)
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -129,6 +129,8 @@ export class PDFExportSettingsComponent {
|
||||
dropdown
|
||||
.addOptions({
|
||||
"center": t("EXPORTDIALOG_PDF_ALIGN_CENTER"),
|
||||
"center-left": t("EXPORTDIALOG_PDF_ALIGN_CENTER_LEFT"),
|
||||
"center-right": t("EXPORTDIALOG_PDF_ALIGN_CENTER_RIGHT"),
|
||||
"top-left": t("EXPORTDIALOG_PDF_ALIGN_TOP_LEFT"),
|
||||
"top-center": t("EXPORTDIALOG_PDF_ALIGN_TOP_CENTER"),
|
||||
"top-right": t("EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT"),
|
||||
|
||||
@@ -226,14 +226,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "createPDF",
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties}): Promise<void>",
|
||||
desc: "",
|
||||
after: "Creates a PDF from the provided SVG elements with specified scaling and page properties.\n" +
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties, filename: string}): Promise<void>",
|
||||
desc: "Creates a PDF from the provided SVG elements with specified scaling and page properties.\n" +
|
||||
"\n" +
|
||||
"@param {Object} params - The parameters for creating the PDF.\n" +
|
||||
"@param {SVGSVGElement[]} params.SVG - An array of SVG elements to be included in the PDF. If multiple SVGs are provided, each will be added to a new page.\n" +
|
||||
"@param {PDFExportScale} [params.scale={ fitToPage: true, zoom: 1 }] - The scaling options for the SVG elements.\n" +
|
||||
"@param {PDFPageProperties} [params.pageProps] - The properties for the PDF pages.\n" +
|
||||
"@param {string} params.filename - The name of the PDF file to be created.\n" +
|
||||
"@returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFExportScale\n" +
|
||||
@@ -244,10 +244,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"@property {{width: number, height: number}} [dimensions] - The dimensions of the PDF pages in pixels. Use getPageDimensions to get standard page sizes.\n" +
|
||||
"@property {string} [backgroundColor] - The background color of the PDF pages.\n" +
|
||||
"@property {PDFMargin} margin - The margins of the PDF pages in pixels.\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.\n" +
|
||||
"\n" +
|
||||
"@example\n" +
|
||||
"const pdfData = await createPDF({\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.",
|
||||
after: "({\n" +
|
||||
" SVG: [svgElement1, svgElement2],\n" +
|
||||
" scale: { fitToPage: true },\n" +
|
||||
" pageProps: {\n" +
|
||||
@@ -255,9 +253,40 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
" backgroundColor: \"#ffffff\",\n" +
|
||||
" margin: { left: 20, right: 20, top: 20, bottom: 20 },\n" +
|
||||
" alignment: \"center\"\n" +
|
||||
" filename: \"myPDF.pdf\"\n" +
|
||||
" }\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "createViewSVG",
|
||||
code: "async createViewSVG({withBackground?: boolean, theme?: 'light' | 'dark', frameRendering?: FrameRenderingOptions, padding?: number, selectedOnly?: boolean, skipInliningFonts?: boolean, embedScene?: boolean}): Promise<SVGSVGElement>",
|
||||
desc: "Creates an SVG representation of the current view with specified options.\n" +
|
||||
"\n" +
|
||||
"@param {Object} options - The options for creating the SVG.\n" +
|
||||
"@param {boolean} [options.withBackground=true] - Whether to include the background in the SVG.\n" +
|
||||
"@param {\"light\" | \"dark\"} [options.theme] - The theme to use for the SVG.\n" +
|
||||
"@param {FrameRenderingOptions} [options.frameRendering={enabled: true, name: true, outline: true, clip: true}] - The frame rendering options.\n" +
|
||||
"@param {number} [options.padding] - The padding to apply around the SVG.\n" +
|
||||
"@param {boolean} [options.selectedOnly=false] - Whether to include only the selected elements in the SVG.\n" +
|
||||
"@param {boolean} [options.skipInliningFonts=false] - Whether to skip inlining fonts in the SVG.\n" +
|
||||
"@param {boolean} [options.embedScene=false] - Whether to embed the scene in the SVG.\n" +
|
||||
"@returns {Promise<SVGSVGElement>} A promise that resolves to the SVG element.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} FrameRenderingOptions\n" +
|
||||
"@property {boolean} enabled - Whether frame rendering is enabled.\n" +
|
||||
"@property {boolean} name - Whether to include the name in the frame rendering.\n" +
|
||||
"@property {boolean} outline - Whether to include the outline in the frame rendering.\n" +
|
||||
"@property {boolean} clip - Whether to clip the frame rendering.\n",
|
||||
after: "({\n" +
|
||||
" withBackground: true,\n" +
|
||||
" theme: 'light',\n" +
|
||||
" frameRendering: { enabled: true, name: true, outline: true, clip: true },\n" +
|
||||
" padding: 10,\n" +
|
||||
" selectedOnly: false,\n" +
|
||||
" skipInliningFonts: false,\n" +
|
||||
" embedScene: false,\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "getPagePDFDimensions",
|
||||
code: "getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions",
|
||||
@@ -273,12 +302,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"\n" +
|
||||
"@typedef {\"A0\" | \"A1\" | \"A2\" | \"A3\" | \"A4\" | \"A5\" | \"Letter\" | \"Legal\" | \"Tabloid\"} PageSize\n" +
|
||||
"\n" +
|
||||
"@typedef {\"portrait\" | \"landscape\"} PageOrientation\n" +
|
||||
"\n" +
|
||||
"@example\n" +
|
||||
"const dimensions = getPDFPageDimensions(\"A4\", \"portrait\");\n" +
|
||||
"console.log(dimensions); // { width: 595.28, height: 841.89 }",
|
||||
after: "",
|
||||
"@typedef {\"portrait\" | \"landscape\"} PageOrientation",
|
||||
after: "(\"A4\", \"portrait\");",
|
||||
},
|
||||
{
|
||||
field: "createPNG",
|
||||
@@ -894,9 +919,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "viewUpdateScene",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,storeAction?: 'capture' | 'none' | 'update'},restore:boolean=false):void",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,captureUpdate?: 'IMMEDIATELY' | 'NEVER' | 'EVENTUALLY'},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties. " +
|
||||
`Note that commitToHistory has been deprecated in Excalidraw and is no longer used. You should use storeAction instead. See ${hyperlink("https://github.com/excalidraw/excalidraw/pull/7898", "ExcalidrawAPI")} documentation for more information.`,
|
||||
`Use captureUpdate to control undo/redo behavior: 'IMMEDIATELY' for immediate undoable updates (most local changes), 'NEVER' for updates that should never be undoable, or 'EVENTUALLY' for updates that should be undoable as part of an async multi-step process. See the ExcalidrawAPI documentation for more information.`,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -704,7 +704,7 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
if (!excalidrawData.getEquation(id).isLoaded) {
|
||||
const latex = equation.latex;
|
||||
const data = await tex2dataURL(latex, 4, this.plugin.app);
|
||||
const data = await tex2dataURL(latex, 4, this.plugin);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
mimeType: data.mimeType,
|
||||
|
||||
@@ -42,9 +42,11 @@ import {
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
addAppendUpdateCustomData,
|
||||
getSVG,
|
||||
getWithBackground,
|
||||
} from "src/utils/utils";
|
||||
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/obsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, SceneData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import { NewFileActions } from "src/shared/Dialogs/Prompt";
|
||||
@@ -84,6 +86,8 @@ import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
import { AddImageOptions, ImageInfo, SVGColorInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { _measureText, cloneElement, createPNG, createSVG, errorMessage, filterColorMap, getEmbeddedFileForImageElment, getFontFamily, getLineBox, getTemplate, isColorStringTransparent, isSVGColorInfo, mergeColorMapIntoSVGColorInfo, normalizeLinePoints, repositionElementsToCursor, svgColorInfoToColorMap, updateOrAddSVGColorInfo, verifyMinimumPluginVersion } from "src/utils/excalidrawAutomateUtils";
|
||||
import { exportToPDF, getMarginValue, getPageDimensions, PageDimensions, PageOrientation, PageSize, PDFExportScale, PDFPageProperties } from "src/utils/exportUtils";
|
||||
import { FrameRenderingOptions } from "src/types/utilTypes";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -885,7 +889,7 @@ export class ExcalidrawAutomate {
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
const item = this.imagesDict[key];
|
||||
if(item.latex) {
|
||||
outString += `${key}: $$${item.latex}$$\n\n`;
|
||||
outString += `${key}: $$${item.latex.trim()}$$\n\n`;
|
||||
} else {
|
||||
if(item.file) {
|
||||
if(item.file instanceof TFile) {
|
||||
@@ -969,6 +973,7 @@ export class ExcalidrawAutomate {
|
||||
* margin: { left: 20, right: 20, top: 20, bottom: 20 },
|
||||
* alignment: "center",
|
||||
* }
|
||||
* filename: "example.pdf",
|
||||
* });
|
||||
*/
|
||||
async createPDF({
|
||||
@@ -1002,6 +1007,68 @@ export class ExcalidrawAutomate {
|
||||
await exportToPDF({SVG, scale, pageProps, filename});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SVG representation of the current view.
|
||||
*
|
||||
* @param {Object} options - The options for creating the SVG.
|
||||
* @param {boolean} [options.withBackground=true] - Whether to include the background in the SVG.
|
||||
* @param {"light" | "dark"} [options.theme] - The theme to use for the SVG.
|
||||
* @param {FrameRenderingOptions} [options.frameRendering={enabled: true, name: true, outline: true, clip: true}] - The frame rendering options.
|
||||
* @param {number} [options.padding] - The padding to apply around the SVG.
|
||||
* @param {boolean} [options.selectedOnly=false] - Whether to include only the selected elements in the SVG.
|
||||
* @param {boolean} [options.skipInliningFonts=false] - Whether to skip inlining fonts in the SVG.
|
||||
* @param {boolean} [options.embedScene=false] - Whether to embed the scene in the SVG.
|
||||
* @returns {Promise<SVGSVGElement>} A promise that resolves to the SVG element.
|
||||
*/
|
||||
async createViewSVG({
|
||||
withBackground = true,
|
||||
theme,
|
||||
frameRendering = {enabled: true, name: true, outline: true, clip: true},
|
||||
padding,
|
||||
selectedOnly = false,
|
||||
skipInliningFonts = false,
|
||||
embedScene = false,
|
||||
} : {
|
||||
withBackground?: boolean,
|
||||
theme?: "light" | "dark",
|
||||
frameRendering?: FrameRenderingOptions,
|
||||
padding?: number,
|
||||
selectedOnly?: boolean,
|
||||
skipInliningFonts?: boolean,
|
||||
embedScene?: boolean,
|
||||
}): Promise<SVGSVGElement> {
|
||||
if(!this.targetView || !this.targetView.file || !this.targetView._loaded) {
|
||||
console.log("No view loaded");
|
||||
return;
|
||||
}
|
||||
const view = this.targetView;
|
||||
const scene = this.targetView.getScene(selectedOnly);
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: view.getViewExportWithBackground(withBackground),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, view.file),
|
||||
skipInliningFonts,
|
||||
frameRendering,
|
||||
};
|
||||
|
||||
return await getSVG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: view.getViewExportTheme(theme),
|
||||
exportEmbedScene: view.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
view.getViewExportPadding(padding),
|
||||
view.file,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SVG image from the ExcalidrawAutomate elements and the template provided.
|
||||
* @param {string} [templatePath] - The template path to use for the SVG.
|
||||
@@ -1848,7 +1915,7 @@ export class ExcalidrawAutomate {
|
||||
*/
|
||||
async addLaTex(topX: number, topY: number, tex: string): Promise<string> {
|
||||
const id = nanoid();
|
||||
const image = await tex2dataURL(tex, 4, this.plugin.app);
|
||||
const image = await tex2dataURL(tex, 4, this.plugin);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
@@ -1890,7 +1957,7 @@ export class ExcalidrawAutomate {
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
return await tex2dataURL(tex,scale, this.plugin.app);
|
||||
return await tex2dataURL(tex,scale, this.plugin);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2171,7 +2238,7 @@ export class ExcalidrawAutomate {
|
||||
this.targetView.updateScene({
|
||||
elements: el.filter((e: ExcalidrawElement) => !ids.includes(e.id)),
|
||||
appState: st,
|
||||
storeAction: "capture",
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
//this.targetView.save();
|
||||
return true;
|
||||
@@ -2498,7 +2565,7 @@ export class ExcalidrawAutomate {
|
||||
appState: {
|
||||
viewModeEnabled: !isFullscreen,
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
this.targetView.toolsPanelRef?.current?.setExcalidrawViewMode(!isFullscreen);
|
||||
}
|
||||
@@ -2520,7 +2587,7 @@ export class ExcalidrawAutomate {
|
||||
return;
|
||||
}
|
||||
const view = this.targetView as ExcalidrawView;
|
||||
view.updateScene({appState:{viewModeEnabled: enabled}, storeAction: "update"});
|
||||
view.updateScene({appState:{viewModeEnabled: enabled}, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
view.toolsPanelRef?.current?.setExcalidrawViewMode(enabled);
|
||||
}
|
||||
|
||||
@@ -2530,8 +2597,9 @@ export class ExcalidrawAutomate {
|
||||
* @param {ExcalidrawElement[]} [scene.elements] - Array of elements in the scene.
|
||||
* @param {AppState} [scene.appState] - The app state of the scene.
|
||||
* @param {BinaryFileData} [scene.files] - The files in the scene.
|
||||
* @param {boolean} [scene.commitToHistory] - Whether to commit the scene to history.
|
||||
* @param {"capture" | "none" | "update"} [scene.storeAction] - The store action for the scene.
|
||||
* @param {boolean} [scene.commitToHistory] - Whether to commit the scene to history. @deprecated Use scene.storageOption instead
|
||||
* @param {"capture" | "none" | "update"} [scene.storeAction] - The store action for the scene. @deprecated Use scene.storageOption instead
|
||||
* @param {"IMMEDIATELY" | "NEVER" | "EVENTUALLY"} [scene.captureUpdate] - The capture update action for the scene.
|
||||
* @param {boolean} [restore=false] - Whether to restore legacy elements in the scene.
|
||||
*/
|
||||
viewUpdateScene (
|
||||
@@ -2541,6 +2609,7 @@ export class ExcalidrawAutomate {
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
storeAction?: "capture" | "none" | "update",
|
||||
captureUpdate?: SceneData["captureUpdate"],
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void {
|
||||
@@ -2557,6 +2626,7 @@ export class ExcalidrawAutomate {
|
||||
appState: scene.appState,
|
||||
files: scene.files,
|
||||
storeAction: scene.storeAction,
|
||||
captureUpdate: scene.captureUpdate,
|
||||
},restore);
|
||||
}
|
||||
|
||||
@@ -3242,7 +3312,7 @@ export class ExcalidrawAutomate {
|
||||
elements.splice(newZIndex, 0, elements.splice(oldZIndex, 1)[0]);
|
||||
this.targetView.updateScene({
|
||||
elements,
|
||||
storeAction: "capture",
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1462,7 +1462,7 @@ export class ExcalidrawData {
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
outString += `${key}: $$${this.equations.get(key).latex}$$\n\n`;
|
||||
outString += `${key}: $$${this.equations.get(key).latex.trim()}$$\n\n`;
|
||||
}
|
||||
}
|
||||
if (this.files.size > 0) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import ExcalidrawView from "../view/ExcalidrawView";
|
||||
import { FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { App } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
declare const loadMathjaxToSVG: Function;
|
||||
let mathjaxLoaded = false;
|
||||
@@ -52,7 +52,7 @@ export const updateEquation = async (
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app: App,
|
||||
plugin: ExcalidrawPlugin,
|
||||
): Promise<{
|
||||
mimeType: MimeType;
|
||||
fileId: FileId;
|
||||
@@ -61,7 +61,7 @@ export async function tex2dataURL(
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
await loadMathJax();
|
||||
return tex2dataURLExternal(tex, scale, app);
|
||||
return tex2dataURLExternal(tex, scale, plugin);
|
||||
}
|
||||
|
||||
export const clearMathJaxVariables = () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ function createWorkerBlob(jsCode:string) {
|
||||
const workerCode = `
|
||||
var LZString=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n<r.length;n++)e[r][r.charAt(n)]=n}return e[r][o]}var i={compressToBase64:function(r){if(null==r)return"";var n=i._compress(r,6,function(r){return o.charAt(r)});switch(n.length%4){default:case 0:return n;case 1:return n+"===";case 2:return n+"==";case 3:return n+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(n){return t(o,r.charAt(n))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(r){return null==r?"":""==r?null:i._decompress(r.length,16384,function(o){return r.charCodeAt(o)-32})},compressToUint8Array:function(r){for(var o=i.compress(r),n=new Uint8Array(2*o.length),e=0,t=o.length;e<t;e++){var s=o.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e<t;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(r){return null==r?"":i._compress(r,6,function(r){return n.charAt(r)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(o){return t(n,r.charAt(o))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(r,o,n){if(null==r)return"";var e,t,i,s={},u={},a="",p="",c="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<r.length;i+=1)if(a=r.charAt(i),Object.prototype.hasOwnProperty.call(s,a)||(s[a]=f++,u[a]=!0),p=c+a,Object.prototype.hasOwnProperty.call(s,p))c=p;else{if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module?module.exports=LZString:"undefined"!=typeof angular&&null!=angular&&angular.module("LZString",[]).factory("LZString",function(){return LZString});
|
||||
self.onmessage = function(e) {
|
||||
const { data, action } = e.data;
|
||||
const { data, action, messageId } = e.data;
|
||||
try {
|
||||
switch (action) {
|
||||
case 'compress':
|
||||
@@ -22,7 +22,7 @@ self.onmessage = function(e) {
|
||||
for (let i = 0; i < compressed.length; i += chunkSize) {
|
||||
result += compressed.slice(i, i + chunkSize) + '\\n\\n';
|
||||
}
|
||||
self.postMessage({ compressed: result.trim() });
|
||||
self.postMessage({ messageId, compressed: result.trim() });
|
||||
break;
|
||||
case 'decompress':
|
||||
if (!data) throw new Error("No input string provided for decompression.");
|
||||
@@ -35,14 +35,13 @@ self.onmessage = function(e) {
|
||||
}
|
||||
}
|
||||
const decompressed = LZString.decompressFromBase64(cleanedData);
|
||||
self.postMessage({ decompressed });
|
||||
self.postMessage({ messageId, decompressed });
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown action.");
|
||||
}
|
||||
} catch (error) {
|
||||
// Post the error message back to the main thread
|
||||
self.postMessage({ error: error.message });
|
||||
self.postMessage({ messageId, error: error.message });
|
||||
}
|
||||
};
|
||||
`;
|
||||
@@ -55,27 +54,48 @@ export function initCompressionWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runCompressionWorker(data:string, action: 'compress' | 'decompress'): Promise<string> {
|
||||
let messageCounter = 0;
|
||||
const pendingOperations = new Map<number, {
|
||||
resolve: (value: string) => void,
|
||||
reject: (reason: Error) => void
|
||||
}>();
|
||||
|
||||
export async function runCompressionWorker(data: string, action: 'compress' | 'decompress'): Promise<string> {
|
||||
const messageId = messageCounter++;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onmessage = function(e) {
|
||||
const { compressed, decompressed, error } = e.data;
|
||||
|
||||
if (error) {
|
||||
reject(new Error(error));
|
||||
} else if (compressed || decompressed) {
|
||||
resolve(compressed || decompressed);
|
||||
} else {
|
||||
reject(new Error('Unexpected response from worker'));
|
||||
}
|
||||
};
|
||||
// Store the promise handlers
|
||||
pendingOperations.set(messageId, { resolve, reject });
|
||||
|
||||
// Set up the worker's error handler
|
||||
worker.onerror = function(error) {
|
||||
reject(new Error(error.message));
|
||||
};
|
||||
// Set up worker message handler if not already set
|
||||
if (!worker.onmessage) {
|
||||
worker.onmessage = function(e) {
|
||||
const { messageId, compressed, decompressed, error } = e.data;
|
||||
const handlers = pendingOperations.get(messageId);
|
||||
|
||||
if (handlers) {
|
||||
if (error) {
|
||||
handlers.reject(new Error(error));
|
||||
} else if (compressed || decompressed) {
|
||||
handlers.resolve(compressed || decompressed);
|
||||
} else {
|
||||
handlers.reject(new Error('Unexpected response from worker'));
|
||||
}
|
||||
pendingOperations.delete(messageId);
|
||||
}
|
||||
};
|
||||
|
||||
// Post the message to the worker
|
||||
worker.postMessage({ data, action });
|
||||
worker.onerror = function(error) {
|
||||
// Handle any pending operations in case of worker error
|
||||
for (const [id, handlers] of pendingOperations) {
|
||||
handlers.reject(new Error(error.message));
|
||||
pendingOperations.delete(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Post message with ID
|
||||
worker.postMessage({ messageId, data, action });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -17,4 +17,11 @@ export enum PreviewImageType {
|
||||
PNG = "PNG",
|
||||
SVGIMG = "SVGIMG",
|
||||
SVG = "SVG"
|
||||
}
|
||||
|
||||
export interface FrameRenderingOptions {
|
||||
enabled: boolean;
|
||||
name: boolean;
|
||||
outline: boolean;
|
||||
clip: boolean;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { DEVICE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { getParentOfClass } from "./obsidianUtils";
|
||||
import { App, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { getLinkParts } from "./utils";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
|
||||
@@ -62,8 +62,12 @@ export function setFileToLocalGraph(app: App, file: TFile) {
|
||||
app.workspace.iterateAllLeaves((l) => {
|
||||
if (l.view?.getViewType() === "localgraph") lgv = l.view;
|
||||
});
|
||||
if (lgv) {
|
||||
//@ts-ignore
|
||||
lgv.loadFile(file);
|
||||
try {
|
||||
if (lgv) {
|
||||
//@ts-ignore
|
||||
lgv.loadFile(file);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { cloneElement } from "./excalidrawAutomateUtils";
|
||||
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { addAppendUpdateCustomData } from "./utils";
|
||||
import { mutateElement } from "src/constants/constants";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
export const setDynamicStyle = (
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -16,8 +17,13 @@ export const setDynamicStyle = (
|
||||
textBackgroundColor?: string,
|
||||
) => {
|
||||
if(dynamicStyle === "none") {
|
||||
view.excalidrawContainer?.removeAttribute("style");
|
||||
setTimeout(()=>view.updateScene({appState:{dynamicStyle: ""}, storeAction: "update"}));
|
||||
//view.excalidrawContainer?.removeAttribute("style");
|
||||
setTimeout(()=>
|
||||
view.updateScene({
|
||||
appState:{dynamicStyle: {}},
|
||||
captureUpdate: CaptureUpdateAction.NEVER
|
||||
})
|
||||
);
|
||||
const toolspanel = view.toolsPanelRef?.current?.containerRef?.current;
|
||||
if(toolspanel) {
|
||||
let toolsStyle = toolspanel.getAttribute("style");
|
||||
@@ -178,7 +184,7 @@ export const setDynamicStyle = (
|
||||
frameColor,
|
||||
dynamicStyle: styleObject
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
view = null;
|
||||
ea = null;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { nanoid } from "nanoid";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { EmbeddedFile } from "src/shared/EmbeddedFileLoader";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
export async function insertImageToView(
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -264,7 +265,7 @@ export async function addBackOfTheNoteCard(
|
||||
api.selectElements([el]);
|
||||
if(activate) {
|
||||
window.setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}, storeAction: "update"});
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}, captureUpdate: CaptureUpdateAction.NEVER,});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,16 @@ import { t } from 'src/lang/helpers';
|
||||
|
||||
const DPI = 96;
|
||||
|
||||
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
||||
export type PDFPageAlignment =
|
||||
| "center"
|
||||
| "top-left"
|
||||
| "top-center"
|
||||
| "top-right"
|
||||
| "bottom-left"
|
||||
| "bottom-center"
|
||||
| "bottom-right"
|
||||
| "center-left"
|
||||
| "center-right";
|
||||
export type PDFPageMarginString = "none" | "tiny" | "normal";
|
||||
|
||||
export interface PDFExportScale {
|
||||
@@ -54,9 +63,9 @@ export type PageSize = keyof typeof STANDARD_PAGE_SIZES;
|
||||
export function getMarginValue(margin:PDFPageMarginString): PDFMargin {
|
||||
switch(margin) {
|
||||
case "none": return { left: 0, right: 0, top: 0, bottom: 0 };
|
||||
case "tiny": return { left: 5, right: 5, top: 5, bottom: 5 };
|
||||
case "normal": return { left: 25, right: 25, top: 25, bottom: 25 };
|
||||
default: return { left: 25, right: 25, top: 25, bottom: 25 };
|
||||
case "tiny": return { left: 10, right: 10, top: 10, bottom: 10 };
|
||||
case "normal": return { left: 60, right: 60, top: 60, bottom: 60 };
|
||||
default: return { left: 60, right: 60, top: 60, bottom: 60 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +212,7 @@ async function printPdf(
|
||||
}
|
||||
|
||||
function calculateDimensions(
|
||||
svg: SVGSVGElement,
|
||||
svgWidth: number,
|
||||
svgHeight: number,
|
||||
pageDim: PageDimensions,
|
||||
@@ -219,6 +229,9 @@ function calculateDimensions(
|
||||
}[],
|
||||
pages: number
|
||||
} {
|
||||
const viewBox = svg.getAttribute('viewBox')?.split(' ').map(Number) || [0, 0, svgWidth, svgHeight];
|
||||
const [viewBoxX, viewBoxY] = viewBox;
|
||||
|
||||
const availableWidth = pageDim.width - margin.left - margin.right;
|
||||
const availableHeight = pageDim.height - margin.top - margin.bottom;
|
||||
|
||||
@@ -262,7 +275,7 @@ function calculateDimensions(
|
||||
|
||||
return {
|
||||
tiles: [{
|
||||
viewBox: `0 0 ${svgWidth} ${svgHeight}`,
|
||||
viewBox: `${viewBoxX} ${viewBoxY} ${svgWidth} ${svgHeight}`,
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
x: position.x,
|
||||
@@ -273,35 +286,98 @@ function calculateDimensions(
|
||||
}
|
||||
|
||||
// Content needs to be tiled
|
||||
const tiles = [];
|
||||
const cols = Math.ceil(finalWidth / availableWidth);
|
||||
const rows = Math.ceil(finalHeight / availableHeight);
|
||||
|
||||
// Calculate total available space considering all margins
|
||||
const totalAvailableWidth = cols * availableWidth;
|
||||
const totalAvailableHeight = rows * availableHeight;
|
||||
|
||||
// Calculate global position in the total available space
|
||||
const globalPosition = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
totalAvailableWidth,
|
||||
totalAvailableHeight,
|
||||
{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
},
|
||||
alignment
|
||||
);
|
||||
|
||||
const tiles = [];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const tileX = (col * availableWidth) / (scale.zoom || 1);
|
||||
const tileY = (row * availableHeight) / (scale.zoom || 1);
|
||||
const tileWidth = Math.min(svgWidth - tileX, availableWidth / (scale.zoom || 1));
|
||||
const tileHeight = Math.min(svgHeight - tileY, availableHeight / (scale.zoom || 1));
|
||||
const scaledTileWidth = tileWidth * (scale.zoom || 1);
|
||||
const scaledTileHeight = tileHeight * (scale.zoom || 1);
|
||||
// Calculate where this tile intersects with the image in global space
|
||||
const tileGlobalX = col * availableWidth;
|
||||
const tileGlobalY = row * availableHeight;
|
||||
|
||||
// Calculate the intersection of the tile with the image
|
||||
const intersectX = Math.max(tileGlobalX, globalPosition.x);
|
||||
const intersectY = Math.max(tileGlobalY, globalPosition.y);
|
||||
const intersectRight = Math.min(tileGlobalX + availableWidth, globalPosition.x + finalWidth);
|
||||
const intersectBottom = Math.min(tileGlobalY + availableHeight, globalPosition.y + finalHeight);
|
||||
|
||||
// Calculate position for each tile
|
||||
const position = calculatePosition(
|
||||
scaledTileWidth,
|
||||
scaledTileHeight,
|
||||
pageDim.width,
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment
|
||||
);
|
||||
// Calculate actual tile dimensions
|
||||
const scaledTileWidth = Math.max(0, intersectRight - intersectX);
|
||||
const scaledTileHeight = Math.max(0, intersectBottom - intersectY);
|
||||
|
||||
if (scaledTileWidth <= 0 || scaledTileHeight <= 0) continue;
|
||||
|
||||
// Calculate viewBox coordinates
|
||||
const tileX = (intersectX - globalPosition.x) / (scale.zoom || 1);
|
||||
const tileY = (intersectY - globalPosition.y) / (scale.zoom || 1);
|
||||
const tileWidth = scaledTileWidth / (scale.zoom || 1);
|
||||
const tileHeight = scaledTileHeight / (scale.zoom || 1);
|
||||
|
||||
let x = margin.left;
|
||||
let y = margin.top;
|
||||
|
||||
// Handle horizontal positioning
|
||||
const widthRatio = scaledTileWidth / availableWidth;
|
||||
if (widthRatio >= 0.99 && widthRatio <= 1.01) {
|
||||
x = margin.left;
|
||||
} else {
|
||||
if (alignment === "center" || alignment === "top-center" || alignment === "bottom-center") {
|
||||
if(col === 0) {
|
||||
x = margin.left + (availableWidth - scaledTileWidth);
|
||||
} else {
|
||||
x = margin.left;
|
||||
}
|
||||
} else if (alignment.endsWith('right')) {
|
||||
x = pageDim.width - margin.right - scaledTileWidth;
|
||||
} else {
|
||||
x = margin.left;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle vertical positioning
|
||||
const heightRatio = scaledTileHeight / availableHeight;
|
||||
if (heightRatio >= 0.99 && heightRatio <= 1.01) {
|
||||
y = margin.top;
|
||||
} else {
|
||||
if (alignment === "center" || alignment === "center-left" || alignment === "center-right") {
|
||||
if(row === 0) {
|
||||
y = margin.top + (availableHeight - scaledTileHeight);
|
||||
} else {
|
||||
y = margin.top;
|
||||
}
|
||||
} else if (alignment.startsWith('bottom')) {
|
||||
y = pageDim.height - margin.bottom - scaledTileHeight;
|
||||
} else {
|
||||
y = margin.top;
|
||||
}
|
||||
}
|
||||
|
||||
tiles.push({
|
||||
viewBox: `${tileX} ${tileY} ${tileWidth} ${tileHeight}`,
|
||||
viewBox: `${tileX + viewBoxX} ${tileY + viewBoxY} ${tileWidth} ${tileHeight}`,
|
||||
width: scaledTileWidth,
|
||||
height: scaledTileHeight,
|
||||
x: position.x,
|
||||
y: position.y
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -324,14 +400,14 @@ function calculatePosition(
|
||||
let y = margin.top;
|
||||
|
||||
// Handle horizontal alignment
|
||||
if (alignment.includes('center')) {
|
||||
if (alignment === "center" || alignment === "top-center" || alignment === "bottom-center") {
|
||||
x = margin.left + (availableWidth - svgWidth) / 2;
|
||||
} else if (alignment.includes('right')) {
|
||||
} else if (alignment.endsWith('right')) {
|
||||
x = margin.left + availableWidth - svgWidth;
|
||||
}
|
||||
|
||||
// Handle vertical alignment
|
||||
if (alignment.startsWith('center')) {
|
||||
if (alignment === "center" || alignment === "center-left" || alignment === "center-right") {
|
||||
y = margin.top + (availableHeight - svgHeight) / 2;
|
||||
} else if (alignment.startsWith('bottom')) {
|
||||
y = margin.top + availableHeight - svgHeight;
|
||||
@@ -364,12 +440,13 @@ export async function exportToPDF({
|
||||
allPagesDiv.style.width = "100%";
|
||||
allPagesDiv.style.height = "fit-content";
|
||||
|
||||
let j = 1;
|
||||
let j = 0;
|
||||
for (const svg of SVG) {
|
||||
const svgWidth = parseFloat(svg.getAttribute('width') || '0');
|
||||
const svgHeight = parseFloat(svg.getAttribute('height') || '0');
|
||||
|
||||
const {tiles} = calculateDimensions(
|
||||
svg,
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
pageProps.dimensions,
|
||||
@@ -378,7 +455,7 @@ export async function exportToPDF({
|
||||
pageProps.alignment
|
||||
);
|
||||
|
||||
let i = 1;
|
||||
let i = 0;
|
||||
for (const tile of tiles) {
|
||||
const pageDiv = createDiv();
|
||||
pageDiv.style.width = `${width}px`;
|
||||
@@ -394,7 +471,7 @@ export async function exportToPDF({
|
||||
clonedSVG.style.height = `${tile.height}px`;
|
||||
clonedSVG.style.position = 'absolute';
|
||||
clonedSVG.style.left = `${tile.x}px`;
|
||||
clonedSVG.style.top = `${tile.y + (i-1)*height}px`;
|
||||
clonedSVG.style.top = `${tile.y + (i+j)*height}px`;
|
||||
|
||||
pageDiv.appendChild(clonedSVG);
|
||||
allPagesDiv.appendChild(pageDiv);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./fileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./modifierkeyHelper";
|
||||
import { EXCALIDRAW_PLUGIN, REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import { DEVICE, EXCALIDRAW_PLUGIN, REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import yaml from "js-yaml";
|
||||
import { debug, DEBUGGING } from "./debugHelper";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
@@ -416,3 +416,12 @@ export async function closeLeafView(leaf: WorkspaceLeaf) {
|
||||
state: {},
|
||||
});
|
||||
}
|
||||
|
||||
//In Obsidian 1.8.x the active excalidraw leaf is obscured by an empty leaf without a parent
|
||||
//In some ways similar to patchMobileView() - though does something completely different, but both mobile leaf related
|
||||
export function isUnwantedLeaf(leaf:WorkspaceLeaf):boolean {
|
||||
return !DEVICE.isDesktop &&
|
||||
leaf.view?.getViewType() === "empty" && leaf.parent && !leaf.parent.parent &&
|
||||
//@ts-ignore
|
||||
leaf.parent.type === "split" && leaf.parent.children.length === 1
|
||||
}
|
||||
@@ -307,7 +307,7 @@ export async function getSVG (
|
||||
: {},
|
||||
},
|
||||
files: scene.files,
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
exportPadding: exportSettings.frameRendering?.enabled ? 0 : padding,
|
||||
exportingFrame: null,
|
||||
renderEmbeddables: true,
|
||||
skipInliningFonts: exportSettings.skipInliningFonts,
|
||||
@@ -365,7 +365,7 @@ export async function getPNG (
|
||||
: {},
|
||||
},
|
||||
files: filterFiles(scene.files),
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
exportPadding: exportSettings.frameRendering?.enabled ? 0 : padding,
|
||||
mimeType: "image/png",
|
||||
getDimensions: (width: number, height: number) => ({
|
||||
width: width * scale,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { App, FileView, WorkspaceLeaf } from "obsidian";
|
||||
import { VIEW_TYPE_EXCALIDRAW_LOADING } from "src/constants/constants";
|
||||
import { act } from "react";
|
||||
import { DEVICE, VIEW_TYPE_EXCALIDRAW_LOADING } from "src/constants/constants";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { setExcalidrawView } from "src/utils/obsidianUtils";
|
||||
import { isUnwantedLeaf, setExcalidrawView } from "src/utils/obsidianUtils";
|
||||
|
||||
export function switchToExcalidraw(app: App) {
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW_LOADING).filter(l=>l.view instanceof ExcalidrawLoading);
|
||||
leaves.forEach(l=>(l.view as ExcalidrawLoading).switchToeExcalidraw());
|
||||
export async function switchToExcalidraw(app: App) {
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW_LOADING);
|
||||
for(const leaf of leaves) {
|
||||
await (leaf.view as ExcalidrawLoading)?.switchToeExcalidraw();
|
||||
}
|
||||
}
|
||||
|
||||
export class ExcalidrawLoading extends FileView {
|
||||
constructor(leaf: WorkspaceLeaf, private plugin: ExcalidrawPlugin) {
|
||||
super(leaf);
|
||||
this.displayLoadingText();
|
||||
}
|
||||
|
||||
public onload() {
|
||||
@@ -19,8 +21,13 @@ export class ExcalidrawLoading extends FileView {
|
||||
this.displayLoadingText();
|
||||
}
|
||||
|
||||
public switchToeExcalidraw() {
|
||||
setExcalidrawView(this.leaf);
|
||||
public async switchToeExcalidraw() {
|
||||
const prevLeaf = this.app.workspace.activeLeaf;
|
||||
await setExcalidrawView(this.leaf);
|
||||
const activeLeaf = this.app.workspace.activeLeaf;
|
||||
if(prevLeaf === this.leaf && activeLeaf !== prevLeaf && isUnwantedLeaf(activeLeaf)) {
|
||||
this.app.workspace.activeLeaf.detach();
|
||||
}
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
ExcalidrawImperativeAPI,
|
||||
Gesture,
|
||||
LibraryItems,
|
||||
SceneData,
|
||||
UIAppState,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import {
|
||||
@@ -152,7 +153,8 @@ import { Position, ViewSemaphores } from "../types/excalidrawViewTypes";
|
||||
import { DropManager } from "./managers/DropManager";
|
||||
import { ImageInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { exportToPDF, getMarginValue, getPageDimensions, PageOrientation, PageSize } from "src/utils/exportUtils";
|
||||
import { create } from "domain";
|
||||
import { FrameRenderingOptions } from "src/types/utilTypes";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
@@ -186,12 +188,7 @@ export interface ExportSettings {
|
||||
withBackground: boolean;
|
||||
withTheme: boolean;
|
||||
isMask: boolean;
|
||||
frameRendering?: { //optional, overrides relevant appState settings for rendering the frame
|
||||
enabled: boolean;
|
||||
name: boolean;
|
||||
outline: boolean;
|
||||
clip: boolean;
|
||||
};
|
||||
frameRendering?: FrameRenderingOptions; //optional, overrides relevant appState settings for rendering the frame
|
||||
skipInliningFonts?: boolean;
|
||||
}
|
||||
|
||||
@@ -257,7 +254,7 @@ export const addFiles = async (
|
||||
view.updateScene({
|
||||
elements: s.scene.elements,
|
||||
appState: s.scene.appState,
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
for (const f of files) {
|
||||
@@ -475,36 +472,75 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
);
|
||||
}
|
||||
|
||||
public getViewExportTheme(theme?:string):string {
|
||||
if(theme) return theme;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.theme : getExportTheme(this.plugin, this.file, this.excalidrawAPI.getAppState().theme)
|
||||
}
|
||||
|
||||
public getViewExportEmbedScene(embedScene?:boolean):boolean {
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene;
|
||||
}
|
||||
|
||||
public getViewExportPadding(padding?: number): number {
|
||||
if(typeof padding !== "undefined") return padding;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.padding : getExportPadding(this.plugin, this.file)
|
||||
}
|
||||
|
||||
public getViewExportScale(scale?: number): number {
|
||||
if(typeof scale !== "undefined") return scale;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? ed.scale : getPNGScale(this.plugin, this.file);
|
||||
}
|
||||
|
||||
public getViewExportWithBackground(withBackground?:boolean) {
|
||||
if(typeof withBackground !== "undefined") return withBackground;
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
}
|
||||
const ed = this.exportDialog;
|
||||
return ed ? !ed.transparent : getWithBackground(this.plugin, this.file)
|
||||
}
|
||||
|
||||
public async svg(scene: any, theme?:string, embedScene?: boolean, embedFont: boolean = false): Promise<SVGSVGElement> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.svg, "ExcalidrawView.svg", scene, theme, embedScene);
|
||||
const ed = this.exportDialog;
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: ed ? !ed.transparent : getWithBackground(this.plugin, this.file),
|
||||
withBackground: this.getViewExportWithBackground(),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
skipInliningFonts: !embedFont,
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getSVG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: theme ?? (ed ? ed.theme : getExportTheme(this.plugin, this.file, scene.appState.theme)),
|
||||
exportEmbedScene: typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene,
|
||||
theme: this.getViewExportTheme(theme),
|
||||
exportEmbedScene: this.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
ed ? ed.padding : getExportPadding(this.plugin, this.file),
|
||||
this.getViewExportPadding(),
|
||||
this.file,
|
||||
);
|
||||
}
|
||||
@@ -567,7 +603,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
public async exportPDF(
|
||||
toVault: boolean,
|
||||
selectedOnly?: boolean,
|
||||
pageSize: PageSize = "A4",
|
||||
orientation: PageOrientation = "portrait"
|
||||
@@ -605,34 +640,27 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
public async png(scene: any, theme?:string, embedScene?: boolean): Promise<Blob> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.png, "ExcalidrawView.png", scene, theme, embedScene);
|
||||
const ed = this.exportDialog;
|
||||
|
||||
const exportSettings: ExportSettings = {
|
||||
withBackground: ed ? !ed.transparent : getWithBackground(this.plugin, this.file),
|
||||
withBackground: this.getViewExportWithBackground(),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
...scene,
|
||||
...{
|
||||
appState: {
|
||||
...scene.appState,
|
||||
theme: theme ?? (ed ? ed.theme : getExportTheme(this.plugin, this.file, scene.appState.theme)),
|
||||
exportEmbedScene: typeof embedScene === "undefined"
|
||||
? (ed ? ed.embedScene : false)
|
||||
: embedScene,
|
||||
theme: this.getViewExportTheme(theme),
|
||||
exportEmbedScene: this.getViewExportEmbedScene(embedScene),
|
||||
},
|
||||
},
|
||||
},
|
||||
exportSettings,
|
||||
ed ? ed.padding : getExportPadding(this.plugin, this.file),
|
||||
ed ? ed.scale : getPNGScale(this.plugin, this.file),
|
||||
this.getViewExportPadding(),
|
||||
this.getViewExportScale(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1069,21 +1097,21 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
toggleDisableBinding() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleDisableBinding, "ExcalidrawView.toggleDisableBinding");
|
||||
const newState = !this.excalidrawAPI.getAppState().invertBindingBehaviour;
|
||||
this.updateScene({appState: {invertBindingBehaviour:newState}, storeAction: "update"});
|
||||
this.updateScene({appState: {invertBindingBehaviour:newState}, captureUpdate: CaptureUpdateAction.NEVER,});
|
||||
new Notice(newState ? t("ARROW_BINDING_INVERSE_MODE") : t("ARROW_BINDING_NORMAL_MODE"));
|
||||
}
|
||||
|
||||
toggleFrameRendering() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleFrameRendering, "ExcalidrawView.toggleFrameRendering");
|
||||
const frameRenderingSt = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().frameRendering;
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, enabled: !frameRenderingSt.enabled}}, storeAction: "update"});
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, enabled: !frameRenderingSt.enabled}}, captureUpdate: CaptureUpdateAction.NEVER,});
|
||||
new Notice(frameRenderingSt.enabled ? t("FRAME_CLIPPING_ENABLED") : t("FRAME_CLIPPING_DISABLED"));
|
||||
}
|
||||
|
||||
toggleFrameClipping() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleFrameClipping, "ExcalidrawView.toggleFrameClipping");
|
||||
const frameRenderingSt = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().frameRendering;
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, clip: !frameRenderingSt.clip}}, storeAction: "update"});
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, clip: !frameRenderingSt.clip}}, captureUpdate: CaptureUpdateAction.NEVER,});
|
||||
new Notice(frameRenderingSt.clip ? "Frame Clipping: Enabled" : "Frame Clipping: Disabled");
|
||||
}
|
||||
|
||||
@@ -1352,7 +1380,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if (this.excalidrawData.hasMermaid(selectedImage.fileId) || getMermaidText(imageElement)) {
|
||||
if(shouldRenderMermaid) {
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
api.updateScene({appState: {openDialog: { name: "ttd", tab: "mermaid" }}, storeAction: "update"})
|
||||
api.updateScene({appState: {openDialog: { name: "ttd", tab: "mermaid" }}, captureUpdate: CaptureUpdateAction.NEVER })
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1779,7 +1807,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
...st,
|
||||
theme,
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1949,6 +1977,11 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
protected async onClose(): Promise<void> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onClose,`ExcalidrawView.onClose, file:${this.file?.name}`);
|
||||
|
||||
//I noticed Obsidian calls this function twice when disabling the plugin
|
||||
//once from "unregisterView"
|
||||
//the from "detachLeavesOfType"
|
||||
if(!this.dropManager) return; //the view is already closed
|
||||
|
||||
// This happens when the user right clicks a tab and selects delete
|
||||
// in this case the onDelete event handler tirggers, but then Obsidian's delete event handler reaches onclose first, and
|
||||
// when the function is called a second time via on delete an error is thrown.)
|
||||
@@ -2714,7 +2747,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
{
|
||||
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
|
||||
files: excalidrawData.files,
|
||||
storeAction: justloaded ? "update" : "update", //was none, but I think based on a false understanding of none
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
},
|
||||
justloaded
|
||||
);
|
||||
@@ -2737,7 +2770,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
pinnedScripts: this.plugin.settings.pinnedScripts,
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens),
|
||||
},
|
||||
storeAction: justloaded ? "update" : "update", //was none, but I think based on a false understanding of none
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
},
|
||||
);
|
||||
if (
|
||||
@@ -3095,7 +3128,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
this.exportDialog.createForm();
|
||||
}
|
||||
this.exportDialog.open();
|
||||
})
|
||||
@@ -3504,7 +3536,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
this.updateScene(
|
||||
{
|
||||
elements,
|
||||
storeAction: "capture",
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
},
|
||||
shouldRestoreElements,
|
||||
);
|
||||
@@ -3904,7 +3936,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
st = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
|
||||
canvasColor = canvasColor ?? st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
|
||||
}
|
||||
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
|
||||
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, captureUpdate: CaptureUpdateAction.NEVER}));
|
||||
}
|
||||
|
||||
private canvasColorChangeHook(st: AppState) {
|
||||
@@ -4157,7 +4189,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
this.excalidrawData.deleteTextElement(clone.id);
|
||||
sceneElements[sceneElements.indexOf(el)] = clone;
|
||||
}
|
||||
this.updateScene({elements: sceneElements, storeAction: "update"});
|
||||
this.updateScene({elements: sceneElements, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
|
||||
//then insert images and embeds
|
||||
//shift text elements down to make space for images and embeds
|
||||
@@ -4281,7 +4313,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
clone.rawText = WARNING;
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.excalidrawData.setTextElement(clone.id,WARNING,()=>{});
|
||||
this.updateScene({elements, storeAction: "update"});
|
||||
this.updateScene({elements, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
api.history.clear();
|
||||
}
|
||||
});
|
||||
@@ -4305,7 +4337,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
clone.isDeleted = true;
|
||||
this.excalidrawData.deleteTextElement(clone.id);
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements, storeAction: "update"});
|
||||
this.updateScene({elements, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
const ea:ExcalidrawAutomate = getEA(this);
|
||||
if(IMAGE_TYPES.contains(file.extension)) {
|
||||
ea.selectElementsInView([await insertImageToView (ea, center, file)]);
|
||||
@@ -4358,7 +4390,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements, storeAction: "update"});
|
||||
this.updateScene({elements, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
if(clone.containerId) this.updateContainerSize(clone.containerId);
|
||||
this.setDirty(8.1);
|
||||
}
|
||||
@@ -4892,7 +4924,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
private actionOpenExportImageDialog() {
|
||||
if(!this.exportDialog) {
|
||||
this.exportDialog = new ExportDialog(this.plugin, this,this.file);
|
||||
this.exportDialog.createForm();
|
||||
}
|
||||
this.exportDialog.open();
|
||||
}
|
||||
@@ -5575,7 +5606,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { pinnedScripts: this.plugin.settings.pinnedScripts },
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5589,7 +5620,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
appState: {
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens),
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5601,7 +5632,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { allowPinchZoom: this.plugin.settings.allowPinchZoom },
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5613,7 +5644,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { allowWheelZoom: this.plugin.settings.allowWheelZoom },
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5626,7 +5657,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
const st = api.getAppState();
|
||||
api.updateScene({
|
||||
appState: { trayModeEnabled: !st.trayModeEnabled },
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
});
|
||||
|
||||
//just in case settings were updated via Obsidian sync
|
||||
@@ -5874,14 +5905,29 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
appState?: any;
|
||||
files?: any;
|
||||
storeAction?: "capture" | "none" | "update"; //https://github.com/excalidraw/excalidraw/pull/7898
|
||||
captureUpdate?: SceneData["captureUpdate"];
|
||||
},
|
||||
shouldRestore: boolean = false,
|
||||
) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateScene, "ExcalidrawView.updateScene", scene, shouldRestore);
|
||||
const api = this.excalidrawAPI;
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(typeof scene.storeAction === "string") {
|
||||
switch(scene.storeAction) {
|
||||
case "capture":
|
||||
scene.captureUpdate = CaptureUpdateAction.IMMEDIATELY;
|
||||
break;
|
||||
case "none":
|
||||
scene.captureUpdate = CaptureUpdateAction.EVENTUALLY;
|
||||
break;
|
||||
default:
|
||||
scene.captureUpdate = CaptureUpdateAction.NEVER;
|
||||
}
|
||||
delete scene.storeAction;
|
||||
}
|
||||
const shouldRestoreElements = scene.elements && shouldRestore;
|
||||
if (shouldRestoreElements) {
|
||||
scene.elements = restore(scene, null, null).elements;
|
||||
|
||||
@@ -15,6 +15,7 @@ import { EmbeddableSettings } from "src/shared/Dialogs/EmbeddableSettings";
|
||||
import { openExternalLink } from "src/utils/excalidrawViewUtils";
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
export class EmbeddableMenu {
|
||||
private menuFadeTimeout: number = 0;
|
||||
@@ -70,7 +71,7 @@ export class EmbeddableMenu {
|
||||
};
|
||||
|
||||
private async actionMarkdownSelection (file: TFile, isExcalidrawFile: boolean, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
const sections = (await this.view.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
@@ -98,7 +99,7 @@ export class EmbeddableMenu {
|
||||
|
||||
private async actionMarkdownBlock (file: TFile, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
if(!file) return;
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, captureUpdate: CaptureUpdateAction.NEVER});
|
||||
const paragraphs = (await this.view.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ICONS, penIcon, stringToSVG } from "../../../constants/actionIcons";
|
||||
import { UniversalInsertFileModal } from "src/shared/Dialogs/UniversalInsertFileModal";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { getExcalidrawViews } from "src/utils/obsidianUtils";
|
||||
import { CaptureUpdateAction } from "src/constants/constants";
|
||||
|
||||
export function setPen (pen: PenStyle, api: any) {
|
||||
const st = api.getAppState();
|
||||
@@ -34,7 +35,7 @@ export function setPen (pen: PenStyle, api: any) {
|
||||
}}
|
||||
: null,
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ export function resetStrokeOptions (resetCustomPen:any, api: ExcalidrawImperativ
|
||||
resetCustomPen: null,
|
||||
...clearCurrentStrokeOptions ? {currentStrokeOptions: null} : null,
|
||||
},
|
||||
storeAction: "update",
|
||||
captureUpdate: CaptureUpdateAction.NEVER
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -684,6 +684,10 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!this.view.plugin._loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const downloadedScriptsRoot = `${this.view.plugin.settings.scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}/`;
|
||||
|
||||
const filterCondition = (key: string): boolean =>
|
||||
|
||||
30
styles.css
30
styles.css
@@ -342,7 +342,9 @@ label.color-input-container > input {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.excalidraw .panelColumn {
|
||||
.excalidraw .selected-shape-actions .panelColumn,
|
||||
.excalidraw .App-mobile-menu .panelColumn
|
||||
{
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -388,11 +390,14 @@ div.excalidraw-draginfo {
|
||||
position: absolute !important;
|
||||
}
|
||||
|
||||
/*Arrow Picker Popover Overflow*/
|
||||
/*
|
||||
.excalidraw .selected-shape-actions .Island.App-menu__left,
|
||||
.excalidraw .selected-shape-actions .Island.App-menu__left .panelColumn,
|
||||
.excalidraw .Island .App-mobile-menu,
|
||||
.excalidraw .Island.App-menu__left {
|
||||
/*Arrow Picker Popover Overflow*/
|
||||
.excalidraw .Island .App-mobile-menu .panelColumn {
|
||||
overflow: visible;
|
||||
}
|
||||
}*/
|
||||
|
||||
.excalidraw__embeddable-container .view-header {
|
||||
display: none !important;
|
||||
@@ -712,4 +717,21 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
.excalidraw-release .nav-button.is-active {
|
||||
border-bottom: 2px solid var(--interactive-accent);
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.excalidraw .picker-content {
|
||||
grid-gap: 0.2rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .picker-content button.picker-option{
|
||||
width: 1.85rem !important;
|
||||
height: 1.85rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .picker {
|
||||
padding: 0.2rem !important;
|
||||
}
|
||||
|
||||
.excalidraw .context-menu {
|
||||
height: fit-content;
|
||||
}
|
||||
Reference in New Issue
Block a user