mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
embed Excalidraw into document
This commit is contained in:
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "1.1.10",
|
||||
"version": "1.3.15",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
@@ -11,7 +11,7 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zsviczian/excalidraw": "0.9.0-obsidian-image-support-3",
|
||||
"@zsviczian/excalidraw": "0.9.0-obsidian-image-support-5",
|
||||
"monkey-around": "^2.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
@@ -19,22 +19,22 @@
|
||||
"roughjs": "4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/core": "^7.15.5",
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@rollup/plugin-commonjs": "^15.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-commonjs": "^21.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
||||
"@rollup/plugin-replace": "^2.4.2",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@types/node": "^15.12.4",
|
||||
"@types/react-dom": "^17.0.8",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"cross-env": "^7.0.3",
|
||||
"nanoid": "^3.1.23",
|
||||
"obsidian": "^0.12.16",
|
||||
"rollup": "^2.52.3",
|
||||
"rollup-plugin-visualizer": "^5.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"typescript": "^4.3.4"
|
||||
"rollup-plugin-visualizer": "^5.5.2",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export interface ExcalidrawAutomate extends Window {
|
||||
}
|
||||
}
|
||||
):Promise<string>;
|
||||
createSVG (templatePath?:string):Promise<SVGSVGElement>;
|
||||
createSVG (templatePath?:string, embedFont?:boolean):Promise<SVGSVGElement>;
|
||||
createPNG (templatePath?:string):Promise<any>;
|
||||
wrapText (text:string, lineLen:number):string;
|
||||
addRect (topX:number, topY:number, width:number, height:number):string;
|
||||
@@ -312,47 +312,50 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
source: "https://excalidraw.com",
|
||||
elements: elements,
|
||||
appState: {
|
||||
theme: template ? template.appState.theme : this.canvas.theme,
|
||||
viewBackgroundColor: template? template.appState.viewBackgroundColor : this.canvas.viewBackgroundColor,
|
||||
currentItemStrokeColor: template? template.appState.currentItemStrokeColor : this.style.strokeColor,
|
||||
currentItemBackgroundColor: template? template.appState.currentItemBackgroundColor : this.style.backgroundColor,
|
||||
currentItemFillStyle: template? template.appState.currentItemFillStyle : this.style.fillStyle,
|
||||
currentItemStrokeWidth: template? template.appState.currentItemStrokeWidth : this.style.strokeWidth,
|
||||
currentItemStrokeStyle: template? template.appState.currentItemStrokeStyle : this.style.strokeStyle,
|
||||
currentItemRoughness: template? template.appState.currentItemRoughness : this.style.roughness,
|
||||
currentItemOpacity: template? template.appState.currentItemOpacity : this.style.opacity,
|
||||
currentItemFontFamily: template? template.appState.currentItemFontFamily : this.style.fontFamily,
|
||||
currentItemFontSize: template? template.appState.currentItemFontSize : this.style.fontSize,
|
||||
currentItemTextAlign: template? template.appState.currentItemTextAlign : this.style.textAlign,
|
||||
currentItemStrokeSharpness: template? template.appState.currentItemStrokeSharpness : this.style.strokeSharpness,
|
||||
currentItemStartArrowhead: template? template.appState.currentItemStartArrowhead: this.style.startArrowHead,
|
||||
currentItemEndArrowhead: template? template.appState.currentItemEndArrowhead : this.style.endArrowHead,
|
||||
currentItemLinearStrokeSharpness: template? template.appState.currentItemLinearStrokeSharpness : this.style.strokeSharpness,
|
||||
gridSize: template ? template.appState.gridSize : this.canvas.gridSize
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
|
||||
currentItemStrokeColor: template?.appState?.currentItemStrokeColor ?? this.style.strokeColor,
|
||||
currentItemBackgroundColor: template?.appState?.currentItemBackgroundColor ?? this.style.backgroundColor,
|
||||
currentItemFillStyle: template?.appState?.currentItemFillStyle ?? this.style.fillStyle,
|
||||
currentItemStrokeWidth: template?.appState?.currentItemStrokeWidth ?? this.style.strokeWidth,
|
||||
currentItemStrokeStyle: template?.appState?.currentItemStrokeStyle ?? this.style.strokeStyle,
|
||||
currentItemRoughness: template?.appState?.currentItemRoughness ?? this.style.roughness,
|
||||
currentItemOpacity: template?.appState?.currentItemOpacity ?? this.style.opacity,
|
||||
currentItemFontFamily: template?.appState?.currentItemFontFamily ?? this.style.fontFamily,
|
||||
currentItemFontSize: template?.appState?.currentItemFontSize ?? this.style.fontSize,
|
||||
currentItemTextAlign: template?.appState?.currentItemTextAlign ?? this.style.textAlign,
|
||||
currentItemStrokeSharpness: template?.appState?.currentItemStrokeSharpness ?? this.style.strokeSharpness,
|
||||
currentItemStartArrowhead: template?.appState?.currentItemStartArrowhead ?? this.style.startArrowHead,
|
||||
currentItemEndArrowhead: template?.appState?.currentItemEndArrowhead ?? this.style.endArrowHead,
|
||||
currentItemLinearStrokeSharpness: template?.appState?.currentItemLinearStrokeSharpness ?? this.style.strokeSharpness,
|
||||
gridSize: template?.appState?.gridSize ?? this.canvas.gridSize,
|
||||
files: template?.appState?.files ?? {},
|
||||
}
|
||||
}))
|
||||
);
|
||||
},
|
||||
async createSVG(templatePath?:string):Promise<SVGSVGElement> {
|
||||
async createSVG(templatePath?:string,embedFont:boolean = false):Promise<SVGSVGElement> {
|
||||
const template = templatePath ? (await getTemplate(templatePath)) : null;
|
||||
let elements = template ? template.elements : [];
|
||||
elements = elements.concat(this.getElements());
|
||||
return await ExcalidrawView.getSVG(
|
||||
const svg = await ExcalidrawView.getSVG(
|
||||
{//createDrawing
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": elements,
|
||||
"appState": {
|
||||
"theme": template ? template.appState.theme : this.canvas.theme,
|
||||
"viewBackgroundColor": template? template.appState.viewBackgroundColor : this.canvas.viewBackgroundColor
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
elements: elements,
|
||||
appState: {
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
|
||||
files: template?.appState?.files ?? {}
|
||||
}
|
||||
},//),
|
||||
},
|
||||
{
|
||||
withBackground: plugin.settings.exportWithBackground,
|
||||
withTheme: plugin.settings.exportWithTheme
|
||||
}
|
||||
)
|
||||
)
|
||||
return embedFont ? ExcalidrawView.embedFontsInSVG(svg) : svg;
|
||||
},
|
||||
async createPNG(templatePath?:string, scale:number=1) {
|
||||
const template = templatePath ? (await getTemplate(templatePath)) : null;
|
||||
@@ -360,13 +363,14 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
elements = elements.concat(this.getElements());
|
||||
return ExcalidrawView.getPNG(
|
||||
{
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": elements,
|
||||
"appState": {
|
||||
"theme": template ? template.appState.theme : this.canvas.theme,
|
||||
"viewBackgroundColor": template? template.appState.viewBackgroundColor : this.canvas.viewBackgroundColor
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: "https://excalidraw.com",
|
||||
elements: elements,
|
||||
appState: {
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
|
||||
files: template?.appState?.files ?? {}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -136,6 +136,9 @@ export class ExcalidrawData {
|
||||
}
|
||||
position += data.match(/((^%%\n)?# Text Elements\n)/m)[0].length
|
||||
|
||||
data = data.substring(position);
|
||||
position = 0;
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN:number = " ^12345678\n\n".length;
|
||||
|
||||
@@ -32,10 +32,10 @@ import {
|
||||
IMAGE_TYPES
|
||||
} from './constants';
|
||||
import ExcalidrawPlugin from './main';
|
||||
import {estimateBounds, ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
|
||||
import {ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
|
||||
import { t } from "./lang/helpers";
|
||||
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
|
||||
import { checkAndCreateFolder, download, getNewUniqueFilepath, splitFolderAndFilename, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { checkAndCreateFolder, download, getNewUniqueFilepath, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { Prompt } from "./Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
|
||||
@@ -556,9 +556,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
let svg = await ExcalidrawView.getSVG(this.getScene(),exportSettings);
|
||||
if(!svg) return null;
|
||||
svg = ExcalidrawView.embedFontsInSVG(svg);
|
||||
download("data:image/svg+xml;base64",btoa(unescape(encodeURIComponent(svg.outerHTML))),this.file.basename+'.svg');
|
||||
download(null,svgToBase64(svg.outerHTML),this.file.basename+'.svg');
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.saveSVG()
|
||||
});
|
||||
})
|
||||
@@ -783,10 +783,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
key: "abc",
|
||||
tabIndex: 0,
|
||||
onKeyDown: (e:any) => {
|
||||
//@ts-ignore
|
||||
if(e.target === excalidrawDiv.ref.current) return; //event should originate from the canvas
|
||||
if(document.fullscreenEnabled && document.fullscreenElement == this.contentEl && e.keyCode==27) {
|
||||
document.exitFullscreen();
|
||||
this.zoomToFit();
|
||||
}
|
||||
|
||||
this.ctrlKeyDown = e.ctrlKey || e.metaKey;
|
||||
this.shiftKeyDown = e.shiftKey;
|
||||
this.altKeyDown = e.altKey;
|
||||
@@ -975,7 +978,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
switch(draggable?.type) {
|
||||
case "file":
|
||||
if (!onDropHook("file",[draggable.file],null)) {
|
||||
if((event.ctrlKey || event.metaKey) && IMAGE_TYPES.contains(draggable.file.extension)) {
|
||||
if((event.ctrlKey || event.metaKey)
|
||||
&& (IMAGE_TYPES.contains(draggable.file.extension)
|
||||
|| this.plugin.isExcalidrawFile(draggable.file))) {
|
||||
const f = draggable.file;
|
||||
const topX = currentPosition.x;
|
||||
const topY = currentPosition.y;
|
||||
|
||||
@@ -21,7 +21,7 @@ export class MigrationPrompt extends Modal {
|
||||
|
||||
createForm(): void {
|
||||
const div = this.contentEl.createDiv();
|
||||
div.addClass("excalidarw-prompt-div");
|
||||
div.addClass("excalidraw-prompt-div");
|
||||
div.style.maxWidth = "600px";
|
||||
div.createEl('p',{text: "This version comes with tons of new features and possibilities. Please read the description in Community Plugins to find out more."});
|
||||
div.createEl('p',{text: ""} , (el) => {
|
||||
|
||||
25
src/Utils.ts
25
src/Utils.ts
@@ -3,7 +3,8 @@ import { Random } from "roughjs/bin/math";
|
||||
import { Zoom } from "@zsviczian/excalidraw/types/types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { IMAGE_TYPES } from "./constants";
|
||||
|
||||
import {ExcalidrawAutomate} from './ExcalidrawAutomate';
|
||||
declare let window: ExcalidrawAutomate;
|
||||
|
||||
/**
|
||||
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
|
||||
@@ -134,20 +135,30 @@ export const getObsidianImage = async (app: App, file: TFile)
|
||||
size: {height: number, width: number},
|
||||
}> => {
|
||||
if(!app || !file) return null;
|
||||
if (!IMAGE_TYPES.contains(file.extension)) return null;
|
||||
const isExcalidrawFile = window.ExcalidrawAutomate.isExcalidrawFile(file);
|
||||
if (!(IMAGE_TYPES.contains(file.extension) || isExcalidrawFile)) {
|
||||
return null;
|
||||
}
|
||||
const ab = await app.vault.readBinary(file);
|
||||
const excalidrawSVG = isExcalidrawFile
|
||||
? svgToBase64((await window.ExcalidrawAutomate.createSVG(file.path,true)).outerHTML)
|
||||
: null;
|
||||
return {
|
||||
imageId: await generateIdFromFile(ab),
|
||||
dataURL: file.extension==="svg" ? await getSVGData(app,file) : await getDataURL(ab),
|
||||
size: await getImageSize(app,file)
|
||||
dataURL: excalidrawSVG ?? (file.extension==="svg" ? await getSVGData(app,file) : await getDataURL(ab)),
|
||||
size: await getImageSize(app,excalidrawSVG??app.vault.getResourcePath(file))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getSVGData = async (app: App, file: TFile): Promise<string> => {
|
||||
const svg = await app.vault.read(file);
|
||||
return "data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.replaceAll(" "," "))))
|
||||
return svgToBase64(svg);
|
||||
}
|
||||
|
||||
export const svgToBase64 = (svg:string):string => {
|
||||
return "data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.replaceAll(" "," "))));
|
||||
}
|
||||
const getDataURL = async (file: ArrayBuffer): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -180,11 +191,11 @@ const generateIdFromFile = async (file: ArrayBuffer):Promise<string> => {
|
||||
return id;
|
||||
};
|
||||
|
||||
const getImageSize = async (app: App, file:TFile):Promise<{height:number, width:number}> => {
|
||||
const getImageSize = async (app: App, src:string):Promise<{height:number, width:number}> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let img = new Image()
|
||||
img.onload = () => resolve({height: img.height, width:img.width});
|
||||
img.onerror = reject;
|
||||
img.src = app.vault.getResourcePath(file);
|
||||
img.src = src;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ import { Prompt } from "./Prompt";
|
||||
import { around } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import { MigrationPrompt } from "./MigrationPrompt";
|
||||
import { checkAndCreateFolder, download, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, splitFolderAndFilename } from "./Utils";
|
||||
import { checkAndCreateFolder, download, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, splitFolderAndFilename, svgToBase64 } from "./Utils";
|
||||
|
||||
declare module "obsidian" {
|
||||
interface App {
|
||||
@@ -239,7 +239,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
svg = ExcalidrawView.embedFontsInSVG(svg);
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
img.setAttribute("src","data:image/svg+xml;base64,"+btoa(unescape(encodeURIComponent(svg.outerHTML.replaceAll(" "," ")))));
|
||||
img.setAttribute("src",svgToBase64(svg.outerHTML));
|
||||
return img;
|
||||
}
|
||||
|
||||
@@ -1179,3 +1179,4 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user