mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
2 Commits
2.1.8.1-be
...
2.1.8.1-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26812dd297 | ||
|
|
ae4f4b4f08 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.1.6.1-beta-1",
|
||||
"version": "2.1.8.1-beta-2",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -10,9 +10,10 @@ const o0 = Decoration.line({ attributes: {class: "ex-opacity-0"} });
|
||||
export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
class {
|
||||
view: EditorView;
|
||||
decorations: DecorationSet;
|
||||
decorations: DecorationSet;
|
||||
reExcalidrawData = /^%%(?:\r\n|\r|\n)# Excalidraw Data$/gm;
|
||||
reTextElements = /^%%(?:\r\n|\r|\n)# Text Elements$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)# Drawing$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)##? Drawing$/gm;
|
||||
linecount = 0;
|
||||
isExcalidraw = false;
|
||||
|
||||
@@ -32,11 +33,15 @@ export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
|
||||
const text = doc.toString();
|
||||
|
||||
let start = text.search(this.reTextElements);
|
||||
let start = text.search(this.reExcalidrawData);
|
||||
if(start == -1) {
|
||||
start = text.search(this.reTextElements);
|
||||
}
|
||||
if(start == -1) {
|
||||
start = text.search(this.reDrawing);
|
||||
if(start == -1) return Decoration.none;
|
||||
}
|
||||
if(start == -1) return Decoration.none;
|
||||
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lines;
|
||||
let builder = new RangeSetBuilder<Decoration>()
|
||||
|
||||
@@ -35,8 +35,6 @@ import {
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
@@ -58,7 +56,7 @@ import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsid
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "src/LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions, Prompt } from "src/dialogs/Prompt";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
@@ -80,7 +78,7 @@ import { TInput } from "colormaster/types";
|
||||
import {ConversionResult, svgToExcalidraw} from "src/svgToExcalidraw/parser"
|
||||
import { ROUNDNESS } from "src/constants/constants";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { emulateKeysForLinkClick, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import PolyBool from "polybooljs";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
@@ -720,7 +718,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
const generateMD = ():string => {
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
@@ -731,7 +729,7 @@ export class ExcalidrawAutomate {
|
||||
})
|
||||
|
||||
outString += Object.keys(this.imagesDict).length > 0
|
||||
? "\n# Embedded files\n"
|
||||
? `\n## Embedded Files\n`
|
||||
: "";
|
||||
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
@@ -2776,9 +2774,9 @@ async function getTemplate(
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search(new RegExp(`^${MD_TEXTELEMENTS}$`,"m"));
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(`${MD_DRAWING}\n`);
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
|
||||
@@ -18,9 +18,6 @@ import {
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
MD_ELEMENTLINKS,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -117,12 +114,12 @@ export const REGEX_LINK = {
|
||||
};
|
||||
|
||||
//added \n at and of DRAWING_REG: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/357
|
||||
const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
const DRAWING_REG = /\n##? Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n##? Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
export const DRAWING_COMPRESSED_REG =
|
||||
/(\n# Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
/(\n##? Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_COMPRESSED_REG_FALLBACK =
|
||||
/(\n# Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
/(\n##? Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
|
||||
|
||||
const isCompressedMD = (data: string): boolean => {
|
||||
@@ -204,10 +201,10 @@ export function getMarkdownDrawingSection(
|
||||
compressed: boolean,
|
||||
) {
|
||||
return compressed
|
||||
? `# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
? `## Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
jsonString,
|
||||
)}\n\x60\x60\x60\n%%`
|
||||
: `# Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
: `## Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,33 +238,97 @@ const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = new RegExp(`^#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = new RegExp(`#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = new RegExp(`^(%%\n)?# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
//The issue is that when editing in markdown embeds the user can delete the last enter causing two sections
|
||||
//to collide. This is particularly problematic when the user is editing the lest section before # Text Elements
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//to collide. This is particularly problematic when the user is editing the last section before # Text Elements
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = new RegExp(`(.*)%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = new RegExp(`(.*)# Excalidraw Data(?:\n|$)`, "m");
|
||||
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)##? Text Elements(?:\n|$)`, "m");
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?${MD_DRAWING}\n`);
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?##? Drawing\n`);
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
//The base case scenario is at the top, continued with fallbacks in order of likelihood and file structure
|
||||
//change history for sake of backward compatibility
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
let trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
|
||||
/* Expected markdown structure (this happens when the user deletes the last empty line of the last back-of-the-card note):
|
||||
bla bla bla#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Excalidraw Data
|
||||
b)
|
||||
bla bla bla
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2;
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
}
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla#
|
||||
%%
|
||||
@@ -641,7 +702,28 @@ export class ExcalidrawData {
|
||||
//link was updated due to filename changes
|
||||
//The .excalidraw JSON is modified to reflect the MD in case of difference
|
||||
//Read the text elements into the textElements Map
|
||||
let position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
let position = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Excalidraw Data
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Excalidraw Data
|
||||
position = data.search(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
}
|
||||
|
||||
if(position === -1) {
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
@@ -661,10 +743,12 @@ export class ExcalidrawData {
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"));
|
||||
const normalMatch = data.match(/^((%%\n)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
??data.match(/^((%%\n)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
: data.match(new RegExp(`(.*${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0];
|
||||
: data.match(/(.*##? Text Elements(?:\n|$))/m)[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
@@ -673,9 +757,13 @@ export class ExcalidrawData {
|
||||
|
||||
//load element links
|
||||
const elementLinkMap = new Map<string,string>();
|
||||
const elementLinksData = data.substring(
|
||||
data.indexOf(`${MD_ELEMENTLINKS}\n`) + `${MD_ELEMENTLINKS}\n`.length,
|
||||
);
|
||||
const indexOfNewElementLinks = data.indexOf("## Element Links\n");
|
||||
const lengthOfNewElementLinks = 17; //`## Element Links\n`.length
|
||||
const indexOfOldElementLinks = data.indexOf("# Element Links\n");
|
||||
const lengthOfOldElementLinks = 16; //`# Element Links\n`.length
|
||||
const elementLinksData = indexOfNewElementLinks>-1
|
||||
? data.substring(indexOfNewElementLinks + lengthOfNewElementLinks)
|
||||
: data.substring(indexOfOldElementLinks + lengthOfOldElementLinks);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
@@ -746,11 +834,15 @@ export class ExcalidrawData {
|
||||
}
|
||||
}
|
||||
|
||||
const indexOfEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
if(indexOfEmbeddedFiles>-1) {
|
||||
data = data.substring(
|
||||
indexOfEmbeddedFiles + "# Embedded files\n".length,
|
||||
);
|
||||
const indexOfNewEmbeddedFiles = data.indexOf("## Embedded Files\n");
|
||||
const embeddedFilesNewLength = 18; //"## Embedded Files\n".length
|
||||
const indexOfOldEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
const embeddedFilesOldLength = 17; //"# Embedded files\n".length
|
||||
|
||||
if(indexOfNewEmbeddedFiles>-1 || indexOfOldEmbeddedFiles>-1) {
|
||||
data = indexOfNewEmbeddedFiles>-1
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
@@ -1265,7 +1357,7 @@ export class ExcalidrawData {
|
||||
disableCompression: boolean = false;
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `${MD_TEXTELEMENTS}\n`;
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
@@ -1281,7 +1373,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
|
||||
outString += `${MD_ELEMENTLINKS}\n`;
|
||||
outString += `## Element Links\n`;
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${key}: ${this.elementLinks.get(key)}\n`;
|
||||
}
|
||||
@@ -1294,7 +1386,7 @@ export class ExcalidrawData {
|
||||
// deliberately not adding mermaids to here. It is enough to have the mermaidText in the image element's customData
|
||||
outString +=
|
||||
this.equations.size > 0 || this.files.size > 0
|
||||
? "# Embedded files\n"
|
||||
? "## Embedded Files\n"
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
|
||||
@@ -9,13 +9,12 @@ export let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
|
||||
export const setExcalidrawPlugin = (plugin: ExcalidrawPlugin) => {
|
||||
EXCALIDRAW_PLUGIN = plugin;
|
||||
};
|
||||
export const MD_TEXTELEMENTS = "# Text Elements";
|
||||
export const MD_JSON_START = "```json\n";
|
||||
export const MD_JSON_END = "```";
|
||||
export const MD_DRAWING = "# Drawing";
|
||||
export const MD_ELEMENTLINKS = "# Element Links";
|
||||
export const MD_EMBEDFILES = "# Embedded files";
|
||||
export const MD_EX_SECTIONS = [MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
const MD_EXCALIDRAW = "# Excalidraw Data";
|
||||
const MD_TEXTELEMENTS = "## Text Elements";
|
||||
const MD_DRAWING = "## Drawing";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "Save image from URL to local file",
|
||||
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
|
||||
ZIP_CURRENT_FILE: "Compress current Excalidraw file",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "Scale selected embeddable elements to 100% relative to the current canvas zoom",
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
FLIP_IMAGE: "Open the back-of-the-note of the selected excalidraw image",
|
||||
NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW",
|
||||
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
|
||||
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
|
||||
|
||||
96
src/main.ts
96
src/main.ts
@@ -20,7 +20,6 @@ import {
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
loadMermaid,
|
||||
requireApiVersion,
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -42,7 +41,6 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin,
|
||||
DEVICE
|
||||
} from "./constants/constants";
|
||||
@@ -105,7 +103,7 @@ import {
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
import { editorInsertText, extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
@@ -188,6 +186,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
public editorHandler: EditorHandler;
|
||||
public activeLeafChangeEventHandler: (leaf: WorkspaceLeaf) => Promise<void>;
|
||||
//if set, the next time this file is opened it will be opened as markdown
|
||||
public forceToOpenInMarkdownFilepath: string = null;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -401,14 +401,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
debug(`ExcalidrawPlugin.switchToExcalidarwAfterLoad app.workspace.onLayoutReady`);
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file) &&
|
||||
fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)
|
||||
) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
if ( leaf.view instanceof MarkdownView && self.isExcalidrawFile(leaf.view.file)) {
|
||||
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
} else {
|
||||
foldExcalidrawSection(leaf.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -878,9 +878,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
(async () => {
|
||||
const data = await this.app.vault.read(activeFile);
|
||||
const parts = data.split("\n# Drawing\n```compressed-json\n");
|
||||
const parts = data.split("\n## Drawing\n```compressed-json\n");
|
||||
if(parts.length!==2) return;
|
||||
const header = parts[0] + "\n# Drawing\n```json\n";
|
||||
const header = parts[0] + "\n## Drawing\n```json\n";
|
||||
const compressed = parts[1].split("\n```\n%%");
|
||||
if(compressed.length!==2) return;
|
||||
const decompressed = decompress(compressed[0]);
|
||||
@@ -1585,6 +1585,31 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "flip-image",
|
||||
name: t("FLIP_IMAGE"),
|
||||
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==="image");
|
||||
if(els.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
return false;
|
||||
}
|
||||
if(!this.isExcalidrawFile(ef.file)) {
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.forceToOpenInMarkdownFilepath = ef.file?.path;
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true);
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -2281,12 +2306,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
private registerMonkeyPatches() {
|
||||
const key = "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues";
|
||||
|
||||
this.register(
|
||||
around(Workspace.prototype, {
|
||||
getActiveViewOfType(old) {
|
||||
return dedupe(key, old, function(...args) {
|
||||
const result = old && old.apply(this, args);
|
||||
const maybeEAView = app?.workspace?.activeLeaf?.view;
|
||||
const maybeEAView = self.app?.workspace?.activeLeaf?.view;
|
||||
if(!maybeEAView || !(maybeEAView instanceof ExcalidrawView)) return result;
|
||||
const error = new Error();
|
||||
const stackTrace = error.stack;
|
||||
@@ -2386,28 +2412,38 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
setViewState(next) {
|
||||
return function (state: ViewState, ...rest: any[]) {
|
||||
const markdownViewLoaded =
|
||||
self._loaded && // Don't force excalidraw mode during shutdown
|
||||
state.type === "markdown" && // If we have a markdown file
|
||||
state.state?.file;
|
||||
if (
|
||||
// Don't force excalidraw mode during shutdown
|
||||
self._loaded &&
|
||||
// If we have a markdown file
|
||||
state.type === "markdown" &&
|
||||
state.state?.file &&
|
||||
// And the current mode of the file is not set to markdown
|
||||
self.excalidrawFileModes[this.id || state.state.file] !==
|
||||
"markdown"
|
||||
markdownViewLoaded &&
|
||||
self.excalidrawFileModes[this.id || state.state.file] !== "markdown"
|
||||
) {
|
||||
if (fileShouldDefaultAsExcalidraw(state.state.file,this.app)) {
|
||||
const file = state.state.file;
|
||||
if ((self.forceToOpenInMarkdownFilepath !== file) && fileShouldDefaultAsExcalidraw(file,this.app)) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
const newState = {
|
||||
...state,
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
};
|
||||
|
||||
self.excalidrawFileModes[state.state.file] =
|
||||
self.excalidrawFileModes[file] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
|
||||
return next.apply(this, [newState, ...rest]);
|
||||
}
|
||||
self.forceToOpenInMarkdownFilepath = null;
|
||||
}
|
||||
|
||||
if(markdownViewLoaded) {
|
||||
const leaf = this;
|
||||
setTimeout(async ()=> {
|
||||
if(!leaf || !leaf.view || !(leaf.view instanceof MarkdownView) ||
|
||||
!leaf.view.file || !self.isExcalidrawFile(leaf.view.file)
|
||||
) return;
|
||||
foldExcalidrawSection(leaf.view)
|
||||
},500);
|
||||
}
|
||||
|
||||
return next.apply(this, [state, ...rest]);
|
||||
@@ -3106,10 +3142,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
leaf = this.app.workspace.openPopoutLeaf();
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
@@ -3190,7 +3226,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
@@ -3269,6 +3305,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
} as ViewState,
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
|
||||
const mdView = leaf.view;
|
||||
if(mdView instanceof MarkdownView) {
|
||||
foldExcalidrawSection(mdView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
|
||||
14
src/types.d.ts
vendored
14
src/types.d.ts
vendored
@@ -58,6 +58,20 @@ declare module "obsidian" {
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
|
||||
@@ -2,13 +2,14 @@ import {
|
||||
App,
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
|
||||
import yaml from "js-yaml";
|
||||
import yaml, { Mark } from "js-yaml";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -350,4 +351,20 @@ export const editorInsertText = (editor: Editor, text: string)=> {
|
||||
const line = editor.getLine(cursor.line);
|
||||
const updatedLine = line.slice(0, cursor.ch) + text + line.slice(cursor.ch);
|
||||
editor.setLine(cursor.line, updatedLine);
|
||||
}
|
||||
|
||||
export const foldExcalidrawSection = (view: MarkdownView) => {
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
const existingFolds = view.currentMode.getFoldInfo();
|
||||
const lineCount = view.editor.lineCount();
|
||||
let i = -1;
|
||||
while(i++<lineCount && view.editor.getLine(i) !== "# Excalidraw Data");
|
||||
|
||||
if(i>-1 && i<lineCount) {
|
||||
const foldPositions = [
|
||||
...(existingFolds?.folds ?? []),
|
||||
...[{from: i, to: lineCount-1}],
|
||||
];
|
||||
view.currentMode.applyFoldInfo({folds: foldPositions, lines:lineCount});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user