mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
1 Commits
PDF-export
...
imagepathh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5475bfde6 |
132
package-lock.json
generated
132
package-lock.json
generated
@@ -9,7 +9,6 @@
|
||||
"version": "2.2.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cantoo/pdf-lib": "^2.2.4",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
"@zsviczian/excalidraw": "0.17.6-26",
|
||||
@@ -42,7 +41,6 @@
|
||||
"@excalidraw/prettier-config": "^1.0.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
@@ -1884,19 +1882,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
|
||||
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
|
||||
},
|
||||
"node_modules/@cantoo/pdf-lib": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.2.4.tgz",
|
||||
"integrity": "sha512-69bECXjl0QVMrBTEsPZDPFmhoyLGwXKiJI5JWPgBnAx0kS5+7v76rNCeYMTbDmZaPgvdIViMvTaz2HVCe8u1fw==",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"color": "^4.2.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"node-html-better-parser": "^1.4.0",
|
||||
"pako": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/commands": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
|
||||
@@ -2361,22 +2346,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/standard-fonts": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
|
||||
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@pdf-lib/upng": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
|
||||
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
@@ -2806,26 +2775,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-json": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
|
||||
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-node-resolve": {
|
||||
"version": "15.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
|
||||
@@ -3847,18 +3796,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@@ -3871,32 +3808,8 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
@@ -4009,11 +3922,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/css-declaration-sorter": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz",
|
||||
@@ -5593,21 +5501,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
|
||||
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/mdevils"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -5702,11 +5595,6 @@
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -6857,14 +6745,6 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/node-html-better-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.4.7.tgz",
|
||||
"integrity": "sha512-cJcBhlrn432XtbzPmzxxMLsHeSUnE5qFQtVbVlHXvUGt2ccqc0/wBsB43CFv39BIkLkP5rrPUN5Hg52CnheH+A==",
|
||||
"dependencies": {
|
||||
"html-entities": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
@@ -8437,14 +8317,6 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
|
||||
@@ -40,8 +40,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"roughjs": "^4.5.2",
|
||||
"woff2sfnt-sfnt2woff": "^1.0.0",
|
||||
"es6-promise-pool": "2.5.0",
|
||||
"@cantoo/pdf-lib": "^2.2.4"
|
||||
"es6-promise-pool": "2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jsesc": "^3.0.2",
|
||||
@@ -60,7 +59,6 @@
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"@rollup/plugin-typescript": "^12.1.2",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@types/chroma-js": "^2.4.0",
|
||||
"@types/js-beautify": "^1.14.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
|
||||
@@ -11,7 +11,6 @@ import postprocess from '@zsviczian/rollup-plugin-postprocess';
|
||||
import cssnano from 'cssnano';
|
||||
import jsesc from 'jsesc';
|
||||
import { minify } from 'uglify-js';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
// Load environment variables
|
||||
import dotenv from 'dotenv';
|
||||
@@ -131,7 +130,6 @@ const BASE_CONFIG = {
|
||||
|
||||
const getRollupPlugins = (tsconfig, ...plugins) => [
|
||||
typescript2(tsconfig),
|
||||
json(),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
|
||||
@@ -105,6 +105,41 @@
|
||||
*/
|
||||
//ea.onFileCreateHook = (data) => {};
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
* You can use this callback to customize the naming and path of pasted images to avoid
|
||||
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
|
||||
* and instead use more meaningful names based on the Excalidraw file or other criteria,
|
||||
* plus save the image in a different folder.
|
||||
*
|
||||
* If the function returns null or undefined, the normal Excalidraw operation will continue
|
||||
* with the excalidraw generated name and default path.
|
||||
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
|
||||
* with the file extension.
|
||||
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
|
||||
*
|
||||
* @param data - An object containing the following properties:
|
||||
* @property {string} [currentImageName] - Default name for the image.
|
||||
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
|
||||
*
|
||||
* @returns {string} - The new filepath for the image including full vault path and extension.
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* onImageFilePathHook: (data) => {
|
||||
* const { currentImageName, drawingFilePath } = data;
|
||||
* const ext = currentImageName.split('.').pop();
|
||||
* // Generate a new filepath based on the drawing file name and other criteria
|
||||
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
|
||||
* }
|
||||
* ```
|
||||
* onImageFilePathHook: (data: {
|
||||
* currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
|
||||
* drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
|
||||
* }) => string = null;
|
||||
*/
|
||||
//ea.onImageFileNameHook = (data) => {};
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
* onCanvasColorChangeHook: (
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -42,7 +42,6 @@ import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/
|
||||
import { HotkeyEditor } from "src/shared/Dialogs/HotkeyEditor";
|
||||
import { getExcalidrawViews } from "src/utils/obsidianUtils";
|
||||
import { createSliderWithText } from "src/utils/sliderUtils";
|
||||
import { PDFExportSettingsComponent, PDFExportSettings } from "src/shared/Dialogs/PDFExportSettingsComponent";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -219,7 +218,6 @@ export interface ExcalidrawSettings {
|
||||
rank: Rank;
|
||||
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
|
||||
showSplashscreen: boolean;
|
||||
pdfSettings: PDFExportSettings;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -499,15 +497,6 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
{modifiers: ["Mod"], key:"G"},
|
||||
],
|
||||
showSplashscreen: true,
|
||||
pdfSettings: {
|
||||
pageSize: "A4",
|
||||
pageOrientation: "portrait",
|
||||
fitToPage: true,
|
||||
paperColor: "white",
|
||||
customPaperColor: "#ffffff",
|
||||
alignment: "center",
|
||||
margin: "normal"
|
||||
},
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -2129,20 +2118,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("PDF_EXPORT_SETTINGS"),
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
|
||||
new PDFExportSettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.pdfSettings,
|
||||
() => {
|
||||
this.applySettingsUpdate();
|
||||
}
|
||||
).render();
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EXPORT_HEAD"),
|
||||
@@ -2287,6 +2262,9 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
el.innerHTML = t("MD_EMBED_CUSTOMDATA_HEAD_DESC");
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.embeddableMarkdownDefaults,
|
||||
|
||||
@@ -386,14 +386,13 @@ FILENAME_HEAD: "Filename",
|
||||
"This setting will not affect the display of the drawing when you are in Excalidraw mode or when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
|
||||
"<li>See other related setting for <a href='#"+TAG_PDFEXPORT+"'>PDF Export</a> under 'Embedding and Exporting' further below.</li></ul><br>" +
|
||||
"You must close the active excalidraw/markdown file and reopen it for this change to take effect.",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render Excalidraw as Image in Obsidian PDF Export",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render the file as an image when exporting an Excalidraw file to PDF",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"This setting controls how Excalidraw files are exported to PDF using Obsidian's built-in <b>Export to PDF</b> feature.<br>" +
|
||||
"<ul><li><b>Enabled:</b> The PDF will include the Excalidraw drawing as an image.</li>" +
|
||||
"<li><b>Disabled:</b> The PDF will include the markdown content as text.</li></ul>" +
|
||||
"Note: This setting does not affect the PDF export feature within Excalidraw itself.<br>" +
|
||||
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearance and Behavior' further above.<br>" +
|
||||
"⚠️ You must close and reopen the Excalidraw/markdown file for changes to take effect. ⚠️",
|
||||
"This setting controls the behavior of Excalidraw when exporting an Excalidraw file to PDF in markdown view mode using Obsidian's <b>Export to PDF</b> feature.<br>" +
|
||||
"<ul><li>When <b>enabled</b> the PDF will show the Excalidraw drawing only;</li>" +
|
||||
"<li>When <b>disabled</b> the PDF will show the markdown side of the document.</li></ul>" +
|
||||
"See the other related setting for <a href='#"+TAG_MDREADINGMODE+"'>Markdown Reading Mode</a> under 'Appearnace and Behavior' further above.<br>" +
|
||||
"⚠️ Note, you must close the active excalidraw/markdown file and reopen for this change to take effect. ⚠️",
|
||||
HOTKEY_OVERRIDE_HEAD: "Hotkey overrides",
|
||||
HOTKEY_OVERRIDE_DESC: `Some of the Excalidraw hotkeys such as <code>${labelCTRL()}+Enter</code> to edit text or <code>${labelCTRL()}+K</code> to create an element link ` +
|
||||
"conflict with Obsidian hotkey settings. The hotkey combinations you add below will override Obsidian's hotkey settings while useing Excalidraw, thus " +
|
||||
@@ -662,7 +661,6 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"Embed Excalidraw scene in exported image. Can be overridden at a file level by adding the <code>excalidraw-export-embed-scene: true/false<code> frontmatter key. " +
|
||||
"The setting only takes effect the next time you (re)open drawings.",
|
||||
PDF_EXPORT_SETTINGS: "PDF Export Settings",
|
||||
EXPORT_HEAD: "Auto-export Settings",
|
||||
EXPORT_SYNC_NAME:
|
||||
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
@@ -1008,75 +1006,4 @@ FILENAME_HEAD: "Filename",
|
||||
LINK_CLICK_POPOUT: "Open in a popout window",
|
||||
LINK_CLICK_NEW_TAB: "Open in a new tab",
|
||||
LINK_CLICK_MD_PROPS: "Show the Markdown image-properties dialog (only relevant if you have embedded a markdown document as an image)",
|
||||
|
||||
//ExportDialog
|
||||
// Dialog and tabs
|
||||
EXPORTDIALOG_TITLE: "Export Drawing",
|
||||
EXPORTDIALOG_TAB_IMAGE: "Image",
|
||||
EXPORTDIALOG_TAB_PDF: "PDF",
|
||||
// Settings persistence
|
||||
EXPORTDIALOG_SAVE_SETTINGS: "Save image settings to file doc.properties?",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_SAVE: "Save as preset",
|
||||
EXPORTDIALOG_SAVE_SETTINGS_ONETIME: "One-time use",
|
||||
// Image settings
|
||||
EXPORTDIALOG_IMAGE_SETTINGS: "Image",
|
||||
EXPORTDIALOG_IMAGE_DESC: "PNG supports transparency. External files can include Excalidraw scene data.",
|
||||
EXPORTDIALOG_PADDING: "Padding",
|
||||
EXPORTDIALOG_SCALE: "Scale",
|
||||
EXPORTDIALOG_CURRENT_PADDING: "Current padding:",
|
||||
EXPORTDIALOG_SIZE_DESC: "Scale affects output size",
|
||||
EXPORTDIALOG_SCALE_VALUE: "Scale:",
|
||||
EXPORTDIALOG_IMAGE_SIZE: "Size:",
|
||||
// Theme and background
|
||||
EXPORTDIALOG_EXPORT_THEME: "Theme",
|
||||
EXPORTDIALOG_THEME_LIGHT: "Light",
|
||||
EXPORTDIALOG_THEME_DARK: "Dark",
|
||||
EXPORTDIALOG_BACKGROUND: "Background",
|
||||
EXPORTDIALOG_BACKGROUND_TRANSPARENT: "Transparent",
|
||||
EXPORTDIALOG_BACKGROUND_USE_COLOR: "Use scene color",
|
||||
// Selection
|
||||
EXPORTDIALOG_SELECTED_ELEMENTS: "Export",
|
||||
EXPORTDIALOG_SELECTED_ALL: "Entire scene",
|
||||
EXPORTDIALOG_SELECTED_SELECTED: "Selection only",
|
||||
// Export options
|
||||
EXPORTDIALOG_EMBED_SCENE: "Include scene data?",
|
||||
EXPORTDIALOG_EMBED_YES: "Yes",
|
||||
EXPORTDIALOG_EMBED_NO: "No",
|
||||
// PDF settings
|
||||
EXPORTDIALOG_PDF_SETTINGS: "PDF",
|
||||
EXPORTDIALOG_PAGE_SIZE: "Size",
|
||||
EXPORTDIALOG_PAGE_ORIENTATION: "Orientation",
|
||||
EXPORTDIALOG_ORIENTATION_PORTRAIT: "Portrait",
|
||||
EXPORTDIALOG_ORIENTATION_LANDSCAPE: "Landscape",
|
||||
EXPORTDIALOG_PDF_FIT_TO_PAGE: "Page Fitting",
|
||||
EXPORTDIALOG_PDF_FIT_OPTION: "Fit to page",
|
||||
EXPORTDIALOG_PDF_SCALE_OPTION: "Use image scale (may span multiple pages)",
|
||||
EXPORTDIALOG_PDF_PAPER_COLOR: "Paper Color",
|
||||
EXPORTDIALOG_PDF_PAPER_WHITE: "White",
|
||||
EXPORTDIALOG_PDF_PAPER_SCENE: "Use scene color",
|
||||
EXPORTDIALOG_PDF_PAPER_CUSTOM: "Custom color",
|
||||
EXPORTDIALOG_PDF_ALIGNMENT: "Position on Page",
|
||||
EXPORTDIALOG_PDF_ALIGN_CENTER: "Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_LEFT: "Top Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_CENTER: "Top Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT: "Top Right",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT: "Bottom Left",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER: "Bottom Center",
|
||||
EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT: "Bottom Right",
|
||||
EXPORTDIALOG_PDF_MARGIN: "Margin",
|
||||
EXPORTDIALOG_PDF_MARGIN_NONE: "None",
|
||||
EXPORTDIALOG_PDF_MARGIN_TINY: "Small",
|
||||
EXPORTDIALOG_PDF_MARGIN_NORMAL: "Normal",
|
||||
EXPORTDIALOG_SAVE_PDF_SETTINGS: "Save PDF settings",
|
||||
EXPORTDIALOG_SAVE_CONFIRMATION: "PDF config saved to plugin settings as default",
|
||||
// Buttons
|
||||
EXPORTDIALOG_PNGTOFILE : "Export PNG",
|
||||
EXPORTDIALOG_SVGTOFILE : "Export SVG",
|
||||
EXPORTDIALOG_PNGTOVAULT : "PNG to Vault",
|
||||
EXPORTDIALOG_SVGTOVAULT : "SVG to Vault",
|
||||
EXPORTDIALOG_EXCALIDRAW: "Excalidraw",
|
||||
EXPORTDIALOG_PNGTOCLIPBOARD : "PNG to Clipboard",
|
||||
EXPORTDIALOG_SVGTOCLIPBOARD : "SVG to Clipboard",
|
||||
EXPORTDIALOG_PDF: "Export PDF",
|
||||
EXPORTDIALOG_PDFTOVAULT: "PDF to Vault",
|
||||
};
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Modal, Notice, Setting, TFile, ButtonComponent } from "obsidian";
|
||||
import { Modal, Setting, TFile } from "obsidian";
|
||||
import { getEA } from "src/core";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/utils";
|
||||
import { PageOrientation, PageSize, PDFMargin, PDFPageAlignment, PDFPageMarginString, STANDARD_PAGE_SIZES, exportSVGToClipboard } from "src/utils/exportUtils";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { PDFExportSettings, PDFExportSettingsComponent } from "./PDFExportSettingsComponent";
|
||||
|
||||
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
@@ -32,17 +27,6 @@ export class ExportDialog extends Modal {
|
||||
public embedScene: boolean;
|
||||
public exportSelectedOnly: boolean;
|
||||
public saveToVault: boolean;
|
||||
public pageSize: PageSize = "A4";
|
||||
public pageOrientation: PageOrientation = "portrait";
|
||||
private activeTab: "image" | "pdf" = "image";
|
||||
private contentContainer: HTMLDivElement;
|
||||
private buttonContainerRow1: HTMLDivElement;
|
||||
private buttonContainerRow2: HTMLDivElement;
|
||||
public fitToPage: boolean = true;
|
||||
public paperColor: "white" | "scene" | "custom" = "white";
|
||||
public customPaperColor: string = "#ffffff";
|
||||
public alignment: PDFPageAlignment = "center";
|
||||
public margin: PDFPageMarginString = "normal";
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
@@ -60,15 +44,6 @@ export class ExportDialog extends Modal {
|
||||
this.exportSelectedOnly = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
|
||||
this.pageSize = plugin.settings.pdfSettings.pageSize;
|
||||
this.pageOrientation = plugin.settings.pdfSettings.pageOrientation;
|
||||
this.fitToPage = plugin.settings.pdfSettings.fitToPage;
|
||||
this.paperColor = plugin.settings.pdfSettings.paperColor;
|
||||
this.customPaperColor = plugin.settings.pdfSettings.customPaperColor;
|
||||
this.alignment = plugin.settings.pdfSettings.alignment;
|
||||
this.margin = plugin.settings.pdfSettings.margin;
|
||||
|
||||
this.saveSettings = false;
|
||||
}
|
||||
|
||||
@@ -87,7 +62,7 @@ export class ExportDialog extends Modal {
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(t("EXPORTDIALOG_TITLE"));
|
||||
this.titleEl.setText(`Export Image`);
|
||||
this.hasSelectedElements = this.view.getViewSelectedElements().length > 0;
|
||||
//@ts-ignore
|
||||
this.selectedOnlySetting.setVisibility(this.hasSelectedElements);
|
||||
@@ -98,100 +73,28 @@ export class ExportDialog extends Modal {
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
if(DEVICE.isDesktop) {
|
||||
// Create tab container
|
||||
const tabContainer = this.contentEl.createDiv("nav-buttons-container");
|
||||
const imageTab = tabContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_TAB_IMAGE"),
|
||||
cls: `nav-button ${this.activeTab === "image" ? "is-active" : ""}`
|
||||
});
|
||||
|
||||
|
||||
const pdfTab = tabContainer.createEl("button", {
|
||||
text: t("EXPORTDIALOG_TAB_PDF"),
|
||||
cls: `nav-button ${this.activeTab === "pdf" ? "is-active" : ""}`
|
||||
});
|
||||
|
||||
// Tab click handlers
|
||||
imageTab.onclick = () => {
|
||||
this.activeTab = "image";
|
||||
imageTab.addClass("is-active");
|
||||
pdfTab.removeClass("is-active");
|
||||
this.renderContent();
|
||||
};
|
||||
|
||||
pdfTab.onclick = () => {
|
||||
this.activeTab = "pdf";
|
||||
pdfTab.addClass("is-active");
|
||||
imageTab.removeClass("is-active");
|
||||
this.renderContent();
|
||||
};
|
||||
}
|
||||
|
||||
// Create content container
|
||||
this.contentContainer = this.contentEl.createDiv();
|
||||
this.buttonContainerRow1 = this.contentEl.createDiv({cls: "excalidraw-export-buttons-div"});
|
||||
this.buttonContainerRow2 = this.contentEl.createDiv({cls: "excalidraw-export-buttons-div"});
|
||||
this.buttonContainerRow2.style.marginTop = "10px";
|
||||
|
||||
this.renderContent();
|
||||
}
|
||||
|
||||
private createSaveSettingsDropdown() {
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SAVE_SETTINGS"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("save", t("EXPORTDIALOG_SAVE_SETTINGS_SAVE"))
|
||||
.addOption("one-time", t("EXPORTDIALOG_SAVE_SETTINGS_ONETIME"))
|
||||
.setValue(this.saveSettings ? "save" : "one-time")
|
||||
.onChange(value => {
|
||||
this.saveSettings = value === "save";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
this.contentContainer.empty();
|
||||
this.buttonContainerRow1.empty();
|
||||
this.buttonContainerRow2.empty();
|
||||
|
||||
if (this.activeTab === "image") {
|
||||
this.createImageSettings();
|
||||
this.createExportSettings();
|
||||
this.createImageButtons();
|
||||
} else {
|
||||
this.createImageSettings();
|
||||
this.createPDFSettings();
|
||||
this.createPDFButton();
|
||||
}
|
||||
}
|
||||
|
||||
private createImageSettings() {
|
||||
let scaleSetting:Setting;
|
||||
let paddingSetting: Setting;
|
||||
let paddingSetting: Setting;
|
||||
|
||||
this.contentContainer.createEl("h1",{text: t("EXPORTDIALOG_IMAGE_SETTINGS")});
|
||||
this.contentContainer.createEl("p",{text: t("EXPORTDIALOG_IMAGE_DESC")})
|
||||
|
||||
this.createSaveSettingsDropdown();
|
||||
this.contentEl.createEl("h1",{text: "Image settings"});
|
||||
this.contentEl.createEl("p",{text: "Transparency only affects PNGs. Excalidraw files can only be exported outside the Vault. PNGs copied to clipboard may not include the scene."})
|
||||
|
||||
const size = ():DocumentFragment => {
|
||||
const width = Math.round(this.scale*this.boundingBox.width + this.padding*2);
|
||||
const height = Math.round(this.scale*this.boundingBox.height + this.padding*2);
|
||||
return fragWithHTML(`${t("EXPORTDIALOG_SIZE_DESC")}<br>${t("EXPORTDIALOG_SCALE_VALUE")} <b>${this.scale}</b><br>${t("EXPORTDIALOG_IMAGE_SIZE")} <b>${width}x${height}</b>`);
|
||||
return fragWithHTML(`The lager the scale, the larger the image.<br>Scale: <b>${this.scale}</b><br>Image size: <b>${width}x${height}</b>`);
|
||||
}
|
||||
|
||||
const padding = ():DocumentFragment => {
|
||||
return fragWithHTML(`${t("EXPORTDIALOG_CURRENT_PADDING")} <b>${this.padding}</b>`);
|
||||
return fragWithHTML(`Current image padding is <b>${this.padding}</b>`);
|
||||
}
|
||||
|
||||
paddingSetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_PADDING"))
|
||||
paddingSetting = new Setting(this.contentEl)
|
||||
.setName("Image padding")
|
||||
.setDesc(padding())
|
||||
.addSlider(slider => {
|
||||
slider
|
||||
.setLimits(0,100,1)
|
||||
.setLimits(0,50,1)
|
||||
.setValue(this.padding)
|
||||
.onChange(value => {
|
||||
this.padding = value;
|
||||
@@ -200,12 +103,12 @@ export class ExportDialog extends Modal {
|
||||
})
|
||||
})
|
||||
|
||||
scaleSetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SCALE"))
|
||||
scaleSetting = new Setting(this.contentEl)
|
||||
.setName("PNG Scale")
|
||||
.setDesc(size())
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0.2,7,0.1)
|
||||
.setLimits(0.5,5,0.5)
|
||||
.setValue(this.scale)
|
||||
.onChange(value => {
|
||||
this.scale = value;
|
||||
@@ -213,213 +116,109 @@ export class ExportDialog extends Modal {
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_EXPORT_THEME"))
|
||||
new Setting(this.contentEl)
|
||||
.setName("Export theme")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("light", t("EXPORTDIALOG_THEME_LIGHT"))
|
||||
.addOption("dark", t("EXPORTDIALOG_THEME_DARK"))
|
||||
.addOption("light","Light")
|
||||
.addOption("dark","Dark")
|
||||
.setValue(this.theme)
|
||||
.onChange(value => {
|
||||
this.theme = value;
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_BACKGROUND"))
|
||||
new Setting(this.contentEl)
|
||||
.setName("Background color")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("transparent", t("EXPORTDIALOG_BACKGROUND_TRANSPARENT"))
|
||||
.addOption("with-color", t("EXPORTDIALOG_BACKGROUND_USE_COLOR"))
|
||||
.addOption("transparent","Transparent")
|
||||
.addOption("with-color","Use scene background color")
|
||||
.setValue(this.transparent?"transparent":"with-color")
|
||||
.onChange(value => {
|
||||
this.transparent = value === "transparent";
|
||||
})
|
||||
)
|
||||
|
||||
this.selectedOnlySetting = new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_SELECTED_ELEMENTS"))
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Save or one-time settings?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("all", t("EXPORTDIALOG_SELECTED_ALL"))
|
||||
.addOption("selected", t("EXPORTDIALOG_SELECTED_SELECTED"))
|
||||
.setValue(this.exportSelectedOnly?"selected":"all")
|
||||
.addOption("save","Save these settings as the preset for this image")
|
||||
.addOption("one-time","These are one-time settings")
|
||||
.setValue(this.saveSettings?"save":"one-time")
|
||||
.onChange(value => {
|
||||
this.exportSelectedOnly = value === "selected";
|
||||
this.saveSettings = value === "save";
|
||||
})
|
||||
);
|
||||
}
|
||||
)
|
||||
|
||||
private createExportSettings() {
|
||||
new Setting(this.contentContainer)
|
||||
.setName(t("EXPORTDIALOG_EMBED_SCENE"))
|
||||
this.contentEl.createEl("h1",{text:"Export settings"});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Embed the Excalidraw scene in the exported file?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("embed",t("EXPORTDIALOG_EMBED_YES"))
|
||||
.addOption("no-embed",t("EXPORTDIALOG_EMBED_NO"))
|
||||
.addOption("embed","Embed scene")
|
||||
.addOption("no-embed","Do not embed scene")
|
||||
.setValue(this.embedScene?"embed":"no-embed")
|
||||
.onChange(value => {
|
||||
this.embedScene = value === "embed";
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private createPDFSettings() {
|
||||
if (!DEVICE.isDesktop) return;
|
||||
|
||||
this.contentContainer.createEl("h1", { text: t("EXPORTDIALOG_PDF_SETTINGS") });
|
||||
|
||||
const pdfSettings: PDFExportSettings = {
|
||||
pageSize: this.pageSize,
|
||||
pageOrientation: this.pageOrientation,
|
||||
fitToPage: this.fitToPage,
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin
|
||||
};
|
||||
|
||||
new PDFExportSettingsComponent(
|
||||
this.contentContainer,
|
||||
pdfSettings,
|
||||
() => {
|
||||
this.pageSize = pdfSettings.pageSize;
|
||||
this.pageOrientation = pdfSettings.pageOrientation;
|
||||
this.fitToPage = pdfSettings.fitToPage;
|
||||
this.paperColor = pdfSettings.paperColor;
|
||||
this.customPaperColor = pdfSettings.customPaperColor;
|
||||
this.alignment = pdfSettings.alignment;
|
||||
this.margin = pdfSettings.margin;
|
||||
}
|
||||
).render();
|
||||
}
|
||||
|
||||
private createImageButtons() {
|
||||
if(DEVICE.isDesktop) {
|
||||
const bPNG = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOFILE"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNG.onclick = () => {
|
||||
this.view.exportPNG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
const bPNGVault = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOVAULT"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNGVault.onclick = () => {
|
||||
this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bPNGClipboard = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PNGTOCLIPBOARD"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPNGClipboard.onclick = async () => {
|
||||
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
|
||||
if(DEVICE.isDesktop) {
|
||||
const bExcalidraw = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_EXCALIDRAW"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bExcalidraw.onclick = () => {
|
||||
this.view.exportExcalidraw();
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bSVG = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOFILE"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVG.onclick = () => {
|
||||
this.view.exportSVG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
new Setting(this.contentEl)
|
||||
.setName("Where to save the image?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("vault","Save image to your Vault")
|
||||
.addOption("outside","Export image outside your Vault")
|
||||
.setValue(this.saveToVault?"vault":"outside")
|
||||
.onChange(value => {
|
||||
this.saveToVault = value === "vault";
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const bSVGVault = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOVAULT"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVGVault.onclick = () => {
|
||||
this.view.saveSVG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly));
|
||||
this.selectedOnlySetting = new Setting(this.contentEl)
|
||||
.setName("Export entire scene or just selected elements?")
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOption("all","Export entire scene")
|
||||
.addOption("selected","Export selected elements")
|
||||
.setValue(this.exportSelectedOnly?"selected":"all")
|
||||
.onChange(value => {
|
||||
this.exportSelectedOnly = value === "selected";
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bPNG = div.createEl("button", { text: "PNG to File", cls: "excalidraw-prompt-button"});
|
||||
bPNG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.savePNG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly))
|
||||
: this.view.exportPNG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
|
||||
const bSVGClipboard = this.buttonContainerRow2.createEl("button", {
|
||||
text: t("EXPORTDIALOG_SVGTOCLIPBOARD"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bSVGClipboard.onclick = async () => {
|
||||
const svg = await this.view.getSVG(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
exportSVGToClipboard(svg);
|
||||
const bSVG = div.createEl("button", { text: "SVG to File", cls: "excalidraw-prompt-button" });
|
||||
bSVG.onclick = () => {
|
||||
this.saveToVault
|
||||
? this.view.saveSVG(this.view.getScene(this.hasSelectedElements && this.exportSelectedOnly))
|
||||
: this.view.exportSVG(this.embedScene,this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
private createPDFButton() {
|
||||
const bSavePDFSettings = this.buttonContainerRow1.createEl("button",
|
||||
{ text: t("EXPORTDIALOG_SAVE_PDF_SETTINGS"), cls: "excalidraw-export-button" }
|
||||
);
|
||||
bSavePDFSettings.onclick = async () => {
|
||||
//in case sync loaded a new version of settings in the mean time
|
||||
await this.plugin.loadSettings();
|
||||
this.plugin.settings.pdfSettings = {
|
||||
pageSize: this.pageSize,
|
||||
pageOrientation: this.pageOrientation,
|
||||
fitToPage: this.fitToPage,
|
||||
paperColor: this.paperColor,
|
||||
customPaperColor: this.customPaperColor,
|
||||
alignment: this.alignment,
|
||||
margin: this.margin
|
||||
const bExcalidraw = div.createEl("button", { text: "Excalidraw", cls: "excalidraw-prompt-button" });
|
||||
bExcalidraw.onclick = () => {
|
||||
this.view.exportExcalidraw(this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
if(DEVICE.isDesktop) {
|
||||
const bPNGClipboard = div.createEl("button", { text: "PNG to Clipboard", cls: "excalidraw-prompt-button" });
|
||||
bPNGClipboard.onclick = () => {
|
||||
this.view.exportPNGToClipboard(this.embedScene, this.hasSelectedElements && this.exportSelectedOnly);
|
||||
this.close();
|
||||
};
|
||||
await this.plugin.saveSettings();
|
||||
new Notice(t("EXPORTDIALOG_SAVE_CONFIRMATION"));
|
||||
};
|
||||
|
||||
const bPDFVault = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PDFTOVAULT"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPDFVault.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
true,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
);
|
||||
this.close();
|
||||
};
|
||||
|
||||
if (!DEVICE.isDesktop) return;
|
||||
const bPDFExport = this.buttonContainerRow1.createEl("button", {
|
||||
text: t("EXPORTDIALOG_PDF"),
|
||||
cls: "excalidraw-export-button"
|
||||
});
|
||||
bPDFExport.onclick = () => {
|
||||
this.view.exportPDF(
|
||||
false,
|
||||
this.hasSelectedElements && this.exportSelectedOnly,
|
||||
this.pageSize,
|
||||
this.pageOrientation
|
||||
);
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
|
||||
public getPaperColor(): string {
|
||||
switch (this.paperColor) {
|
||||
case "white": return "#ffffff";
|
||||
case "scene": return this.api.getAppState().viewBackgroundColor;
|
||||
case "custom": return this.customPaperColor;
|
||||
default: return "#ffffff";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import { Setting } from "obsidian";
|
||||
import { PageOrientation, PageSize, PDFPageAlignment, PDFPageMarginString, STANDARD_PAGE_SIZES } from "src/utils/exportUtils";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export interface PDFExportSettings {
|
||||
pageSize: PageSize;
|
||||
pageOrientation: PageOrientation;
|
||||
fitToPage: boolean;
|
||||
paperColor: "white" | "scene" | "custom";
|
||||
customPaperColor: string;
|
||||
alignment: PDFPageAlignment;
|
||||
margin: PDFPageMarginString;
|
||||
}
|
||||
|
||||
export class PDFExportSettingsComponent {
|
||||
constructor(
|
||||
private contentEl: HTMLElement,
|
||||
private settings: PDFExportSettings,
|
||||
private update?: Function,
|
||||
) {
|
||||
if (!update) this.update = () => {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const pageSizeOptions: Record<string, string> = Object.keys(STANDARD_PAGE_SIZES)
|
||||
.reduce((acc, key) => ({
|
||||
...acc,
|
||||
[key]: key
|
||||
}), {});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PAGE_SIZE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions(pageSizeOptions)
|
||||
.setValue(this.settings.pageSize)
|
||||
.onChange(value => {
|
||||
this.settings.pageSize = value as PageSize;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PAGE_ORIENTATION"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"portrait": t("EXPORTDIALOG_ORIENTATION_PORTRAIT"),
|
||||
"landscape": t("EXPORTDIALOG_ORIENTATION_LANDSCAPE")
|
||||
})
|
||||
.setValue(this.settings.pageOrientation)
|
||||
.onChange(value => {
|
||||
this.settings.pageOrientation = value as PageOrientation;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_FIT_TO_PAGE"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"fit": t("EXPORTDIALOG_PDF_FIT_OPTION"),
|
||||
"scale": t("EXPORTDIALOG_PDF_SCALE_OPTION")
|
||||
})
|
||||
.setValue(this.settings.fitToPage ? "fit" : "scale")
|
||||
.onChange(value => {
|
||||
this.settings.fitToPage = value === "fit";
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_MARGIN"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"none": t("EXPORTDIALOG_PDF_MARGIN_NONE"),
|
||||
"tiny": t("EXPORTDIALOG_PDF_MARGIN_TINY"),
|
||||
"normal": t("EXPORTDIALOG_PDF_MARGIN_NORMAL")
|
||||
})
|
||||
.setValue(this.settings.margin)
|
||||
.onChange(value => {
|
||||
this.settings.margin = value as PDFPageMarginString;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
const paperColorSetting = new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_PAPER_COLOR"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"white": t("EXPORTDIALOG_PDF_PAPER_WHITE"),
|
||||
"scene": t("EXPORTDIALOG_PDF_PAPER_SCENE"),
|
||||
"custom": t("EXPORTDIALOG_PDF_PAPER_CUSTOM")
|
||||
})
|
||||
.setValue(this.settings.paperColor)
|
||||
.onChange(value => {
|
||||
this.settings.paperColor = value as typeof this.settings.paperColor;
|
||||
colorInput.style.display = (value === "custom") ? "block" : "none";
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
const colorInput = paperColorSetting.controlEl.createEl("input", {
|
||||
type: "color",
|
||||
value: this.settings.customPaperColor
|
||||
});
|
||||
colorInput.style.width = "50px";
|
||||
colorInput.style.marginLeft = "10px";
|
||||
colorInput.style.display = this.settings.paperColor === "custom" ? "block" : "none";
|
||||
colorInput.addEventListener("change", (e) => {
|
||||
this.settings.customPaperColor = (e.target as HTMLInputElement).value;
|
||||
this.update();
|
||||
});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("EXPORTDIALOG_PDF_ALIGNMENT"))
|
||||
.addDropdown(dropdown =>
|
||||
dropdown
|
||||
.addOptions({
|
||||
"center": t("EXPORTDIALOG_PDF_ALIGN_CENTER"),
|
||||
"top-left": t("EXPORTDIALOG_PDF_ALIGN_TOP_LEFT"),
|
||||
"top-center": t("EXPORTDIALOG_PDF_ALIGN_TOP_CENTER"),
|
||||
"top-right": t("EXPORTDIALOG_PDF_ALIGN_TOP_RIGHT"),
|
||||
"bottom-left": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_LEFT"),
|
||||
"bottom-center": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_CENTER"),
|
||||
"bottom-right": t("EXPORTDIALOG_PDF_ALIGN_BOTTOM_RIGHT")
|
||||
})
|
||||
.setValue(this.settings.alignment)
|
||||
.onChange(value => {
|
||||
this.settings.alignment = value as PDFPageAlignment;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -224,62 +224,6 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Use ExcalidrawAutomate.getExportSettings(boolean,boolean) to create an ExportSettings object.\nUse ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?) to create an EmbeddedFilesLoader object.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "createPDF",
|
||||
code: "async createPDF({SVG: SVGSVGElement[], scale?: PDFExportScale, pageProps?: PDFPageProperties}): Promise<ArrayBuffer>",
|
||||
desc: "",
|
||||
after: "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" +
|
||||
"@returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFExportScale\n" +
|
||||
"@property {boolean} fitToPage - Whether to fit the SVG to the page.\n" +
|
||||
"@property {number} [zoom=1] - The zoom level for the SVG. Used only if fitToPage is false. If the SVG does not fit the page, it will be tiled over multiple pages.\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PDFPageProperties\n" +
|
||||
"@property {{width: number, height: number}} [dimensions] - The dimensions of the PDF pages. 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.\n" +
|
||||
"@property {PDFPageAlignment} alignment - The alignment of the SVG on the PDF pages.\n" +
|
||||
"\n" +
|
||||
"@example\n" +
|
||||
"const pdfData = await createPDF({\n" +
|
||||
" SVG: [svgElement1, svgElement2],\n" +
|
||||
" scale: { fitToPage: true },\n" +
|
||||
" pageProps: {\n" +
|
||||
" dimensions: { width: 595.28, height: 841.89 },\n" +
|
||||
" backgroundColor: \"#ffffff\",\n" +
|
||||
" margin: { left: 20, right: 20, top: 20, bottom: 20 },\n" +
|
||||
" alignment: \"center\"\n" +
|
||||
" }\n" +
|
||||
"});",
|
||||
},
|
||||
{
|
||||
field: "getPagePDFDimensions",
|
||||
code: "getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions",
|
||||
desc: "Returns the dimensions of a standard page size in points (pt).\n" +
|
||||
"\n" +
|
||||
"@param {PageSize} pageSize - The standard page size. Possible values are \"A0\", \"A1\", \"A2\", \"A3\", \"A4\", \"A5\", \"Letter\", \"Legal\", \"Tabloid\".\n" +
|
||||
"@param {PageOrientation} orientation - The orientation of the page. Possible values are \"portrait\" and \"landscape\".\n" +
|
||||
"@returns {PageDimensions} - An object containing the width and height of the page in points (pt).\n" +
|
||||
"\n" +
|
||||
"@typedef {Object} PageDimensions\n" +
|
||||
"@property {number} width - The width of the page in points (pt).\n" +
|
||||
"@property {number} height - The height of the page in points (pt).\n" +
|
||||
"\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: "",
|
||||
},
|
||||
{
|
||||
field: "createPNG",
|
||||
code: "async createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string,padding?: number): Promise<any>;",
|
||||
|
||||
@@ -83,7 +83,6 @@ import { ExcalidrawLib } from "../types/excalidrawLib";
|
||||
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";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -931,75 +930,6 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the dimensions of a standard page size in points (pt).
|
||||
*
|
||||
* @param {PageSize} pageSize - The standard page size. Possible values are "A0", "A1", "A2", "A3", "A4", "A5", "Letter", "Legal", "Tabloid".
|
||||
* @param {PageOrientation} orientation - The orientation of the page. Possible values are "portrait" and "landscape".
|
||||
* @returns {PageDimensions} - An object containing the width and height of the page in points (pt).
|
||||
*
|
||||
* @typedef {Object} PageDimensions
|
||||
* @property {number} width - The width of the page in points (pt).
|
||||
* @property {number} height - The height of the page in points (pt).
|
||||
*
|
||||
* @example
|
||||
* const dimensions = getPageDimensions("A4", "portrait");
|
||||
* console.log(dimensions); // { width: 595.28, height: 841.89 }
|
||||
*/
|
||||
getPagePDFDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions {
|
||||
return getPageDimensions(pageSize, orientation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PDF from the provided SVG elements with specified scaling and page properties.
|
||||
*
|
||||
* @param {Object} params - The parameters for creating the PDF.
|
||||
* @param {SVGSVGElement[]} params.SVG - An array of SVG elements to be included in the PDF.
|
||||
* @param {PDFExportScale} [params.scale={ fitToPage: true, zoom: 1 }] - The scaling options for the SVG elements.
|
||||
* @param {PDFPageProperties} [params.pageProps] - The properties for the PDF pages.
|
||||
* @returns {Promise<ArrayBuffer>} - A promise that resolves to an ArrayBuffer containing the PDF data.
|
||||
*
|
||||
* @example
|
||||
* const pdfData = await createToPDF({
|
||||
* SVG: [svgElement1, svgElement2],
|
||||
* scale: { fitToPage: true },
|
||||
* pageProps: {
|
||||
* dimensions: { width: 595.28, height: 841.89 },
|
||||
* backgroundColor: "#ffffff",
|
||||
* margin: { left: 20, right: 20, top: 20, bottom: 20 },
|
||||
* alignment: "center"
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
async createPDF({
|
||||
SVG,
|
||||
scale = { fitToPage: true, zoom: 1 },
|
||||
pageProps,
|
||||
}: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale?: PDFExportScale;
|
||||
pageProps?: PDFPageProperties;
|
||||
}): Promise<ArrayBuffer> {
|
||||
if(!pageProps) {
|
||||
pageProps = {
|
||||
alignment: this.plugin.settings.pdfSettings.alignment,
|
||||
margin: getMarginValue(this.plugin.settings.pdfSettings.margin),
|
||||
};
|
||||
}
|
||||
|
||||
if(!pageProps.dimensions) {
|
||||
pageProps.dimensions = getPageDimensions(
|
||||
this.plugin.settings.pdfSettings.pageSize,
|
||||
this.plugin.settings.pdfSettings.pageOrientation
|
||||
)
|
||||
}
|
||||
if(!pageProps.backgroundColor) {
|
||||
pageProps.backgroundColor = "#ffffff";
|
||||
}
|
||||
|
||||
return await exportToPDF({SVG, scale, pageProps});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SVG image from the ExcalidrawAutomate elements and the template provided.
|
||||
* @param {string} [templatePath] - The template path to use for the SVG.
|
||||
@@ -2740,6 +2670,40 @@ export class ExcalidrawAutomate {
|
||||
pointerPosition: { x: number; y: number }; //the pointer position on canvas
|
||||
}) => boolean = null;
|
||||
|
||||
/**
|
||||
* If set, this callback is triggered when a image is being saved in Excalidraw.
|
||||
* You can use this callback to customize the naming and path of pasted images to avoid
|
||||
* default names like "Pasted image 123147170.png" being saved in the attachments folder,
|
||||
* and instead use more meaningful names based on the Excalidraw file or other criteria,
|
||||
* plus save the image in a different folder.
|
||||
*
|
||||
* If the function returns null or undefined, the normal Excalidraw operation will continue
|
||||
* with the excalidraw generated name and default path.
|
||||
* If a filepath is returned, that will be used. Include the full Vault filepath and filename
|
||||
* with the file extension.
|
||||
* The currentImageName is the name of the image generated by excalidraw or provided during paste.
|
||||
*
|
||||
* @param data - An object containing the following properties:
|
||||
* @property {string} [currentImageName] - Default name for the image.
|
||||
* @property {string} drawingFilePath - The file path of the Excalidraw file where the image is being used.
|
||||
*
|
||||
* @returns {string} - The new filepath for the image including full vault path and extension.
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* onImageFilePathHook: (data) => {
|
||||
* const { currentImageName, drawingFilePath } = data;
|
||||
* // Generate a new filepath based on the drawing file name and other criteria
|
||||
* const ext = currentImageName.split('.').pop();
|
||||
* return `${drawingFileName} - ${currentImageName || 'image'}.${ext}`;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
onImageFilePathHook: (data: {
|
||||
currentImageName: string; // Excalidraw generated name of the image, or the name received from the file system.
|
||||
drawingFilePath: string; // The full filepath of the Excalidraw file where the image is being used.
|
||||
}) => string = null;
|
||||
|
||||
/**
|
||||
* if set, this callback is triggered, when an Excalidraw file is opened
|
||||
* You can use this callback in case you want to do something additional when the file is opened.
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
loadSceneFonts,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { TextMode } from "../view/ExcalidrawView";
|
||||
import ExcalidrawView, { TextMode } from "../view/ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
@@ -52,7 +52,7 @@ import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "..
|
||||
import { DEBUGGING, debug } from "../utils/debugHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { updateElementIdsInScene } from "../utils/excalidrawSceneUtils";
|
||||
import { getNewUniqueFilepath } from "../utils/fileUtils";
|
||||
import { getNewUniqueFilepath, splitFolderAndFilename } from "../utils/fileUtils";
|
||||
import { t } from "../lang/helpers";
|
||||
import { displayFontMessage } from "../utils/excalidrawViewUtils";
|
||||
import { getPDFRect } from "../utils/PDFUtils";
|
||||
@@ -480,7 +480,7 @@ export class ExcalidrawData {
|
||||
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private plugin: ExcalidrawPlugin, private view?: ExcalidrawView,
|
||||
) {
|
||||
this.app = this.plugin.app;
|
||||
this.files = new Map<FileId, EmbeddedFile>();
|
||||
@@ -1546,13 +1546,23 @@ export class ExcalidrawData {
|
||||
}
|
||||
}
|
||||
|
||||
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
|
||||
const filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
|
||||
let hookFilepath:string;
|
||||
const ea = this.view?.getHookServer();
|
||||
if(ea?.onImageFilePathHook) {
|
||||
hookFilepath = ea.onImageFilePathHook({
|
||||
currentImageName: fname,
|
||||
drawingFilePath: this.view?.file?.path,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
const filepath = (
|
||||
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
|
||||
).filepath;*/
|
||||
let filepath:string;
|
||||
if(hookFilepath) {
|
||||
const {folderpath, filename} = splitFolderAndFilename(hookFilepath);
|
||||
filepath = getNewUniqueFilepath(this.app.vault,filename,folderpath);
|
||||
} else {
|
||||
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
|
||||
filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
|
||||
}
|
||||
|
||||
const arrayBuffer = await getBinaryFileFromDataURL(dataURL);
|
||||
if(!arrayBuffer) return null;
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
import { PDFDocument, rgb } from '@cantoo/pdf-lib';
|
||||
import { getEA } from 'src/core';
|
||||
|
||||
|
||||
export type PDFPageAlignment = "center" | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
||||
export type PDFPageMarginString = "none" | "tiny" | "normal";
|
||||
|
||||
export interface PDFExportScale {
|
||||
fitToPage: boolean;
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
export interface PDFMargin {
|
||||
left: number;
|
||||
right: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface PDFPageProperties {
|
||||
dimensions?: {width: number; height: number};
|
||||
backgroundColor?: string;
|
||||
margin: PDFMargin;
|
||||
alignment: PDFPageAlignment;
|
||||
}
|
||||
|
||||
export interface PageDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export type PageOrientation = "portrait" | "landscape";
|
||||
|
||||
// All dimensions in points (pt)
|
||||
export const STANDARD_PAGE_SIZES = {
|
||||
"A0": { width: 2383.94, height: 3370.39 },
|
||||
"A1": { width: 1683.78, height: 2383.94 },
|
||||
"A2": { width: 1190.55, height: 1683.78 },
|
||||
"A3": { width: 841.89, height: 1190.55 },
|
||||
"A4": { width: 595.28, height: 841.89 },
|
||||
"A5": { width: 419.53, height: 595.28 },
|
||||
"Letter": { width: 612, height: 792 },
|
||||
"Legal": { width: 612, height: 1008 },
|
||||
"Tabloid": { width: 792, height: 1224 },
|
||||
} as const;
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
export function getPageDimensions(pageSize: PageSize, orientation: PageOrientation): PageDimensions {
|
||||
const dimensions = STANDARD_PAGE_SIZES[pageSize];
|
||||
return orientation === "portrait"
|
||||
? { width: dimensions.width, height: dimensions.height }
|
||||
: { width: dimensions.height, height: dimensions.width };
|
||||
}
|
||||
|
||||
interface SVGDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
x: number;
|
||||
y: number;
|
||||
sourceX?: number;
|
||||
sourceY?: number;
|
||||
sourceWidth?: number;
|
||||
sourceHeight?: number;
|
||||
}
|
||||
|
||||
function calculatePosition(
|
||||
svgWidth: number,
|
||||
svgHeight: number,
|
||||
pageWidth: number,
|
||||
pageHeight: number,
|
||||
margin: PDFMargin,
|
||||
alignment: PDFPageAlignment,
|
||||
scale: PDFExportScale
|
||||
): {x: number, y: number} {
|
||||
const availableWidth = pageWidth - margin.left - margin.right;
|
||||
const availableHeight = pageHeight - margin.top - margin.bottom;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Debug',
|
||||
input: {
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
pageWidth,
|
||||
pageHeight,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
},
|
||||
calculated: {
|
||||
availableWidth,
|
||||
availableHeight
|
||||
}
|
||||
}));
|
||||
|
||||
let x = margin.left;
|
||||
let y = margin.bottom;
|
||||
|
||||
// Handle horizontal alignment
|
||||
if (alignment.includes('center')) {
|
||||
x = margin.left + (availableWidth - svgWidth) / 2;
|
||||
} else if (alignment.includes('right')) {
|
||||
x = margin.left + availableWidth - svgWidth;
|
||||
}
|
||||
|
||||
// Handle vertical alignment
|
||||
if (alignment.startsWith('center')) {
|
||||
y = margin.bottom + (availableHeight - svgHeight) / 2;
|
||||
} else if (alignment.startsWith('top')) {
|
||||
y = margin.bottom;
|
||||
} else if (alignment.startsWith('bottom')) {
|
||||
y = pageHeight - margin.top - svgHeight;
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Intermediate',
|
||||
x,
|
||||
y,
|
||||
alignment,
|
||||
availableHeight,
|
||||
marginTop: margin.top,
|
||||
marginBottom: margin.bottom,
|
||||
svgHeight,
|
||||
pageHeight
|
||||
}));
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Position Result',
|
||||
x,
|
||||
y,
|
||||
finalPosition: {
|
||||
bottom: y,
|
||||
top: y + svgHeight,
|
||||
left: x,
|
||||
right: x + svgWidth
|
||||
}
|
||||
}));
|
||||
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
function calculateDimensions(
|
||||
svgWidth: number,
|
||||
svgHeight: number,
|
||||
pageDim: PageDimensions,
|
||||
margin: PDFPageProperties['margin'],
|
||||
scale: PDFExportScale,
|
||||
alignment: PDFPageAlignment
|
||||
): SVGDimensions[] {
|
||||
const availableWidth = pageDim.width - margin.left - margin.right;
|
||||
const availableHeight = pageDim.height - margin.top - margin.bottom;
|
||||
|
||||
let finalWidth: number;
|
||||
let finalHeight: number;
|
||||
|
||||
if (scale.fitToPage) {
|
||||
const ratio = Math.min(availableWidth / svgWidth, availableHeight / svgHeight);
|
||||
finalWidth = svgWidth * ratio;
|
||||
finalHeight = svgHeight * ratio;
|
||||
|
||||
const position = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
pageDim.width,
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
);
|
||||
|
||||
return [{
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
x: position.x,
|
||||
y: position.y
|
||||
}];
|
||||
} else {
|
||||
// Scale mode - may need multiple pages
|
||||
finalWidth = svgWidth * (scale.zoom || 1);
|
||||
finalHeight = svgHeight * (scale.zoom || 1);
|
||||
|
||||
if (finalWidth <= availableWidth && finalHeight <= availableHeight) {
|
||||
// Content fits on one page
|
||||
const position = calculatePosition(
|
||||
finalWidth,
|
||||
finalHeight,
|
||||
pageDim.width,
|
||||
pageDim.height,
|
||||
margin,
|
||||
alignment,
|
||||
scale
|
||||
);
|
||||
|
||||
return [{
|
||||
width: finalWidth,
|
||||
height: finalHeight,
|
||||
x: position.x,
|
||||
y: position.y
|
||||
}];
|
||||
} else {
|
||||
// Content needs to be tiled across multiple pages
|
||||
const dimensions: SVGDimensions[] = [];
|
||||
const cols = Math.ceil(finalWidth / availableWidth);
|
||||
const rows = Math.ceil(finalHeight / availableHeight);
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const tileWidth = Math.min(availableWidth, finalWidth - col * availableWidth);
|
||||
const tileHeight = Math.min(availableHeight, finalHeight - row * availableHeight);
|
||||
|
||||
// Calculate y coordinate following the same logic as single-page rendering
|
||||
// We start from the bottom margin and work our way up
|
||||
//const y = margin.bottom + row * availableHeight;
|
||||
|
||||
dimensions.push({
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
x: margin.left,
|
||||
y: margin.top,
|
||||
sourceX: col * availableWidth / (scale.zoom || 1),
|
||||
sourceY: row * availableHeight / (scale.zoom || 1),
|
||||
sourceWidth: tileWidth / (scale.zoom || 1),
|
||||
sourceHeight: tileHeight / (scale.zoom || 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addSVGToPage(
|
||||
pdfDoc: PDFDocument,
|
||||
svg: SVGSVGElement,
|
||||
dimensions: SVGDimensions,
|
||||
pageDim: PageDimensions,
|
||||
backgroundColor?: string
|
||||
) {
|
||||
const page = pdfDoc.addPage([pageDim.width, pageDim.height]);
|
||||
|
||||
if (backgroundColor && backgroundColor !== '#ffffff') {
|
||||
const { r, g, b } = hexToRGB(backgroundColor);
|
||||
page.drawRectangle({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: pageDim.width,
|
||||
height: pageDim.height,
|
||||
color: rgb(r/255, g/255, b/255),
|
||||
});
|
||||
}
|
||||
|
||||
// Clone and modify SVG for tiling if needed
|
||||
let svgToEmbed = svg;
|
||||
if (dimensions.sourceX !== undefined) {
|
||||
svgToEmbed = svg.cloneNode(true) as SVGSVGElement;
|
||||
const viewBox = `${dimensions.sourceX} ${dimensions.sourceY} ${dimensions.sourceWidth} ${dimensions.sourceHeight}`;
|
||||
svgToEmbed.setAttribute('viewBox', viewBox);
|
||||
svgToEmbed.setAttribute('width', String(dimensions.sourceWidth));
|
||||
svgToEmbed.setAttribute('height', String(dimensions.sourceHeight));
|
||||
}
|
||||
|
||||
const svgImage = await pdfDoc.embedSvg(svgToEmbed.outerHTML);
|
||||
|
||||
console.log(JSON.stringify({message: "addSVGToPage", dimensions, html: svgToEmbed.outerHTML}));
|
||||
|
||||
// Adjust y-coordinate to account for PDF coordinate system
|
||||
const adjustedY = pageDim.height - dimensions.y;
|
||||
|
||||
page.drawSvg(svgImage, {
|
||||
x: dimensions.x,
|
||||
y: adjustedY,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
message: 'PDF Draw SVG',
|
||||
x: dimensions.x,
|
||||
y: adjustedY,
|
||||
width: dimensions.width,
|
||||
height: dimensions.height
|
||||
}));
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
export async function exportToPDF({
|
||||
SVG,
|
||||
scale = { fitToPage: true, zoom: 1 },
|
||||
pageProps,
|
||||
}: {
|
||||
SVG: SVGSVGElement[];
|
||||
scale: PDFExportScale;
|
||||
pageProps: PDFPageProperties;
|
||||
}): Promise<ArrayBuffer> {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
for (const svg of SVG) {
|
||||
const svgWidth = parseFloat(svg.getAttribute('width') || '0');
|
||||
const svgHeight = parseFloat(svg.getAttribute('height') || '0');
|
||||
|
||||
const dimensions = calculateDimensions(
|
||||
svgWidth,
|
||||
svgHeight,
|
||||
pageProps.dimensions,
|
||||
pageProps.margin,
|
||||
scale,
|
||||
pageProps.alignment
|
||||
);
|
||||
|
||||
for (const dim of dimensions) {
|
||||
await addSVGToPage(pdfDoc, svg, dim, pageProps.dimensions, pageProps.backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
return pdfDoc.save();
|
||||
}
|
||||
|
||||
function hexToRGB(hex: string): { r: number; g: number; b: number } {
|
||||
const ea = getEA();
|
||||
const color = ea.getCM(hex);
|
||||
if (color) {
|
||||
return { r: color.red, g: color.green, b: color.blue };
|
||||
}
|
||||
return {r: 255, g: 255, b: 255};
|
||||
}
|
||||
|
||||
// Helper function to split SVG into pages if needed
|
||||
function splitSVGIntoPages(
|
||||
svg: SVGSVGElement,
|
||||
maxWidth: number,
|
||||
maxHeight: number
|
||||
): SVGSVGElement[] {
|
||||
const width = parseFloat(svg.getAttribute('width') || '0');
|
||||
const height = parseFloat(svg.getAttribute('height') || '0');
|
||||
|
||||
if (width <= maxWidth && height <= maxHeight) {
|
||||
return [svg];
|
||||
}
|
||||
|
||||
const pages: SVGSVGElement[] = [];
|
||||
const cols = Math.ceil(width / maxWidth);
|
||||
const rows = Math.ceil(height / maxHeight);
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const viewBox = `${col * maxWidth} ${row * maxHeight} ${maxWidth} ${maxHeight}`;
|
||||
const clonedSvg = svg.cloneNode(true) as SVGSVGElement;
|
||||
clonedSvg.setAttribute('viewBox', viewBox);
|
||||
clonedSvg.setAttribute('width', String(maxWidth));
|
||||
clonedSvg.setAttribute('height', String(maxHeight));
|
||||
pages.push(clonedSvg);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
export async function exportSVGToClipboard(svg: SVGSVGElement) {
|
||||
try {
|
||||
const svgString = svg.outerHTML;
|
||||
await navigator.clipboard.writeText(svgString);
|
||||
} catch (error) {
|
||||
console.error("Failed to copy SVG to clipboard: ", error);
|
||||
}
|
||||
}
|
||||
@@ -290,17 +290,6 @@ export const blobToBase64 = async (blob: Blob): Promise<string> => {
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
export const arrayBufferToBase64 = (arrayBuffer: ArrayBuffer): string => {
|
||||
const bytes = new Uint8Array(arrayBuffer);
|
||||
let binary = '';
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
};
|
||||
|
||||
export const getPDFDoc = async (f: TFile): Promise<any> => {
|
||||
if(typeof window.pdfjsLib === "undefined") await loadPdfJs();
|
||||
return await window.pdfjsLib.getDocument(EXCALIDRAW_PLUGIN.app.vault.getResourcePath(f)).promise;
|
||||
@@ -493,22 +482,3 @@ export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:
|
||||
const fileList = getExcalidrawEmbeddedFilesFiletree(sourceFile, plugin);
|
||||
return fileList.some(f=>f.stat.mtime > mtime);
|
||||
}
|
||||
|
||||
export async function createOrOverwriteFile(app: App, path: string, content: string | ArrayBuffer): Promise<TFile> {
|
||||
const file = app.vault.getAbstractFileByPath(normalizePath(path));
|
||||
if(content instanceof ArrayBuffer) {
|
||||
if(file && file instanceof TFile) {
|
||||
await app.vault.modifyBinary(file, content);
|
||||
return file;
|
||||
} else {
|
||||
return await app.vault.createBinary(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
if (file && file instanceof TFile) {
|
||||
await app.vault.modify(file, content);
|
||||
return file;
|
||||
} else {
|
||||
return await app.vault.create(path, content);
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,7 @@ import {
|
||||
getExcalidrawMarkdownHeaderSection,
|
||||
} from "../shared/ExcalidrawData";
|
||||
import {
|
||||
arrayBufferToBase64,
|
||||
checkAndCreateFolder,
|
||||
createOrOverwriteFile,
|
||||
download,
|
||||
getDataURLFromURL,
|
||||
getIMGFilename,
|
||||
@@ -151,8 +149,6 @@ import { getPDFCropRect } from "../utils/PDFUtils";
|
||||
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";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
@@ -372,7 +368,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
|
||||
super(leaf);
|
||||
this._plugin = plugin;
|
||||
this.excalidrawData = new ExcalidrawData(plugin);
|
||||
this.excalidrawData = new ExcalidrawData(plugin, this);
|
||||
this.canvasNodeFactory = new CanvasNodeFactory(this);
|
||||
this.setHookServer();
|
||||
this.dropManager = new DropManager(this);
|
||||
@@ -519,13 +515,19 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const svg = await this.svg(scene,theme, embedScene, true);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
|
||||
const svgString = svg.outerHTML;
|
||||
await createOrOverwriteFile(this.app, filepath, svgString);
|
||||
if (file && file instanceof TFile) {
|
||||
await this.app.vault.modify(file, svgString);
|
||||
} else {
|
||||
await this.app.vault.create(filepath, svgString);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
@@ -553,83 +555,6 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
);
|
||||
}
|
||||
|
||||
public async getSVG(embedScene?: boolean, selectedOnly?: boolean):Promise<SVGSVGElement> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getSVG, "ExcalidrawView.getSVG", embedScene, selectedOnly);
|
||||
if (!this.excalidrawAPI || !this.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = await this.svg(this.getScene(selectedOnly),undefined,embedScene, true);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
public async exportPDF(
|
||||
toVault: boolean,
|
||||
selectedOnly?: boolean,
|
||||
pageSize: PageSize = "A4",
|
||||
orientation: PageOrientation = "portrait"
|
||||
): Promise<void> {
|
||||
if (!this.excalidrawAPI || !this.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = await this.svg(
|
||||
this.getScene(selectedOnly),
|
||||
undefined,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pdfArrayBuffer = await exportToPDF({
|
||||
SVG: [svg],
|
||||
scale: {
|
||||
...this.exportDialog.fitToPage
|
||||
? { fitToPage: true }
|
||||
: { zoom: this.exportDialog.scale, fitToPage: false },
|
||||
},
|
||||
pageProps: {
|
||||
dimensions: getPageDimensions(pageSize, orientation),
|
||||
backgroundColor: this.exportDialog.getPaperColor(),
|
||||
margin: getMarginValue(this.exportDialog.margin),
|
||||
alignment: this.exportDialog.alignment,
|
||||
}
|
||||
});
|
||||
|
||||
if (!pdfArrayBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(toVault) {
|
||||
const filepath = getIMGFilename(this.file.path, "pdf");
|
||||
const file = await createOrOverwriteFile(this.app, filepath, pdfArrayBuffer);
|
||||
let leaf: WorkspaceLeaf;
|
||||
this.app.workspace.getLeavesOfType("pdf").forEach((l) => {
|
||||
//@ts-ignore
|
||||
if(l.view?.file === file) {
|
||||
leaf = l;
|
||||
}
|
||||
});
|
||||
if(leaf) {
|
||||
this.app.workspace.revealLeaf(leaf);
|
||||
} else {
|
||||
this.app.workspace.getLeaf("split").openFile(file);
|
||||
}
|
||||
} else {
|
||||
download(
|
||||
"data:application/pdf;base64",
|
||||
arrayBufferToBase64(pdfArrayBuffer),
|
||||
`${this.file.basename}.pdf`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -673,11 +598,17 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const png = await this.png(scene, theme, embedScene);
|
||||
if (!png) {
|
||||
return;
|
||||
}
|
||||
await createOrOverwriteFile(this.app, filepath, await png.arrayBuffer());
|
||||
if (file && file instanceof TFile) {
|
||||
await this.app.vault.modifyBinary(file, await png.arrayBuffer());
|
||||
} else {
|
||||
await this.app.vault.createBinary(filepath, await png.arrayBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
if(this.plugin.settings.autoExportLightAndDark) {
|
||||
|
||||
30
styles.css
30
styles.css
@@ -80,11 +80,6 @@ button.ToolIcon_type_button[title="Export"] {
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.excalidraw-export-button {
|
||||
width: 9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-buttons-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -92,13 +87,6 @@ button.ToolIcon_type_button[title="Export"] {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.excalidraw-export-buttons-div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
li[data-testid] {
|
||||
border: 0 !important;
|
||||
margin: 0 !important;
|
||||
@@ -684,21 +672,3 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
background-color: var(--background-modifier-hover);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.excalidraw-release .nav-buttons-container {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.excalidraw-release .nav-button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.excalidraw-release .nav-button.is-active {
|
||||
border-bottom: 2px solid var(--interactive-accent);
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
Reference in New Issue
Block a user