1.5.23 WIP (ExcalidrawAutomate suggester)

This commit is contained in:
Zsolt Viczian
2022-01-26 21:57:27 +01:00
parent 4fd5c13d1e
commit 46cbcc581c
8 changed files with 558 additions and 79 deletions

View File

@@ -0,0 +1,301 @@
export const EXCALIDRAW_AUTOMATE_INFO = [
{
field: "plugin",
desc: "The ExcalidrawPlugin object",
after: '.',
alt: true,
},
{
field: "elementsDict",
desc: "The {} dictionary object, contains the ExcalidrawElements currently edited in Automate indexed by el.id",
after: '[""]',
alt: true,
},
{
field: "imagesDict",
desc: "the images files including DataURL, indexed by fileId",
after: '[""]',
alt: true,
},
{
field: "style.strokeColor",
desc: "[string] valid css color. See https://www.w3schools.com/colors/default.asp for more.",
after: '',
alt: true,
},
{
field: "style.backgroundColor",
desc: "[string] valid css color. See https://www.w3schools.com/colors/default.asp for more.",
after: '',
alt: true,
},
{
field: "style.angle",
desc: "[number] rotation of the object in radian",
after: '',
alt: true,
},
{
field: "style.fillStyle",
desc: "[string] 'hachure' | 'cross-hatch' | 'solid'",
after: '',
alt: true,
},
{
field: "style.strokeWidth",
desc: "[number]",
after: '',
alt: true,
},
{
field: "style.strokeStyle",
desc: "[string] 'solid' | 'dashed' | 'dotted'",
after: '',
alt: true,
},
{
field: "style.roughness",
desc: "[number]",
after: '',
alt: true,
},
{
field: "style.opacity",
desc: "[number]",
after: '',
alt: true,
},
{
field: "style.strokeSharpness",
desc: "[string] 'round' | 'sharp'",
after: '',
alt: true,
},
{
field: "style.fontFamily",
desc: "[number] 1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont",
after: '',
alt: true,
},
{
field: "style.fontSize",
desc: "[number]",
after: '',
alt: true,
},
{
field: "style.textAlign",
desc: "[string] 'left' | 'right' | 'center'",
after: '',
alt: true,
},
{
field: "style.verticalAlign",
desc: "[string] for future use, has no effect currently; 'top' | 'bottom' | 'middle'",
after: '',
alt: true,
},
{
field: "style.startArrowHead",
desc: "[string] 'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: '',
alt: true,
},
{
field: "style.endArrowHead",
desc: "[string] 'triangle' | 'dot' | 'arrow' | 'bar' | null",
after: '',
alt: true,
},
{
field: "canvas.theme",
desc: "[string] 'dark' | 'light'",
after: '',
alt: true,
},
{
field: "canvas.viewBackgroundColor",
desc: "[string] valid css color. See https://www.w3schools.com/colors/default.asp for more.",
after: '',
alt: true,
},
{
field: "canvas.gridSize",
desc: "[number]",
after: '',
alt: true,
},
{
field: "addToGroup",
desc: "addToGroup(objectIds: []): string;",
after: '',
alt: true,
},
{
field: "toCliboard",
desc: "toClipboard(templatePath?: string): void; //copies current elements using template to clipboard, ready to be pasted into an excalidraw canvas",
after: '',
alt: true,
},
{
field: "getElements",
desc: "getElements(): ExcalidrawElement[]; //get all elements from ExcalidrawAutomate elementsDict",
after: '',
alt: true,
},
getElement(id: string): ExcalidrawElement; //get single element from ExcalidrawAutomate elementsDict
create(params?: {
//create a drawing and save it to filename
filename?: string; //if null: default filename as defined in Excalidraw settings
foldername?: string; //if null: default folder as defined in Excalidraw settings
templatePath?: string;
onNewPane?: boolean;
frontmatterKeys?: {
"excalidraw-plugin"?: "raw" | "parsed";
"excalidraw-link-prefix"?: string;
"excalidraw-link-brackets"?: boolean;
"excalidraw-url-prefix"?: string;
};
}): Promise<string>;
createSVG(
templatePath?: string,
embedFont?: boolean,
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
theme?: string,
): Promise<SVGSVGElement>;
createPNG(
templatePath?: string,
scale?: number,
exportSettings?: ExportSettings, //use ExcalidrawAutomate.getExportSettings(boolean,boolean)
loader?: EmbeddedFilesLoader, //use ExcalidrawAutomate.getEmbeddedFilesLoader(boolean?)
theme?: string,
): Promise<any>;
wrapText(text: string, lineLen: number): string;
addRect(topX: number, topY: number, width: number, height: number): string;
addDiamond(topX: number, topY: number, width: number, height: number): string;
addEllipse(topX: number, topY: number, width: number, height: number): string;
addBlob(topX: number, topY: number, width: number, height: number): string;
addText(
topX: number,
topY: number,
text: string,
formatting?: {
wrapAt?: number;
width?: number;
height?: number;
textAlign?: string;
box?: boolean | "box" | "blob" | "ellipse" | "diamond"; //if !null, text will be boxed
boxPadding?: number;
},
id?: string,
): string;
addLine(points: [[x: number, y: number]]): string;
addArrow(
points: [[x: number, y: number]],
formatting?: {
startArrowHead?: string;
endArrowHead?: string;
startObjectId?: string;
endObjectId?: string;
},
): string;
addImage(topX: number, topY: number, imageFile: TFile): Promise<string>;
addLaTex(topX: number, topY: number, tex: string): Promise<string>;
connectObjects(
objectA: string,
connectionA: ConnectionPoint, //type ConnectionPoint = "top" | "bottom" | "left" | "right" | null
objectB: string,
connectionB: ConnectionPoint, //when passed null, Excalidraw will automatically decide
formatting?: {
numberOfPoints?: number; //points on the line. Default is 0 ie. line will only have a start and end point
startArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
endArrowHead?: string; //"triangle"|"dot"|"arrow"|"bar"|null
padding?: number;
},
): void;
clear(): void; //clear elementsDict and imagesDict only
reset(): void; //clear() + reset all style values to default
isExcalidrawFile(f: TFile): boolean; //returns true if MD file is an Excalidraw file
//view manipulation
targetView: ExcalidrawView; //the view currently edited
setView(view: ExcalidrawView | "first" | "active"): ExcalidrawView;
getExcalidrawAPI(): any; //https://github.com/excalidraw/excalidraw/tree/master/src/packages/excalidraw#ref
getViewElements(): ExcalidrawElement[]; //get elements in View
deleteViewElements(el: ExcalidrawElement[]): boolean;
getViewSelectedElement(): ExcalidrawElement; //get the selected element in the view, if more are selected, get the first
getViewSelectedElements(): ExcalidrawElement[];
getViewFileForImageElement(el: ExcalidrawElement): TFile | null; //Returns the TFile file handle for the image element
copyViewElementsToEAforEditing(elements: ExcalidrawElement[]): void; //copies elements from view to elementsDict for editing
viewToggleFullScreen(forceViewMode?: boolean): void;
connectObjectWithViewSelectedElement( //connect an object to the selected element in the view
objectA: string, //see connectObjects
connectionA: ConnectionPoint,
connectionB: ConnectionPoint,
formatting?: {
numberOfPoints?: number;
startArrowHead?: string;
endArrowHead?: string;
padding?: number;
},
): boolean;
addElementsToView( //Adds elements from elementsDict to the current view
repositionToCursor?: boolean, //default is false
save?: boolean, //default is true
//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
newElementsOnTop?: boolean, //default is false, i.e. the new elements get to the bottom of the stack
): Promise<boolean>;
onDropHook(data: {
//if set Excalidraw will call this function onDrop events
ea: ExcalidrawAutomate;
event: React.DragEvent<HTMLDivElement>;
draggable: any; //Obsidian draggable object
type: "file" | "text" | "unknown";
payload: {
files: TFile[]; //TFile[] array of dropped files
text: string; //string
};
excalidrawFile: TFile; //the file receiving the drop event
view: ExcalidrawView; //the excalidraw view receiving the drop
pointerPosition: { x: number; y: number }; //the pointer position on canvas at the time of drop
}): boolean; //a return of true will stop the default onDrop processing in Excalidraw
mostRecentMarkdownSVG: SVGSVGElement; //Markdown renderer will drop a copy of the most recent SVG here for debugging purposes
getEmbeddedFilesLoader(isDark?: boolean): EmbeddedFilesLoader; //utility function to generate EmbeddedFilesLoader object
getExportSettings( //utility function to generate ExportSettings object
withBackground: boolean,
withTheme: boolean,
): ExportSettings;
getBoundingBox(elements: ExcalidrawElement[]): {
//get bounding box of elements
topX: number; //bounding box is the box encapsulating all of the elements completely
topY: number;
width: number;
height: number;
};
//elements grouped by the highest level groups
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
getLargestElement(elements: ExcalidrawElement[]): ExcalidrawElement;
// 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, //if given, element is inflated by this value
): Point[];
//See OCR plugin for example on how to use scriptSettings
activeScript: string; //Set automatically by the ScriptEngine
getScriptSettings(): {}; //Returns script settings. Saves settings in plugin settings, under the activeScript key
setScriptSettings(settings: any): Promise<void>; //sets script settings.
openFileInNewOrAdjacentLeaf(file: TFile): WorkspaceLeaf; //Open a file in a new workspaceleaf or reuse an existing adjacent leaf depending on Excalidraw Plugin Settings
measureText(text: string): { width: number; height: number }; //measure text size based on current style settings
//verifyMinimumPluginVersion returns true if plugin version is >= than required
//recommended use:
//if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.5.20")) {new Notice("message");return;}
verifyMinimumPluginVersion(requiredVersion: string): boolean;

