mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
21 Commits
1.9.28
...
2.0.1-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a69fefffdc | ||
|
|
1d0466dae7 | ||
|
|
6e5a853d0f | ||
|
|
0c702ddf7b | ||
|
|
fdbffce1f9 | ||
|
|
2872b4e3ce | ||
|
|
0ba55e51e9 | ||
|
|
5887bf377d | ||
|
|
c440dd9cf0 | ||
|
|
21bc1f7fa6 | ||
|
|
4279b13554 | ||
|
|
0e106b7c7b | ||
|
|
4d7d1fba3a | ||
|
|
a35ea5e9da | ||
|
|
48466f624d | ||
|
|
f306b20449 | ||
|
|
fc4fd685ba | ||
|
|
7449df6ac6 | ||
|
|
1390333c4c | ||
|
|
a9f545a1b2 | ||
|
|
f291c15bbc |
772
docs/API/ExcalidrawAutomate.d.ts
vendored
Normal file
772
docs/API/ExcalidrawAutomate.d.ts
vendored
Normal file
@@ -0,0 +1,772 @@
|
||||
/// <reference types="react" />
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
import { PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
export declare class ExcalidrawAutomate {
|
||||
/**
|
||||
* Utility function that returns the Obsidian Module object.
|
||||
*/
|
||||
get obsidian(): typeof obsidian_module;
|
||||
get DEVICE(): DeviceType;
|
||||
getAttachmentFilepath(filename: string): Promise<string>;
|
||||
/**
|
||||
* Prompts the user with a dialog to select new file action.
|
||||
* - create markdown file
|
||||
* - create excalidraw file
|
||||
* - cancel action
|
||||
* The new file will be relative to this.targetView.file.path, unless parentFile is provided.
|
||||
* If shouldOpenNewFile is true, the new file will be opened in a workspace leaf.
|
||||
* targetPane control which leaf will be used for the new file.
|
||||
* Returns the TFile for the new file or null if the user cancelled the action.
|
||||
* @param newFileNameOrPath
|
||||
* @param shouldOpenNewFile
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @param parentFile
|
||||
* @returns
|
||||
*/
|
||||
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
|
||||
/**
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if avaialble, etc.
|
||||
* @param origo // the currently active leaf, the origin of the new leaf
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @returns
|
||||
*/
|
||||
getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;
|
||||
/**
|
||||
* Returns the editor or leaf.view of the currently active embedded obsidian file.
|
||||
* If view is not provided, ea.targetView is used.
|
||||
* If the embedded file is a markdown document the function will return
|
||||
* {file:TFile, editor:Editor} otherwise it will return {view:any}. You can check view type with view.getViewType();
|
||||
* @param view
|
||||
* @returns
|
||||
*/
|
||||
getActiveEmbeddableViewOrEditor(view?: ExcalidrawView): {
|
||||
view: any;
|
||||
} | {
|
||||
file: TFile;
|
||||
editor: Editor;
|
||||
} | null;
|
||||
plugin: ExcalidrawPlugin;
|
||||
elementsDict: {
|
||||
[key: string]: any;
|
||||
};
|
||||
imagesDict: {
|
||||
[key: FileId]: any;
|
||||
};
|
||||
mostRecentMarkdownSVG: SVGSVGElement;
|
||||
style: {
|
||||
strokeColor: string;
|
||||
backgroundColor: string;
|
||||
angle: number;
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
strokeStyle: StrokeStyle;
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness?: StrokeRoundness;
|
||||
roundness: null | {
|
||||
type: RoundnessType;
|
||||
value?: number;
|
||||
};
|
||||
fontFamily: number;
|
||||
fontSize: number;
|
||||
textAlign: string;
|
||||
verticalAlign: string;
|
||||
startArrowHead: string;
|
||||
endArrowHead: string;
|
||||
};
|
||||
canvas: {
|
||||
theme: string;
|
||||
viewBackgroundColor: string;
|
||||
gridSize: number;
|
||||
};
|
||||
colorPalette: {};
|
||||
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
|
||||
/**
|
||||
*
|
||||
* @returns the last recorded pointer position on the Excalidraw canvas
|
||||
*/
|
||||
getViewLastPointerPosition(): {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getAPI(view?: ExcalidrawView): ExcalidrawAutomate;
|
||||
/**
|
||||
* @param val //0:"hachure", 1:"cross-hatch" 2:"solid"
|
||||
* @returns
|
||||
*/
|
||||
setFillStyle(val: number): "hachure" | "cross-hatch" | "solid";
|
||||
/**
|
||||
* @param val //0:"solid", 1:"dashed", 2:"dotted"
|
||||
* @returns
|
||||
*/
|
||||
setStrokeStyle(val: number): "solid" | "dashed" | "dotted";
|
||||
/**
|
||||
* @param val //0:"round", 1:"sharp"
|
||||
* @returns
|
||||
*/
|
||||
setStrokeSharpness(val: number): "round" | "sharp";
|
||||
/**
|
||||
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
|
||||
* @returns
|
||||
*/
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
|
||||
/**
|
||||
* @param val //0:"light", 1:"dark"
|
||||
* @returns
|
||||
*/
|
||||
setTheme(val: number): "light" | "dark";
|
||||
/**
|
||||
* @param objectIds
|
||||
* @returns
|
||||
*/
|
||||
addToGroup(objectIds: string[]): string;
|
||||
/**
|
||||
* @param templatePath
|
||||
*/
|
||||
toClipboard(templatePath?: string): Promise<void>;
|
||||
/**
|
||||
* @param file: TFile
|
||||
* @returns ExcalidrawScene
|
||||
*/
|
||||
getSceneFromFile(file: TFile): Promise<{
|
||||
elements: ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
}>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
*/
|
||||
getElements(): ExcalidrawElement[];
|
||||
/**
|
||||
* get single element from ExcalidrawAutomate elementsDict
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
getElement(id: string): ExcalidrawElement;
|
||||
/**
|
||||
* create a drawing and save it to filename
|
||||
* @param params
|
||||
* filename: if null, default filename as defined in Excalidraw settings
|
||||
* foldername: if null, default folder as defined in Excalidraw settings
|
||||
* @returns
|
||||
*/
|
||||
create(params?: {
|
||||
filename?: string;
|
||||
foldername?: string;
|
||||
templatePath?: string;
|
||||
onNewPane?: boolean;
|
||||
frontmatterKeys?: {
|
||||
"excalidraw-plugin"?: "raw" | "parsed";
|
||||
"excalidraw-link-prefix"?: string;
|
||||
"excalidraw-link-brackets"?: boolean;
|
||||
"excalidraw-url-prefix"?: string;
|
||||
"excalidraw-export-transparent"?: boolean;
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
"excalidraw-autoexport"?: boolean;
|
||||
};
|
||||
plaintext?: string;
|
||||
}): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
* @param embedFont
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
createSVG(templatePath?: string, embedFont?: boolean, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<SVGSVGElement>;
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
* @param scale
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
* @param loader use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
|
||||
* @param theme
|
||||
* @returns
|
||||
*/
|
||||
createPNG(templatePath?: string, scale?: number, exportSettings?: ExportSettings, loader?: EmbeddedFilesLoader, theme?: string, padding?: number): Promise<any>;
|
||||
/**
|
||||
*
|
||||
* @param text
|
||||
* @param lineLen
|
||||
* @returns
|
||||
*/
|
||||
wrapText(text: string, lineLen: number): string;
|
||||
private boxedElement;
|
||||
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addRect(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addDiamond(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addEllipse(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addBlob(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
* Refresh the size of a text element to fit its contents
|
||||
* @param id - the id of the text element
|
||||
*/
|
||||
refreshTextElementSize(id: string): void;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param text
|
||||
* @param formatting
|
||||
* box: if !null, text will be boxed
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
addText(topX: number, topY: number, text: string, formatting?: {
|
||||
wrapAt?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
boxStrokeColor?: string;
|
||||
textVerticalAlign?: "top" | "middle" | "bottom";
|
||||
}, id?: string): string;
|
||||
/**
|
||||
*
|
||||
* @param points
|
||||
* @returns
|
||||
*/
|
||||
addLine(points: [[x: number, y: number]]): string;
|
||||
/**
|
||||
*
|
||||
* @param points
|
||||
* @param formatting
|
||||
* @returns
|
||||
*/
|
||||
addArrow(points: [x: number, y: number][], formatting?: {
|
||||
startArrowHead?: string;
|
||||
endArrowHead?: string;
|
||||
startObjectId?: string;
|
||||
endObjectId?: string;
|
||||
}): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param imageFile
|
||||
* @returns
|
||||
*/
|
||||
addImage(topX: number, topY: number, imageFile: TFile | string, scale?: boolean, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
|
||||
anchor?: boolean): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param tex
|
||||
* @returns
|
||||
*/
|
||||
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param objectA
|
||||
* @param connectionA type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
|
||||
* @param objectB
|
||||
* @param connectionB when passed null, Excalidraw will automatically decide
|
||||
* @param formatting
|
||||
* numberOfPoints: points on the line. Default is 0 ie. line will only have a start and end point
|
||||
* startArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
|
||||
* endArrowHead: "triangle"|"dot"|"arrow"|"bar"|null
|
||||
* padding:
|
||||
* @returns
|
||||
*/
|
||||
connectObjects(objectA: string, connectionA: ConnectionPoint | null, objectB: string, connectionB: ConnectionPoint | null, formatting?: {
|
||||
numberOfPoints?: number;
|
||||
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
padding?: number;
|
||||
}): string;
|
||||
/**
|
||||
* Adds a text label to a line or arrow. Currently only works with a straight (2 point - start & end - line)
|
||||
* @param lineId id of the line or arrow object in elementsDict
|
||||
* @param label the label text
|
||||
* @returns undefined (if unsuccessful) or the id of the new text element
|
||||
*/
|
||||
addLabelToLine(lineId: string, label: string): string;
|
||||
/**
|
||||
* clear elementsDict and imagesDict only
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* clear() + reset all style values to default
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* returns true if MD file is an Excalidraw file
|
||||
* @param f
|
||||
* @returns
|
||||
*/
|
||||
isExcalidrawFile(f: TFile): boolean;
|
||||
targetView: ExcalidrawView;
|
||||
/**
|
||||
* sets the target view for EA. All the view operations and the access to Excalidraw API will be performend on this view
|
||||
* if view is null or undefined, the function will first try setView("active"), then setView("first").
|
||||
* @param view
|
||||
* @returns targetView
|
||||
*/
|
||||
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView;
|
||||
/**
|
||||
*
|
||||
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
|
||||
*/
|
||||
getExcalidrawAPI(): any;
|
||||
/**
|
||||
* get elements in View
|
||||
* @returns
|
||||
*/
|
||||
getViewElements(): ExcalidrawElement[];
|
||||
/**
|
||||
*
|
||||
* @param elToDelete
|
||||
* @returns
|
||||
*/
|
||||
deleteViewElements(elToDelete: ExcalidrawElement[]): boolean;
|
||||
/**
|
||||
* get the selected element in the view, if more are selected, get the first
|
||||
* @returns
|
||||
*/
|
||||
getViewSelectedElement(): any;
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getViewSelectedElements(): any[];
|
||||
/**
|
||||
*
|
||||
* @param el
|
||||
* @returns TFile file handle for the image element
|
||||
*/
|
||||
getViewFileForImageElement(el: ExcalidrawElement): TFile | null;
|
||||
/**
|
||||
* copies elements from view to elementsDict for editing
|
||||
* @param elements
|
||||
*/
|
||||
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void;
|
||||
/**
|
||||
*
|
||||
* @param forceViewMode
|
||||
* @returns
|
||||
*/
|
||||
viewToggleFullScreen(forceViewMode?: boolean): void;
|
||||
setViewModeEnabled(enabled: boolean): void;
|
||||
/**
|
||||
* This function gives you a more hands on access to Excalidraw.
|
||||
* @param scene - The scene you want to load to Excalidraw
|
||||
* @param restore - Use this if the scene includes legacy excalidraw file elements that need to be converted to the latest excalidraw data format (not a typical usecase)
|
||||
* @returns
|
||||
*/
|
||||
viewUpdateScene(scene: {
|
||||
elements?: ExcalidrawElement[];
|
||||
appState?: AppState;
|
||||
files?: BinaryFileData;
|
||||
commitToHistory?: boolean;
|
||||
}, restore?: boolean): void;
|
||||
/**
|
||||
* connect an object to the selected element in the view
|
||||
* @param objectA ID of the element
|
||||
* @param connectionA
|
||||
* @param connectionB
|
||||
* @param formatting
|
||||
* @returns
|
||||
*/
|
||||
connectObjectWithViewSelectedElement(objectA: string, connectionA: ConnectionPoint | null, connectionB: ConnectionPoint | null, formatting?: {
|
||||
numberOfPoints?: number;
|
||||
startArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
padding?: number;
|
||||
}): boolean;
|
||||
/**
|
||||
* zoom tarteView to fit elements provided as input
|
||||
* elements === [] will zoom to fit the entire scene
|
||||
* selectElements toggles whether the elements should be in a selected state at the end of the operation
|
||||
* @param selectElements
|
||||
* @param elements
|
||||
*/
|
||||
viewZoomToElements(selectElements: boolean, elements: ExcalidrawElement[]): void;
|
||||
/**
|
||||
* Adds elements from elementsDict to the current view
|
||||
* @param repositionToCursor default is false
|
||||
* @param save default is true
|
||||
* @param newElementsOnTop controls whether elements created with ExcalidrawAutomate
|
||||
* are added at the bottom of the stack or the top of the stack of elements already in the view
|
||||
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
|
||||
* position in the stack of elements in the view even if modified using EA
|
||||
* default is false, i.e. the new elements get to the bottom of the stack
|
||||
* @param shouldRestoreElements - restore elements - auto-corrects broken, incomplete or old elements included in the update
|
||||
* @returns
|
||||
*/
|
||||
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean, shouldRestoreElements?: boolean): Promise<boolean>;
|
||||
/**
|
||||
* Register instance of EA to use for hooks with TargetView
|
||||
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
|
||||
* Using this event you can set a different instance of Excalidraw Automate for hooks
|
||||
* @returns true if successful
|
||||
*/
|
||||
registerThisAsViewEA(): boolean;
|
||||
/**
|
||||
* Sets the targetView EA to window.ExcalidrawAutomate
|
||||
* @returns true if successful
|
||||
*/
|
||||
deregisterThisAsViewEA(): boolean;
|
||||
/**
|
||||
* If set, this callback is triggered when the user closes an Excalidraw view.
|
||||
*/
|
||||
onViewUnloadHook: (view: ExcalidrawView) => void;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user changes the view mode.
|
||||
* You can use this callback in case you want to do something additional when the user switches to view mode and back.
|
||||
*/
|
||||
onViewModeChangeHook: (isViewModeEnabled: boolean, view: ExcalidrawView, ea: ExcalidrawAutomate) => void;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user hovers a link in the scene.
|
||||
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
|
||||
*/
|
||||
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when the user clicks a link in the scene.
|
||||
* You can use this callback in case you want to do something additional when the onLinkClick event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onLinkClick action you must return false, it will stop the native excalidraw onLinkClick management flow.
|
||||
*/
|
||||
onLinkClickHook: (element: ExcalidrawElement, linkText: string, event: MouseEvent, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when Excalidraw receives an onDrop event.
|
||||
* You can use this callback in case you want to do something additional when the onDrop event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onDrop action you must return false, it will stop the native excalidraw onDrop management flow.
|
||||
*/
|
||||
onDropHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
event: React.DragEvent<HTMLDivElement>;
|
||||
draggable: any;
|
||||
type: "file" | "text" | "unknown";
|
||||
payload: {
|
||||
files: TFile[];
|
||||
text: string;
|
||||
};
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
pointerPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when Excalidraw receives an onPaste event.
|
||||
* You can use this callback in case you want to do something additional when the
|
||||
* onPaste event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onPaste action you must return false,
|
||||
* it will stop the native excalidraw onPaste management flow.
|
||||
*/
|
||||
onPasteHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
payload: ClipboardData;
|
||||
event: ClipboardEvent;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
pointerPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}) => boolean;
|
||||
/**
|
||||
* 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.
|
||||
* This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
|
||||
*/
|
||||
onFileOpenHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
}) => Promise<void>;
|
||||
/**
|
||||
* if set, this callback is triggered, when an Excalidraw file is created
|
||||
* see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
|
||||
*/
|
||||
onFileCreateHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
}) => Promise<void>;
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
*/
|
||||
onCanvasColorChangeHook: (ea: ExcalidrawAutomate, view: ExcalidrawView, //the excalidraw view
|
||||
color: string) => void;
|
||||
/**
|
||||
* utility function to generate EmbeddedFilesLoader object
|
||||
* @param isDark
|
||||
* @returns
|
||||
*/
|
||||
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader;
|
||||
/**
|
||||
* utility function to generate ExportSettings object
|
||||
* @param withBackground
|
||||
* @param withTheme
|
||||
* @returns
|
||||
*/
|
||||
getExportSettings(withBackground: boolean, withTheme: boolean): ExportSettings;
|
||||
/**
|
||||
* get bounding box of elements
|
||||
* bounding box is the box encapsulating all of the elements completely
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getBoundingBox(elements: ExcalidrawElement[]): {
|
||||
topX: number;
|
||||
topY: number;
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
/**
|
||||
* elements grouped by the highest level groups
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getMaximumGroups(elements: ExcalidrawElement[]): ExcalidrawElement[][];
|
||||
/**
|
||||
* gets the largest element from a group. useful when a text element is grouped with a box, and you want to connect an arrow to the box
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
|
||||
/**
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @param gap
|
||||
* @returns 2 or 0 intersection points between line going through `a` and `b`
|
||||
* and the `element`, in ascending order of distance from `a`.
|
||||
*/
|
||||
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
|
||||
/**
|
||||
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
|
||||
* @param elements
|
||||
* @returns null or the groupId
|
||||
*/
|
||||
getCommonGroupForElements(elements: ExcalidrawElement[]): string;
|
||||
/**
|
||||
* Gets all the elements from elements[] that share one or more groupIds with element.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
/**
|
||||
* Gets all the elements from elements[] that are contained in the frame.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
/**
|
||||
* See OCR plugin for example on how to use scriptSettings
|
||||
* Set by the ScriptEngine
|
||||
*/
|
||||
activeScript: string;
|
||||
/**
|
||||
*
|
||||
* @returns script settings. Saves settings in plugin settings, under the activeScript key
|
||||
*/
|
||||
getScriptSettings(): {};
|
||||
/**
|
||||
* sets script settings.
|
||||
* @param settings
|
||||
* @returns
|
||||
*/
|
||||
setScriptSettings(settings: any): Promise<void>;
|
||||
/**
|
||||
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
|
||||
* @param file
|
||||
* @param openState - if not provided {active: true} will be used
|
||||
* @returns
|
||||
*/
|
||||
openFileInNewOrAdjacentLeaf(file: TFile, openState?: OpenViewState): WorkspaceLeaf;
|
||||
/**
|
||||
* measure text size based on current style settings
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
measureText(text: string): {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
/**
|
||||
* Returns the size of the image element at 100% (i.e. the original size)
|
||||
* @param imageElement an image element from the active scene on targetView
|
||||
*/
|
||||
getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>;
|
||||
/**
|
||||
* verifyMinimumPluginVersion returns true if plugin version is >= than required
|
||||
* recommended use:
|
||||
* if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
|
||||
* @param requiredVersion
|
||||
* @returns
|
||||
*/
|
||||
verifyMinimumPluginVersion(requiredVersion: string): boolean;
|
||||
/**
|
||||
* Check if view is instance of ExcalidrawView
|
||||
* @param view
|
||||
* @returns
|
||||
*/
|
||||
isExcalidrawView(view: any): boolean;
|
||||
/**
|
||||
* sets selection in view
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
selectElementsInView(elements: ExcalidrawElement[] | string[]): void;
|
||||
/**
|
||||
* @returns an 8 character long random id
|
||||
*/
|
||||
generateElementId(): string;
|
||||
/**
|
||||
* @param element
|
||||
* @returns a clone of the element with a new id
|
||||
*/
|
||||
cloneElement(element: ExcalidrawElement): ExcalidrawElement;
|
||||
/**
|
||||
* Moves the element to a specific position in the z-index
|
||||
*/
|
||||
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hexStringToRgb(color: string): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHexString(color: number[]): string;
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hslToRgb(color: number[]): number[];
|
||||
/**
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHsl(color: number[]): number[];
|
||||
/**
|
||||
*
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
colorNameToHex(color: string): string;
|
||||
/**
|
||||
* https://github.com/lbragile/ColorMaster
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
getCM(color: TInput): ColorMaster;
|
||||
importSVG(svgString: string): boolean;
|
||||
}
|
||||
export declare function initExcalidrawAutomate(plugin: ExcalidrawPlugin): Promise<ExcalidrawAutomate>;
|
||||
export declare function destroyExcalidrawAutomate(): void;
|
||||
export declare function _measureText(newText: string, fontSize: number, fontFamily: number, lineHeight: number): {
|
||||
w: number;
|
||||
h: number;
|
||||
baseline: number;
|
||||
};
|
||||
export declare const generatePlaceholderDataURL: (width: number, height: number) => DataURL;
|
||||
export declare function createPNG(templatePath: string, scale: number, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any): Promise<Blob>;
|
||||
export declare function createSVG(templatePath: string, embedFont: boolean, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any, convertMarkdownLinksToObsidianURLs?: boolean): Promise<SVGSVGElement>;
|
||||
export declare function estimateBounds(elements: ExcalidrawElement[]): [number, number, number, number];
|
||||
export declare function repositionElementsToCursor(elements: ExcalidrawElement[], newPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
}, center: boolean, api: ExcalidrawImperativeAPI): ExcalidrawElement[];
|
||||
export declare const insertLaTeXToView: (view: ExcalidrawView) => void;
|
||||
export declare const search: (view: ExcalidrawView) => Promise<void>;
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export declare const getTextElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export declare const getFrameElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
|
||||
export declare const cloneElement: (el: ExcalidrawElement) => any;
|
||||
export declare const verifyMinimumPluginVersion: (requiredVersion: string) => boolean;
|
||||
@@ -2,36 +2,110 @@
|
||||
## Attributes and functions overview
|
||||
Here's the interface implemented by ExcalidrawAutomate:
|
||||
|
||||
```typescript
|
||||
export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
plugin: ExcalidrawPlugin;
|
||||
targetView: ExcalidrawView = null; //the view currently edited
|
||||
elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id
|
||||
imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId
|
||||
mostRecentMarkdownSVG:SVGSVGElement = null; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
|
||||
style: {
|
||||
strokeColor: string; //https://www.w3schools.com/colors/default.asp
|
||||
backgroundColor: string;
|
||||
angle: number; //radian
|
||||
fillStyle: FillStyle; //type FillStyle = "hachure" | "cross-hatch" | "solid"
|
||||
strokeWidth: number;
|
||||
strokeStyle: StrokeStyle; //type StrokeStyle = "solid" | "dashed" | "dotted"
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness: StrokeSharpness; //type StrokeSharpness = "round" | "sharp"
|
||||
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
|
||||
fontSize: number;
|
||||
textAlign: string; //"left"|"right"|"center"
|
||||
verticalAlign: string; //"top"|"bottom"|"middle" :for future use, has no effect currently
|
||||
startArrowHead: string; //"triangle"|"dot"|"arrow"|"bar"|null
|
||||
endArrowHead: string;
|
||||
};
|
||||
canvas: {
|
||||
theme: string; //"dark"|"light"
|
||||
viewBackgroundColor: string;
|
||||
gridSize: number;
|
||||
};
|
||||
You can find the source file here: [ExcalidrawAutomate.d.ts](ExcalidrawAutomate.d.ts).
|
||||
|
||||
```javascript
|
||||
/// <reference types="react" />
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { FillStyle, StrokeStyle, ExcalidrawElement, ExcalidrawBindableElement, FileId, NonDeletedExcalidrawElement, ExcalidrawImageElement, StrokeRoundness, RoundnessType } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { Editor, OpenViewState, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
import { PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
export declare class ExcalidrawAutomate {
|
||||
/**
|
||||
* Utility function that returns the Obsidian Module object.
|
||||
*/
|
||||
get obsidian(): typeof obsidian_module;
|
||||
get DEVICE(): DeviceType;
|
||||
getAttachmentFilepath(filename: string): Promise<string>;
|
||||
/**
|
||||
* Prompts the user with a dialog to select new file action.
|
||||
* - create markdown file
|
||||
* - create excalidraw file
|
||||
* - cancel action
|
||||
* The new file will be relative to this.targetView.file.path, unless parentFile is provided.
|
||||
* If shouldOpenNewFile is true, the new file will be opened in a workspace leaf.
|
||||
* targetPane control which leaf will be used for the new file.
|
||||
* Returns the TFile for the new file or null if the user cancelled the action.
|
||||
* @param newFileNameOrPath
|
||||
* @param shouldOpenNewFile
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @param parentFile
|
||||
* @returns
|
||||
*/
|
||||
newFilePrompt(newFileNameOrPath: string, shouldOpenNewFile: boolean, targetPane?: PaneTarget, parentFile?: TFile): Promise<TFile | null>;
|
||||
/**
|
||||
* Generates a new Obsidian Leaf following Excalidraw plugin settings such as open in Main Workspace or not, open in adjacent pane if avaialble, etc.
|
||||
* @param origo // the currently active leaf, the origin of the new leaf
|
||||
* @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
|
||||
* @returns
|
||||
*/
|
||||
getLeaf(origo: WorkspaceLeaf, targetPane?: PaneTarget): WorkspaceLeaf;
|
||||
/**
|
||||
* Returns the editor or leaf.view of the currently active embedded obsidian file.
|
||||
* If view is not provided, ea.targetView is used.
|
||||
* If the embedded file is a markdown document the function will return
|
||||
* {file:TFile, editor:Editor} otherwise it will return {view:any}. You can check view type with view.getViewType();
|
||||
* @param view
|
||||
* @returns
|
||||
*/
|
||||
getActiveEmbeddableViewOrEditor(view?: ExcalidrawView): {
|
||||
view: any;
|
||||
} | {
|
||||
file: TFile;
|
||||
editor: Editor;
|
||||
} | null;
|
||||
plugin: ExcalidrawPlugin;
|
||||
elementsDict: {
|
||||
[key: string]: any;
|
||||
};
|
||||
imagesDict: {
|
||||
[key: FileId]: any;
|
||||
};
|
||||
mostRecentMarkdownSVG: SVGSVGElement;
|
||||
style: {
|
||||
strokeColor: string;
|
||||
backgroundColor: string;
|
||||
angle: number;
|
||||
fillStyle: FillStyle;
|
||||
strokeWidth: number;
|
||||
strokeStyle: StrokeStyle;
|
||||
roughness: number;
|
||||
opacity: number;
|
||||
strokeSharpness?: StrokeRoundness;
|
||||
roundness: null | {
|
||||
type: RoundnessType;
|
||||
value?: number;
|
||||
};
|
||||
fontFamily: number;
|
||||
fontSize: number;
|
||||
textAlign: string;
|
||||
verticalAlign: string;
|
||||
startArrowHead: string;
|
||||
endArrowHead: string;
|
||||
};
|
||||
canvas: {
|
||||
theme: string;
|
||||
viewBackgroundColor: string;
|
||||
gridSize: number;
|
||||
};
|
||||
colorPalette: {};
|
||||
constructor(plugin: ExcalidrawPlugin, view?: ExcalidrawView);
|
||||
/**
|
||||
*
|
||||
* @returns the last recorded pointer position on the Excalidraw canvas
|
||||
*/
|
||||
getViewLastPointerPosition(): {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
@@ -71,6 +145,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @param templatePath
|
||||
*/
|
||||
toClipboard(templatePath?: string): Promise<void>;
|
||||
/**
|
||||
* @param file: TFile
|
||||
* @returns ExcalidrawScene
|
||||
*/
|
||||
getSceneFromFile(file: TFile): Promise<{
|
||||
elements: ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
}>;
|
||||
/**
|
||||
* get all elements from ExcalidrawAutomate elementsDict
|
||||
* @returns elements from elemenetsDict
|
||||
@@ -101,10 +183,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
"excalidraw-url-prefix"?: string;
|
||||
"excalidraw-export-transparent"?: boolean;
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-svgpadding"?: number;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
"excalidraw-autoexport"?: boolean;
|
||||
};
|
||||
plaintext?: string;
|
||||
}): Promise<string>;
|
||||
/**
|
||||
*
|
||||
@@ -134,6 +220,16 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
*/
|
||||
wrapText(text: string, lineLen: number): string;
|
||||
private boxedElement;
|
||||
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
@@ -170,6 +266,11 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
addBlob(topX: number, topY: number, width: number, height: number): string;
|
||||
/**
|
||||
* Refresh the size of a text element to fit its contents
|
||||
* @param id - the id of the text element
|
||||
*/
|
||||
refreshTextElementSize(id: string): void;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
@@ -184,9 +285,11 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
wrapAt?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: string;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
boxStrokeColor?: string;
|
||||
textVerticalAlign?: "top" | "middle" | "bottom";
|
||||
}, id?: string): string;
|
||||
/**
|
||||
*
|
||||
@@ -213,7 +316,8 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @param imageFile
|
||||
* @returns
|
||||
*/
|
||||
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
|
||||
addImage(topX: number, topY: number, imageFile: TFile | string, scale?: boolean, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size
|
||||
anchor?: boolean): Promise<string>;
|
||||
/**
|
||||
*
|
||||
* @param topX
|
||||
@@ -262,12 +366,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
isExcalidrawFile(f: TFile): boolean;
|
||||
targetView: ExcalidrawView;
|
||||
/**
|
||||
*
|
||||
* sets the target view for EA. All the view operations and the access to Excalidraw API will be performend on this view
|
||||
* if view is null or undefined, the function will first try setView("active"), then setView("first").
|
||||
* @param view
|
||||
* @returns
|
||||
* @returns targetView
|
||||
*/
|
||||
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
|
||||
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView;
|
||||
/**
|
||||
*
|
||||
* @returns https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
|
||||
@@ -311,6 +417,19 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
viewToggleFullScreen(forceViewMode?: boolean): void;
|
||||
setViewModeEnabled(enabled: boolean): void;
|
||||
/**
|
||||
* This function gives you a more hands on access to Excalidraw.
|
||||
* @param scene - The scene you want to load to Excalidraw
|
||||
* @param restore - Use this if the scene includes legacy excalidraw file elements that need to be converted to the latest excalidraw data format (not a typical usecase)
|
||||
* @returns
|
||||
*/
|
||||
viewUpdateScene(scene: {
|
||||
elements?: ExcalidrawElement[];
|
||||
appState?: AppState;
|
||||
files?: BinaryFileData;
|
||||
commitToHistory?: boolean;
|
||||
}, restore?: boolean): void;
|
||||
/**
|
||||
* connect an object to the selected element in the view
|
||||
* @param objectA ID of the element
|
||||
@@ -325,6 +444,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
endArrowHead?: "triangle" | "dot" | "arrow" | "bar" | null;
|
||||
padding?: number;
|
||||
}): boolean;
|
||||
/**
|
||||
* zoom tarteView to fit elements provided as input
|
||||
* elements === [] will zoom to fit the entire scene
|
||||
* selectElements toggles whether the elements should be in a selected state at the end of the operation
|
||||
* @param selectElements
|
||||
* @param elements
|
||||
*/
|
||||
viewZoomToElements(selectElements: boolean, elements: ExcalidrawElement[]): void;
|
||||
/**
|
||||
* Adds elements from elementsDict to the current view
|
||||
* @param repositionToCursor default is false
|
||||
@@ -334,9 +461,10 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* Note that elements copied to the view with copyViewElementsToEAforEditing retain their
|
||||
* position in the stack of elements in the view even if modified using EA
|
||||
* default is false, i.e. the new elements get to the bottom of the stack
|
||||
* @param shouldRestoreElements - restore elements - auto-corrects broken, incomplete or old elements included in the update
|
||||
* @returns
|
||||
*/
|
||||
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean): Promise<boolean>;
|
||||
addElementsToView(repositionToCursor?: boolean, save?: boolean, newElementsOnTop?: boolean, shouldRestoreElements?: boolean): Promise<boolean>;
|
||||
/**
|
||||
* Register instance of EA to use for hooks with TargetView
|
||||
* By default ExcalidrawViews will check window.ExcalidrawAutomate for event hooks.
|
||||
@@ -362,7 +490,7 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* If set, this callback is triggered, when the user hovers a link in the scene.
|
||||
* You can use this callback in case you want to do something additional when the onLinkHover event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onLinkHover action you must return true, it will stop the native excalidraw onLinkHover management flow.
|
||||
* In case you want to prevent the excalidraw onLinkHover action you must return false, it will stop the native excalidraw onLinkHover management flow.
|
||||
*/
|
||||
onLinkHoverHook: (element: NonDeletedExcalidrawElement, linkText: string, view: ExcalidrawView, ea: ExcalidrawAutomate) => boolean;
|
||||
/**
|
||||
@@ -394,6 +522,49 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
y: number;
|
||||
};
|
||||
}) => boolean;
|
||||
/**
|
||||
* If set, this callback is triggered, when Excalidraw receives an onPaste event.
|
||||
* You can use this callback in case you want to do something additional when the
|
||||
* onPaste event occurs.
|
||||
* This callback must return a boolean value.
|
||||
* In case you want to prevent the excalidraw onPaste action you must return false,
|
||||
* it will stop the native excalidraw onPaste management flow.
|
||||
*/
|
||||
onPasteHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
payload: ClipboardData;
|
||||
event: ClipboardEvent;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
pointerPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}) => boolean;
|
||||
/**
|
||||
* 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.
|
||||
* This will run before the file level script defined in the `excalidraw-onload-script` frontmatter.
|
||||
*/
|
||||
onFileOpenHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
}) => Promise<void>;
|
||||
/**
|
||||
* if set, this callback is triggered, when an Excalidraw file is created
|
||||
* see also: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1124
|
||||
*/
|
||||
onFileCreateHook: (data: {
|
||||
ea: ExcalidrawAutomate;
|
||||
excalidrawFile: TFile;
|
||||
view: ExcalidrawView;
|
||||
}) => Promise<void>;
|
||||
/**
|
||||
* If set, this callback is triggered whenever the active canvas color changes
|
||||
*/
|
||||
onCanvasColorChangeHook: (ea: ExcalidrawAutomate, view: ExcalidrawView, //the excalidraw view
|
||||
color: string) => void;
|
||||
/**
|
||||
* utility function to generate EmbeddedFilesLoader object
|
||||
* @param isDark
|
||||
@@ -431,6 +602,15 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
|
||||
/**
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @param gap
|
||||
* @returns 2 or 0 intersection points between line going through `a` and `b`
|
||||
* and the `element`, in ascending order of distance from `a`.
|
||||
*/
|
||||
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
|
||||
/**
|
||||
* Gets the groupId for the group that contains all the elements, or null if such a group does not exist
|
||||
* @param elements
|
||||
@@ -445,14 +625,12 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
*/
|
||||
getElementsInTheSameGroupWithElement(element: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
/**
|
||||
* Gets all the elements from elements[] that are contained in the frame.
|
||||
* @param element
|
||||
* @param a
|
||||
* @param b
|
||||
* @param gap
|
||||
* @returns 2 or 0 intersection points between line going through `a` and `b`
|
||||
* and the `element`, in ascending order of distance from `a`.
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @returns
|
||||
*/
|
||||
intersectElementWithLine(element: ExcalidrawBindableElement, a: readonly [number, number], b: readonly [number, number], gap?: number): Point[];
|
||||
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[];
|
||||
/**
|
||||
* See OCR plugin for example on how to use scriptSettings
|
||||
* Set by the ScriptEngine
|
||||
@@ -472,9 +650,10 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
/**
|
||||
* Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
|
||||
* @param file
|
||||
* @param openState - if not provided {active: true} will be used
|
||||
* @returns
|
||||
*/
|
||||
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf;
|
||||
openFileInNewOrAdjacentLeaf(file: TFile, openState?: OpenViewState): WorkspaceLeaf;
|
||||
/**
|
||||
* measure text size based on current style settings
|
||||
* @param text
|
||||
@@ -484,6 +663,14 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
/**
|
||||
* Returns the size of the image element at 100% (i.e. the original size)
|
||||
* @param imageElement an image element from the active scene on targetView
|
||||
*/
|
||||
getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>;
|
||||
/**
|
||||
* verifyMinimumPluginVersion returns true if plugin version is >= than required
|
||||
* recommended use:
|
||||
@@ -503,7 +690,7 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @param elements
|
||||
* @returns
|
||||
*/
|
||||
selectElementsInView(elements: ExcalidrawElement[]): void;
|
||||
selectElementsInView(elements: ExcalidrawElement[] | string[]): void;
|
||||
/**
|
||||
* @returns an 8 character long random id
|
||||
*/
|
||||
@@ -518,25 +705,25 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
*/
|
||||
moveViewElementToZIndex(elementId: number, newZIndex: number): void;
|
||||
/**
|
||||
*
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hexStringToRgb(color: string): number[];
|
||||
/**
|
||||
*
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
rgbToHexString(color: number[]): string;
|
||||
/**
|
||||
*
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
hslToRgb(color: number[]): number[];
|
||||
/**
|
||||
*
|
||||
* Depricated. Use getCM / ColorMaster instead
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
@@ -547,5 +734,47 @@ export declare class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
|
||||
* @returns
|
||||
*/
|
||||
colorNameToHex(color: string): string;
|
||||
}```
|
||||
|
||||
/**
|
||||
* https://github.com/lbragile/ColorMaster
|
||||
* @param color
|
||||
* @returns
|
||||
*/
|
||||
getCM(color: TInput): ColorMaster;
|
||||
importSVG(svgString: string): boolean;
|
||||
}
|
||||
export declare function initExcalidrawAutomate(plugin: ExcalidrawPlugin): Promise<ExcalidrawAutomate>;
|
||||
export declare function destroyExcalidrawAutomate(): void;
|
||||
export declare function _measureText(newText: string, fontSize: number, fontFamily: number, lineHeight: number): {
|
||||
w: number;
|
||||
h: number;
|
||||
baseline: number;
|
||||
};
|
||||
export declare const generatePlaceholderDataURL: (width: number, height: number) => DataURL;
|
||||
export declare function createPNG(templatePath: string, scale: number, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any): Promise<Blob>;
|
||||
export declare function createSVG(templatePath: string, embedFont: boolean, exportSettings: ExportSettings, loader: EmbeddedFilesLoader, forceTheme: string, canvasTheme: string, canvasBackgroundColor: string, automateElements: ExcalidrawElement[], plugin: ExcalidrawPlugin, depth: number, padding?: number, imagesDict?: any, convertMarkdownLinksToObsidianURLs?: boolean): Promise<SVGSVGElement>;
|
||||
export declare function estimateBounds(elements: ExcalidrawElement[]): [number, number, number, number];
|
||||
export declare function repositionElementsToCursor(elements: ExcalidrawElement[], newPosition: {
|
||||
x: number;
|
||||
y: number;
|
||||
}, center: boolean, api: ExcalidrawImperativeAPI): ExcalidrawElement[];
|
||||
export declare const insertLaTeXToView: (view: ExcalidrawView) => void;
|
||||
export declare const search: (view: ExcalidrawView) => Promise<void>;
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export declare const getTextElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export declare const getFrameElementsMatchingQuery: (elements: ExcalidrawElement[], query: string[], exactMatch?: boolean) => ExcalidrawElement[];
|
||||
export declare const cloneElement: (el: ExcalidrawElement) => any;
|
||||
export declare const verifyMinimumPluginVersion: (requiredVersion: string) => boolean;
|
||||
```
|
||||
@@ -288,7 +288,7 @@ let busy = false;
|
||||
const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = TRANSITION_STEP_COUNT) => {
|
||||
const startTimer = Date.now();
|
||||
let watchdog = 0;
|
||||
while(busy && watchdog++<15) await(100);
|
||||
while(busy && watchdog++<15) await sleep(100);
|
||||
if(busy && watchdog >= 15) return;
|
||||
busy = true;
|
||||
excalidrawAPI.updateScene({appState:{shouldCacheIgnoreZoom:true}});
|
||||
@@ -746,4 +746,4 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
timestamp
|
||||
};
|
||||
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.9.19.2",
|
||||
"version": "2.0.1-beta-2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.9.28",
|
||||
"version": "2.0.1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"fundingUrl": "https://ko-fi.com/zsolt",
|
||||
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@ import { ExcalidrawElement, ExcalidrawImageElement, FileId } from "@zsviczian/ex
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
ASSISTANT_FONT,
|
||||
CASCADIA_FONT,
|
||||
VIRGIL_FONT,
|
||||
} from "./constFonts";
|
||||
import {
|
||||
DEFAULT_MD_EMBED_CSS,
|
||||
fileid,
|
||||
FRONTMATTER_KEY_BORDERCOLOR,
|
||||
@@ -15,7 +19,6 @@ import {
|
||||
IMAGE_TYPES,
|
||||
nanoid,
|
||||
THEME_FILTER,
|
||||
VIRGIL_FONT,
|
||||
} from "./constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
|
||||
@@ -105,8 +108,12 @@ const replaceSVGColors = (svg: SVGSVGElement | string, colorMap: ColorMap | null
|
||||
for (const [oldColor, newColor] of Object.entries(colorMap)) {
|
||||
const fillRegex = new RegExp(`fill="${oldColor}"`, 'gi');
|
||||
svg = svg.replaceAll(fillRegex, `fill="${newColor}"`);
|
||||
const fillStyleRegex = new RegExp(`fill:${oldColor}`, 'gi');
|
||||
svg = svg.replaceAll(fillStyleRegex, `fill:${newColor}`);
|
||||
const strokeRegex = new RegExp(`stroke="${oldColor}"`, 'gi');
|
||||
svg = svg.replaceAll(strokeRegex, `stroke="${newColor}"`);
|
||||
const strokeStyleRegex = new RegExp(`stroke:${oldColor}`, 'gi');
|
||||
svg = svg.replaceAll(strokeStyleRegex, `stroke:${newColor}`);
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
@@ -739,6 +746,9 @@ export class EmbeddedFilesLoader {
|
||||
case "Cascadia":
|
||||
fontDef = CASCADIA_FONT;
|
||||
break;
|
||||
case "Assistant":
|
||||
fontDef = ASSISTANT_FONT;
|
||||
break;
|
||||
case "":
|
||||
fontDef = "";
|
||||
break;
|
||||
|
||||
@@ -80,6 +80,7 @@ import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/Modifie
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
|
||||
import PolyBool from "polybooljs";
|
||||
import { compressToBase64, decompressFromBase64 } from "lz-string";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -725,6 +726,7 @@ export class ExcalidrawAutomate {
|
||||
w: number,
|
||||
h: number,
|
||||
link: string | null = null,
|
||||
scale?: [number, number],
|
||||
) {
|
||||
return {
|
||||
id,
|
||||
@@ -755,6 +757,7 @@ export class ExcalidrawAutomate {
|
||||
boundElements: [] as any,
|
||||
link,
|
||||
locked: false,
|
||||
...scale ? {scale} : {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -770,7 +773,15 @@ export class ExcalidrawAutomate {
|
||||
* @param height
|
||||
* @returns
|
||||
*/
|
||||
public addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string {
|
||||
public addEmbeddable(
|
||||
topX: number,
|
||||
topY: number,
|
||||
width: number,
|
||||
height: number,
|
||||
url?: string,
|
||||
file?: TFile,
|
||||
embeddableCustomData?: EmbeddableMDCustomProps,
|
||||
): string {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "addEmbeddable()");
|
||||
@@ -797,7 +808,9 @@ export class ExcalidrawAutomate {
|
||||
false, //file.extension === "md", //changed this to false because embedable link navigation in ExcaliBrain
|
||||
)
|
||||
}]]` : "",
|
||||
[1,1],
|
||||
);
|
||||
this.elementsDict[id].customData = {mdProps: embeddableCustomData ?? this.plugin.settings.embeddableMarkdownDefaults};
|
||||
return id;
|
||||
};
|
||||
|
||||
@@ -1556,11 +1569,7 @@ export class ExcalidrawAutomate {
|
||||
errorMessage("targetView not set", "getViewElements()");
|
||||
return [];
|
||||
}
|
||||
const api = this.targetView.excalidrawAPI;
|
||||
if (!api) {
|
||||
return [];
|
||||
}
|
||||
return api.getSceneElements();
|
||||
return this.targetView.getViewElements();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1574,12 +1583,12 @@ export class ExcalidrawAutomate {
|
||||
errorMessage("targetView not set", "deleteViewElements()");
|
||||
return false;
|
||||
}
|
||||
const current = this.targetView?.excalidrawRef?.current;
|
||||
if (!current) {
|
||||
const api = this.targetView?.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
if (!api) {
|
||||
return false;
|
||||
}
|
||||
const el: ExcalidrawElement[] = current.getSceneElements();
|
||||
const st: AppState = current.getAppState();
|
||||
const el: ExcalidrawElement[] = api.getSceneElements() as ExcalidrawElement[];
|
||||
const st: AppState = api.getAppState();
|
||||
this.targetView.updateScene({
|
||||
elements: el.filter((e: ExcalidrawElement) => !elToDelete.includes(e)),
|
||||
appState: st,
|
||||
@@ -2603,12 +2612,12 @@ export async function createPNG(
|
||||
);
|
||||
}
|
||||
|
||||
const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
|
||||
export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
|
||||
elements: ExcalidrawElement[];
|
||||
hostFile: TFile;
|
||||
}): ExcalidrawElement[] => {
|
||||
return elements.map((el)=>{
|
||||
if(el.type!=="embeddable" && el.link && el.link.startsWith("[")) {
|
||||
if(el.link && el.link.startsWith("[")) {
|
||||
const partsArray = REGEX_LINK.getResList(el.link)[0];
|
||||
if(!partsArray?.value) return el;
|
||||
let linkText = REGEX_LINK.getLink(partsArray);
|
||||
@@ -2696,6 +2705,7 @@ export async function createSVG(
|
||||
withTheme,
|
||||
},
|
||||
padding,
|
||||
null,
|
||||
);
|
||||
|
||||
if (withTheme && theme === "dark") addFilterToForeignObjects(svg);
|
||||
|
||||
@@ -1149,7 +1149,7 @@ export class ExcalidrawData {
|
||||
} else {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/829
|
||||
const path = ef.file
|
||||
? ef.linkParts.original.replace(PATHREG,app.metadataCache.fileToLinktext(ef.file,this.file.path))
|
||||
? ef.linkParts.original.replace(PATHREG,this.app.metadataCache.fileToLinktext(ef.file,this.file.path))
|
||||
: ef.linkParts.original;
|
||||
const colorMap = ef.colorMap ? " " + JSON.stringify(ef.colorMap) : "";
|
||||
outString += `${key}: [[${path}]]${colorMap}\n`;
|
||||
|
||||
3
src/ExcalidrawLib.d.ts
vendored
3
src/ExcalidrawLib.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import { RestoredDataState } from "@zsviczian/excalidraw/types/data/restore";
|
||||
import { ImportedDataState } from "@zsviczian/excalidraw/types/data/types";
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/element/bounds";
|
||||
import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, Theme } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { AppState, BinaryFiles, ExportOpts, Point, Zoom } from "@zsviczian/excalidraw/types/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
|
||||
|
||||
@@ -44,6 +44,7 @@ declare namespace ExcalidrawLib {
|
||||
appState?: AppState;
|
||||
files?: any;
|
||||
exportPadding?: number;
|
||||
exportingFrame: ExcalidrawFrameElement | null | undefined;
|
||||
renderEmbeddables?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ import {
|
||||
hasExportTheme,
|
||||
convertSVGStringToElement,
|
||||
} from "./utils/Utils";
|
||||
import { getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { getParentOfClass, isObsidianThemeDark, getFileCSSClasses } from "./utils/ObsidianUtils";
|
||||
import { linkClickModifierType } from "./utils/ModifierkeyHelper";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
|
||||
@@ -30,7 +30,7 @@ interface imgElementAttributes {
|
||||
fname: string; //Excalidraw filename
|
||||
fwidth: string; //Display width of image
|
||||
fheight: string; //Display height of image
|
||||
style: string; //css style to apply to IMG element
|
||||
style: string[]; //css style to apply to IMG element
|
||||
}
|
||||
|
||||
let plugin: ExcalidrawPlugin;
|
||||
@@ -123,8 +123,16 @@ const setStyle = ({element,imgAttributes,onCanvas}:{
|
||||
style += `height:${imgAttributes.fheight}px;`;
|
||||
}
|
||||
if(!onCanvas) element.setAttribute("style", style);
|
||||
element.addClass(imgAttributes.style);
|
||||
element.addClass("excalidraw-embedded-img");
|
||||
element.classList.add(...Array.from(imgAttributes.style))
|
||||
if(!element.hasClass("excalidraw-embedded-img")) {
|
||||
element.addClass("excalidraw-embedded-img");
|
||||
}
|
||||
if(
|
||||
window.ExcalidrawAutomate.plugin.settings.canvasImmersiveEmbed &&
|
||||
!element.hasClass("excalidraw-canvas-immersive")
|
||||
) {
|
||||
element.addClass("excalidraw-canvas-immersive");
|
||||
}
|
||||
}
|
||||
|
||||
const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSettings,loader}:{
|
||||
@@ -254,7 +262,7 @@ const getIMG = async (
|
||||
const filenameParts = getEmbeddedFilenameParts(imgAttributes.fname);
|
||||
|
||||
// https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/387
|
||||
imgAttributes.style = imgAttributes.style.replaceAll(" ", "-");
|
||||
imgAttributes.style = imgAttributes.style.map(s=>s.replaceAll(" ", "-"));
|
||||
|
||||
const forceTheme = hasExportTheme(plugin, file)
|
||||
? getExportTheme(plugin, file, "light")
|
||||
@@ -389,7 +397,7 @@ const createImgElement = async (
|
||||
fname: fileSource,
|
||||
fwidth: imgOrDiv.getAttribute("w"),
|
||||
fheight: imgOrDiv.getAttribute("h"),
|
||||
style: imgOrDiv.getAttribute("class"),
|
||||
style: [...Array.from(imgOrDiv.classList)],
|
||||
}, onCanvas);
|
||||
parent.empty();
|
||||
if(!onCanvas) {
|
||||
@@ -399,6 +407,20 @@ const createImgElement = async (
|
||||
newImg.setAttribute("fileSource",fileSource);
|
||||
parent.append(newImg);
|
||||
});
|
||||
const cssClasses = getFileCSSClasses(attr.file);
|
||||
cssClasses.forEach((cssClass) => {
|
||||
if(imgOrDiv.hasClass(cssClass)) return;
|
||||
imgOrDiv.addClass(cssClass);
|
||||
});
|
||||
if(window.ExcalidrawAutomate.plugin.settings.canvasImmersiveEmbed) {
|
||||
if(!imgOrDiv.hasClass("excalidraw-canvas-immersive")) {
|
||||
imgOrDiv.addClass("excalidraw-canvas-immersive");
|
||||
}
|
||||
} else {
|
||||
if(imgOrDiv.hasClass("excalidraw-canvas-immersive")) {
|
||||
imgOrDiv.removeClass("excalidraw-canvas-immersive");
|
||||
}
|
||||
}
|
||||
return imgOrDiv;
|
||||
}
|
||||
|
||||
@@ -407,7 +429,7 @@ const createImageDiv = async (
|
||||
onCanvas: boolean = false
|
||||
): Promise<HTMLDivElement> => {
|
||||
const img = await createImgElement(attr, onCanvas);
|
||||
return createDiv(attr.style, (el) => el.append(img));
|
||||
return createDiv(attr.style.join(" "), (el) => el.append(img));
|
||||
};
|
||||
|
||||
const processReadingMode = async (
|
||||
@@ -449,7 +471,7 @@ const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Prom
|
||||
fname: "",
|
||||
fheight: "",
|
||||
fwidth: "",
|
||||
style: "",
|
||||
style: [],
|
||||
};
|
||||
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
@@ -466,7 +488,7 @@ const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Prom
|
||||
: getDefaultWidth(plugin);
|
||||
attr.fheight = internalEmbedEl.getAttribute("height");
|
||||
let alt = internalEmbedEl.getAttribute("alt");
|
||||
attr.style = "excalidraw-svg";
|
||||
attr.style = ["excalidraw-svg"];
|
||||
processAltText(src.split("#")[0],alt,attr);
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
attr.fname = file?.path + (fnameParts.hasBlockref||fnameParts.hasSectionref?fnameParts.linkpartReference:"");
|
||||
@@ -485,14 +507,14 @@ const processAltText = (
|
||||
attr.fwidth = parts[2] ?? attr.fwidth;
|
||||
attr.fheight = parts[3] ?? attr.fheight;
|
||||
if (parts[4] && !parts[4].startsWith(fname)) {
|
||||
attr.style = `excalidraw-svg${`-${parts[4]}`}`;
|
||||
attr.style = [`excalidraw-svg${`-${parts[4]}`}`];
|
||||
}
|
||||
if (
|
||||
(!parts[4] || parts[4]==="") &&
|
||||
(!parts[2] || parts[2]==="") &&
|
||||
parts[0] && parts[0] !== ""
|
||||
) {
|
||||
attr.style = `excalidraw-svg${`-${parts[0]}`}`;
|
||||
attr.style = [`excalidraw-svg${`-${parts[0]}`}`];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -550,7 +572,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
fname: ctx.sourcePath,
|
||||
fheight: "",
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
style: "excalidraw-svg",
|
||||
style: ["excalidraw-svg"],
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
@@ -729,7 +751,7 @@ export const observer = new MutationObserver(async (m) => {
|
||||
fname: file.path,
|
||||
fwidth: "300",
|
||||
fheight: null,
|
||||
style: "excalidraw-svg",
|
||||
style: ["excalidraw-svg"],
|
||||
});
|
||||
const div = createDiv("", async (el) => {
|
||||
el.appendChild(img);
|
||||
|
||||
6
src/constFonts.ts
Normal file
6
src/constFonts.ts
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
||||
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { NonDeletedExcalidrawElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { Notice, WorkspaceLeaf, WorkspaceSplit } from "obsidian";
|
||||
import * as React from "react";
|
||||
@@ -7,6 +7,7 @@ import { DEVICE, EXTENDED_EVENT_TYPES, KEYBOARD_EVENT_TYPES } from "./constants"
|
||||
import { ExcalidrawImperativeAPI, UIAppState } from "@zsviczian/excalidraw/types/types";
|
||||
import { ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
|
||||
import { processLinkText, patchMobileView } from "./utils/CustomEmbeddableUtils";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
|
||||
declare module "obsidian" {
|
||||
interface Workspace {
|
||||
@@ -18,6 +19,14 @@ declare module "obsidian" {
|
||||
}
|
||||
}
|
||||
|
||||
const getTheme = (view: ExcalidrawView, theme:string): string => view.excalidrawData.embeddableTheme === "dark"
|
||||
? "theme-dark"
|
||||
: view.excalidrawData.embeddableTheme === "light"
|
||||
? "theme-light"
|
||||
: view.excalidrawData.embeddableTheme === "auto"
|
||||
? theme === "dark" ? "theme-dark" : "theme-light"
|
||||
: isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Render webview for anything other than Vimeo and Youtube
|
||||
//Vimeo and Youtube are rendered by Excalidraw because of the window messaging
|
||||
@@ -59,13 +68,15 @@ export const renderWebView = (src: string, view: ExcalidrawView, id: string, app
|
||||
//Render WorkspaceLeaf or CanvasNode
|
||||
//--------------------------------------------------------------------------------
|
||||
function RenderObsidianView(
|
||||
{ element, linkText, view, containerRef, appState, theme }:{
|
||||
{ mdProps, element, linkText, view, containerRef, activeEmbeddable, theme, canvasColor }:{
|
||||
mdProps: EmbeddableMDCustomProps;
|
||||
element: NonDeletedExcalidrawElement;
|
||||
linkText: string;
|
||||
view: ExcalidrawView;
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
appState: UIAppState;
|
||||
activeEmbeddable: {element: NonDeletedExcalidrawElement; state: string};
|
||||
theme: string;
|
||||
canvasColor: string;
|
||||
}): JSX.Element {
|
||||
|
||||
const { subpath, file } = processLinkText(linkText, view);
|
||||
@@ -79,8 +90,19 @@ function RenderObsidianView(
|
||||
const leafRef = react.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode} | null>(null);
|
||||
const isEditingRef = react.useRef(false);
|
||||
const isActiveRef = react.useRef(false);
|
||||
const themeRef = react.useRef(theme);
|
||||
const elementRef = react.useRef(element);
|
||||
|
||||
// Update themeRef when theme changes
|
||||
react.useEffect(() => {
|
||||
themeRef.current = theme;
|
||||
}, [theme]);
|
||||
|
||||
// Update elementRef when element changes
|
||||
react.useEffect(() => {
|
||||
elementRef.current = element;
|
||||
}, [element]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//block propagation of events to the parent if the iframe element is active
|
||||
//--------------------------------------------------------------------------------
|
||||
@@ -192,6 +214,7 @@ function RenderObsidianView(
|
||||
//This runs only when the file is added, thus should not be a major performance issue
|
||||
await leafRef.current.leaf.setViewState({state: {file:null}})
|
||||
leafRef.current.node = view.canvasNodeFactory.createFileNote(file, subpath, containerRef.current, element.id);
|
||||
setColors(containerRef.current, element, mdProps, canvasColor);
|
||||
} else {
|
||||
const workspaceLeaf:HTMLDivElement = rootSplit.containerEl.querySelector("div.workspace-leaf");
|
||||
if(workspaceLeaf) workspaceLeaf.style.borderRadius = "var(--embeddable-radius)";
|
||||
@@ -205,9 +228,69 @@ function RenderObsidianView(
|
||||
return () => {}; //cleanup on unmount
|
||||
}, [linkText, subpath, containerRef]);
|
||||
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvas: string) => {
|
||||
if(!mdProps) return;
|
||||
if (!leafRef.current?.hasOwnProperty("node")) return;
|
||||
|
||||
const canvasNodeContainer = containerRef.current?.firstElementChild as HTMLElement;
|
||||
|
||||
if(mdProps.useObsidianDefaults) {
|
||||
canvasNode?.style.removeProperty("--canvas-background");
|
||||
canvasNodeContainer?.style.removeProperty("background-color");
|
||||
canvasNode?.style.removeProperty("--canvas-border");
|
||||
canvasNodeContainer?.style.removeProperty("border-color");
|
||||
return;
|
||||
}
|
||||
|
||||
const ea = view.plugin.ea;
|
||||
if(mdProps.backgroundMatchElement) {
|
||||
const opacity = (mdProps?.backgroundOpacity ?? 50)/100;
|
||||
const color = element?.backgroundColor
|
||||
? ea.getCM(element.backgroundColor).alphaTo(opacity).stringHEX()
|
||||
: "transparent";
|
||||
canvasNode?.style.setProperty("--canvas-background", color);
|
||||
canvasNodeContainer?.style.setProperty("background-color", color);
|
||||
} else if (!(mdProps?.backgroundMatchElement ?? true )) {
|
||||
const color = mdProps.backgroundMatchCanvas
|
||||
? ea.getCM(canvasColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX()
|
||||
: ea.getCM(mdProps.backgroundColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX();
|
||||
containerRef.current?.style.setProperty("--canvas-background", color);
|
||||
canvasNodeContainer?.style.setProperty("background-color", color);
|
||||
}
|
||||
|
||||
if(mdProps.borderMatchElement) {
|
||||
const opacity = (mdProps?.borderOpacity ?? 50)/100;
|
||||
const color = element?.strokeColor
|
||||
? ea.getCM(element?.strokeColor).alphaTo(opacity).stringHEX()
|
||||
: "transparent";
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
} else if(!(mdProps?.borderMatchElement ?? true)) {
|
||||
const color = ea.getCM(mdProps.borderColor).alphaTo((mdProps.borderOpacity??100)/100).stringHEX();
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
}
|
||||
}
|
||||
|
||||
react.useEffect(() => {
|
||||
if(!containerRef.current) {
|
||||
return;
|
||||
}
|
||||
const element = elementRef.current;
|
||||
const canvasNode = containerRef.current;
|
||||
if(!canvasNode.hasClass("canvas-node")) return;
|
||||
setColors(canvasNode, element, mdProps, canvasColor);
|
||||
}, [
|
||||
mdProps,
|
||||
elementRef.current,
|
||||
containerRef.current,
|
||||
canvasColor,
|
||||
])
|
||||
|
||||
react.useEffect(() => {
|
||||
if(isEditingRef.current) {
|
||||
if(leafRef.current?.node) {
|
||||
containerRef.current?.addClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.stopEditing(leafRef.current.node);
|
||||
}
|
||||
isEditingRef.current = false;
|
||||
@@ -242,10 +325,12 @@ function RenderObsidianView(
|
||||
patchMobileView(view);
|
||||
} else if (leafRef.current?.node) {
|
||||
//Handle canvas node
|
||||
view.canvasNodeFactory.startEditing(leafRef.current.node, theme);
|
||||
const newTheme = getTheme(view, themeRef.current);
|
||||
containerRef.current?.addClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.startEditing(leafRef.current.node, newTheme);
|
||||
}
|
||||
}
|
||||
}, [leafRef.current?.leaf, element.id]);
|
||||
}, [leafRef.current?.leaf, element.id, view, themeRef.current]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Set isActiveRef and switch to preview mode when the iframe is not active
|
||||
@@ -256,7 +341,7 @@ function RenderObsidianView(
|
||||
}
|
||||
|
||||
const previousIsActive = isActiveRef.current;
|
||||
isActiveRef.current = (appState.activeEmbeddable?.element.id === element.id) && (appState.activeEmbeddable?.state === "active");
|
||||
isActiveRef.current = (activeEmbeddable?.element.id === element.id) && (activeEmbeddable?.state === "active");
|
||||
|
||||
if (previousIsActive === isActiveRef.current) {
|
||||
return;
|
||||
@@ -278,20 +363,17 @@ function RenderObsidianView(
|
||||
}
|
||||
} else if (leafRef.current?.node) {
|
||||
//Handle canvas node
|
||||
containerRef.current?.removeClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.stopEditing(leafRef.current.node);
|
||||
}
|
||||
}, [
|
||||
containerRef,
|
||||
leafRef,
|
||||
isActiveRef,
|
||||
appState.activeEmbeddable?.element,
|
||||
appState.activeEmbeddable?.state,
|
||||
activeEmbeddable?.element,
|
||||
activeEmbeddable?.state,
|
||||
element,
|
||||
view,
|
||||
linkText,
|
||||
subpath,
|
||||
file,
|
||||
theme,
|
||||
isEditingRef,
|
||||
view.canvasNodeFactory
|
||||
]);
|
||||
@@ -299,16 +381,12 @@ function RenderObsidianView(
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; view: ExcalidrawView; appState: UIAppState; linkText: string}> = ({ element, view, appState, linkText }) => {
|
||||
const react = view.plugin.getPackage(view.ownerWindow).react;
|
||||
const containerRef: React.RefObject<HTMLDivElement> = react.useRef(null);
|
||||
const theme = view.excalidrawData.embeddableTheme === "dark"
|
||||
? "theme-dark"
|
||||
: view.excalidrawData.embeddableTheme === "light"
|
||||
? "theme-light"
|
||||
: view.excalidrawData.embeddableTheme === "auto"
|
||||
? appState.theme === "dark" ? "theme-dark" : "theme-light"
|
||||
: isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
const theme = getTheme(view, appState.theme);
|
||||
const mdProps: EmbeddableMDCustomProps = element.customData?.mdProps || null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -319,15 +397,19 @@ export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; v
|
||||
borderRadius: "var(--embeddable-radius)",
|
||||
color: `var(--text-normal)`,
|
||||
}}
|
||||
className={theme}
|
||||
className={`${theme} canvas-node ${
|
||||
mdProps?.filenameVisible ? "" : "excalidraw-mdEmbed-hideFilename"}`}
|
||||
>
|
||||
<RenderObsidianView
|
||||
mdProps={mdProps}
|
||||
element={element}
|
||||
linkText={linkText}
|
||||
view={view}
|
||||
containerRef={containerRef}
|
||||
appState={appState}
|
||||
theme={theme}/>
|
||||
activeEmbeddable={appState.activeEmbeddable}
|
||||
theme={appState.theme}
|
||||
canvasColor={appState.viewBackgroundColor}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
175
src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts
Normal file
175
src/dialogs/EmbeddableMDFileCustomDataSettingsComponent.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { Setting, ToggleComponent } from "obsidian";
|
||||
import { EmbeddableMDCustomProps } from "./EmbeddableSettings";
|
||||
import { fragWithHTML } from "src/utils/Utils";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
constructor (
|
||||
private contentEl: HTMLElement,
|
||||
private mdCustomData: EmbeddableMDCustomProps,
|
||||
private update?: Function,
|
||||
) {
|
||||
if(!update) this.update = () => {};
|
||||
}
|
||||
|
||||
render() {
|
||||
let detailsDIV: HTMLDivElement;
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("ES_USE_OBSIDIAN_DEFAULTS"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.useObsidianDefaults)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.useObsidianDefaults = value;
|
||||
detailsDIV.style.display = value ? "none" : "block";
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
this.contentEl.createEl("hr", { cls: "excalidraw-setting-hr" });
|
||||
|
||||
detailsDIV = this.contentEl.createDiv();
|
||||
detailsDIV.style.display = this.mdCustomData.useObsidianDefaults ? "none" : "block";
|
||||
|
||||
const contentEl = detailsDIV
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_FILENAME_VISIBLE"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.filenameVisible)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.filenameVisible = value;
|
||||
})
|
||||
);
|
||||
|
||||
contentEl.createEl("h4",{text: t("ES_BACKGROUND_HEAD")});
|
||||
|
||||
let bgSetting: Setting;
|
||||
let bgMatchElementToggle: ToggleComponent;
|
||||
let bgMatchCanvasToggle: ToggleComponent;
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_BACKGROUND_MATCH_ELEMENT"))
|
||||
.addToggle(toggle => {
|
||||
bgMatchElementToggle = toggle;
|
||||
toggle
|
||||
.setValue(this.mdCustomData.backgroundMatchElement)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.backgroundMatchElement = value;
|
||||
if(value) {
|
||||
bgSetting.settingEl.style.display = "none";
|
||||
if(this.mdCustomData.backgroundMatchCanvas) {
|
||||
bgMatchCanvasToggle.setValue(false);
|
||||
}
|
||||
} else {
|
||||
if(!this.mdCustomData.backgroundMatchCanvas) {
|
||||
bgSetting.settingEl.style.display = "";
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
})
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_BACKGROUND_MATCH_CANVAS"))
|
||||
.addToggle(toggle => {
|
||||
bgMatchCanvasToggle = toggle;
|
||||
toggle
|
||||
.setValue(this.mdCustomData.backgroundMatchCanvas)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.backgroundMatchCanvas = value;
|
||||
if(value) {
|
||||
bgSetting.settingEl.style.display = "none";
|
||||
if(this.mdCustomData.backgroundMatchElement) {
|
||||
bgMatchElementToggle.setValue(false);
|
||||
}
|
||||
} else {
|
||||
if(!this.mdCustomData.backgroundMatchElement) {
|
||||
bgSetting.settingEl.style.display = "";
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
})
|
||||
});
|
||||
|
||||
if(this.mdCustomData.backgroundMatchElement && this.mdCustomData.backgroundMatchCanvas) {
|
||||
bgMatchCanvasToggle.setValue(false);
|
||||
}
|
||||
|
||||
bgSetting = new Setting(contentEl)
|
||||
.setName(t("ES_BACKGROUND_COLOR"))
|
||||
.addColorPicker(colorPicker =>
|
||||
colorPicker
|
||||
.setValue(this.mdCustomData.backgroundColor)
|
||||
.onChange((value) => {
|
||||
this.mdCustomData.backgroundColor = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
bgSetting.settingEl.style.display = (this.mdCustomData.backgroundMatchElement || this.mdCustomData.backgroundMatchCanvas) ? "none" : "";
|
||||
const opacity = (value:number):DocumentFragment => {
|
||||
return fragWithHTML(`Current transparency is <b>${value}%</b>`);
|
||||
}
|
||||
|
||||
const bgOpacitySetting = new Setting(contentEl)
|
||||
.setName(t("ES_BACKGROUND_OPACITY"))
|
||||
.setDesc(opacity(this.mdCustomData.backgroundOpacity))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,100,5)
|
||||
.setValue(this.mdCustomData.backgroundOpacity)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.backgroundOpacity = value;
|
||||
bgOpacitySetting.setDesc(opacity(value));
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
contentEl.createEl("h4",{text: t("ES_BORDER_HEAD")});
|
||||
let borderSetting: Setting;
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_MATCH_ELEMENT"))
|
||||
.addToggle(toggle =>
|
||||
toggle
|
||||
.setValue(this.mdCustomData.borderMatchElement)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderMatchElement = value;
|
||||
if(value) {
|
||||
borderSetting.settingEl.style.display = "none";
|
||||
} else {
|
||||
borderSetting.settingEl.style.display = "";
|
||||
}
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_COLOR"))
|
||||
.addColorPicker(colorPicker =>
|
||||
colorPicker
|
||||
.setValue(this.mdCustomData.borderColor)
|
||||
.onChange((value) => {
|
||||
this.mdCustomData.borderColor = value;
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
|
||||
borderSetting.settingEl.style.display = this.mdCustomData.borderMatchElement ? "none" : "";
|
||||
|
||||
const borderOpacitySetting = new Setting(contentEl)
|
||||
.setName(t("ES_BORDER_OPACITY"))
|
||||
.setDesc(opacity(this.mdCustomData.borderOpacity))
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(0,100,5)
|
||||
.setValue(this.mdCustomData.borderOpacity)
|
||||
.onChange(value => {
|
||||
this.mdCustomData.borderOpacity = value;
|
||||
borderOpacitySetting.setDesc(opacity(value));
|
||||
this.update();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
180
src/dialogs/EmbeddableSettings.ts
Normal file
180
src/dialogs/EmbeddableSettings.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { ExcalidrawEmbeddableElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
|
||||
import { Modal, Notice, Setting, TFile, ToggleComponent } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { t } from "src/lang/helpers";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/FileUtils";
|
||||
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/Utils";
|
||||
import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent";
|
||||
|
||||
export type EmbeddableMDCustomProps = {
|
||||
useObsidianDefaults: boolean;
|
||||
backgroundMatchCanvas: boolean;
|
||||
backgroundMatchElement: boolean;
|
||||
backgroundColor: string;
|
||||
backgroundOpacity: number;
|
||||
borderMatchElement: boolean;
|
||||
borderColor: string;
|
||||
borderOpacity: number;
|
||||
filenameVisible: boolean;
|
||||
}
|
||||
|
||||
export class EmbeddableSettings extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
private updatedFilepath: string = null;
|
||||
private zoomValue: number;
|
||||
private isYouTube: boolean;
|
||||
private youtubeStart: string = null;
|
||||
private isMDFile: boolean;
|
||||
private mdCustomData: EmbeddableMDCustomProps;
|
||||
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
private file: TFile,
|
||||
private element: ExcalidrawEmbeddableElement
|
||||
) {
|
||||
super(plugin.app);
|
||||
this.ea = getEA(this.view);
|
||||
this.ea.copyViewElementsToEAforEditing([this.element]);
|
||||
this.zoomValue = element.scale[0];
|
||||
this.isYouTube = isYouTube(this.element.link);
|
||||
this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file)
|
||||
if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link);
|
||||
|
||||
this.mdCustomData = element.customData?.mdProps ?? view.plugin.settings.embeddableMarkdownDefaults;
|
||||
if(!element.customData?.mdProps) {
|
||||
const bgCM = this.plugin.ea.getCM(element.backgroundColor);
|
||||
this.mdCustomData.backgroundColor = bgCM.stringHEX({alpha: false});
|
||||
this.mdCustomData.backgroundOpacity = element.opacity;
|
||||
const borderCM = this.plugin.ea.getCM(element.strokeColor);
|
||||
this.mdCustomData.borderColor = borderCM.stringHEX({alpha: false});
|
||||
this.mdCustomData.borderOpacity = element.opacity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(t("ES_TITLE"));
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
|
||||
}
|
||||
|
||||
async createForm() {
|
||||
|
||||
this.contentEl.createEl("h1",{text: t("ES_TITLE")});
|
||||
|
||||
if(this.file) {
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("ES_RENAME"))
|
||||
.addText(text =>
|
||||
text
|
||||
.setValue(getPathWithoutExtension(this.file))
|
||||
.onChange(async (value) => {
|
||||
this.updatedFilepath = value;
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const zoomValue = ():DocumentFragment => {
|
||||
return fragWithHTML(`Current zoom is <b>${Math.round(this.zoomValue*100)}%</b>`);
|
||||
}
|
||||
const zoomSetting = new Setting(this.contentEl)
|
||||
.setName(t("ES_ZOOM"))
|
||||
.setDesc(zoomValue())
|
||||
.addSlider(slider =>
|
||||
slider
|
||||
.setLimits(10,400,5)
|
||||
.setValue(this.zoomValue*100)
|
||||
.onChange(value => {
|
||||
this.zoomValue = value/100;
|
||||
zoomSetting.setDesc(zoomValue());
|
||||
})
|
||||
)
|
||||
|
||||
if(this.isYouTube) {
|
||||
new Setting(this.contentEl)
|
||||
.setName(t("ES_YOUTUBE_START"))
|
||||
.setDesc(t("ES_YOUTUBE_START_DESC"))
|
||||
.addText(text =>
|
||||
text
|
||||
.setValue(this.youtubeStart)
|
||||
.onChange(async (value) => {
|
||||
this.youtubeStart = value;
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if(this.isMDFile) {
|
||||
this.contentEl.createEl("h3",{text: t("ES_EMBEDDABLE_SETTINGS")});
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData).render();
|
||||
}
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bOk = div.createEl("button", { text: t("PROMPT_BUTTON_OK"), cls: "excalidraw-prompt-button"});
|
||||
bOk.onclick = async () => {
|
||||
let dirty = false;
|
||||
const el = this.ea.getElement(this.element.id) as Mutable<ExcalidrawEmbeddableElement>;
|
||||
if(this.updatedFilepath) {
|
||||
const newPathWithExt = `${this.updatedFilepath}.${this.file.extension}`;
|
||||
if(newPathWithExt !== this.file.path) {
|
||||
const fnparts = splitFolderAndFilename(newPathWithExt);
|
||||
const newPath = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
fnparts.folderpath,
|
||||
fnparts.filename,
|
||||
);
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
|
||||
dirty = true;
|
||||
if(isValidYouTubeStart(this.youtubeStart)) {
|
||||
el.link = updateYouTubeStartTime(el.link,this.youtubeStart);
|
||||
} else {
|
||||
new Notice(t("ES_YOUTUBE_START_INVALID"));
|
||||
}
|
||||
}
|
||||
if(
|
||||
this.isMDFile && (
|
||||
this.mdCustomData.backgroundColor !== this.element.customData?.backgroundColor ||
|
||||
this.mdCustomData.borderColor !== this.element.customData?.borderColor ||
|
||||
this.mdCustomData.backgroundOpacity !== this.element.customData?.backgroundOpacity ||
|
||||
this.mdCustomData.borderOpacity !== this.element.customData?.borderOpacity ||
|
||||
this.mdCustomData.filenameVisible !== this.element.customData?.filenameVisible)
|
||||
) {
|
||||
addAppendUpdateCustomData(el,{mdProps: this.mdCustomData});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
if(this.zoomValue !== this.element.scale[0]) {
|
||||
dirty = true;
|
||||
|
||||
el.scale = [this.zoomValue,this.zoomValue];
|
||||
}
|
||||
if(dirty) {
|
||||
this.ea.addElementsToView();
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
const bCancel = div.createEl("button", { text: t("PROMPT_BUTTON_CANCEL"), cls: "excalidraw-prompt-button" });
|
||||
bCancel.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
true,
|
||||
);
|
||||
}
|
||||
this.addText(`[[${filepath + (item.alias ? `|${item.alias}` : "")}]]`);
|
||||
this.addText(`[[${filepath + (item.alias ? `|${item.alias}` : "")}]]`, filepath, item.alias);
|
||||
}
|
||||
|
||||
public start(drawingPath: string, addText: Function) {
|
||||
|
||||
@@ -16,6 +16,39 @@ export const RELEASE_NOTES: { [k: string]: string } = {
|
||||
I develop this plugin as a hobby, spending my free time doing this. If you find it valuable, then please say THANK YOU or...
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.0.1":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/xmqiBTrlbEM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- bug with cssclasses in frontmatter
|
||||
- styling of help screen keyboard shortcuts [#1437](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1437)
|
||||
`,
|
||||
"2.0.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/JC1E-jeiWhI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Added support for applying CSS classes in frontmatter. Now, when embedding Excalidraw drawings into Obsidian Canvas, you can use [Canvas Candy](https://tfthacker.com/canvas-candy) classes. For instance, ${String.fromCharCode(96)}cssclasses: cc-border-none${String.fromCharCode(96)} removes the canvas node border around the drawing.
|
||||
- Introduced new context menu actions:
|
||||
- Navigate to link or embedded image.
|
||||
- Add any file from the vault to the canvas.
|
||||
- Convert the selected text element or sticky note to an embedded markdown file.
|
||||
- Add a link from the Vault to the selected element.
|
||||
- Frames are now rendered in exported images.
|
||||
- SVG Export includes the ${String.fromCharCode(96)}.excalidraw-svg${String.fromCharCode(96)} class, enabling post-processing of SVGs using publish.js when using custom domains with Obsidian Publish. Also, added a command palette action ${String.fromCharCode(96)}Obsidian Publish: Find SVG and PNG exports that are out of date${String.fromCharCode(96)}.
|
||||
- Added a new Command palette action to open the corresponding Excalidraw file based on the embedded SVG or PNG file. ${String.fromCharCode(96)}Open Excalidraw Drawing${String.fromCharCode(96)} [Issue #1411](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1411)
|
||||
|
||||
## Fixed and Improved
|
||||
- Resolved issue with the Mermaid Timeline graph displaying all black. [Issue #1424](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1424)
|
||||
- Enabled toggling pen mode off after activation by a pen touch.
|
||||
- Now you are able to unlock elements on mobile; previously, locked elements couldn't be selected.
|
||||
- Fixed the disabled ${String.fromCharCode(96)}complete line button${String.fromCharCode(96)} for multipoint lines on mobile.
|
||||

|
||||
|
||||
`,
|
||||
"1.9.28":`
|
||||
## Fixed & Improved
|
||||
|
||||
129
src/dialogs/PublishOutOfDateFiles.ts
Normal file
129
src/dialogs/PublishOutOfDateFiles.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { Modal, Setting, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getIMGFilename } from "src/utils/FileUtils";
|
||||
import { addIframe } from "src/utils/Utils";
|
||||
|
||||
const haveLinkedFilesChanged = (depth: number, mtime: number, path: string, sourceList: Set<string>, plugin: ExcalidrawPlugin):boolean => {
|
||||
if(depth++ > 5) return false;
|
||||
sourceList.add(path);
|
||||
const links = plugin.app.metadataCache.resolvedLinks[path];
|
||||
if(!links) return false;
|
||||
for(const link of Object.keys(links)) {
|
||||
if(sourceList.has(link)) continue;
|
||||
const file = plugin.app.vault.getAbstractFileByPath(link);
|
||||
if(!file || !(file instanceof TFile)) continue;
|
||||
console.log(path, {mtimeLinked: file.stat.mtime, mtimeSource: mtime, path: file.path});
|
||||
if(file.stat.mtime > mtime) return true;
|
||||
if(plugin.isExcalidrawFile(file)) {
|
||||
if(haveLinkedFilesChanged(depth, mtime, file.path, sourceList, plugin)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const listOfOutOfSyncImgExports = async(plugin: ExcalidrawPlugin, recursive: boolean, statusEl: HTMLParagraphElement):Promise<TFile[]> => {
|
||||
const app = plugin.app;
|
||||
|
||||
const publish = app.internalPlugins.plugins["publish"].instance;
|
||||
if(!publish) return;
|
||||
const list = await app.internalPlugins.plugins["publish"].instance.apiList();
|
||||
if(!list || !list.files) return;
|
||||
const outOfSyncFiles = new Set<TFile>();
|
||||
const allFiles = list.files.filter((f:any)=>(f.path.endsWith(".svg") || f.path.endsWith(".png")))
|
||||
const totalCount = allFiles.length;
|
||||
allFiles.forEach((f:any, idx:number)=>{
|
||||
const maybeExcalidraFilePath = getIMGFilename(f.path,"md");
|
||||
const imgFile = app.vault.getAbstractFileByPath(f.path);
|
||||
const excalidrawFile = app.vault.getAbstractFileByPath(maybeExcalidraFilePath);
|
||||
statusEl.innerText = `Status: ${idx+1}/${totalCount} ${imgFile ? imgFile.name : f.path}`;
|
||||
if(!excalidrawFile || !imgFile || !(excalidrawFile instanceof TFile) || !(imgFile instanceof TFile)) return;
|
||||
if(excalidrawFile.stat.mtime <= imgFile.stat.mtime) {
|
||||
if(!recursive) return;
|
||||
if(!haveLinkedFilesChanged(0, excalidrawFile.stat.mtime, excalidrawFile.path, new Set<string>(), plugin)) return;
|
||||
}
|
||||
outOfSyncFiles.add(excalidrawFile);
|
||||
});
|
||||
return Array.from(outOfSyncFiles);
|
||||
}
|
||||
|
||||
export class PublishOutOfDateFilesDialog extends Modal {
|
||||
constructor(
|
||||
private plugin: ExcalidrawPlugin,
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
async onClose() {}
|
||||
|
||||
onOpen() {
|
||||
this.containerEl.classList.add("excalidraw-release");
|
||||
this.titleEl.setText(`Out of Date SVG Files`);
|
||||
this.createForm(false);
|
||||
}
|
||||
|
||||
async createForm(recursive: boolean) {
|
||||
const detailsEl = this.contentEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: "Video about Obsidian Publish support",
|
||||
});
|
||||
detailsEl.createEl("br");
|
||||
addIframe(detailsEl, "JC1E-jeiWhI");
|
||||
const p = this.contentEl.createEl("p",{text: "Collecting data..."});
|
||||
const statusEl = this.contentEl.createEl("p", {text: "Status: "});
|
||||
const files = await listOfOutOfSyncImgExports(this.plugin, recursive, statusEl);
|
||||
statusEl.style.display = "none";
|
||||
|
||||
if(!files || files.length === 0) {
|
||||
p.innerText = "No out of date files found.";
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bClose = div.createEl("button", { text: "Close", cls: "excalidraw-prompt-button"});
|
||||
bClose.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
if(!recursive) {
|
||||
const bRecursive = div.createEl("button", { text: "Check Recursive", cls: "excalidraw-prompt-button"});
|
||||
bRecursive.onclick = () => {
|
||||
this.contentEl.empty();
|
||||
this.createForm(true);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const filesMap = new Map<TFile,boolean>();
|
||||
p.innerText = "Select files to open.";
|
||||
files.forEach((f:TFile) => {
|
||||
filesMap.set(f,true);
|
||||
new Setting(this.contentEl)
|
||||
.setName(f.path)
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(true)
|
||||
.onChange(value => {
|
||||
filesMap.set(f,value);
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const div = this.contentEl.createDiv({cls: "excalidraw-prompt-buttons-div"});
|
||||
const bClose = div.createEl("button", { text: "Close", cls: "excalidraw-prompt-button"});
|
||||
bClose.onclick = () => {
|
||||
this.close();
|
||||
};
|
||||
if(!recursive) {
|
||||
const bRecursive = div.createEl("button", { text: "Check Recursive", cls: "excalidraw-prompt-button"});
|
||||
bRecursive.onclick = () => {
|
||||
this.contentEl.empty();
|
||||
this.createForm(true);
|
||||
};
|
||||
}
|
||||
const bOpen = div.createEl("button", { text: "Open Selected", cls: "excalidraw-prompt-button" });
|
||||
bOpen.onclick = () => {
|
||||
filesMap.forEach((value:boolean,key:TFile) => {
|
||||
if(value) {
|
||||
this.plugin.openDrawing(key,"new-tab",true);
|
||||
}
|
||||
});
|
||||
this.close();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/Modifierke
|
||||
// English
|
||||
export default {
|
||||
// main.ts
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
OPEN_IMAGE_SOURCE: "Open Excalidraw drawing",
|
||||
INSTALL_SCRIPT: "Install the script",
|
||||
UPDATE_SCRIPT: "Update available - Click to install",
|
||||
CHECKING_SCRIPT:
|
||||
@@ -62,7 +65,7 @@ export default {
|
||||
IMPORT_SVG: "Import an SVG file as Excalidraw strokes (limited SVG support, TEXT currently not supported)",
|
||||
INSERT_MD: "Insert markdown file from vault",
|
||||
INSERT_PDF: "Insert PDF file from vault",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file from your Vault to the active drawing",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||
INSERT_LATEX:
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
@@ -100,6 +103,7 @@ export default {
|
||||
ERROR_SAVING_IMAGE: "Unknown error occured while fetching the image. It could be that for some reason the image is not available or rejected the fetch request from Obsidian",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED",
|
||||
USE_INSERT_FILE_MODAL: "Use 'Insert Any File' to embed a markdown note",
|
||||
CONVERT_TO_MARKDOWN: "Convert to file...",
|
||||
|
||||
//settings.ts
|
||||
RELEASE_NOTES_NAME: "Display Release Notes after update",
|
||||
@@ -255,13 +259,13 @@ FILENAME_HEAD: "Filename",
|
||||
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
|
||||
ADJACENT_PANE_NAME: "Reuse adjacent pane",
|
||||
ADJACENT_PANE_DESC:
|
||||
`When ${labelCTRL()}+${labelSHIFT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` +
|
||||
`When ${labelCTRL()}+${labelALT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane. ` +
|
||||
"Turning this setting on, Excalidraw will first look for an existing pane, and try to open the link there. " +
|
||||
"Excalidraw will look for the other workspace pane based on your focus/navigation history, i.e. the workpane that was active before you " +
|
||||
"activated Excalidraw.",
|
||||
MAINWORKSPACE_PANE_NAME: "Open in main workspace",
|
||||
MAINWORKSPACE_PANE_DESC:
|
||||
`When ${labelCTRL()}+${labelSHIFT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane in the current active window. ` +
|
||||
`When ${labelCTRL()}+${labelALT()} clicking a link in Excalidraw, by default the plugin will open the link in a new pane in the current active window. ` +
|
||||
"Turning this setting on, Excalidraw will open the link in an existing or new pane in the main workspace. ",
|
||||
LINK_BRACKETS_NAME: "Show <code>[[brackets]]</code> around links",
|
||||
LINK_BRACKETS_DESC: `${
|
||||
@@ -279,7 +283,7 @@ FILENAME_HEAD: "Filename",
|
||||
"You can override this setting for a specific drawing by adding <code>"
|
||||
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> to the file's frontmatter.`,
|
||||
PARSE_TODO_NAME: "Parse todo",
|
||||
PARSE_TODO_DESC: "Convert '- [ ] ' and '- [x] ' to checkpox and tick in the box.",
|
||||
PARSE_TODO_DESC: "Convert '- [ ] ' and '- [x] ' to checkbox and tick in the box.",
|
||||
TODO_NAME: "Open TODO icon",
|
||||
TODO_DESC: "Icon to use for open TODO items",
|
||||
DONE_NAME: "Completed TODO icon",
|
||||
@@ -321,8 +325,11 @@ FILENAME_HEAD: "Filename",
|
||||
PDF_TO_IMAGE_SCALE_NAME: "PDF to Image conversion scale",
|
||||
PDF_TO_IMAGE_SCALE_DESC: "Sets the resolution of the image that is generated from the PDF page. Higher resolution will result in bigger images in memory and consequently a higher load on your system (slower performance), but sharper imagee. " +
|
||||
"Additionally, if you want to copy PDF pages (as images) to Excalidraw.com, the bigger image size may result in exceeding the 2MB limit on Excalidraw.com.",
|
||||
EMBED_TOEXCALIDRAW_HEAD: "Embed files into Excalidraw",
|
||||
EMBED_TOEXCALIDRAW_DESC: "In the Embed Files section of Excalidraw Settings, you can configure how various files are embedded into Excalidraw. This includes options for embedding interactive markdown files, PDFs, and markdown files as images.",
|
||||
MD_HEAD: "Embed markdown into Excalidraw as image",
|
||||
MD_HEAD_DESC: `In the "Embed markdown as image settings," you can configure various options for how markdown documents are embedded as images within Excalidraw. These settings allow you to control the default width and maximum height of embedded markdown files, choose the font typeface, font color, and border color for embedded markdown content. Additionally, you can specify a custom CSS file to style the embedded markdown content. Note you can also embed markdown documents as interactive frames. The color setting of frames is under the Display Settings section.`,
|
||||
MD_EMBED_CUSTOMDATA_HEAD_NAME: "Interactive Markdown Files",
|
||||
MD_EMBED_CUSTOMDATA_HEAD_DESC: `These settings will only effect future embeds. Current embeds remain unchanged. The theme setting of embedded frames is under the "Excalidraw appearance and behavior" section.`,
|
||||
MD_TRANSCLUDE_WIDTH_NAME: "Default width of a transcluded markdown document",
|
||||
MD_TRANSCLUDE_WIDTH_DESC:
|
||||
"The width of the markdown page. This affects the word wrapping when transcluding longer paragraphs, and the width of " +
|
||||
@@ -361,6 +368,11 @@ FILENAME_HEAD: "Filename",
|
||||
EMBED_HEAD: "Embedding Excalidraw into your Notes and Exporting",
|
||||
EMBED_DESC: `In the "Embed & Export" settings, you can configure how images and Excalidraw drawings are embedded and exported within your documents. Key settings include choosing the image type for markdown preview (such as Native SVG or PNG), specifying the type of file to insert into the document (original Excalidraw, PNG, or SVG), and managing image caching for embedding in markdown. You can also control image sizing, whether to embed drawings using wiki links or markdown links, and adjust settings related to image themes, background colors, and Obsidian integration.
|
||||
Additionally, there are settings for auto-export, which automatically generates SVG and/or PNG files to match the title of your Excalidraw drawings, keeping them in sync with file renames and deletions.`,
|
||||
EMBED_CANVAS: "Obsidian Canvas support",
|
||||
EMBED_CANVAS_NAME: "Immersive embedding",
|
||||
EMBED_CANVAS_DESC:
|
||||
"Hide canvas node border and background when embedding an Excalidraw drawing to Canvas. " +
|
||||
"Note that for a full transparent background for your image, you will still need to configure Excalidraw to export images with transparent background.",
|
||||
EMBED_CACHING: "Image caching",
|
||||
EXPORT_SUBHEAD: "Export Settings",
|
||||
EMBED_SIZING: "Image sizing",
|
||||
@@ -403,6 +415,11 @@ FILENAME_HEAD: "Filename",
|
||||
"or a PNG or an SVG copy. You need to enable auto-export PNG / SVG (see below under Export Settings) for those image types to be available in the dropdown. For drawings that do not have a " +
|
||||
"a corresponding PNG or SVG readily available the command palette action will insert a broken link. You need to open the original drawing and initiate export manually. " +
|
||||
"This option will not autogenerate PNG/SVG files, but will simply reference the already existing files.",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "Embed link to drawing as comment",
|
||||
EMBED_MARKDOWN_COMMENT_DESC:
|
||||
"Embed the link to the original Excalidraw file as a markdown link under the image, e.g.:<code>%%[[drawing.excalidraw]]%%</code>.<br>" +
|
||||
"Instead of adding a markdown comment you may also select the embedded SVG or PNG line and use the command palette action: " +
|
||||
"'<code>Excalidraw: Open Excalidraw drawing</code>' to open the drawing.",
|
||||
EMBED_WIKILINK_NAME: "Embed Drawing using Wiki link",
|
||||
EMBED_WIKILINK_DESC:
|
||||
"<b><u>Toggle ON:</u></b> Excalidraw will embed a [[wiki link]].<br><b><u>Toggle OFF:</u></b> Excalidraw will embed a [markdown](link).",
|
||||
@@ -433,7 +450,7 @@ FILENAME_HEAD: "Filename",
|
||||
"Embed the .svg file into your documents instead of Excalidraw making you embeds platform independent. " +
|
||||
"While the auto-export switch is on, this file will get updated every time you edit the Excalidraw drawing with the matching name. " +
|
||||
"You can override this setting on a file level by adding the <code>excalidraw-autoexport</code> frontmatter key. Valid values for this key are " +
|
||||
"<code>none</code>,<code>both</code>,<code>svg</code>, and <code>png</code>",
|
||||
"<code>none</code>,<code>both</code>,<code>svg</code>, and <code>png</code>.",
|
||||
EXPORT_PNG_NAME: "Auto-export PNG",
|
||||
EXPORT_PNG_DESC: "Same as the auto-export SVG, but for *.PNG",
|
||||
EXPORT_BOTH_DARK_AND_LIGHT_NAME: "Export both dark- and light-themed image",
|
||||
@@ -455,10 +472,10 @@ FILENAME_HEAD: "Filename",
|
||||
"and the file explorer are going to be all legacy *.excalidraw files. This setting will also turn off the reminder message " +
|
||||
"when you open a legacy file for editing.",
|
||||
MATHJAX_NAME: "MathJax (LaTeX) javascript library host",
|
||||
MATHJAX_DESC: "If you are using LaTeX equiations in Excalidraw then the plugin needs to load a javascript library for that. " +
|
||||
"Some users are unable to access certain host servers. If you are experiencing issues try changing the host here. You may need to "+
|
||||
MATHJAX_DESC: "If you are using LaTeX equations in Excalidraw, then the plugin needs to load a javascript library for that. " +
|
||||
"Some users are unable to access certain host servers. If you are experiencing issues, try changing the host here. You may need to "+
|
||||
"restart Obsidian after closing settings, for this change to take effect.",
|
||||
LATEX_DEFAULT_NAME: "Default LaTeX formual for new equations",
|
||||
LATEX_DEFAULT_NAME: "Default LaTeX formula for new equations",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
@@ -545,7 +562,7 @@ FILENAME_HEAD: "Filename",
|
||||
TOGGLE_DISABLEBINDING: "Toggle to invert default binding behavior",
|
||||
TOGGLE_FRAME_RENDERING: "Toggle frame rendering",
|
||||
TOGGLE_FRAME_CLIPPING: "Toggle frame clipping",
|
||||
OPEN_LINK_CLICK: "Navigate to selected element link",
|
||||
OPEN_LINK_CLICK: "Open Link",
|
||||
OPEN_LINK_PROPS: "Open markdown-embed properties or open link in new window",
|
||||
|
||||
//IFrameActionsMenu.tsx
|
||||
@@ -555,6 +572,27 @@ FILENAME_HEAD: "Filename",
|
||||
ZOOM_TO_FIT: "Zoom to fit",
|
||||
RELOAD: "Reload original link",
|
||||
OPEN_IN_BROWSER: "Open current link in browser",
|
||||
PROPERTIES: "Properties",
|
||||
|
||||
//EmbeddableSettings.tsx
|
||||
ES_TITLE: "Element Settings",
|
||||
ES_RENAME: "Rename File",
|
||||
ES_ZOOM: "Embedded Content Zoom",
|
||||
ES_YOUTUBE_START: "YouTube Start Time",
|
||||
ES_YOUTUBE_START_DESC: "ss, mm:ss, hh:mm:ss",
|
||||
ES_YOUTUBE_START_INVALID: "The YouTube Start Time is invalid. Please check the format and try again",
|
||||
ES_FILENAME_VISIBLE: "Filename Visible",
|
||||
ES_BACKGROUND_HEAD: "Embedded note background color",
|
||||
ES_BACKGROUND_MATCH_ELEMENT: "Match Element Background Color",
|
||||
ES_BACKGROUND_MATCH_CANVAS: "Match Canvas Background Color",
|
||||
ES_BACKGROUND_COLOR: "Background Color",
|
||||
ES_BORDER_HEAD: "Embedded note border color",
|
||||
ES_BORDER_COLOR: "Border Color",
|
||||
ES_BORDER_MATCH_ELEMENT: "Match Element Border Color",
|
||||
ES_BACKGROUND_OPACITY: "Background Transparency",
|
||||
ES_BORDER_OPACITY: "Border Transparency",
|
||||
ES_EMBEDDABLE_SETTINGS: "Embeddable Markdown Settings",
|
||||
ES_USE_OBSIDIAN_DEFAULTS: "Use Obsidian Defaults",
|
||||
|
||||
//Prompts.ts
|
||||
PROMPT_FILE_DOES_NOT_EXIST: "File does not exist. Do you want to create it?",
|
||||
|
||||
136
src/main.ts
136
src/main.ts
@@ -21,7 +21,6 @@ import {
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
loadMermaid,
|
||||
moment,
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -38,13 +37,15 @@ import {
|
||||
DARK_BLANK_DRAWING,
|
||||
SCRIPT_INSTALL_CODEBLOCK,
|
||||
SCRIPT_INSTALL_FOLDER,
|
||||
VIRGIL_FONT,
|
||||
VIRGIL_DATAURL,
|
||||
EXPORT_TYPES,
|
||||
EXPORT_IMG_ICON_NAME,
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
} from "./constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
VIRGIL_DATAURL,
|
||||
} from "./constFonts";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
changeThemeOfExcalidrawMD,
|
||||
@@ -90,9 +91,9 @@ import {
|
||||
getExportTheme,
|
||||
isCallerFromTemplaterPlugin,
|
||||
} from "./utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import { extractSVGPNGFileName, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
//import { OneOffs } from "./OneOffs";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
hoverEvent,
|
||||
@@ -115,6 +116,8 @@ import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { StylesManager } from "./utils/StylesManager";
|
||||
import { MATHJAX_SOURCE_LZCOMPRESSED } from "./constMathJaxSource";
|
||||
import { PublishOutOfDateFilesDialog } from "./dialogs/PublishOutOfDateFiles";
|
||||
import { EmbeddableSettings } from "./dialogs/EmbeddableSettings";
|
||||
|
||||
declare const EXCALIDRAW_PACKAGES:string;
|
||||
declare const react:any;
|
||||
@@ -691,7 +694,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach((leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
if (excalidrawView.file && excalidrawView.excalidrawRef) {
|
||||
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
|
||||
excalidrawView.setTheme(theme);
|
||||
}
|
||||
});
|
||||
@@ -836,6 +839,70 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
),
|
||||
);
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-publish-svg-check",
|
||||
name: t("PUBLISH_SVG_CHECK"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const publish = app.internalPlugins.plugins["publish"].instance;
|
||||
if (!publish) {
|
||||
return false;
|
||||
}
|
||||
if (checking) {
|
||||
return true;
|
||||
}
|
||||
(new PublishOutOfDateFilesDialog(this)).open();
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-embeddable-poroperties",
|
||||
name: t("EMBEDDABLE_PROPERTIES"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="embeddable") as ExcalidrawEmbeddableElement[];
|
||||
if(els.length !== 1) {
|
||||
if(checking) return false;
|
||||
new Notice("Select a single embeddable element and try again");
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
new EmbeddableSettings(view.plugin,view,null,els[0]).open();
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "open-image-excalidraw-source",
|
||||
name: t("OPEN_IMAGE_SOURCE"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if(!view) return false;
|
||||
if(view.leaf !== this.app.workspace.activeLeaf) return false;
|
||||
const editor = view.editor;
|
||||
if(!editor) return false;
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const fname = extractSVGPNGFileName(line);
|
||||
if(!fname) return false;
|
||||
const imgFile = this.app.metadataCache.getFirstLinkpathDest(fname, view.file.path);
|
||||
if(!imgFile) return false;
|
||||
const excalidrawFname = getIMGFilename(imgFile.path, "md");
|
||||
let excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname, view.file.path);
|
||||
if(!excalidrawFile) {
|
||||
if(excalidrawFname.endsWith(".dark.md")) {
|
||||
excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname.replace(/\.dark\.md$/,".md"), view.file.path);
|
||||
}
|
||||
if(excalidrawFname.endsWith(".light.md")) {
|
||||
excalidrawFile = this.app.metadataCache.getFirstLinkpathDest(excalidrawFname.replace(/\.light\.md$/,".md"), view.file.path);
|
||||
}
|
||||
if(!excalidrawFile) return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.openDrawing(excalidrawFile, "new-tab", true);
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-disable-autosave",
|
||||
name: t("TEMPORARY_DISABLE_AUTOSAVE"),
|
||||
@@ -1241,6 +1308,22 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "convert-text2MD",
|
||||
name: t("CONVERT_TO_MARKDOWN"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView)
|
||||
if(!view) return false;
|
||||
const selectedTextElements = view.getViewSelectedElements().filter(el=>el.type === "text");
|
||||
if(selectedTextElements.length !==1 ) return false;
|
||||
const selectedTextElement = selectedTextElements[0] as ExcalidrawTextElement;
|
||||
const containerElement = (view.getViewElements() as ExcalidrawElement[]).find(el=>el.id === selectedTextElement.containerId);
|
||||
if(containerElement && containerElement.type === "arrow") return false;
|
||||
if(checking) return true;
|
||||
view.convertTextElementToMarkdown(selectedTextElement, containerElement);
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-link",
|
||||
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "k" }],
|
||||
@@ -1251,7 +1334,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
this.insertLinkDialog.start(view.file.path, view.addText);
|
||||
this.insertLinkDialog.start(view.file.path, view.addLink);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1455,7 +1538,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (!view || !view.excalidrawRef) {
|
||||
if (!view || !view.excalidrawAPI) {
|
||||
return false;
|
||||
}
|
||||
const st = view.excalidrawAPI.getAppState();
|
||||
@@ -1949,7 +2032,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
//close excalidraw view where this file is open
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (let i = 0; i < leaves.length; i++) {
|
||||
if ((leaves[i].view as ExcalidrawView).file.path == file.path) {
|
||||
await leaves[i].setViewState({
|
||||
@@ -2327,7 +2410,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
)
|
||||
: "";
|
||||
|
||||
theme = theme===""?"":theme+".";
|
||||
theme = (theme === "")
|
||||
? ""
|
||||
: theme + ".";
|
||||
|
||||
const imageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
@@ -2339,12 +2424,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
);
|
||||
|
||||
//will hold incorrect value if theme==="", however in that case it won't be used
|
||||
const otherTheme = theme === "dark." ? "light.":"dark.";
|
||||
const otherImageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
otherTheme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
const otherTheme = theme === "dark." ? "light." : "dark.";
|
||||
const otherImageRelativePath = theme === ""
|
||||
? null
|
||||
: getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
otherTheme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath);
|
||||
if (!imgFile) {
|
||||
@@ -2352,13 +2438,21 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await sleep(200); //wait for metadata cache to update
|
||||
}
|
||||
|
||||
const inclCom = this.settings.embedMarkdownCommentLinks;
|
||||
|
||||
editor.replaceSelection(
|
||||
this.settings.embedWikiLink
|
||||
? `![[${imageRelativePath}]]\n%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
|
||||
otherImageRelativePath ? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]":""}%%`
|
||||
: `})\n%%[🖋 Edit in Excalidraw](${encodeURI(
|
||||
excalidrawRelativePath,
|
||||
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%`,
|
||||
? `![[${imageRelativePath}]]\n` +
|
||||
(inclCom
|
||||
? `%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
|
||||
otherImageRelativePath
|
||||
? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]"
|
||||
: ""
|
||||
}%%`
|
||||
: "")
|
||||
: `})\n` +
|
||||
(inclCom ? `%%[🖋 Edit in Excalidraw](${encodeURI(excalidrawRelativePath,
|
||||
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%` : ""),
|
||||
);
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Globe, RotateCcw, Scan } from "lucide-react";
|
||||
import { Cog, Globe, RotateCcw, Scan, Settings } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
|
||||
@@ -29,6 +29,7 @@ export const ICONS = {
|
||||
Reload: (<RotateCcw />),
|
||||
Globe: (<Globe />),
|
||||
ZoomToSelectedElement: (<Scan />),
|
||||
Properties: (<Settings />),
|
||||
ZoomToSection: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } f
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import { processLinkText, useDefaultExcalidrawFrame } from "src/utils/CustomEmbeddableUtils";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { EmbeddableSettings } from "src/dialogs/EmbeddableSettings";
|
||||
|
||||
export class EmbeddableMenu {
|
||||
|
||||
@@ -91,7 +92,8 @@ export class EmbeddableMenu {
|
||||
|
||||
if(!isObsidianiFrame) {
|
||||
const { subpath, file } = processLinkText(link, view);
|
||||
if(!file || file.extension!=="md") return null;
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const { x, y } = sceneCoordsToViewportCoords( { sceneX: element.x, sceneY: element.y }, appState);
|
||||
const top = `${y-2.5*ROOTELEMENTSIZE-appState.offsetTop}px`;
|
||||
const left = `${x-appState.offsetLeft}px`;
|
||||
@@ -116,69 +118,73 @@ export class EmbeddableMenu {
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
<ActionButton
|
||||
key={"MarkdownSection"}
|
||||
title={t("NARROW_TO_HEADING")}
|
||||
action={async () => {
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading");
|
||||
const values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
const newSubpath = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!newSubpath && newSubpath!=="") return;
|
||||
if (newSubpath !== subpath) {
|
||||
this.updateElement(newSubpath, element, file);
|
||||
}
|
||||
}}
|
||||
icon={ICONS.ZoomToSection}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"MarkdownBlock"}
|
||||
title={t("NARROW_TO_BLOCK")}
|
||||
action={async () => {
|
||||
if(!file) return;
|
||||
const paragrphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
const values = ["entire-file"].concat(paragrphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragrphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
const selectedBlock = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!selectedBlock) return;
|
||||
{isMD && (
|
||||
<ActionButton
|
||||
key={"MarkdownSection"}
|
||||
title={t("NARROW_TO_HEADING")}
|
||||
action={async () => {
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading");
|
||||
const values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
const newSubpath = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!newSubpath && newSubpath!=="") return;
|
||||
if (newSubpath !== subpath) {
|
||||
this.updateElement(newSubpath, element, file);
|
||||
}
|
||||
}}
|
||||
icon={ICONS.ZoomToSection}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
{isMD && (
|
||||
<ActionButton
|
||||
key={"MarkdownBlock"}
|
||||
title={t("NARROW_TO_BLOCK")}
|
||||
action={async () => {
|
||||
if(!file) return;
|
||||
const paragrphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
const values = ["entire-file"].concat(paragrphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragrphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
const selectedBlock = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
if(!selectedBlock) return;
|
||||
|
||||
if(selectedBlock==="entire-file") {
|
||||
if(subpath==="") return;
|
||||
this.updateElement("", element, file);
|
||||
return;
|
||||
}
|
||||
|
||||
let blockID = selectedBlock.node.id;
|
||||
if(blockID && (`#^${blockID}` === subpath)) return;
|
||||
if (!blockID) {
|
||||
const offset = selectedBlock.node?.position?.end?.offset;
|
||||
if(!offset) return;
|
||||
blockID = nanoid();
|
||||
const fileContents = await app.vault.cachedRead(file);
|
||||
if(!fileContents) return;
|
||||
await app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await sleep(200); //wait for cache to update
|
||||
}
|
||||
this.updateElement(`#^${blockID}`, element, file);
|
||||
}}
|
||||
icon={ICONS.ZoomToBlock}
|
||||
view={view}
|
||||
/>
|
||||
if(selectedBlock==="entire-file") {
|
||||
if(subpath==="") return;
|
||||
this.updateElement("", element, file);
|
||||
return;
|
||||
}
|
||||
|
||||
let blockID = selectedBlock.node.id;
|
||||
if(blockID && (`#^${blockID}` === subpath)) return;
|
||||
if (!blockID) {
|
||||
const offset = selectedBlock.node?.position?.end?.offset;
|
||||
if(!offset) return;
|
||||
blockID = nanoid();
|
||||
const fileContents = await app.vault.cachedRead(file);
|
||||
if(!fileContents) return;
|
||||
await app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await sleep(200); //wait for cache to update
|
||||
}
|
||||
this.updateElement(`#^${blockID}`, element, file);
|
||||
}}
|
||||
icon={ICONS.ZoomToBlock}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
<ActionButton
|
||||
key={"ZoomToElement"}
|
||||
title={t("ZOOM_TO_FIT")}
|
||||
@@ -189,6 +195,16 @@ export class EmbeddableMenu {
|
||||
icon={ICONS.ZoomToSelectedElement}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"Properties"}
|
||||
title={t("PROPERTIES")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
new EmbeddableSettings(view.plugin,view,file,element).open();
|
||||
}}
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -256,6 +272,16 @@ export class EmbeddableMenu {
|
||||
icon={ICONS.ZoomToSelectedElement}
|
||||
view={view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"Properties"}
|
||||
title={t("PROPERTIES")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
new EmbeddableSettings(view.plugin,view,null,element).open();
|
||||
}}
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
140
src/settings.ts
140
src/settings.ts
@@ -21,12 +21,15 @@ import {
|
||||
} from "./utils/FileUtils";
|
||||
import { PENS } from "./utils/Pens";
|
||||
import {
|
||||
addIframe,
|
||||
fragWithHTML,
|
||||
setLeftHandedMode,
|
||||
} from "./utils/Utils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import de from "./lang/locale/de";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/EmbeddableMDFileCustomDataSettingsComponent";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -88,6 +91,7 @@ export interface ExcalidrawSettings {
|
||||
autoExportLightAndDark: boolean;
|
||||
autoexportExcalidraw: boolean;
|
||||
embedType: "excalidraw" | "PNG" | "SVG";
|
||||
embedMarkdownCommentLinks: boolean;
|
||||
embedWikiLink: boolean;
|
||||
syncExcalidraw: boolean;
|
||||
compatibilityMode: boolean;
|
||||
@@ -147,6 +151,8 @@ export interface ExcalidrawSettings {
|
||||
DECAY_LENGTH: number,
|
||||
COLOR: string,
|
||||
};
|
||||
embeddableMarkdownDefaults: EmbeddableMDCustomProps;
|
||||
canvasImmersiveEmbed: boolean,
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -211,6 +217,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
autoExportLightAndDark: false,
|
||||
autoexportExcalidraw: false,
|
||||
embedType: "excalidraw",
|
||||
embedMarkdownCommentLinks: true,
|
||||
embedWikiLink: true,
|
||||
syncExcalidraw: false,
|
||||
experimentalFileType: false,
|
||||
@@ -275,7 +282,19 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
DECAY_LENGTH: 50,
|
||||
DECAY_TIME: 1000,
|
||||
COLOR: "#ff0000",
|
||||
}
|
||||
},
|
||||
embeddableMarkdownDefaults: {
|
||||
useObsidianDefaults: false,
|
||||
backgroundMatchCanvas: false,
|
||||
backgroundMatchElement: true,
|
||||
backgroundColor: "#fff",
|
||||
backgroundOpacity: 60,
|
||||
borderMatchElement: true,
|
||||
borderColor: "#fff",
|
||||
borderOpacity: 0,
|
||||
filenameVisible: false,
|
||||
},
|
||||
canvasImmersiveEmbed: true,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -346,20 +365,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
async display() {
|
||||
let detailsEl: HTMLElement;
|
||||
|
||||
const addIframe = (link:string, startAt?: number) => {
|
||||
const wrapper = detailsEl.createDiv({cls: "excalidraw-videoWrapper settings"})
|
||||
wrapper.createEl("iframe", {
|
||||
attr: {
|
||||
allowfullscreen: true,
|
||||
allow: "encrypted-media;picture-in-picture",
|
||||
frameborder: "0",
|
||||
title: "YouTube video player",
|
||||
src: "https://www.youtube.com/embed/" + link + (startAt ? "?start=" + startAt : ""),
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
await this.plugin.loadSettings(); //in case sync loaded changed settings in the background
|
||||
this.requestEmbedUpdate = false;
|
||||
this.requestReloadDrawings = false;
|
||||
@@ -450,7 +455,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe("jgUpYznHP9A",216);
|
||||
addIframe(detailsEl, "jgUpYznHP9A",216);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SCRIPT_FOLDER_NAME"))
|
||||
@@ -678,7 +683,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe("H8Njp7ZXYag",999);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",999);
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
@@ -701,7 +706,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe("fypDth_-8q0");
|
||||
addIframe(detailsEl, "fypDth_-8q0");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("IFRAME_MATCH_THEME_NAME"))
|
||||
@@ -714,7 +719,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
);
|
||||
addIframe("ICpoyMv6KSs");
|
||||
addIframe(detailsEl, "ICpoyMv6KSs");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MATCH_THEME_NAME"))
|
||||
@@ -787,7 +792,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe("rBarRfcSxNo",107);
|
||||
addIframe(detailsEl, "rBarRfcSxNo",107);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEFAULT_WHEELZOOM_NAME"))
|
||||
@@ -1209,11 +1214,11 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
addIframe("yZQoJg2RCKI");
|
||||
addIframe("opLd1SqaH_I",8);
|
||||
addIframe(detailsEl, "yZQoJg2RCKI");
|
||||
addIframe(detailsEl, "opLd1SqaH_I",8);
|
||||
|
||||
let dropdown: DropdownComponent;
|
||||
|
||||
let embedComment: Setting;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_TYPE_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_TYPE_DESC")))
|
||||
@@ -1237,9 +1242,24 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.onChange(async (value) => {
|
||||
//@ts-ignore
|
||||
this.plugin.settings.embedType = value;
|
||||
embedComment.settingEl.style.display = value === "excalidraw" ? "none":"";
|
||||
this.applySettingsUpdate();
|
||||
});
|
||||
});
|
||||
|
||||
embedComment = new Setting(detailsEl)
|
||||
.setName(t("EMBED_MARKDOWN_COMMENT_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_MARKDOWN_COMMENT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.embedMarkdownCommentLinks)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.embedMarkdownCommentLinks = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
embedComment.settingEl.style.display = this.plugin.settings.embedType === "excalidraw" ? "none":"";
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_WIKILINK_NAME"))
|
||||
@@ -1253,6 +1273,24 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = embedDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_CANVAS"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_CANVAS_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_CANVAS_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.canvasImmersiveEmbed)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.canvasImmersiveEmbed = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = embedDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_CACHING"),
|
||||
@@ -1308,7 +1346,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
text: t("EXPORT_SUBHEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe("wTtaXmRJ7wg",171);
|
||||
addIframe(detailsEl, "wTtaXmRJ7wg",171);
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_SIZING"),
|
||||
@@ -1496,15 +1534,28 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.plugin.settings.autoExportLightAndDark = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
);
|
||||
|
||||
detailsEl = embedDetailsEl.createEl("details");
|
||||
// ------------------------------------------------
|
||||
// Embedding settings
|
||||
// ------------------------------------------------
|
||||
containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
|
||||
containerEl.createDiv({ text: t("EMBED_TOEXCALIDRAW_DESC"), cls: "setting-item-description" });
|
||||
|
||||
detailsEl = this.containerEl.createEl("details");
|
||||
const embedFilesDetailsEl = detailsEl;
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_TOEXCALIDRAW_HEAD"),
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
detailsEl = embedFilesDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("PDF_TO_IMAGE"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
addIframe("nB4cOfn0xAs");
|
||||
addIframe(detailsEl, "nB4cOfn0xAs");
|
||||
new Setting(detailsEl)
|
||||
.setName(t("PDF_TO_IMAGE_SCALE_NAME"))
|
||||
.setDesc(fragWithHTML(t("PDF_TO_IMAGE_SCALE_DESC")))
|
||||
@@ -1523,20 +1574,28 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
// ------------------------------------------------
|
||||
// Markdown embedding settings
|
||||
// ------------------------------------------------
|
||||
containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
|
||||
containerEl.createDiv({ text: t("MD_HEAD_DESC"), cls: "setting-item-description" });
|
||||
detailsEl = this.containerEl.createEl("details");
|
||||
detailsEl = embedFilesDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("MD_EMBED_CUSTOMDATA_HEAD_NAME"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
detailsEl.createEl("span", {text: t("MD_EMBED_CUSTOMDATA_HEAD_DESC")});
|
||||
|
||||
new EmbeddalbeMDFileCustomDataSettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.embeddableMarkdownDefaults,
|
||||
this.applySettingsUpdate,
|
||||
).render();
|
||||
|
||||
detailsEl = embedFilesDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("MD_HEAD"),
|
||||
cls: "excalidraw-setting-h1",
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MD_TRANSCLUDE_WIDTH_NAME"))
|
||||
.setDesc(fragWithHTML(t("MD_TRANSCLUDE_WIDTH_DESC")))
|
||||
@@ -1595,6 +1654,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.addDropdown(async (d: DropdownComponent) => {
|
||||
d.addOption("Virgil", "Virgil");
|
||||
d.addOption("Cascadia", "Cascadia");
|
||||
d.addOption("Assistant", "Assistant");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2"].contains(f.extension))
|
||||
@@ -1669,7 +1729,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
text: t("CUSTOM_PEN_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe("OjNhjaH2KjI",69);
|
||||
addIframe(detailsEl, "OjNhjaH2KjI",69);
|
||||
new Setting(detailsEl)
|
||||
.setName(t("CUSTOM_PEN_NAME"))
|
||||
.setDesc(t("CUSTOM_PEN_DESC"))
|
||||
@@ -1699,7 +1759,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
text: t("CUSTOM_FONT_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe("eKFmrSQhFA4");
|
||||
addIframe(detailsEl, "eKFmrSQhFA4");
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ENABLE_FOURTH_FONT_NAME"))
|
||||
.setDesc(fragWithHTML(t("ENABLE_FOURTH_FONT_DESC")))
|
||||
@@ -1765,7 +1825,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
})
|
||||
})*/
|
||||
|
||||
addIframe("r08wk-58DPk");
|
||||
addIframe(detailsEl, "r08wk-58DPk");
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LATEX_DEFAULT_NAME"))
|
||||
.setDesc(fragWithHTML(t("LATEX_DEFAULT_DESC")))
|
||||
@@ -1837,7 +1897,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
detailsEl.createDiv( { text: t("TASKBONE_DESC"), cls: "setting-item-description" });
|
||||
let taskboneAPIKeyText: TextComponent;
|
||||
|
||||
addIframe("7gu4ETx7zro");
|
||||
addIframe(detailsEl, "7gu4ETx7zro");
|
||||
new Setting(detailsEl)
|
||||
.setName(t("TASKBONE_ENABLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("TASKBONE_ENABLE_DESC")))
|
||||
@@ -2090,7 +2150,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
addIframe("H8Njp7ZXYag",52);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",52);
|
||||
Object.keys(this.plugin.settings.scriptEngineSettings)
|
||||
.filter((s) => scripts.contains(s))
|
||||
.forEach((scriptName: string) => {
|
||||
|
||||
1
src/types.d.ts
vendored
1
src/types.d.ts
vendored
@@ -32,6 +32,7 @@ declare global {
|
||||
|
||||
declare module "obsidian" {
|
||||
interface App {
|
||||
internalPlugins: any;
|
||||
isMobile(): boolean;
|
||||
getObsidianUrl(file:TFile): string;
|
||||
}
|
||||
|
||||
@@ -72,8 +72,9 @@ export class CanvasNodeFactory {
|
||||
const node = this.canvas.createFileNode({pos: {x:0,y:0}, file, subpath, save: false});
|
||||
node.setFilePath(file.path,subpath);
|
||||
node.render();
|
||||
containerEl.style.background = "var(--background-primary)";
|
||||
containerEl.appendChild(node.contentEl)
|
||||
//containerEl.style.background = "var(--background-primary)";
|
||||
node.containerEl.querySelector(".canvas-node-content-blocker")?.remove();
|
||||
containerEl.appendChild(node.containerEl)
|
||||
this.nodes.set(elementId, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { DynamicStyle } from "src/types";
|
||||
import { cloneElement } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/element/types";
|
||||
import { addAppendUpdateCustomData } from "./Utils";
|
||||
import { mutateElement } from "src/constants";
|
||||
|
||||
export const setDynamicStyle = (
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -55,57 +60,59 @@ export const setDynamicStyle = (
|
||||
const cmBlack = () => ea.getCM("#000000").lightnessTo(bgLightness);
|
||||
|
||||
|
||||
const gray1 = isGray
|
||||
const gray1 = () => isGray
|
||||
? isDark ? cmBlack().lighterBy(10) : cmBlack().darkerBy(10)
|
||||
: isDark ? cmBG().lighterBy(10).mix({color:cmBlack(),ratio:0.5}) : cmBG().darkerBy(10).mix({color:cmBlack(),ratio:0.5});
|
||||
const gray2 = isGray
|
||||
const gray2 = () => isGray
|
||||
? isDark ? cmBlack().lighterBy(4) : cmBlack().darkerBy(4)
|
||||
: isDark ? cmBG().lighterBy(4).mix({color:cmBlack(),ratio:0.5}) : cmBG().darkerBy(4).mix({color:cmBlack(),ratio:0.5});
|
||||
|
||||
|
||||
const text = cmBG().mix({color:isDark?lighter:darker, ratio:mixRatio});
|
||||
|
||||
const str = (cm: ColorMaster) => cm.stringHEX({alpha:false});
|
||||
const styleObject:{[x: string]: string;} = {
|
||||
[`--color-primary`]: str(accent()),
|
||||
[`--color-surface-low`]: str(gray1),
|
||||
[`--color-surface-mid`]: str(gray1),
|
||||
[`--color-surface-lowest`]: str(gray2),
|
||||
[`--color-surface-high`]: str(gray1.lighterBy(step)),
|
||||
[`--color-surface-low`]: str(gray1()),
|
||||
[`--color-surface-mid`]: str(gray1()),
|
||||
[`--color-surface-lowest`]: str(gray2()),
|
||||
[`--color-surface-high`]: str(gray1().lighterBy(step)),
|
||||
[`--color-on-primary-container`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
|
||||
[`--color-surface-primary-container`]: str(isDark?accent().darkerBy(step):accent().lighterBy(step)),
|
||||
//[`--color-primary-darker`]: str(accent().darkerBy(step)),
|
||||
//[`--color-primary-darkest`]: str(accent().darkerBy(step)),
|
||||
[`--button-gray-1`]: str(gray1),
|
||||
[`--button-gray-2`]: str(gray2),
|
||||
[`--input-border-color`]: str(gray1),
|
||||
[`--input-bg-color`]: str(gray2),
|
||||
[`--button-gray-1`]: str(gray1()),
|
||||
[`--button-gray-2`]: str(gray2()),
|
||||
[`--input-border-color`]: str(gray1()),
|
||||
[`--input-bg-color`]: str(gray2()),
|
||||
[`--input-label-color`]: str(text),
|
||||
[`--island-bg-color`]: gray2.alphaTo(0.93).stringHEX(),
|
||||
[`--popup-secondary-bg-color`]: gray2.alphaTo(0.93).stringHEX(),
|
||||
[`--island-bg-color`]: gray2().alphaTo(0.93).stringHEX(),
|
||||
[`--popup-secondary-bg-color`]: gray2().alphaTo(0.93).stringHEX(),
|
||||
[`--icon-fill-color`]: str(text),
|
||||
[`--text-primary-color`]: str(text),
|
||||
[`--overlay-bg-color`]: gray2.alphaTo(0.6).stringHEX(),
|
||||
[`--popup-bg-color`]: str(gray1),
|
||||
[`--overlay-bg-color`]: gray2().alphaTo(0.6).stringHEX(),
|
||||
[`--popup-bg-color`]: str(gray1()),
|
||||
[`--color-on-surface`]: str(text),
|
||||
//[`--color-gray-100`]: str(text),
|
||||
[`--color-gray-40`]: str(text), //frame
|
||||
[`--color-gray-50`]: str(text), //frame
|
||||
[`--color-surface-highlight`]: str(gray1),
|
||||
[`--color-surface-highlight`]: str(gray1()),
|
||||
//[`--color-gray-30`]: str(gray1),
|
||||
[`--color-gray-80`]: str(isDark?text.lighterBy(15):text.darkerBy(15)), //frame
|
||||
[`--sidebar-border-color`]: str(gray1),
|
||||
[`--sidebar-border-color`]: str(gray1()),
|
||||
[`--color-primary-light`]: str(accent().lighterBy(step)),
|
||||
[`--button-hover-bg`]: str(gray1),
|
||||
[`--sidebar-bg-color`]: gray2.alphaTo(0.93).stringHEX(),
|
||||
[`--sidebar-shadow`]: str(gray1),
|
||||
[`--button-hover-bg`]: str(gray1()),
|
||||
[`--sidebar-bg-color`]: gray2().alphaTo(0.93).stringHEX(),
|
||||
[`--sidebar-shadow`]: str(gray1()),
|
||||
[`--popup-text-color`]: str(text),
|
||||
[`--code-normal`]: str(text),
|
||||
[`--code-background`]: str(gray2),
|
||||
[`--code-background`]: str(gray2()),
|
||||
[`--h1-color`]: str(text),
|
||||
[`--h2-color`]: str(text),
|
||||
[`--h3-color`]: str(text),
|
||||
[`--h4-color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
[`--select-highlight-color`]: str(gray1),
|
||||
[`--select-highlight-color`]: str(gray1()),
|
||||
};
|
||||
|
||||
const styleString = Object.keys(styleObject)
|
||||
@@ -117,13 +124,36 @@ export const setDynamicStyle = (
|
||||
styleString
|
||||
)*/
|
||||
|
||||
setTimeout(()=>view.updateScene({appState:{
|
||||
frameColor: {
|
||||
stroke: isDark?str(gray2.lighterBy(15)):str(gray2.darkerBy(15)),
|
||||
fill: str((isDark?gray2.lighterBy(30):gray2.darkerBy(30)).alphaTo(0.2)),
|
||||
},
|
||||
dynamicStyle: styleObject
|
||||
}}));
|
||||
setTimeout(()=>{
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
if(!api) return;
|
||||
const frameColor = {
|
||||
stroke: str(isDark?gray2().lighterBy(15):gray2().darkerBy(15)),
|
||||
fill: str((isDark?gray2().lighterBy(30):gray2().darkerBy(30)).alphaTo(0.2)),
|
||||
nameColor: str(isDark?gray2().lighterBy(40):gray2().darkerBy(40)),
|
||||
}
|
||||
const scene = api.getSceneElements();
|
||||
scene.filter(el=>el.type==="frame").forEach((e:ExcalidrawFrameElement)=>{
|
||||
const f = cloneElement(e);
|
||||
addAppendUpdateCustomData(f,{frameColor});
|
||||
if(
|
||||
e.customData && e.customData.frameColor &&
|
||||
e.customData.frameColor.stroke === frameColor.stroke &&
|
||||
e.customData.frameColor.fill === frameColor.fill &&
|
||||
e.customData.frameColor.nameColor === frameColor.nameColor
|
||||
) {
|
||||
return;
|
||||
}
|
||||
mutateElement(e,{customData: f.customData});
|
||||
});
|
||||
|
||||
view.updateScene({
|
||||
appState:{
|
||||
frameColor,
|
||||
dynamicStyle: styleObject
|
||||
}
|
||||
});
|
||||
});
|
||||
const toolspanel = view.toolsPanelRef?.current?.containerRef?.current;
|
||||
if(toolspanel) {
|
||||
let toolsStyle = toolspanel.getAttribute("style");
|
||||
|
||||
@@ -313,4 +313,9 @@ export const readLocalFileBinary = async (filePath:string): Promise<ArrayBuffer>
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const getPathWithoutExtension = (f:TFile): string => {
|
||||
if(!f) return null;
|
||||
return f.path.substring(0, f.path.lastIndexOf("."));
|
||||
}
|
||||
@@ -270,27 +270,42 @@ class ImageCache {
|
||||
return !!this.db && !this.isInitializing && !!this.plugin && this.plugin.settings.allowImageCache;
|
||||
}
|
||||
|
||||
private fullyInitialized = false;
|
||||
|
||||
public async getImageFromCache(key_: ImageKey): Promise<string | SVGSVGElement | undefined> {
|
||||
if (!this.isReady()) {
|
||||
return null; // Database not initialized yet
|
||||
}
|
||||
|
||||
const key = getKey(key_);
|
||||
const cachedData = await this.getCacheData(key);
|
||||
const file = this.app.vault.getAbstractFileByPath(key_.filepath.split("#")[0]);
|
||||
if (!file || !(file instanceof TFile)) return undefined;
|
||||
if (cachedData && cachedData.mtime === file.stat.mtime) {
|
||||
if(cachedData.svg) {
|
||||
return convertSVGStringToElement(cachedData.svg);
|
||||
|
||||
try {
|
||||
const cachedData = this.fullyInitialized
|
||||
? await this.getCacheData(key)
|
||||
: await Promise.race([
|
||||
this.getCacheData(key),
|
||||
new Promise<undefined>((_,reject) => setTimeout(() => reject(undefined), 100))
|
||||
]);
|
||||
this.fullyInitialized = true;
|
||||
if(!cachedData) return undefined;
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(key_.filepath.split("#")[0]);
|
||||
if (!file || !(file instanceof TFile)) return undefined;
|
||||
if (cachedData && cachedData.mtime === file.stat.mtime) {
|
||||
if(cachedData.svg) {
|
||||
return convertSVGStringToElement(cachedData.svg);
|
||||
}
|
||||
if(this.obsidanURLCache.has(key)) {
|
||||
return this.obsidanURLCache.get(key);
|
||||
}
|
||||
const obsidianURL = URL.createObjectURL(cachedData.blob);
|
||||
this.obsidanURLCache.set(key, obsidianURL);
|
||||
return obsidianURL;
|
||||
}
|
||||
if(this.obsidanURLCache.has(key)) {
|
||||
return this.obsidanURLCache.get(key);
|
||||
}
|
||||
const obsidianURL = URL.createObjectURL(cachedData.blob);
|
||||
this.obsidanURLCache.set(key, obsidianURL);
|
||||
return obsidianURL;
|
||||
return undefined;
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async getBAKFromCache(filepath: string): Promise<BackupData | null> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
App,
|
||||
normalizePath, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
normalizePath, parseFrontMatterEntry, TFile, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
@@ -233,3 +233,24 @@ export const obsidianPDFQuoteWithRef = (text:string):{quote: string, link: strin
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const extractSVGPNGFileName = (text:string) => {
|
||||
const regex = /\[\[([^\]|#^]+\.(?:svg|png))(?:[^\]]+)?\]\]|\[[^\]]+\]\(([^\)]+\.(?:svg|png))\)/;
|
||||
const match = text.match(regex);
|
||||
return match ? (match[1] || match[2]) : null;
|
||||
}
|
||||
|
||||
export const getFileCSSClasses = (
|
||||
file: TFile,
|
||||
): string[] => {
|
||||
if (file) {
|
||||
const plugin = window.ExcalidrawAutomate.plugin;
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if(!fileCache?.frontmatter) return [];
|
||||
const x = parseFrontMatterEntry(fileCache.frontmatter, "cssclasses");
|
||||
if (Array.isArray(x)) return x
|
||||
if (typeof x === "string") return Array.from(new Set(x.split(/[, ]+/).filter(Boolean)));
|
||||
return [];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
App,
|
||||
Notice,
|
||||
parseFrontMatterEntry,
|
||||
request,
|
||||
requestUrl,
|
||||
TFile,
|
||||
@@ -9,8 +10,11 @@ import {
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { BinaryFileData, DataURL} from "@zsviczian/excalidraw/types/types";
|
||||
import {
|
||||
ASSISTANT_FONT,
|
||||
CASCADIA_FONT,
|
||||
VIRGIL_FONT,
|
||||
} from "src/constFonts";
|
||||
import {
|
||||
FRONTMATTER_KEY_EXPORT_DARK,
|
||||
FRONTMATTER_KEY_EXPORT_TRANSPARENT,
|
||||
FRONTMATTER_KEY_EXPORT_SVGPADDING,
|
||||
@@ -29,7 +33,9 @@ import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
||||
import ExcalidrawScene from "src/svgToExcalidraw/elements/ExcalidrawScene";
|
||||
import { FILENAMEPARTS } from "./UtilTypes";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading } from "./ObsidianUtils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils";
|
||||
import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
|
||||
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -257,6 +263,7 @@ export const getSVG = async (
|
||||
scene: any,
|
||||
exportSettings: ExportSettings,
|
||||
padding: number,
|
||||
srcFile: TFile|null, //if set, will replace markdown links with obsidian links
|
||||
): Promise<SVGSVGElement> => {
|
||||
let elements:ExcalidrawElement[] = scene.elements;
|
||||
if(elements.some(el => el.type === "embeddable")) {
|
||||
@@ -267,8 +274,13 @@ export const getSVG = async (
|
||||
}
|
||||
|
||||
try {
|
||||
return await exportToSvg({
|
||||
elements,
|
||||
const svg = await exportToSvg({
|
||||
elements: srcFile
|
||||
? updateElementLinksToObsidianLinks({
|
||||
elements,
|
||||
hostFile: srcFile,
|
||||
})
|
||||
: elements,
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
@@ -278,7 +290,17 @@ export const getSVG = async (
|
||||
},
|
||||
files: scene.files,
|
||||
exportPadding: padding,
|
||||
exportingFrame: null,
|
||||
renderEmbeddables: true,
|
||||
});
|
||||
if(svg) {
|
||||
svg.addClass("excalidraw-svg");
|
||||
if(srcFile instanceof TFile) {
|
||||
const cssClasses = getFileCSSClasses(srcFile);
|
||||
cssClasses.forEach((cssClass) => svg.addClass(cssClass));
|
||||
}
|
||||
}
|
||||
return svg;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
@@ -358,12 +380,15 @@ export const embedFontsInSVG = (
|
||||
svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
const includesCascadia = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
const includesAssistant = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Assistant']") != null;
|
||||
const includesLocalFont =
|
||||
svg.querySelector("text[font-family^='LocalFont']") != null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont)) {
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont || includesAssistant)) {
|
||||
defs.innerHTML = `<style>${includesVirgil ? VIRGIL_FONT : ""}${
|
||||
includesCascadia ? CASCADIA_FONT : ""
|
||||
includesCascadia ? CASCADIA_FONT : ""}${
|
||||
includesAssistant ? ASSISTANT_FONT : ""
|
||||
}${includesLocalFont ? plugin.fourthFontDef : ""}</style>`;
|
||||
}
|
||||
return svg;
|
||||
@@ -767,3 +792,17 @@ export const convertSVGStringToElement = (svg: string): SVGSVGElement => {
|
||||
}
|
||||
|
||||
export const escapeRegExp = (str:string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
|
||||
export const addIframe = (containerEl: HTMLElement, link:string, startAt?: number, style:string = "settings") => {
|
||||
const wrapper = containerEl.createDiv({cls: `excalidraw-videoWrapper ${style}`})
|
||||
wrapper.createEl("iframe", {
|
||||
attr: {
|
||||
allowfullscreen: true,
|
||||
allow: "encrypted-media;picture-in-picture",
|
||||
frameborder: "0",
|
||||
title: "YouTube video player",
|
||||
src: "https://www.youtube.com/embed/" + link + (startAt ? "?start=" + startAt : ""),
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
}
|
||||
57
src/utils/YoutTubeUtils.ts
Normal file
57
src/utils/YoutTubeUtils.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
const REG_YOUTUBE = /^(?:http(?:s)?:\/\/)?(?:www\.)?youtu(?:be\.com|\.be)\/(embed\/|watch\?v=|shorts\/|playlist\?list=|embed\/videoseries\?list=)?([a-zA-Z0-9_-]+)(?:\?t=|.*&t=|\?start=|.*&start=)?([a-zA-Z0-9_-]+)?[^\s]*$/;
|
||||
export const isYouTube = (url: string): boolean => {
|
||||
return Boolean(
|
||||
url.match(REG_YOUTUBE)
|
||||
);
|
||||
}
|
||||
|
||||
export const getYouTubeStartAt = (url: string): string => {
|
||||
const ytLink = url.match(REG_YOUTUBE);
|
||||
if (ytLink?.[2]) {
|
||||
const time = ytLink[3] ? parseInt(ytLink[3]) : 0;
|
||||
const hours = Math.floor(time / 3600);
|
||||
const minutes = Math.floor((time - hours * 3600) / 60);
|
||||
const seconds = time - hours * 3600 - minutes * 60;
|
||||
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
||||
}
|
||||
return "00:00:00";
|
||||
};
|
||||
|
||||
export const isValidYouTubeStart = (value: string): boolean => {
|
||||
if(/^[0-9]+$/.test(value)) return true; // Matches only numbers (seconds)
|
||||
if(/^[0-9]+:[0-9]+$/.test(value)) return true; // Matches only numbers (minutes and seconds)
|
||||
if(/^[0-9]+:[0-9]+:[0-9]+$/.test(value)) return true; // Matches only numbers (hours, minutes, and seconds
|
||||
};
|
||||
|
||||
export const updateYouTubeStartTime = (link: string, startTime: string): string => {
|
||||
const match = link.match(REG_YOUTUBE);
|
||||
if (match?.[2]) {
|
||||
const startTimeParam = `t=${timeStringToSeconds(startTime)}`;
|
||||
let updatedLink = link;
|
||||
if (match[3]) {
|
||||
// If start time already exists, update it
|
||||
updatedLink = link.replace(/([?&])t=[a-zA-Z0-9_-]+/, `$1${startTimeParam}`);
|
||||
updatedLink = updatedLink.replace(/([?&])start=[a-zA-Z0-9_-]+/, `$1${startTimeParam}`);
|
||||
} else {
|
||||
// If no start time exists, add it to the link
|
||||
updatedLink += (link.includes('?') ? '&' : '?') + startTimeParam;
|
||||
}
|
||||
return updatedLink;
|
||||
}
|
||||
return link;
|
||||
};
|
||||
|
||||
const timeStringToSeconds = (time: string): number => {
|
||||
const timeParts = time.split(':').map(Number);
|
||||
const totalParts = timeParts.length;
|
||||
|
||||
if (totalParts === 1) {
|
||||
return timeParts[0]; // Only seconds provided (ss)
|
||||
} else if (totalParts === 2) {
|
||||
return timeParts[0] * 60 + timeParts[1]; // Minutes and seconds provided (mm:ss)
|
||||
} else if (totalParts === 3) {
|
||||
return timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]; // Hours, minutes, and seconds provided (hh:mm:ss)
|
||||
}
|
||||
|
||||
return 0; // Invalid format, return 0 or handle accordingly
|
||||
};
|
||||
23
styles.css
23
styles.css
@@ -345,7 +345,7 @@ label.color-input-container > input {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.excalidraw-settings input {
|
||||
.excalidraw-settings input:not([type="color"]) {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
@@ -371,10 +371,6 @@ div.excalidraw-draginfo {
|
||||
background: initial;
|
||||
}
|
||||
|
||||
.excalidraw .HelpDialog__key {
|
||||
background-color: var(--color-gray-80) !important;
|
||||
}
|
||||
|
||||
.excalidraw .embeddable-menu {
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
@@ -475,4 +471,21 @@ summary.excalidraw-setting-h4 {
|
||||
|
||||
hr.excalidraw-setting-hr {
|
||||
margin: 1rem 0rem 0rem 0rem;
|
||||
}
|
||||
|
||||
.excalidraw-mdEmbed-hideFilename .mod-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.canvas-node:not(.is-editing):has(.excalidraw-canvas-immersive) {
|
||||
::-webkit-scrollbar,
|
||||
::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
}
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.canvas-node:not(.is-editing) .canvas-node-container:has(.excalidraw-canvas-immersive) {
|
||||
border: unset;
|
||||
box-shadow: unset;
|
||||
}
|
||||
Reference in New Issue
Block a user