mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 096efc45d7 | |||
| 55291d8c27 | |||
| e81787ee4b |
@@ -175,7 +175,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
elements.push(this.elementsDict[this.elementIds[i]]);
|
||||
}
|
||||
plugin.createDrawing(
|
||||
params?.filename ? params.filename + '.excalidraw' : this.plugin.getNextDefaultFilename(),
|
||||
params?.filename ? params.filename + '.excalidraw.md' : this.plugin.getNextDefaultFilename(),
|
||||
params?.onNewPane ? params.onNewPane : false,
|
||||
params?.foldername ? params.foldername : this.plugin.settings.folder,
|
||||
FRONTMATTER + exportSceneToMD(
|
||||
|
||||
@@ -230,7 +230,7 @@ export class ExcalidrawData {
|
||||
//1 2
|
||||
const REG_FILE_BLOCKREF = /(.*)#\^(.*)/g;
|
||||
const parts=text.matchAll(REG_FILE_BLOCKREF).next();
|
||||
if(!parts.value[1] || !parts.value[2]) return text; //filename and/or blockref not found
|
||||
if(parts.done || !parts.value[1] || !parts.value[2]) return text; //filename and/or blockref not found
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(parts.value[1],this.file.path);
|
||||
const contents = await this.app.vault.cachedRead(file);
|
||||
//get transcluded line and take the part before ^blockref
|
||||
|
||||
@@ -60,7 +60,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private justLoaded: boolean = false;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private dirty: boolean = false;
|
||||
private autosaveTimer: any = null;
|
||||
public autosaveTimer: any = null;
|
||||
public isTextLocked:boolean = false;
|
||||
private lockedElement:HTMLElement;
|
||||
private unlockedElement:HTMLElement;
|
||||
@@ -240,16 +240,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
private setupAutosaveTimer() {
|
||||
public setupAutosaveTimer() {
|
||||
const timer = async () => {
|
||||
//console.log("ExcalidrawView.autosaveTimer(), dirty", this.dirty);
|
||||
if(this.dirty) {
|
||||
console.log("autosave",Date.now());
|
||||
this.dirty = false;
|
||||
if(this.excalidrawRef) await this.save();
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
}
|
||||
}
|
||||
this.autosaveTimer = setInterval(timer,30000);
|
||||
if(this.plugin.settings.autosave) {
|
||||
this.autosaveTimer = setInterval(timer,30000);
|
||||
}
|
||||
}
|
||||
|
||||
//save current drawing when user closes workspace leaf
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { App, Modal } from "obsidian";
|
||||
import { t } from "./lang/helpers";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
|
||||
export class MigrationPrompt extends Modal {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(app: App, plugin:ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.titleEl.setText("Welcome to Excalidraw 1.2");
|
||||
this.createForm();
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
this.contentEl.empty();
|
||||
}
|
||||
|
||||
createForm(): void {
|
||||
const div = this.contentEl.createDiv();
|
||||
div.addClass("excalidarw-prompt-div");
|
||||
div.style.maxWidth = "600px";
|
||||
div.createEl('p',{text: "This version comes with many new features and possibilities. Please read the description in Community Plugins to find out more."});
|
||||
div.createEl('p',{text: "⚠ WARNING: Drawings you have created with version 1.1.x need to be converted, they WILL NOT WORK out of the box. "+
|
||||
"During conversion your old *.excalidraw files will be replaced with new *.excalidraw.md files."});
|
||||
div.createEl('p',{text: "Click CONVERT to convert all of your *.excalidraw files now, or if you prefer to make a backup first, then select CANCEL."});
|
||||
div.createEl('p',{text: "To convert files manually, select 'Excalidraw: Convert *.excalidraw files to *.md files' from the Command Palette at any time in the future."});
|
||||
div.createEl('p',{text: "This message will only appear maximum 3 times."});
|
||||
const bConvert = div.createEl('button', {text: "CONVERT FILES"});
|
||||
bConvert.onclick = (ev)=>{
|
||||
this.plugin.convertExcalidrawToMD();
|
||||
this.close();
|
||||
};
|
||||
const bCancel = div.createEl('button', {text: "CANCEL"});
|
||||
bCancel.onclick = (ev)=>{
|
||||
this.close();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,7 @@ export default {
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
MIGRATE_TO_2: "MIGRATE to version 1.2: convert *.excalidraw to *.md files",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW : "New Excalidraw drawing",
|
||||
OPEN_EXISTING_NEW_PANE: "Open an existing drawing - IN A NEW PANE",
|
||||
OPEN_EXISTING_ACTIVE_PANE: "Open an existing drawing - IN THE CURRENT ACTIVE PANE",
|
||||
@@ -48,7 +48,12 @@ export default {
|
||||
TEMPLATE_DESC: "Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and it's name is " +
|
||||
"Template.excalidraw, the setting would be: Excalidraw/Template.excalidraw",
|
||||
FILENAME_HEAD: "Filenam for drawings",
|
||||
AUTOSAVE_NAME: "Autosave",
|
||||
AUTOSAVE_DESC: "Automatically save the active drawing every 30 seconds. Save normally happens when you close Excalidraw or Obsidian, or move "+
|
||||
"focus to another pane. In rare cases autosave may slightly disrupt your drawing flow. I created this feature with mobile " +
|
||||
"phones in mind (I only have experience with Android), where 'swiping out Obsidian to close it' led to some data loss, and because " +
|
||||
"I wasn't able to force save on application termination on mobiles. If you use Excalidraw on a desktop this is likely not needed.",
|
||||
FILENAME_HEAD: "Filename",
|
||||
FILENAME_DESC: "<p>The auto-generated filename consists of a prefix and a date. " +
|
||||
"e.g.'Drawing 2021-05-24 12.58.07'.</p>"+
|
||||
"<p>Click this link for the <a href='https://momentjs.com/docs/#/displaying/format/'>"+
|
||||
@@ -58,7 +63,7 @@ export default {
|
||||
FILENAME_PREFIX_DESC: "The first part of the filename",
|
||||
FILENAME_DATE_NAME: "Filename date",
|
||||
FILENAME_DATE_DESC: "The second part of the filename",
|
||||
LINKS_HEAD: "Links in drawings",
|
||||
LINKS_HEAD: "Links",
|
||||
LINKS_DESC: "CTRL/META + 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 " +
|
||||
@@ -76,7 +81,7 @@ export default {
|
||||
LINK_CTRL_CLICK_NAME: "CTRL + CLICK on text to open them as links",
|
||||
LINK_CTRL_CLICK_DESC: "You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
EMBED_HEAD: "Embedded/Export image settings",
|
||||
EMBED_HEAD: "Embed & Export",
|
||||
EMBED_WIDTH_NAME: "Default width of embedded (transcluded) image",
|
||||
EMBED_WIDTH_DESC: "The default width of an embedded drawing. You can specify a custom " +
|
||||
"width when embedding an image using the ![[drawing.excalidraw|100]] or " +
|
||||
|
||||
+58
-27
@@ -15,6 +15,7 @@ import {
|
||||
Tasks,
|
||||
MarkdownRenderer,
|
||||
ViewState,
|
||||
Notice,
|
||||
} from "obsidian";
|
||||
|
||||
import {
|
||||
@@ -55,6 +56,7 @@ import {
|
||||
import { Prompt } from "./Prompt";
|
||||
import { around } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import { MigrationPrompt } from "./MigrationPrompt";
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
public excalidrawFileModes: { [file: string]: string } = {};
|
||||
@@ -94,8 +96,21 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
if(this.settings.loadCount<3) this.migrationNotice();
|
||||
}
|
||||
|
||||
private migrationNotice(){
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
self.settings.loadCount++;
|
||||
self.saveSettings();
|
||||
const files = this.app.vault.getFiles().filter((f)=>f.extension=="excalidraw");
|
||||
if(files.length>0) {
|
||||
const prompt = new MigrationPrompt(self.app, self);
|
||||
prompt.open();
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Displays a transcluded .excalidraw image in markdown preview mode
|
||||
*/
|
||||
@@ -265,15 +280,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
|
||||
const fileMenuHandler = (menu: Menu, file: TFile) => {
|
||||
if (file instanceof TFolder) {
|
||||
menu.addItem((item: MenuItem) => {
|
||||
item.setTitle(t("CREATE_NEW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick(evt => {
|
||||
this.createDrawing(this.getNextDefaultFilename(),false,file.path);
|
||||
})
|
||||
});
|
||||
}
|
||||
menu.addItem((item: MenuItem) => {
|
||||
item.setTitle(t("CREATE_NEW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.onClick(evt => {
|
||||
let folderpath = file.path;
|
||||
if(file instanceof TFile) {
|
||||
folderpath = normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name)));
|
||||
}
|
||||
this.createDrawing(this.getNextDefaultFilename(),false,folderpath);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
this.registerEvent(
|
||||
@@ -510,23 +527,32 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
/*1.2 migration command */
|
||||
this.addCommand({
|
||||
id: "migrate-to-1.2.x",
|
||||
name: t("MIGRATE_TO_2"),
|
||||
callback: async () => {
|
||||
const files = this.app.vault.getFiles().filter((f)=>f.extension=="excalidraw");
|
||||
for (const file of files) {
|
||||
const data = await this.app.vault.read(file);
|
||||
const fname = this.getNewUniqueFilepath(file.name+'.md',normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name))));
|
||||
console.log(fname);
|
||||
await this.app.vault.create(fname,FRONTMATTER + exportSceneToMD(data));
|
||||
this.app.vault.delete(file);
|
||||
id: "convert-excalidraw",
|
||||
name: t("CONVERT_EXCALIDRAW"),
|
||||
checkCallback: (checking) => {
|
||||
if (checking) {
|
||||
const files = this.app.vault.getFiles().filter((f)=>f.extension=="excalidraw");
|
||||
return files.length>0;
|
||||
}
|
||||
this.convertExcalidrawToMD()
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async convertExcalidrawToMD() {
|
||||
const files = this.app.vault.getFiles().filter((f)=>f.extension=="excalidraw");
|
||||
for (const file of files) {
|
||||
const data = await this.app.vault.read(file);
|
||||
const fname = this.getNewUniqueFilepath(file.name+'.md',normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name))));
|
||||
console.log(fname);
|
||||
await this.app.vault.create(fname,FRONTMATTER + exportSceneToMD(data));
|
||||
this.app.vault.delete(file);
|
||||
}
|
||||
new Notice("Converted " + files.length + " files.")
|
||||
}
|
||||
|
||||
private registerMonkeyPatches() {
|
||||
const self = this;
|
||||
|
||||
@@ -695,14 +721,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//save Excalidraw leaf and update embeds when switching to another leaf
|
||||
const activeLeafChangeEventHandler = async (leaf:WorkspaceLeaf) => {
|
||||
const activeview:ExcalidrawView = (leaf.view instanceof ExcalidrawView) ? leaf.view as ExcalidrawView : null;
|
||||
if(self.activeExcalidrawView && self.activeExcalidrawView != activeview) {
|
||||
await self.activeExcalidrawView.save();
|
||||
self.triggerEmbedUpdates(self.activeExcalidrawView.file?.path);
|
||||
const activeExcalidrawView = self.activeExcalidrawView;
|
||||
const newActiveview:ExcalidrawView = (leaf.view instanceof ExcalidrawView) ? leaf.view as ExcalidrawView : null;
|
||||
if(activeExcalidrawView && activeExcalidrawView != newActiveview) {
|
||||
await activeExcalidrawView.save(false);
|
||||
if(activeExcalidrawView.file) {
|
||||
self.triggerEmbedUpdates(activeExcalidrawView.file.path);
|
||||
}
|
||||
}
|
||||
self.activeExcalidrawView = activeview;
|
||||
if(self.activeExcalidrawView) {
|
||||
self.lastActiveExcalidrawFilePath = self.activeExcalidrawView.file?.path;
|
||||
self.activeExcalidrawView = newActiveview;
|
||||
if(newActiveview) {
|
||||
self.lastActiveExcalidrawFilePath = newActiveview.file?.path;
|
||||
}
|
||||
};
|
||||
self.registerEvent(
|
||||
@@ -748,6 +777,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public openDrawing(drawingFile: TFile, onNewPane: boolean) {
|
||||
this.settings.drawingOpenCount++;
|
||||
this.saveSettings();
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
let leaf:WorkspaceLeaf = null;
|
||||
|
||||
|
||||
+28
-2
@@ -16,7 +16,7 @@ export interface ExcalidrawSettings {
|
||||
width: string,
|
||||
showLinkBrackets: boolean,
|
||||
linkPrefix: string,
|
||||
// validLinksOnly: boolean, //valid link as in [[valid Obsidian link]] - how to treat text elements in drawings
|
||||
autosave: boolean;
|
||||
allowCtrlClick: boolean, //if disabled only the link button in the view header will open links
|
||||
exportWithTheme: boolean,
|
||||
exportWithBackground: boolean,
|
||||
@@ -24,6 +24,8 @@ export interface ExcalidrawSettings {
|
||||
autoexportPNG: boolean,
|
||||
keepInSync: boolean,
|
||||
library: string,
|
||||
loadCount: number, //version 1.2 migration counter
|
||||
drawingOpenCount: number,
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
@@ -34,7 +36,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
width: '400',
|
||||
linkPrefix: ">> ",
|
||||
showLinkBrackets: true,
|
||||
// validLinksOnly: false,
|
||||
autosave: false,
|
||||
allowCtrlClick: true,
|
||||
exportWithTheme: true,
|
||||
exportWithBackground: true,
|
||||
@@ -42,6 +44,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
autoexportPNG: false,
|
||||
keepInSync: false,
|
||||
library: `{"type":"excalidrawlib","version":1,"library":[]}`,
|
||||
loadCount: 0,
|
||||
drawingOpenCount: 0,
|
||||
}
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -78,6 +82,28 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName(t("AUTOSAVE_NAME"))
|
||||
.setDesc(t("AUTOSAVE_DESC"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.autosave)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosave = value;
|
||||
await this.plugin.saveSettings();
|
||||
const exs = this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for(const v of exs) {
|
||||
if(v.view instanceof ExcalidrawView) {
|
||||
if(v.view.autosaveTimer) {
|
||||
clearInterval(v.view.autosaveTimer)
|
||||
v.view.autosaveTimer = null;
|
||||
}
|
||||
if(value) {
|
||||
v.view.setupAutosaveTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.containerEl.createEl('h1', {text: t("FILENAME_HEAD")});
|
||||
containerEl.createDiv('',(el) => {
|
||||
el.innerHTML = t("FILENAME_DESC");
|
||||
|
||||
@@ -51,6 +51,7 @@ button.ToolIcon_type_button[title="Export"] {
|
||||
|
||||
.excalidraw-prompt-div {
|
||||
display: flex;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.excalidraw-prompt-form {
|
||||
|
||||
Reference in New Issue
Block a user