View File

@@ -5,7 +5,7 @@
originalText: this is the text without added linebreaks for wrapping. This will be parsed or markup depending on view mode
rawText: text with original markdown markup and without the added linebreaks for wrapping
*/
import { App, TFile } from "obsidian";
import { App, Notice, TFile } from "obsidian";
import {
nanoid,
FRONTMATTER_KEY_CUSTOM_PREFIX,
@@ -34,6 +34,7 @@ import {
} from "@zsviczian/excalidraw/types/element/types";
import { BinaryFiles, SceneData } from "@zsviczian/excalidraw/types/types";
import { EmbeddedFile } from "./EmbeddedFileLoader";
import { t } from "./lang/helpers";
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
@@ -248,15 +249,33 @@ export class ExcalidrawData {
}
}
//Load scene: Read the JSON string after "# Drawing"
const sceneJSONandPOS = getJSON(data);
if (sceneJSONandPOS.pos === -1) {
return false; //JSON not found
// https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/396
let sceneJSONandPOS = null;
const loadJSON = ():{ scene: string; pos: number } => {
//Load scene: Read the JSON string after "# Drawing"
const sceneJSONandPOS = getJSON(data);
if (sceneJSONandPOS.pos === -1) {
throw(new Error("Excalidraw JSON not found in the file"))
}
if (!this.scene) {
this.scene = JSON_parse(sceneJSONandPOS.scene); //this is a workaround to address when files are mereged by sync and one version is still an old markdown without the codeblock ```
}
return sceneJSONandPOS
}
if (!this.scene) {
this.scene = JSON_parse(sceneJSONandPOS.scene); //this is a workaround to address when files are mereged by sync and one version is still an old markdown without the codeblock ```
try {
sceneJSONandPOS = loadJSON()
} catch(e) {
const bakfile = this.app.vault.getAbstractFileByPath(file.path+".bak");
if(bakfile && bakfile instanceof TFile)
{
data = await this.app.vault.read(bakfile);
sceneJSONandPOS = loadJSON();
new Notice(t("LOAD_FROM_BACKUP"),4000);
} else {
throw(e);
}
}
if (!this.scene.files) {
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
}
@@ -1142,3 +1161,4 @@ export const getTransclusion = async (
}
return { contents: linkParts.original.trim(), lineNum: 0 };
};

View File

@@ -265,7 +265,16 @@ export default class ExcalidrawView extends TextFileView {
//debug({where:"ExcalidrawView.save",file:this.file.name,dataTheme:this.excalidrawData.scene.appState.theme,before:"loadDrawing(false)"})
await this.loadDrawing(false);
}
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/396
const bakfilepath= this.file.path+".bak";
if(await this.app.vault.adapter.exists(bakfilepath)) {
await this.app.vault.adapter.remove(bakfilepath);
}
await this.app.vault.adapter.copy(this.file.path,bakfilepath);
await super.save();
await this.app.vault.adapter.remove(bakfilepath);
if (!this.autosaving) {
if (this.plugin.settings.autoexportSVG) {
@@ -763,8 +772,8 @@ export default class ExcalidrawView extends TextFileView {
e.message === "Cannot read property 'index' of undefined"
? "\n'# Drawing' section is likely missing"
: ""
}\nTry manually fixing the file or restoring an earlier version from sync history`,
8000,
}\n\nTry manually fixing the file or restoring an earlier version from sync history.\n\nYou may also look for .bak file with last working version in the same folder. Note the .bak file might not get synchronized, so look for .bak file on other devices as well.`,
10000,
);
this.setMarkdownView();
return;

73
src/FieldSuggestor.ts Normal file
View File

@@ -0,0 +1,73 @@
import {
Editor,
EditorPosition,
EditorSuggest,
EditorSuggestContext,
EditorSuggestTriggerInfo,
TFile,
} from "obsidian";
import { FRONTMATTER_KEYS_INFO } from "./constants";
import type ExcalidrawPlugin from "./main";
export class FieldSuggestor extends EditorSuggest<string> {
plugin: ExcalidrawPlugin;
constructor(plugin: ExcalidrawPlugin) {
super(plugin.app);
this.plugin = plugin;
}
onTrigger(
cursor: EditorPosition,
editor: Editor,
_: TFile
): EditorSuggestTriggerInfo | null {
if (this.plugin.settings.fieldSuggestor) {
const sub = editor.getLine(cursor.line).substring(0, cursor.ch);
const match = sub.match(/^excalidraw-(.*)$/)?.[1];
if (match !== undefined) {
return {
end: cursor,
start: {
ch: sub.lastIndexOf(match),
line: cursor.line,
},
query: match,
};
}
}
return null;
}
getSuggestions = (context: EditorSuggestContext) => {
const { query } = context;
return FRONTMATTER_KEYS_INFO.map((sug) => sug.field).filter((sug) =>
sug.includes(query)
);
};
renderSuggestion(suggestion: string, el: HTMLElement): void {
el.createDiv({
text: suggestion.replace("BC-", ""),
cls: "BC-suggester-container",
attr: {
"aria-label": FRONTMATTER_KEYS_INFO.find((f) => f.field === suggestion)?.desc,
"aria-label-position": "right",
},
});
}
selectSuggestion(suggestion: string): void {
const { context } = this;
if (context) {
const replacement = `${suggestion}${
FRONTMATTER_KEYS_INFO.find((f) => f.field === suggestion)?.after
}`;
context.editor.replaceRange(
replacement,
{ ch: 0, line: context.start.line },
context.end
);
}
}
}

View File

@@ -29,6 +29,58 @@ export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode";
export const FRONTMATTER_KEY_FONT = "excalidraw-font";
export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color";
export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css";
export const FRONTMATTER_KEYS_INFO = [
{
field: FRONTMATTER_KEY,
desc: "Denotes an excalidraw file. If key is not present, the file will not be recognized as an Excalidarw file. Valid values are 'parsed' and 'raw'",
after: ': parsed',
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_PREFIX,
desc: "Set custom prefix to denote text element containing a valid internal link. Set to empty string if you do not want to show a prefix",
after: ': "📍"',
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_URL_PREFIX,
desc: "Set custom prefix to denote text element containing a valid external link. Set to empty string if you do not want to show a prefix",
after: ': "🌐"',
alt: true,
},
{
field: FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS,
desc: "Set to true, if you want to display [[square brackets]] around the links in Text Elements",
after: ': true',
alt: true,
},
{
field: FRONTMATTER_KEY_DEFAULT_MODE,
desc: "Specifies how Excalidraw should open by default. Valid values are: view|zen",
after: ': view',
alt: true,
},
{
field: FRONTMATTER_KEY_FONT,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: Virgil|Cascadia|font_file_name.extension",
after: ': Virgil',
alt: true,
},
{
field: FRONTMATTER_KEY_FONTCOLOR,
desc: "This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: css-color-name|#HEXcolor|any-other-html-standard-format",
after: ': SteelBlue',
alt: true,
},
{
field: FRONTMATTER_KEY_MD_STYLE,
desc: 'This key applies to Markdown Embeds. You can control the appearance of the embedded markdown file on a file by file bases by adding the this front matter keys to your markdown document. Valid values are: "css-filename|css snippet"',
after: ': ""',
alt: true,
},
];
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
export const ICON_NAME = "excalidraw-icon";
export const MAX_COLORS = 5;

View File

@@ -57,7 +57,7 @@ export default {
"SHIFT CLICK this button to open the link in a new pane.\n" +
"CTRL/CMD CLICK the Image or TextElement on the canvas has the same effect!",
TEXT_ELEMENT_EMPTY:
"No ImageElement is selected or TextElement is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
"No ImageElement is selected or TextElement is empty, or [[valid-link|alias]]</code> or <code>[alias](valid-link) is not found",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\ < > : | ?',
FILE_DOES_NOT_EXIST:
@@ -144,33 +144,33 @@ export default {
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
LINKS_HEAD: "Links and transclusion",
LINKS_DESC:
"CTRL/CMD + CLICK on [[Text Elements]] to open them as links. " +
"If the selected text has more than one [[valid Obsidian links]], only the first will be opened. " +
"If the text starts as a valid web link (i.e. https:// or http://), then " +
"CTRL/CMD + CLICK on <code>[[Text Elements]]</code> to open them as links. " +
"If the selected text has more than one <code>[[valid Obsidian links]]</code>, only the first will be opened. " +
"If the text starts as a valid web link (i.e. <code>https://</code> or <code>http://</code>), then " +
"the plugin will open it in a browser. " +
"When Obsidian files change, the matching [[link]] in your drawings will also change. " +
"If you don't want text accidentally changing in your drawings use [[links|with aliases]].",
"When Obsidian files change, the matching <code>[[link]]</code> in your drawings will also change. " +
"If you don't want text accidentally changing in your drawings use <code>[[links|with aliases]]</code>.",
ADJACENT_PANE_NAME: "Open in adjacent pane",
ADJACENT_PANE_DESC:
"When CTRL/CMD+SHIFT 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 adjacent pane, and try to open the link there. " +
"Excalidraw will first look too the right, then to the left, then down, then up. If no pane is found, Excalidraw will open " +
"a new pane.",
LINK_BRACKETS_NAME: "Show [[brackets]] around links",
LINK_BRACKETS_NAME: "Show <code>[[brackets]]</code> around links",
LINK_BRACKETS_DESC: `${
"In PREVIEW mode, when parsing Text Elements, place brackets around links. " +
"You can override this setting for a specific drawing by adding '"
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false' to the file's frontmatter.`,
"You can override this setting for a specific drawing by adding <code>"
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false</code> to the file's frontmatter.`,
LINK_PREFIX_NAME: "Link prefix",
LINK_PREFIX_DESC: `${
"In PREVIEW mode, if the Text Element contains a link, precede the text with these characters. " +
"You can override this setting for a specific drawing by adding '"
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "' to the file's frontmatter.`,
"You can override this setting for a specific drawing by adding <code>"
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "</code> to the file's frontmatter.`,
URL_PREFIX_NAME: "URL prefix",
URL_PREFIX_DESC: `${
"In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " +
"You can override this setting for a specific drawing by adding '"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "' to the file's frontmatter.`,
"You can override this setting for a specific drawing by adding <code>"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> to the file's frontmatter.`,
LINK_CTRL_CLICK_NAME:
"CTRL/CMD + CLICK on text with [[links]] or [](links) to open them",
LINK_CTRL_CLICK_DESC:
@@ -187,7 +187,7 @@ export default {
"![[markdown page]] format.",
GET_URL_TITLE_NAME: "Use iframely to resolve page title",
GET_URL_TITLE_DESC:
"Use the http://iframely.server.crestify.com/iframely?url= to get title of page when dropping a link into Excalidraw",
"Use the <code>http://iframely.server.crestify.com/iframely?url=</code> to get title of page when dropping a link into Excalidraw",
MD_HEAD: "Markdown-embed settings",
MD_HEAD_DESC:
"You can transclude formatted markdown documents into drawings as images CTRL/CMD drop from the file explorer or using " +
@@ -196,17 +196,17 @@ export default {
MD_TRANSCLUDE_WIDTH_DESC:
"The width of the markdown page. This effects the word wrapping when transcluding longer paragraphs, and the width of " +
"the image element. You can override the default width of " +
"an embedded file using the [[filename#heading|WIDTHxMAXHEIGHT]] syntax in markdown view mode under embedded files.",
"an embedded file using the <code>[[filename#heading|WIDTHxMAXHEIGHT]]</code> syntax in markdown view mode under embedded files.",
MD_TRANSCLUDE_HEIGHT_NAME:
"Default maximum height of a transcluded markdown document",
MD_TRANSCLUDE_HEIGHT_DESC:
"The embedded image will be as high as the markdown text requries, but not higher than this value. " +
"You can override this value by editing the embedded image link in markdown view mode with the following syntax [[filename#^blockref|WIDTHxMAXHEIGHT]].",
"You can override this value by editing the embedded image link in markdown view mode with the following syntax <code>[[filename#^blockref|WIDTHxMAXHEIGHT]]</code>.",
MD_DEFAULT_FONT_NAME:
"The default font typeface to use for embedded markdown files.",
MD_DEFAULT_FONT_DESC:
'Set this value to "Virgil" or "Cascadia" or the filename of a valid .ttf, .woff, or .woff2 font e.g. "MyFont.woff2" ' +
'You can override this setting by adding the following frontmatter-key to the embedded markdown file: "excalidraw-font: font_or_filename"',
'Set this value to "Virgil" or "Cascadia" or the filename of a valid <code>.ttf</code>, <code>.woff</code>, or <code>.woff2</code> font e.g. <code>MyFont.woff2</code> ' +
'You can override this setting by adding the following frontmatter-key to the embedded markdown file: <code>excalidraw-font: font_or_filename</code>',
MD_DEFAULT_COLOR_NAME:
"The default font color to use for embedded markdown files.",
MD_DEFAULT_COLOR_DESC:
@@ -232,8 +232,8 @@ export default {
EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image",
EMBED_WIDTH_DESC:
"Only relevant if embed type is excalidraw. Has no effect on PNG and SVG embeds. The default width of an embedded drawing. You can specify a custom " +
"width when embedding an image using the ![[drawing.excalidraw|100]] or " +
"[[drawing.excalidraw|100x100]] format.",
"width when embedding an image using the <code>![[drawing.excalidraw|100]]</code> or " +
"<code>[[drawing.excalidraw|100x100]]</code> format.",
EMBED_TYPE_NAME: "Type of file to insert into the document",
EMBED_TYPE_DESC:
"When you embed an image into a document using the command palette this setting will specify if Excalidraw should embed the original excalidraw file " +
@@ -278,7 +278,12 @@ export default {
"when you open a legacy file for editing.",
EXPERIMENTAL_HEAD: "Experimental features",
EXPERIMENTAL_DESC:
"These setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",
"Some of these setting will not take effect immediately, only when the File Explorer is refreshed, or Obsidian restarted.",
FIELD_SUGGESTOR_NAME: "Enable Field Suggestor",
FIELD_SUGGESTOR_DESC: 'You can customize Excalidraw by adding special frontmatter tags to your drawings, or to markdown ' +
'files embedded in Excalidraw drawings, for example <code>excalidraw-link-prefix: "😀"</code>.</br>The Field ' +
'Suggestor will show an autocomplete menu with all available Excalidraw field options ' +
'when you type <code>excalidraw-</code>.',
FILETYPE_NAME: "Display type (✏️) for excalidraw.md files in File Explorer",
FILETYPE_DESC:
"Excalidraw files will receive an indicator using the emojii or text defined in the next setting.",
@@ -320,4 +325,7 @@ export default {
//Scripts.ts
SCRIPT_EXECUTION_ERROR:
"Script execution error. Please find error message on the developer console.",
//ExcalidrawData.ts
LOAD_FROM_BACKUP: "Excalidraw file was corrupted. Loading from backup file.",
};

View File

@@ -80,6 +80,7 @@ import {
markdownPostProcessor,
observer,
} from "./MarkdownPostProcessor";
import { FieldSuggestor } from "./FieldSuggestor";
declare module "obsidian" {
interface App {
@@ -156,7 +157,8 @@ export default class ExcalidrawPlugin extends Plugin {
this.registerCommands();
this.registerEventListeners();
this.initializeFourthFont();
this.registerEditorSuggest(new FieldSuggestor(this));
//inspiration taken from kanban:
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
this.registerMonkeyPatches();

View File

@@ -1,4 +1,3 @@
import { borderTopRightRadius } from "html2canvas/dist/types/css/property-descriptors/border-radius";
import {
App,
DropdownComponent,
@@ -52,6 +51,7 @@ export interface ExcalidrawSettings {
experimentalLivePreview: boolean;
experimentalEnableFourthFont: boolean;
experimantalFourthFont: string;
fieldSuggestor: boolean;
loadCount: number; //version 1.2 migration counter
drawingOpenCount: number;
library: string;
@@ -107,6 +107,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
experimentalLivePreview: true,
experimentalEnableFourthFont: false,
experimantalFourthFont: "Virgil",
fieldSuggestor: true,
compatibilityMode: false,
loadCount: 0,
drawingOpenCount: 0,
@@ -129,6 +130,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
scriptEngineSettings: {},
};
const fragWithHTML = (html: string) =>
createFragment((frag) => (frag.createDiv().innerHTML = html));
export class ExcalidrawSettingTab extends PluginSettingTab {
plugin: ExcalidrawPlugin;
private requestEmbedUpdate: boolean = false;
@@ -196,7 +200,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FOLDER_NAME"))
.setDesc(t("FOLDER_DESC"))
.setDesc(fragWithHTML(t("FOLDER_DESC")))
.addText((text) =>
text
.setPlaceholder("Excalidraw")
@@ -209,7 +213,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FOLDER_EMBED_NAME"))
.setDesc(t("FOLDER_EMBED_DESC"))
.setDesc(fragWithHTML(t("FOLDER_EMBED_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.embedUseExcalidrawFolder)
@@ -221,7 +225,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("TEMPLATE_NAME"))
.setDesc(t("TEMPLATE_DESC"))
.setDesc(fragWithHTML(t("TEMPLATE_DESC")))
.addText((text) =>
text
.setPlaceholder("Excalidraw/Template")
@@ -234,7 +238,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("SCRIPT_FOLDER_NAME"))
.setDesc(t("SCRIPT_FOLDER_DESC"))
.setDesc(fragWithHTML(t("SCRIPT_FOLDER_DESC")))
.addText((text) =>
text
.setPlaceholder("Excalidraw/Scripts")
@@ -263,7 +267,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FILENAME_PREFIX_NAME"))
.setDesc(t("FILENAME_PREFIX_DESC"))
.setDesc(fragWithHTML(t("FILENAME_PREFIX_DESC")))
.addText((text) =>
text
.setPlaceholder("Drawing ")
@@ -281,7 +285,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FILENAME_PREFIX_EMBED_NAME"))
.setDesc(t("FILENAME_PREFIX_EMBED_DESC"))
.setDesc(fragWithHTML(t("FILENAME_PREFIX_EMBED_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.drawingEmbedPrefixWithFilename)
@@ -293,7 +297,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FILENAME_DATE_NAME"))
.setDesc(t("FILENAME_DATE_DESC"))
.setDesc(fragWithHTML(t("FILENAME_DATE_DESC")))
.addText((text) =>
text
.setPlaceholder("YYYY-MM-DD HH.mm.ss")
@@ -313,7 +317,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MATCH_THEME_NAME"))
.setDesc(t("MATCH_THEME_DESC"))
.setDesc(fragWithHTML(t("MATCH_THEME_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.matchTheme)
@@ -325,7 +329,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MATCH_THEME_ALWAYS_NAME"))
.setDesc(t("MATCH_THEME_ALWAYS_DESC"))
.setDesc(fragWithHTML(t("MATCH_THEME_ALWAYS_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.matchThemeAlways)
@@ -337,7 +341,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MATCH_THEME_TRIGGER_NAME"))
.setDesc(t("MATCH_THEME_TRIGGER_DESC"))
.setDesc(fragWithHTML(t("MATCH_THEME_TRIGGER_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.matchThemeTrigger)
@@ -349,7 +353,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("DEFAULT_OPEN_MODE_NAME"))
.setDesc(t("DEFAULT_OPEN_MODE_DESC"))
.setDesc(fragWithHTML(t("DEFAULT_OPEN_MODE_DESC")))
.addDropdown((dropdown) =>
dropdown
.addOption("normal", "Normal Mode")
@@ -364,7 +368,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("ZOOM_TO_FIT_NAME"))
.setDesc(t("ZOOM_TO_FIT_DESC"))
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.zoomToFitOnResize)
@@ -378,7 +382,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("ZOOM_TO_FIT_MAX_LEVEL_NAME"))
.setDesc(t("ZOOM_TO_FIT_MAX_LEVEL_DESC"))
.setDesc(fragWithHTML(t("ZOOM_TO_FIT_MAX_LEVEL_DESC")))
.addSlider((slider) =>
slider
.setLimits(0.5, 10, 0.5)
@@ -397,11 +401,11 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
});
this.containerEl.createEl("h1", { text: t("LINKS_HEAD") });
this.containerEl.createEl("p", { text: t("LINKS_DESC") });
this.containerEl.createEl("span", undefined, (el)=>el.innerHTML = t("LINKS_DESC") );
new Setting(containerEl)
.setName(t("ADJACENT_PANE_NAME"))
.setDesc(t("ADJACENT_PANE_DESC"))
.setDesc(fragWithHTML(t("ADJACENT_PANE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.openInAdjacentPane)
@@ -413,7 +417,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("LINK_BRACKETS_NAME"))
.setDesc(t("LINK_BRACKETS_DESC"))
.setDesc(fragWithHTML(t("LINK_BRACKETS_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.showLinkBrackets)
@@ -425,7 +429,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("LINK_PREFIX_NAME"))
.setDesc(t("LINK_PREFIX_DESC"))
.setDesc(fragWithHTML(t("LINK_PREFIX_DESC")))
.addText((text) =>
text
.setPlaceholder(t("INSERT_EMOJI"))
@@ -438,7 +442,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("URL_PREFIX_NAME"))
.setDesc(t("URL_PREFIX_DESC"))
.setDesc(fragWithHTML(t("URL_PREFIX_DESC")))
.addText((text) =>
text
.setPlaceholder(t("INSERT_EMOJI"))
@@ -451,7 +455,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("LINK_CTRL_CLICK_NAME"))
.setDesc(t("LINK_CTRL_CLICK_DESC"))
.setDesc(fragWithHTML(t("LINK_CTRL_CLICK_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.allowCtrlClick)
@@ -463,7 +467,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
const s = new Setting(containerEl)
.setName(t("TRANSCLUSION_WRAP_NAME"))
.setDesc(t("TRANSCLUSION_WRAP_DESC"))
.setDesc(fragWithHTML(t("TRANSCLUSION_WRAP_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.forceWrap)
@@ -478,7 +482,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("PAGE_TRANSCLUSION_CHARCOUNT_NAME"))
.setDesc(t("PAGE_TRANSCLUSION_CHARCOUNT_DESC"))
.setDesc(fragWithHTML(t("PAGE_TRANSCLUSION_CHARCOUNT_DESC")))
.addText((text) =>
text
.setPlaceholder("Enter a number")
@@ -507,7 +511,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("GET_URL_TITLE_NAME"))
.setDesc(t("GET_URL_TITLE_DESC"))
.setDesc(fragWithHTML(t("GET_URL_TITLE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.iframelyAllowed)
@@ -522,7 +526,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MD_TRANSCLUDE_WIDTH_NAME"))
.setDesc(t("MD_TRANSCLUDE_WIDTH_DESC"))
.setDesc(fragWithHTML(t("MD_TRANSCLUDE_WIDTH_DESC")))
.addText((text) =>
text
.setPlaceholder("Enter a number e.g. 500")
@@ -548,7 +552,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MD_TRANSCLUDE_HEIGHT_NAME"))
.setDesc(t("MD_TRANSCLUDE_HEIGHT_DESC"))
.setDesc(fragWithHTML(t("MD_TRANSCLUDE_HEIGHT_DESC")))
.addText((text) =>
text
.setPlaceholder("Enter a number e.g. 800")
@@ -574,7 +578,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MD_DEFAULT_FONT_NAME"))
.setDesc(t("MD_DEFAULT_FONT_DESC"))
.setDesc(fragWithHTML(t("MD_DEFAULT_FONT_DESC")))
.addDropdown(async (d: DropdownComponent) => {
d.addOption("Virgil", "Virgil");
d.addOption("Cascadia", "Cascadia");
@@ -593,7 +597,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MD_DEFAULT_COLOR_NAME"))
.setDesc(t("MD_DEFAULT_COLOR_DESC"))
.setDesc(fragWithHTML(t("MD_DEFAULT_COLOR_DESC")))
.addText((text) =>
text
.setPlaceholder("CSS Color-name|RGB-HEX")
@@ -607,7 +611,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("MD_CSS_NAME"))
.setDesc(t("MD_CSS_DESC"))
.setDesc(fragWithHTML(t("MD_CSS_DESC")))
.addText((text) =>
text
.setPlaceholder("filename of css file in vault")
@@ -623,7 +627,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EMBED_PREVIEW_SVG_NAME"))
.setDesc(t("EMBED_PREVIEW_SVG_DESC"))
.setDesc(fragWithHTML(t("EMBED_PREVIEW_SVG_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.displaySVGInPreview)
@@ -635,7 +639,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("PREVIEW_MATCH_OBSIDIAN_NAME"))
.setDesc(t("PREVIEW_MATCH_OBSIDIAN_DESC"))
.setDesc(fragWithHTML(t("PREVIEW_MATCH_OBSIDIAN_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.previewMatchObsidianTheme)
@@ -647,7 +651,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EMBED_WIDTH_NAME"))
.setDesc(t("EMBED_WIDTH_DESC"))
.setDesc(fragWithHTML(t("EMBED_WIDTH_DESC")))
.addText((text) =>
text
.setPlaceholder("400")
@@ -663,7 +667,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EMBED_TYPE_NAME"))
.setDesc(t("EMBED_TYPE_DESC"))
.setDesc(fragWithHTML(t("EMBED_TYPE_DESC")))
.addDropdown(async (d: DropdownComponent) => {
dropdown = d;
dropdown.addOption("excalidraw", "excalidraw");
@@ -692,7 +696,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_PNG_SCALE_NAME"))
.setDesc(t("EXPORT_PNG_SCALE_DESC"))
.setDesc(fragWithHTML(t("EXPORT_PNG_SCALE_DESC")))
.addSlider((slider) =>
slider
.setLimits(1, 5, 0.5)
@@ -712,7 +716,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_BACKGROUND_NAME"))
.setDesc(t("EXPORT_BACKGROUND_DESC"))
.setDesc(fragWithHTML(t("EXPORT_BACKGROUND_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.exportWithBackground)
@@ -725,7 +729,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_THEME_NAME"))
.setDesc(t("EXPORT_THEME_DESC"))
.setDesc(fragWithHTML(t("EXPORT_THEME_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.exportWithTheme)
@@ -740,7 +744,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_SYNC_NAME"))
.setDesc(t("EXPORT_SYNC_DESC"))
.setDesc(fragWithHTML(t("EXPORT_SYNC_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.keepInSync)
@@ -761,7 +765,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_SVG_NAME"))
.setDesc(t("EXPORT_SVG_DESC"))
.setDesc(fragWithHTML(t("EXPORT_SVG_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoexportSVG)
@@ -782,7 +786,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_PNG_NAME"))
.setDesc(t("EXPORT_PNG_DESC"))
.setDesc(fragWithHTML(t("EXPORT_PNG_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoexportPNG)
@@ -805,7 +809,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("COMPATIBILITY_MODE_NAME"))
.setDesc(t("COMPATIBILITY_MODE_DESC"))
.setDesc(fragWithHTML(t("COMPATIBILITY_MODE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.compatibilityMode)
@@ -817,7 +821,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("EXPORT_EXCALIDRAW_NAME"))
.setDesc(t("EXPORT_EXCALIDRAW_DESC"))
.setDesc(fragWithHTML(t("EXPORT_EXCALIDRAW_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoexportExcalidraw)
@@ -829,7 +833,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("SYNC_EXCALIDRAW_NAME"))
.setDesc(t("SYNC_EXCALIDRAW_DESC"))
.setDesc(fragWithHTML(t("SYNC_EXCALIDRAW_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.syncExcalidraw)
@@ -842,9 +846,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
this.containerEl.createEl("h1", { text: t("EXPERIMENTAL_HEAD") });
this.containerEl.createEl("p", { text: t("EXPERIMENTAL_DESC") });
new Setting(containerEl)
.setName(t("FIELD_SUGGESTOR_NAME"))
.setDesc(fragWithHTML(t("FIELD_SUGGESTOR_DESC")))
.addToggle((toggle) =>
toggle.setValue(this.plugin.settings.fieldSuggestor).onChange(async (value) => {
this.plugin.settings.fieldSuggestor = value;
this.applySettingsUpdate();
})
);
new Setting(containerEl)
.setName(t("FILETYPE_NAME"))
.setDesc(t("FILETYPE_DESC"))
.setDesc(fragWithHTML(t("FILETYPE_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.experimentalFileType)
@@ -857,7 +871,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FILETAG_NAME"))
.setDesc(t("FILETAG_DESC"))
.setDesc(fragWithHTML(t("FILETAG_DESC")))
.addText((text) =>
text
.setPlaceholder(t("INSERT_EMOJI"))
@@ -870,7 +884,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("LIVEPREVIEW_NAME"))
.setDesc(t("LIVEPREVIEW_DESC"))
.setDesc(fragWithHTML(t("LIVEPREVIEW_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.experimentalLivePreview)
@@ -882,7 +896,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("ENABLE_FOURTH_FONT_NAME"))
.setDesc(t("ENABLE_FOURTH_FONT_DESC"))
.setDesc(fragWithHTML(t("ENABLE_FOURTH_FONT_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.experimentalEnableFourthFont)
@@ -895,7 +909,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
new Setting(containerEl)
.setName(t("FOURTH_FONT_NAME"))
.setDesc(t("FOURTH_FONT_DESC"))
.setDesc(fragWithHTML(t("FOURTH_FONT_DESC")))
.addDropdown(async (d: DropdownComponent) => {
d.addOption("Virgil", "Virgil");
this.app.vault
@@ -1095,4 +1109,4 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
});
}
}
}
}