mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4eb09df098 |
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,6 +8,4 @@ package-lock.json
|
||||
|
||||
# build
|
||||
main.js
|
||||
*.js.map
|
||||
stats.html
|
||||
hot-reload.bat
|
||||
*.js.map
|
||||
48
README.md
48
README.md
@@ -1,47 +1,7 @@
|
||||
## Obsidian Excalidraw Plugin
|
||||
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault and you can transclude drawings into your documents. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html).
|
||||
## Obsidian Daily Stats
|
||||
|
||||

|
||||
This is a daily word count plugin for Obsidian (https://obsidian.md). You can see today's word count in the bottom right corner of your screen, and also see the historical logs.
|
||||
|
||||
### Key features
|
||||
- The plugin adds 3 commands to the command palette.
|
||||
- To create a new drawing.
|
||||
- To find and edit existing drawings in your vault, and
|
||||
- To embed (transclude) a drawing into a document.
|
||||
- You can also use the file explorer in your vault to open Excalidraw files.
|
||||
- Open settings to set up a default folder for new drawings.
|
||||
- Set up a Template by creating a drawing, customizing it the way you like it, and specifying the file as the template in settings.
|
||||
- The plugin saves drawings to your vault as a file with the *.excalidraw* file extension.
|
||||
- You can customize the size of embedded image using the [[image.excalidraw|100]] or [[image.excalidraw|100x100]] format.
|
||||
This plugin was inspired by liamcain's [Calender](https://github.com/liamcain/obsidian-calendar-plugin) and lukeleppan's [Better Word Count](https://github.com/lukeleppan/better-word-count).
|
||||
|
||||
### How to?
|
||||
#### Part 1: Intro to Obsidian-Excalidraw - Start a new drawing (3:12)
|
||||
[](https://youtu.be/i-hIfY-Ecjg)
|
||||
#### Part 2: Intro to Obsidian-Excalidraw - Basic features (6:06)
|
||||
[](https://youtu.be/-dk7pvdl-H0)
|
||||
#### Part 3: Intro to Obsidian-Excalidraw - Advanced features (3:26)
|
||||
[](https://youtu.be/2cKlEwo8WU0)
|
||||
#### Part 4: Intro to Obsidian-Excalidraw - Setting up a template (1:45)
|
||||
[](https://youtu.be/oNPYZEpmuJ8)
|
||||
#### Part 5: Intro to Obsidian-Excalidraw - Stencil Library (3:16)
|
||||
[](https://youtu.be/rLx-9FvlzgI)
|
||||
#### Part 6: Intro to Obsidian-Excalidraw: Embedding drawings (2:08)
|
||||
[](https://youtu.be/JQeJ-Hh-xAI)
|
||||
|
||||
### Known issues
|
||||
- On iPad: As you draw left to right it opens left sidebar. Draw right to left, opens right sidebar. Draw down, opens commands palette. So seems open is emulating the gestures, even when drawing towards the center. I understand that the issue will be resolved in the next release of Obsidian mobile.
|
||||
|
||||
### Excalidraw in Obsidian
|
||||
https://user-images.githubusercontent.com/14358394/115386872-3fc8d180-a1da-11eb-9366-16d0e064932a.mp4
|
||||
|
||||
### Contributing
|
||||
Feel free to contribute.
|
||||
|
||||
By clicking [here](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues) you can create an issue to report a bug, suggest an improvement for this plugin, ask a question, etc.
|
||||
|
||||
### Support
|
||||
If you want to support me and my work, you can donate me a little something.
|
||||
|
||||
[<img src="https://user-images.githubusercontent.com/14358394/115450238-f39e8100-a21b-11eb-89d0-fa4b82cdbce8.png" width="200">](https://ko-fi.com/zsolt)
|
||||
|
||||
[https://ko-fi/zsolt](https://ko-fi.com/zsolt)
|
||||

|
||||
@@ -1 +0,0 @@
|
||||
{"openFile":"Blog/attachements/security through obscurity.excalidraw","settings":{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidraw"}}
|
||||
@@ -1 +0,0 @@
|
||||
{"folder":"excalidraw","templateFilePath":"excalidraw/Template.excalidra","width":"400","openFile":"excalidraw/new file.excalidraw","settings":{"folder":"excalidraw","templateFilePath":""}}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"minify": true
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 227 KiB |
118
main.ts
Normal file
118
main.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { TFile, Plugin, MarkdownView } from 'obsidian';
|
||||
|
||||
interface WordCount {
|
||||
initial: number;
|
||||
current: number;
|
||||
}
|
||||
|
||||
interface DailyStatsSettings {
|
||||
dayCounts: Record<string, number>;
|
||||
todaysWordCount: Record<string, WordCount>;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: DailyStatsSettings = {
|
||||
dayCounts: {},
|
||||
todaysWordCount: {}
|
||||
}
|
||||
|
||||
export default class DailyStats extends Plugin {
|
||||
settings: DailyStatsSettings;
|
||||
statusBarEl: HTMLElement;
|
||||
currentWordCount: number;
|
||||
today: string;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.statusBarEl = this.addStatusBarItem();
|
||||
this.updateDate();
|
||||
if (this.settings.dayCounts.hasOwnProperty(this.today)) {
|
||||
this.updateCounts();
|
||||
} else {
|
||||
this.currentWordCount = 0;
|
||||
}
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("quit", this.onunload.bind(this))
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("quick-preview", this.onQuickPreview.bind(this))
|
||||
);
|
||||
|
||||
this.registerInterval(
|
||||
window.setInterval(() => {
|
||||
this.statusBarEl.setText(this.currentWordCount + " words today ");
|
||||
}, 200)
|
||||
);
|
||||
|
||||
this.registerInterval(window.setInterval(() => {
|
||||
this.updateDate();
|
||||
this.saveSettings();
|
||||
}, 1000));
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
||||
onQuickPreview(file: TFile, contents: string) {
|
||||
if (this.app.workspace.getActiveViewOfType(MarkdownView)) {
|
||||
this.updateWordCount(contents, file.path);
|
||||
}
|
||||
}
|
||||
|
||||
//Credit: better-word-count by Luke Leppan (https://github.com/lukeleppan/better-word-count)
|
||||
getWordCount(text: string) {
|
||||
let words: number = 0;
|
||||
|
||||
const matches = text.match(
|
||||
/[a-zA-Z0-9_\u0392-\u03c9\u00c0-\u00ff\u0600-\u06ff]+|[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\uac00-\ud7af]+/gm
|
||||
);
|
||||
|
||||
if (matches) {
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
if (matches[i].charCodeAt(0) > 19968) {
|
||||
words += matches[i].length;
|
||||
} else {
|
||||
words += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return words;
|
||||
}
|
||||
|
||||
updateWordCount(contents: string, filepath: string) {
|
||||
const curr = this.getWordCount(contents);
|
||||
if (this.settings.dayCounts.hasOwnProperty(this.today)) {
|
||||
if (this.settings.todaysWordCount.hasOwnProperty(filepath)) {//updating existing file
|
||||
this.settings.todaysWordCount[filepath].current = curr;
|
||||
} else {//created new file during session
|
||||
this.settings.todaysWordCount[filepath] = { initial: curr, current: curr };
|
||||
}
|
||||
} else {//new day, flush the cache
|
||||
this.settings.todaysWordCount = {};
|
||||
this.settings.todaysWordCount[filepath] = { initial: curr, current: curr };
|
||||
}
|
||||
this.updateCounts();
|
||||
}
|
||||
|
||||
updateDate() {
|
||||
const d = new Date();
|
||||
this.today = d.getFullYear() + "/" + d.getMonth() + "/" + d.getDate();
|
||||
}
|
||||
|
||||
updateCounts() {
|
||||
this.currentWordCount = Object.values(this.settings.todaysWordCount).map((wordCount) => Math.max(0, wordCount.current - wordCount.initial)).reduce((a, b) => a + b, 0);
|
||||
this.settings.dayCounts[this.today] = this.currentWordCount;
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "1.0.5",
|
||||
"minAppVersion": "0.11.13",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
"authorUrl": "https://zsolt.blog",
|
||||
"id": "obsidian-daily-stats",
|
||||
"name": "Daily Stats",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.9.10",
|
||||
"description": "Track your daily word count across all notes in your vault.",
|
||||
"author": "Dhruvik Parikh",
|
||||
"authorUrl": "https://github.com/dhruvik7",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
64
package.json
64
package.json
@@ -1,41 +1,23 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "1.0.4",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js -w",
|
||||
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@excalidraw/excalidraw": "0.7.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-scripts": "4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.3.3",
|
||||
"@babel/preset-env": "^7.3.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@rollup/plugin-babel": "5.3.0",
|
||||
"@rollup/plugin-commonjs": "^15.1.0",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^6.0.0",
|
||||
"@types/node": "^14.14.2",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"cross-env": "7.0.3",
|
||||
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
|
||||
"postcss": "^8.2.6",
|
||||
"rollup": "2.45.2",
|
||||
"rollup-plugin-copy": "3.4.0",
|
||||
"rollup-plugin-minify": "1.0.3",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.4.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.0.3",
|
||||
"webpack-bundle-analyzer": "^4.4.1"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "obsidian-daily-stats",
|
||||
"version": "1.0.0",
|
||||
"description": "This is an Obsidian.md plugin that lets you view your daily word count.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "rollup --config rollup.config.js -w",
|
||||
"build": "rollup --config rollup.config.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^15.1.0",
|
||||
"@rollup/plugin-node-resolve": "^9.0.0",
|
||||
"@rollup/plugin-typescript": "^6.0.0",
|
||||
"@types/node": "^14.14.2",
|
||||
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
|
||||
"rollup": "^2.32.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,9 @@
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import {nodeResolve} from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
//import copy from 'rollup-plugin-copy';
|
||||
import { env } from "process";
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import visualizer from "rollup-plugin-visualizer";
|
||||
|
||||
const isProd = (process.env.NODE_ENV === "production");
|
||||
console.log("Is production", isProd);
|
||||
|
||||
export default {
|
||||
input: 'src/main.ts',
|
||||
input: 'main.ts',
|
||||
output: {
|
||||
dir: '.',
|
||||
sourcemap: 'inline',
|
||||
@@ -20,16 +12,8 @@ export default {
|
||||
},
|
||||
external: ['obsidian'],
|
||||
plugins: [
|
||||
typescript({inlineSources: !isProd}),
|
||||
nodeResolve({ browser: true, preferBuiltins: true }),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||
}),
|
||||
babel({
|
||||
exclude: "node_modules/**"
|
||||
}),
|
||||
typescript(),
|
||||
nodeResolve({browser: true}),
|
||||
commonjs(),
|
||||
visualizer(),
|
||||
]
|
||||
};
|
||||
@@ -1,215 +0,0 @@
|
||||
import {
|
||||
TextFileView,
|
||||
WorkspaceLeaf,
|
||||
} from "obsidian";
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import Excalidraw, {exportToSvg} from "@excalidraw/excalidraw";
|
||||
import { ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types";
|
||||
import {
|
||||
AppState,
|
||||
LibraryItems
|
||||
} from "@excalidraw/excalidraw/types/types";
|
||||
import {
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
EXCALIDRAW_FILE_EXTENSION,
|
||||
ICON_NAME,
|
||||
EXCALIDRAW_LIB_HEADER,
|
||||
} from './constants';
|
||||
import ExcalidrawPlugin from './main';
|
||||
|
||||
export default class ExcalidrawView extends TextFileView {
|
||||
private getScene: any;
|
||||
private excalidrawRef: React.MutableRefObject<any>;
|
||||
private justLoaded: boolean;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: ExcalidrawPlugin) {
|
||||
super(leaf);
|
||||
this.getScene = null;
|
||||
this.excalidrawRef = null;
|
||||
this.plugin = plugin;
|
||||
this.justLoaded = false;
|
||||
}
|
||||
|
||||
// get the new file content
|
||||
getViewData () {
|
||||
if(this.getScene)
|
||||
return this.getScene();
|
||||
else return this.data;
|
||||
}
|
||||
|
||||
async onunload() {
|
||||
if(this.excalidrawRef) await this.save();
|
||||
}
|
||||
|
||||
setViewData (data: string, clear: boolean) {
|
||||
if (this.app.workspace.layoutReady) {
|
||||
this.loadDrawing(data,clear);
|
||||
} else {
|
||||
this.registerEvent(this.app.workspace.on('layout-ready', async () => this.loadDrawing(data,clear)));
|
||||
}
|
||||
}
|
||||
|
||||
// clear the view content
|
||||
clear() {
|
||||
if(this.excalidrawRef) {
|
||||
this.excalidrawRef = null;
|
||||
this.getScene = null;
|
||||
ReactDOM.unmountComponentAtNode(this.contentEl);
|
||||
}
|
||||
//this.excalidrawRef.current.resetScene({ resetLoadingState: true });
|
||||
}
|
||||
|
||||
private async loadDrawing (data:string, clear:boolean) {
|
||||
if(clear) this.clear();
|
||||
this.justLoaded = true; //a flag to trigger zoom to fit after the drawing has been loaded
|
||||
const excalidrawData = JSON.parse(data);
|
||||
if(this.excalidrawRef) {
|
||||
this.excalidrawRef.current.updateScene({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
});
|
||||
} else {
|
||||
this.instantiateExcalidraw({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
scrollToContent: true,
|
||||
libraryItems: await this.getLibrary(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// gets the title of the document
|
||||
getDisplayText() {
|
||||
if(this.file) return this.file.basename;
|
||||
else return "Excalidraw (no file)";
|
||||
|
||||
}
|
||||
|
||||
// confirms this view can accept csv extension
|
||||
canAcceptExtension(extension: string) {
|
||||
return extension == EXCALIDRAW_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
// the view type name
|
||||
getViewType() {
|
||||
return VIEW_TYPE_EXCALIDRAW;
|
||||
}
|
||||
|
||||
// icon for the view
|
||||
getIcon() {
|
||||
return ICON_NAME;
|
||||
}
|
||||
|
||||
async getLibrary() {
|
||||
const data = JSON.parse(this.plugin.settings.library);
|
||||
return data?.library ? data.library : [];
|
||||
}
|
||||
|
||||
|
||||
private instantiateExcalidraw(initdata: any) {
|
||||
const reactElement = React.createElement(() => {
|
||||
const excalidrawRef = React.useRef(null);
|
||||
const excalidrawWrapperRef = React.useRef(null);
|
||||
const [dimensions, setDimensions] = React.useState({
|
||||
width: undefined,
|
||||
height: undefined
|
||||
});
|
||||
|
||||
this.excalidrawRef = excalidrawRef;
|
||||
React.useEffect(() => {
|
||||
setDimensions({
|
||||
width: this.contentEl.clientWidth,
|
||||
height: this.contentEl.clientHeight,
|
||||
});
|
||||
|
||||
const onResize = () => {
|
||||
try {
|
||||
setDimensions({
|
||||
width: this.contentEl.clientWidth,
|
||||
height: this.contentEl.clientHeight,
|
||||
});
|
||||
} catch(err) {console.log ("Excalidraw React-Wrapper, onResize ",err)}
|
||||
};
|
||||
window.addEventListener("resize", onResize);
|
||||
return () => window.removeEventListener("resize", onResize);
|
||||
}, [excalidrawWrapperRef]);
|
||||
|
||||
this.getScene = () => {
|
||||
if(!excalidrawRef?.current) {
|
||||
return null;
|
||||
}
|
||||
const el: ExcalidrawElement[] = excalidrawRef.current.getSceneElements();
|
||||
const st: AppState = excalidrawRef.current.getAppState();
|
||||
return JSON.stringify({
|
||||
"type": "excalidraw",
|
||||
"version": 2,
|
||||
"source": "https://excalidraw.com",
|
||||
"elements": el,
|
||||
"appState": {
|
||||
"theme": st.theme,
|
||||
"viewBackgroundColor": st.viewBackgroundColor,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return React.createElement(
|
||||
React.Fragment,
|
||||
null,
|
||||
React.createElement(
|
||||
"div",
|
||||
{
|
||||
className: "excalidraw-wrapper",
|
||||
ref: excalidrawWrapperRef,
|
||||
key: "abc",
|
||||
},
|
||||
React.createElement(Excalidraw.default, {
|
||||
ref: excalidrawRef,
|
||||
key: "xyz",
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
UIOptions: {
|
||||
canvasActions: {
|
||||
loadScene: false,
|
||||
saveScene: false,
|
||||
saveAsScene: false
|
||||
},
|
||||
},
|
||||
initialData: initdata,
|
||||
onChange: (et:ExcalidrawElement[],st:AppState) => {
|
||||
if(this.justLoaded) {
|
||||
this.justLoaded = false;
|
||||
const e = new KeyboardEvent("keydown", {bubbles : true, cancelable : true, shiftKey : true, code:"Digit1"});
|
||||
this.contentEl.querySelector("canvas")?.dispatchEvent(e);
|
||||
}
|
||||
},
|
||||
onLibraryChange: async (items:LibraryItems) => {
|
||||
this.plugin.settings.library = EXCALIDRAW_LIB_HEADER+JSON.stringify(items)+'}';
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
ReactDOM.render(reactElement,(this as any).contentEl);
|
||||
|
||||
}
|
||||
|
||||
public static getSVG(data:string):SVGSVGElement {
|
||||
try {
|
||||
const excalidrawData = JSON.parse(data);
|
||||
return exportToSvg({
|
||||
elements: excalidrawData.elements,
|
||||
appState: {
|
||||
exportBackground: true,
|
||||
exportWithDarkMode: excalidrawData.appState?.theme=="light" ? false : true,
|
||||
... excalidrawData.appState,},
|
||||
exportPadding:10,
|
||||
metadata: "Generated by Excalidraw-Obsidian plugin",
|
||||
});
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||
export const EXCALIDRAW_FILE_EXTENSION = "excalidraw";
|
||||
export const ICON_NAME = "excalidraw-icon";
|
||||
export const CODEBLOCK_EXCALIDRAW = "excalidraw";
|
||||
export const MAX_COLORS = 5;
|
||||
export const COLOR_FREQ = 6;
|
||||
export const BLANK_DRAWING = '{"type":"excalidraw","version":2,"source":"https://excalidraw.com","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff"}}';
|
||||
export const EMPTY_MESSAGE = "Hit enter to create a new drawing";
|
||||
export const EXCALIDRAW_ICON = `<g transform="translate(30,0)"><path fill="currentColor" stroke="currentColor" d="M14.45 1.715c-2.723 2.148-6.915 5.797-10.223 8.93l-2.61 2.445.477 3.207c.258 1.75.738 5.176 1.031 7.582.332 2.406.66 4.668.773 4.996.145.438 0 .656-.406.656-.699 0-.734-.183 1.176 5.832.7 2.297 1.363 4.414 1.434 4.633.074.254.367.363.699.254.332-.145.515-.438.406-.691-.113-.293.074-.586.367-.696.403-.144.367-.437-.258-1.492-.992-1.64-3.53-15.64-3.675-20.164-.11-3.207-.11-3.242 1.25-5.066 1.324-1.786 4.375-4.485 9.078-7.91 1.324-.985 2.648-2.079 3.015-2.446.551-.656.809-.472 5.442 4.414 2.683 2.805 5.664 5.688 6.617 6.414l1.766 1.313-1.36 2.844c-.734 1.53-3.715 7.437-6.656 13.054-6.137 11.813-4.887 10.68-12.02 10.79l-4.632.038-1.547 1.75c-1.617 1.86-1.836 2.551-1.063 3.72.293.398.512 1.054.512 1.456 0 .656.258.766 1.73.84.918.035 1.762.145 1.875.254.11.11.258 2.371.368 5.031l.144 4.813-2.46 5.25C1.616 72.516 0 76.527 0 77.84c0 .691.148 1.273.293 1.273.367 0 .367-.035 15.332-30.988 6.95-14.363 13.531-27.89 14.633-30.113 1.101-2.227 2.094-4.266 2.168-4.559.074-.328-2.461-2.844-6.508-6.379C22.281 3.864 19.082.95 18.785.621c-.844-1.023-2.094-.695-4.336 1.094zM15.7 43.64c-1.692 3.246-1.766 3.28-6.4 3.5-4.081.218-4.152.183-4.152-.582 0-.438-.148-1.024-.332-1.313-.222-.328-.074-.914.442-1.715l.808-1.238h3.676c2.024-.04 4.34-.184 5.149-.328.808-.149 1.507-.219 1.578-.184.074.035-.293.875-.77 1.86zm-3.09 5.832c-.294.765-1.067 2.37-1.692 3.574-1.027 2.043-1.137 2.113-1.395 1.277-.148-.511-.257-2.008-.296-3.355-.036-2.66-.11-2.625 2.98-2.809l.992-.035zm0 0"/><path fill="currentColor" stroke="currentColor" d="M15.55 10.39c-.66.473-.843.95-.843 2.153 0 1.422.11 1.64 1.102 2.039.992.402 1.25.367 2.39-.398 1.508-1.024 1.543-1.278.442-2.918-.957-1.422-1.914-1.676-3.09-.875zm2.098 1.313c.586 1.02.22 1.785-.882 1.785-.993 0-1.434-.984-.883-1.968.441-.801 1.285-.727 1.765.183zm0 0M38.602 18.594c0 .183-.22.363-.477.363-.219 0-.844 1.023-1.324 2.262-1.469 3.793-16.176 32.629-16.211 31.718 0-.472-.223-.8-.59-.8-.516 0-.59.289-.367 1.71.219 1.641.074 2.008-5.149 12.071-2.941 5.723-6.101 11.703-7.02 13.305-.956 1.68-1.69 3.5-1.765 4.265-.11 1.313.035 1.496 3.235 4.23 1.84 1.606 4.191 3.61 5.222 4.52 4.63 4.196 6.801 5.871 7.387 5.762.883-.145 14.523-14.328 14.559-15.129 0-.367-.66-5.906-1.47-12.324-1.398-10.938-2.722-23.734-2.573-24.973.109-.765-.442-4.633-.844-6.308-.332-1.313-.184-1.86 2.46-7.84 1.544-3.535 3.567-7.875 4.45-9.625.844-1.75 1.582-3.281 1.582-3.39 0-.11-.258-.18-.55-.18-.298 0-.555.144-.555.363zm-8.454 27.234c.403 2.55 1.211 8.676 1.801 13.598 1.14 9.043 2.461 19.07 2.832 21.62.219 1.278.07 1.532-2.316 4.157-4.156 4.629-8.567 9.188-10.074 10.356l-1.399 1.093-7.168-6.636c-6.617-6.051-7.168-6.672-6.765-7.403.222-.398 2.097-3.789 4.156-7.508 2.058-3.718 4.777-8.68 6.027-11.011 1.29-2.371 2.465-4.41 2.684-4.52.258-.148.332 3.535.258 11.375-.149 11.703-.11 11.739 1.066 11.485.148 0 .258-5.907.258-13.09V56.293l3.86-7.656c2.132-4.23 3.898-7.621 3.972-7.586.07.039.441 2.187.808 4.777zm0 0"/></g>`;
|
||||
export const EXCALIDRAW_LIB_HEADER = `{"type":"excalidrawlib","version":1,"library":`;
|
||||
204
src/main.ts
204
src/main.ts
@@ -1,204 +0,0 @@
|
||||
import {
|
||||
TFile,
|
||||
TFolder,
|
||||
Plugin,
|
||||
WorkspaceLeaf,
|
||||
addIcon,
|
||||
App,
|
||||
PluginManifest,
|
||||
MarkdownView,
|
||||
normalizePath,
|
||||
} from 'obsidian';
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
EXCALIDRAW_ICON,
|
||||
ICON_NAME,
|
||||
EXCALIDRAW_FILE_EXTENSION,
|
||||
CODEBLOCK_EXCALIDRAW,
|
||||
} from './constants';
|
||||
import ExcalidrawView from './ExcalidrawView';
|
||||
import {
|
||||
ExcalidrawSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
ExcalidrawSettingTab
|
||||
} from './settings';
|
||||
import {
|
||||
openDialogAction,
|
||||
OpenFileDialog
|
||||
} from './openDrawing';
|
||||
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
public settings: ExcalidrawSettings;
|
||||
private openDialog: OpenFileDialog;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
}
|
||||
|
||||
async onload() {
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this)
|
||||
);
|
||||
|
||||
this.registerExtensions([EXCALIDRAW_FILE_EXTENSION],VIEW_TYPE_EXCALIDRAW);
|
||||
|
||||
this.registerMarkdownCodeBlockProcessor(CODEBLOCK_EXCALIDRAW, async (source,el,ctx) => {
|
||||
const parseError = (message: string) => {
|
||||
el.createDiv("excalidraw-error",(el)=> {
|
||||
el.createEl("p","Please provide a link to an excalidraw file: [[file."+EXCALIDRAW_FILE_EXTENSION+"]]");
|
||||
el.createEl("p",message);
|
||||
el.createEl("p",source);
|
||||
})
|
||||
}
|
||||
|
||||
const filename = source.match(/\[{2}(.*)\]{2}/m);
|
||||
const filenameWH = source.match(/\[{2}(.*)\|(\d*)x(\d*)\]{2}/m);
|
||||
const filenameW = source.match(/\[{2}(.*)\|(\d*)\]{2}/m);
|
||||
|
||||
let fname:string = '';
|
||||
let fwidth:string = this.settings.width;
|
||||
let fheight:string = null;
|
||||
|
||||
if (filenameWH) {
|
||||
fname = filenameWH[1];
|
||||
fwidth = filenameWH[2];
|
||||
fheight = filenameWH[3];
|
||||
} else if (filenameW) {
|
||||
fname = filenameW[1];
|
||||
fwidth = filenameW[2];
|
||||
} else if (filename) {
|
||||
fname = filename[1];
|
||||
}
|
||||
|
||||
if(fname == '') {
|
||||
parseError("No link to file found in codeblock.");
|
||||
return;
|
||||
}
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(fname);
|
||||
if(!(file && file instanceof TFile)) {
|
||||
parseError("File does not exist. " + fname);
|
||||
return;
|
||||
}
|
||||
|
||||
if(file.extension != EXCALIDRAW_FILE_EXTENSION) {
|
||||
parseError("Not an excalidraw file. Must have extension " + EXCALIDRAW_FILE_EXTENSION);
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await this.app.vault.read(file);
|
||||
const svg = ExcalidrawView.getSVG(content);
|
||||
if(!svg) {
|
||||
parseError("Parse error. Not a valid Excalidraw file.");
|
||||
return;
|
||||
}
|
||||
el.createDiv("excalidraw-svg",(el)=> {
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
svg.style.setProperty('width',fwidth);
|
||||
if(fheight) svg.style.setProperty('height',fheight);
|
||||
el.appendChild(svg);
|
||||
});
|
||||
});
|
||||
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
|
||||
this.openDialog = new OpenFileDialog(this.app, this);
|
||||
this.addRibbonIcon(ICON_NAME, 'Excalidraw', async () => {
|
||||
this.openDialog.start(openDialogAction.openFile);
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-open",
|
||||
name: "Open an existing drawing or create new one",
|
||||
callback: () => {
|
||||
this.openDialog.start(openDialogAction.openFile);
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-insert-transclusion",
|
||||
name: "Transclude an ."+EXCALIDRAW_FILE_EXTENSION+" file into a markdown document",
|
||||
callback: () => {
|
||||
this.openDialog.start(openDialogAction.insertLink);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
this.addCommand({
|
||||
id: "excalidraw-autocreate",
|
||||
name: "Create a new drawing",
|
||||
callback: () => {
|
||||
this.createDrawing(this.getNextDefaultFilename());
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public insertCodeblock(data:string) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if(activeView) {
|
||||
const editor = activeView.editor;
|
||||
editor.replaceSelection(
|
||||
String.fromCharCode(96,96,96) +
|
||||
CODEBLOCK_EXCALIDRAW +
|
||||
"\n[["+data+"]]\n" +
|
||||
String.fromCharCode(96,96,96));
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
public async openDrawing(drawingFile: TFile) {
|
||||
const leafs = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
let leaf:WorkspaceLeaf = null;
|
||||
|
||||
if (leafs?.length > 0) {
|
||||
leaf = leafs[0];
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.activeLeaf;
|
||||
}
|
||||
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf();
|
||||
}
|
||||
|
||||
leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: {file: drawingFile.path}}
|
||||
);
|
||||
}
|
||||
|
||||
private getNextDefaultFilename():string {
|
||||
return this.settings.folder+'/Drawing ' + window.moment().format('YYYY-MM-DD HH.mm.ss')+'.'+EXCALIDRAW_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
public async createDrawing(filename: string) {
|
||||
const folder = this.app.vault.getAbstractFileByPath(normalizePath(this.settings.folder));
|
||||
if (!(folder && folder instanceof TFolder)) {
|
||||
await this.app.vault.createFolder(this.settings.folder);
|
||||
}
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(this.settings.templateFilePath));
|
||||
if(file && file instanceof TFile) {
|
||||
const content = await this.app.vault.read(file);
|
||||
this.openDrawing(await this.app.vault.create(filename,content==''?BLANK_DRAWING:content));
|
||||
} else {
|
||||
this.openDrawing(await this.app.vault.create(filename,BLANK_DRAWING));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
App,
|
||||
FuzzySuggestModal,
|
||||
TFile
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from './main';
|
||||
import {
|
||||
EMPTY_MESSAGE,
|
||||
EXCALIDRAW_FILE_EXTENSION
|
||||
} from './constants';
|
||||
|
||||
export enum openDialogAction {
|
||||
openFile,
|
||||
insertLink,
|
||||
}
|
||||
|
||||
export class OpenFileDialog extends FuzzySuggestModal<TFile> {
|
||||
public app: App;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private action: openDialogAction;
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app);
|
||||
this.app = app;
|
||||
this.action = openDialogAction.openFile;
|
||||
this.plugin = plugin;
|
||||
this.setInstructions([{
|
||||
command: "Type name of drawing to select.",
|
||||
purpose: "",
|
||||
}]);
|
||||
|
||||
this.inputEl.onkeyup = (e) => {
|
||||
if(e.key=="Enter" && this.action == openDialogAction.openFile) {
|
||||
if (this.containerEl.innerText.includes(EMPTY_MESSAGE)) {
|
||||
this.plugin.createDrawing(this.plugin.settings.folder+'/'+this.inputEl.value+'.'+EXCALIDRAW_FILE_EXTENSION);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getItems(): TFile[] {
|
||||
const excalidrawFiles = this.app.vault.getFiles();
|
||||
return (excalidrawFiles || []).filter((f:TFile) => (f.extension==EXCALIDRAW_FILE_EXTENSION));
|
||||
}
|
||||
|
||||
getItemText(item: TFile): string {
|
||||
return item.basename;
|
||||
}
|
||||
|
||||
onChooseItem(item: TFile, _evt: MouseEvent | KeyboardEvent): void {
|
||||
switch(this.action) {
|
||||
case(openDialogAction.openFile):
|
||||
this.plugin.openDrawing(item);
|
||||
break;
|
||||
case(openDialogAction.insertLink):
|
||||
this.plugin.insertCodeblock(item.path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start(action:openDialogAction): void {
|
||||
this.action = action;
|
||||
switch(action) {
|
||||
case (openDialogAction.openFile):
|
||||
this.emptyStateText = EMPTY_MESSAGE;
|
||||
this.setPlaceholder("Select existing drawing or type name of new and hit enter.");
|
||||
break;
|
||||
case (openDialogAction.insertLink):
|
||||
this.emptyStateText = "No file matches your query.";
|
||||
this.setPlaceholder("Select existing drawing to insert into document.");
|
||||
break;
|
||||
}
|
||||
this.open();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
import {
|
||||
App,
|
||||
PluginSettingTab,
|
||||
Setting
|
||||
} from 'obsidian';
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string,
|
||||
templateFilePath: string,
|
||||
width: string,
|
||||
library: string,
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
folder: 'Excalidraw',
|
||||
templateFilePath: 'Excalidraw/Template.excalidraw',
|
||||
width: '400',
|
||||
library: `{"type":"excalidrawlib","version":1,"library":[]}`,
|
||||
}
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
plugin: ExcalidrawPlugin;
|
||||
|
||||
constructor(app: App, plugin: ExcalidrawPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
let {containerEl} = this;
|
||||
|
||||
this.containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Excalidraw folder')
|
||||
.setDesc('Default location for your Excalidraw drawings. Leaving this empty means drawings will be created in the Vault root.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Excalidraw')
|
||||
.setValue(this.plugin.settings.folder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.folder = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Excalidraw template file')
|
||||
.setDesc('Full path to file containing the file you want to use as the template for new Excalidraw drawings. '+
|
||||
'Note that Excalidraw files will have an extension of ".excalidraw" ' +
|
||||
'Assuming your template is in the default excalidraw folder, the setting would be: excalidraw/Template.excalidraw')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Excalidraw/Template.excalidraw')
|
||||
.setValue(this.plugin.settings.templateFilePath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.templateFilePath = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default width of embedded image')
|
||||
.setDesc('The default width of an embedded drawing. You can specify a different ' +
|
||||
'width when embedding an image using the [[drawing.excalidraw|100]] or ' +
|
||||
'[[drawing.excalidraw|100x100]] format.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('400')
|
||||
.setValue(this.plugin.settings.width)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.width = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
18
styles.css
18
styles.css
@@ -1,18 +0,0 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.excalidraw-wrapper {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.context-menu-option__shortcut {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.block-language-excalidraw {
|
||||
text-align:center;
|
||||
}
|
||||
@@ -13,13 +13,10 @@
|
||||
"dom",
|
||||
"es5",
|
||||
"scripthost",
|
||||
"es2015",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "react",
|
||||
"es2015"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx", "src/openDrawing.ts",
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"1.0.5": "0.11.13"
|
||||
"1.0.0": "0.9.10"
|
||||
}
|
||||
Reference in New Issue
Block a user