mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-28 18:42:28 +08:00
Merge branch 'develop' into theme
This commit is contained in:
commit
d665a4c611
22
src/public/app/services/utils.spec.ts
Normal file
22
src/public/app/services/utils.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { getSizeFromSvg } from "./utils.js";
|
||||||
|
|
||||||
|
describe("getSizeFromSvg", () => {
|
||||||
|
it("parses width & height attribute", () => {
|
||||||
|
const svg = `<svg aria-roledescription="sequence" role="graphics-document document" viewBox="-50 -10 714 574" height="574" xmlns="http://www.w3.org/2000/svg" width="714" id="mermaid-graph-2"></svg>`;
|
||||||
|
const result = getSizeFromSvg(svg);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
width: 714,
|
||||||
|
height: 574,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses viewbox", () => {
|
||||||
|
const svg = `<svg aria-roledescription="er" role="graphics-document document" viewBox="0 0 872.2750244140625 655" style="max-width: 872.2750244140625px;" class="erDiagram" xmlns="http://www.w3.org/2000/svg" width="100%" id="mermaid-graph-2">`;
|
||||||
|
const result = getSizeFromSvg(svg);
|
||||||
|
expect(result).toMatchObject({
|
||||||
|
width: 872.2750244140625,
|
||||||
|
height: 655
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2,6 +2,8 @@ import dayjs from "dayjs";
|
|||||||
import { Modal } from "bootstrap";
|
import { Modal } from "bootstrap";
|
||||||
import type { ViewScope } from "./link.js";
|
import type { ViewScope } from "./link.js";
|
||||||
|
|
||||||
|
const SVG_MIME = "image/svg+xml";
|
||||||
|
|
||||||
function reloadFrontendApp(reason?: string) {
|
function reloadFrontendApp(reason?: string) {
|
||||||
if (reason) {
|
if (reason) {
|
||||||
logInfo(`Frontend app reload: ${reason}`);
|
logInfo(`Frontend app reload: ${reason}`);
|
||||||
@ -650,31 +652,27 @@ function triggerDownload(fileName: string, dataUrl: string) {
|
|||||||
*
|
*
|
||||||
* @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
|
* @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
|
||||||
* @param svgContent the content of the SVG file download.
|
* @param svgContent the content of the SVG file download.
|
||||||
* @returns `true` if the operation succeeded (width/height present), or `false` if the download was not triggered.
|
* @returns a promise which resolves if the operation was successful, or rejects if it failed (permissions issue or some other issue).
|
||||||
*/
|
*/
|
||||||
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
|
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
|
||||||
const mime = "image/svg+xml";
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
|
||||||
// First, we need to determine the width and the height from the input SVG.
|
// First, we need to determine the width and the height from the input SVG.
|
||||||
const svgDocument = (new DOMParser()).parseFromString(svgContent, mime);
|
const result = getSizeFromSvg(svgContent);
|
||||||
const width = svgDocument.documentElement?.getAttribute("width");
|
if (!result) {
|
||||||
const height = svgDocument.documentElement?.getAttribute("height");
|
reject();
|
||||||
|
return;
|
||||||
if (!width || !height) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the image to a blob.
|
// Convert the image to a blob.
|
||||||
const svgBlob = new Blob([ svgContent ], {
|
const { width, height } = result;
|
||||||
type: mime
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create an image element and load the SVG.
|
// Create an image element and load the SVG.
|
||||||
const imageEl = new Image();
|
const imageEl = new Image();
|
||||||
imageEl.width = parseFloat(width);
|
imageEl.width = width;
|
||||||
imageEl.height = parseFloat(height);
|
imageEl.height = height;
|
||||||
imageEl.src = URL.createObjectURL(svgBlob);
|
imageEl.crossOrigin = "anonymous";
|
||||||
imageEl.onload = () => {
|
imageEl.onload = () => {
|
||||||
|
try {
|
||||||
// Draw the image with a canvas.
|
// Draw the image with a canvas.
|
||||||
const canvasEl = document.createElement("canvas");
|
const canvasEl = document.createElement("canvas");
|
||||||
canvasEl.width = imageEl.width;
|
canvasEl.width = imageEl.width;
|
||||||
@ -682,15 +680,52 @@ function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
|
|||||||
document.body.appendChild(canvasEl);
|
document.body.appendChild(canvasEl);
|
||||||
|
|
||||||
const ctx = canvasEl.getContext("2d");
|
const ctx = canvasEl.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
ctx?.drawImage(imageEl, 0, 0);
|
ctx?.drawImage(imageEl, 0, 0);
|
||||||
URL.revokeObjectURL(imageEl.src);
|
|
||||||
|
|
||||||
const imgUri = canvasEl.toDataURL("image/png")
|
const imgUri = canvasEl.toDataURL("image/png")
|
||||||
triggerDownload(`${nameWithoutExtension}.png`, imgUri);
|
triggerDownload(`${nameWithoutExtension}.png`, imgUri);
|
||||||
document.body.removeChild(canvasEl);
|
document.body.removeChild(canvasEl);
|
||||||
|
resolve();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
reject();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
imageEl.onerror = (e) => reject(e);
|
||||||
|
imageEl.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
export function getSizeFromSvg(svgContent: string) {
|
||||||
|
const svgDocument = (new DOMParser()).parseFromString(svgContent, SVG_MIME);
|
||||||
|
|
||||||
|
// Try to use width & height attributes if available.
|
||||||
|
let width = svgDocument.documentElement?.getAttribute("width");
|
||||||
|
let height = svgDocument.documentElement?.getAttribute("height");
|
||||||
|
|
||||||
|
// If not, use the viewbox.
|
||||||
|
if (!width || !height) {
|
||||||
|
const viewBox = svgDocument.documentElement?.getAttribute("viewBox");
|
||||||
|
if (viewBox) {
|
||||||
|
const viewBoxParts = viewBox.split(" ");
|
||||||
|
width = viewBoxParts[2];
|
||||||
|
height = viewBoxParts[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width && height) {
|
||||||
|
return {
|
||||||
|
width: parseFloat(width),
|
||||||
|
height: parseFloat(height)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("SVG export error", svgDocument.documentElement);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,7 +2,9 @@ import type { EventData } from "../../components/app_context.js";
|
|||||||
import type FNote from "../../entities/fnote.js";
|
import type FNote from "../../entities/fnote.js";
|
||||||
import { t } from "../../services/i18n.js";
|
import { t } from "../../services/i18n.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
|
import toast from "../../services/toast.js";
|
||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
|
import ws from "../../services/ws.js";
|
||||||
import OnClickButtonWidget from "../buttons/onclick_button.js";
|
import OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||||
import AbstractSplitTypeWidget from "./abstract_split_type_widget.js";
|
import AbstractSplitTypeWidget from "./abstract_split_type_widget.js";
|
||||||
|
|
||||||
@ -218,11 +220,18 @@ export default abstract class AbstractSvgSplitTypeWidget extends AbstractSplitTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
async exportPngEvent({ ntxId }: EventData<"exportPng">) {
|
async exportPngEvent({ ntxId }: EventData<"exportPng">) {
|
||||||
|
console.log("Export to PNG", this.noteContext?.noteId, ntxId, this.svg);
|
||||||
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
|
if (!this.isNoteContext(ntxId) || this.note?.type !== "mermaid" || !this.svg) {
|
||||||
|
console.log("Return");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.downloadSvgAsPng(this.note.title, this.svg);
|
try {
|
||||||
|
await utils.downloadSvgAsPng(this.note.title, this.svg);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
toast.showError(t("svg.export_to_png"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1746,5 +1746,8 @@
|
|||||||
},
|
},
|
||||||
"png_export_button": {
|
"png_export_button": {
|
||||||
"button_title": "Export diagram as PNG"
|
"button_title": "Export diagram as PNG"
|
||||||
|
},
|
||||||
|
"svg": {
|
||||||
|
"export_to_png": "The diagram could not be exported to PNG."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user