Compare commits
No commits in common. "main" and "v0.3.0" have entirely different histories.
11 changed files with 133 additions and 327 deletions
|
@ -1,9 +1,7 @@
|
||||||
name: Build on Push and create Release on Tag
|
name: Create Release on Tag Push
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
|
|
||||||
|
@ -30,12 +28,6 @@ jobs:
|
||||||
version: 8
|
version: 8
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
# Validate Tag Matches JSON Versions
|
|
||||||
- name: Validate Tag Matches JSON Versions
|
|
||||||
if: github.ref_type == 'tag'
|
|
||||||
run: |
|
|
||||||
node scripts/validate_tag.cjs ${{ github.ref }}
|
|
||||||
|
|
||||||
# Get pnpm store directory
|
# Get pnpm store directory
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
id: pnpm-cache
|
id: pnpm-cache
|
||||||
|
@ -60,22 +52,11 @@ jobs:
|
||||||
- name: Build for production
|
- name: Build for production
|
||||||
run: pnpm build
|
run: pnpm build
|
||||||
|
|
||||||
# Move file
|
- name: Release
|
||||||
- name: Move file
|
uses: ncipollo/release-action@v1
|
||||||
run: mkdir built; mv package.zip built/package.zip
|
|
||||||
|
|
||||||
# Upload artifacts
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
with:
|
||||||
path: built/package.zip
|
allowUpdates: true
|
||||||
overwrite: true
|
artifactErrorsFailBuild: true
|
||||||
|
artifacts: "package.zip"
|
||||||
# Create Forgejo Release
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Create Forgejo Release
|
prerelease: false
|
||||||
if: github.ref_type == 'tag'
|
|
||||||
uses: actions/forgejo-release@v1
|
|
||||||
with:
|
|
||||||
direction: upload
|
|
||||||
release-dir: built
|
|
||||||
token: ${{ secrets.FORGE_TOKEN }}
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "siyuan-jsdraw-plugin",
|
"name": "siyuan-jsdraw-plugin",
|
||||||
"version": "0.4.0",
|
"version": "0.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Include a whiteboard for freehand drawing anywhere in your documents.",
|
"description": "Include a whiteboard for freehand drawing anywhere in your documents.",
|
||||||
"repository": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin",
|
"repository": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin",
|
||||||
|
@ -36,7 +36,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@js-draw/material-icons": "^1.29.0",
|
"@js-draw/material-icons": "^1.29.0",
|
||||||
"js-draw": "^1.29.0",
|
"js-draw": "^1.29.0"
|
||||||
"ts-serializable": "^4.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "siyuan-jsdraw-plugin",
|
"name": "siyuan-jsdraw-plugin",
|
||||||
"author": "massivebox",
|
"author": "massivebox",
|
||||||
"url": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin",
|
"url": "https://git.massive.box/massivebox/siyuan-jsdraw-plugin",
|
||||||
"version": "0.4.0",
|
"version": "0.3.0",
|
||||||
"minAppVersion": "3.0.12",
|
"minAppVersion": "3.0.12",
|
||||||
"backends": [
|
"backends": [
|
||||||
"windows",
|
"windows",
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"custom": [
|
"custom": [
|
||||||
"https://s.massive.box/jsdraw-plugin-donate"
|
""
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -1,44 +1,3 @@
|
||||||
{
|
{
|
||||||
"insertDrawing": "Insert Drawing",
|
"insertDrawing": "Insert Drawing"
|
||||||
"editDrawing": "Edit with js-draw",
|
|
||||||
"errNoFileID": "File ID missing - couldn't open file.",
|
|
||||||
"errSyncIDNotFound": "Couldn't find SyncID in document for drawing, make sure you're trying to edit a drawing that is included in at least a note.",
|
|
||||||
"errCreateUnknown": "Unknown error while creating editor, please try again.",
|
|
||||||
"errInvalidBackgroundColor": "Invalid background color! Please enter an HEX color, like #000000 (black) or #FFFFFF (white). The old background color will be used.",
|
|
||||||
"drawing": "Drawing",
|
|
||||||
"settings": {
|
|
||||||
"name": "js-draw Plugin Settings",
|
|
||||||
"suggestedColors":{
|
|
||||||
"white": "White",
|
|
||||||
"black": "Black",
|
|
||||||
"transparent": "Transparent",
|
|
||||||
"custom": "Custom",
|
|
||||||
"darkBlue": "Dark Blue",
|
|
||||||
"darkGray": "Dark Gray"
|
|
||||||
},
|
|
||||||
"grid": {
|
|
||||||
"title": "Enable grid by default",
|
|
||||||
"description": "Enable to automatically turn on the grid on new drawings."
|
|
||||||
},
|
|
||||||
"backgroundDropdown":{
|
|
||||||
"title": "Background color",
|
|
||||||
"description": "Default background color for new drawings."
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"title": "Custom background",
|
|
||||||
"description": "Hexadecimal code of the custom background color for new drawings.<br /><b>This setting is only applied if \"Background Color\" is set to \"Custom\"!</b>"
|
|
||||||
},
|
|
||||||
"dialogOnDesktop": {
|
|
||||||
"title": "Open editor as dialog on desktop",
|
|
||||||
"description": "Dialog mode provides a larger drawing area, but it's not as handy to use as tabs (default).<br />The editor will always open as a dialog on mobile."
|
|
||||||
},
|
|
||||||
"analytics": {
|
|
||||||
"title": "Analytics",
|
|
||||||
"description": "Enable to send anonymous usage data to the developer. <a href='https://s.massive.box/jsdraw-plugin-privacy'>Privacy Policy</a>"
|
|
||||||
},
|
|
||||||
"restorePosition": {
|
|
||||||
"title": "Remember editor position",
|
|
||||||
"description": "When enabled, the editor will remember the zoom factor and position, and it will restore them the next time you open the drawing."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
Binary file not shown.
Before Width: | Height: | Size: 719 B |
|
@ -1,24 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const [tagName] = process.argv.slice(2); // Get tag from CLI arguments
|
|
||||||
if (!tagName) {
|
|
||||||
console.error('Error: No tag name provided.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAG_VERSION = tagName.replace('refs/tags/v', '');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8'));
|
|
||||||
const pluginJson = JSON.parse(fs.readFileSync(path.resolve('plugin.json'), 'utf8'));
|
|
||||||
|
|
||||||
if (TAG_VERSION !== packageJson.version || TAG_VERSION !== pluginJson.version) {
|
|
||||||
console.error(`Error: Tag version (${TAG_VERSION}) does not match package.json (${packageJson.version}) or plugin.json (${pluginJson.version})`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.log('Tag version matches both JSON files.');
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to read or parse JSON files:', err.message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
140
src/config.ts
140
src/config.ts
|
@ -1,16 +1,17 @@
|
||||||
import {PluginFile} from "@/file";
|
import {PluginFile} from "@/file";
|
||||||
import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const";
|
import {CONFIG_FILENAME, JSON_MIME, STORAGE_PATH} from "@/const";
|
||||||
import {Plugin, showMessage} from "siyuan";
|
import {Plugin} from "siyuan";
|
||||||
import {SettingUtils} from "@/libs/setting-utils";
|
import {SettingUtils} from "@/libs/setting-utils";
|
||||||
import {getFirstDefined} from "@/helper";
|
import {validateColor} from "@/helper";
|
||||||
|
|
||||||
export interface Options {
|
type Options = {
|
||||||
|
grid: boolean
|
||||||
|
background: string
|
||||||
dialogOnDesktop: boolean
|
dialogOnDesktop: boolean
|
||||||
analytics: boolean
|
analytics: boolean
|
||||||
editorOptions: EditorOptions
|
};
|
||||||
}
|
|
||||||
export interface EditorOptions {
|
export type DefaultEditorOptions = {
|
||||||
restorePosition: boolean;
|
|
||||||
grid: boolean
|
grid: boolean
|
||||||
background: string
|
background: string
|
||||||
}
|
}
|
||||||
|
@ -28,23 +29,30 @@ export class PluginConfig {
|
||||||
this.file = new PluginFile(STORAGE_PATH, CONFIG_FILENAME, JSON_MIME);
|
this.file = new PluginFile(STORAGE_PATH, CONFIG_FILENAME, JSON_MIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDefaultEditorOptions(): DefaultEditorOptions {
|
||||||
|
return {
|
||||||
|
grid: this.options.grid,
|
||||||
|
background: this.options.background,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.firstRun = false;
|
this.firstRun = false;
|
||||||
await this.file.loadFromSiYuanFS();
|
await this.file.loadFromSiYuanFS();
|
||||||
const jsonObj = JSON.parse(this.file.getContent());
|
this.options = JSON.parse(this.file.getContent());
|
||||||
if(jsonObj == null) {
|
if(this.options == null) {
|
||||||
this.firstRun = true;
|
this.loadDefaultConfig();
|
||||||
}
|
}
|
||||||
// if more than one fallback, the intermediate ones are from a legacy config file version
|
}
|
||||||
|
|
||||||
|
private loadDefaultConfig() {
|
||||||
this.options = {
|
this.options = {
|
||||||
dialogOnDesktop: getFirstDefined(jsonObj?.dialogOnDesktop, false),
|
grid: true,
|
||||||
analytics: getFirstDefined(jsonObj?.analytics, true),
|
background: "#000000",
|
||||||
editorOptions: {
|
dialogOnDesktop: false,
|
||||||
restorePosition: getFirstDefined(jsonObj?.editorOptions?.restorePosition, jsonObj?.restorePosition, true),
|
analytics: true,
|
||||||
grid: getFirstDefined(jsonObj?.editorOptions?.grid, jsonObj?.grid, true),
|
|
||||||
background: getFirstDefined(jsonObj?.editorOptions?.background, jsonObj?.background, "#00000000")
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
this.firstRun = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
|
@ -53,14 +61,12 @@ export class PluginConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig(config: Options) {
|
setConfig(config: Options) {
|
||||||
this.options = config;
|
if(!validateColor(config.background)) {
|
||||||
|
alert("Invalid background color! Please enter an HEX color, like #000000 (black) or #FFFFFF (white)");
|
||||||
|
config.background = this.options.background;
|
||||||
}
|
}
|
||||||
|
|
||||||
static validateColor(hex: string) {
|
this.options = config;
|
||||||
hex = hex.replace('#', '');
|
|
||||||
return typeof hex === 'string'
|
|
||||||
&& (hex.length === 6 || hex.length === 8)
|
|
||||||
&& !isNaN(Number('0x' + hex))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -70,100 +76,62 @@ export class PluginConfigViewer {
|
||||||
config: PluginConfig;
|
config: PluginConfig;
|
||||||
settingUtils: SettingUtils;
|
settingUtils: SettingUtils;
|
||||||
plugin: Plugin;
|
plugin: Plugin;
|
||||||
private readonly backgroundDropdownOptions;
|
|
||||||
|
|
||||||
constructor(config: PluginConfig, plugin: Plugin) {
|
constructor(config: PluginConfig, plugin: Plugin) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.backgroundDropdownOptions = {
|
|
||||||
'#00000000': plugin.i18n.settings.suggestedColors.transparent,
|
|
||||||
'CUSTOM': plugin.i18n.settings.suggestedColors.custom,
|
|
||||||
'#ffffff': plugin.i18n.settings.suggestedColors.white,
|
|
||||||
'#1e2227': plugin.i18n.settings.suggestedColors.darkBlue,
|
|
||||||
'#1e1e1e': plugin.i18n.settings.suggestedColors.darkGray,
|
|
||||||
'#000000': plugin.i18n.settings.suggestedColors.black,
|
|
||||||
}
|
|
||||||
this.populateSettingMenu();
|
this.populateSettingMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
async configSaveCallback(data) {
|
|
||||||
|
|
||||||
let color = data.backgroundDropdown === "CUSTOM" ? data.background : data.backgroundDropdown;
|
|
||||||
if(!PluginConfig.validateColor(color)) {
|
|
||||||
showMessage(this.plugin.i18n.errInvalidBackgroundColor, 0, 'error');
|
|
||||||
data.background = this.config.options.editorOptions.background;
|
|
||||||
this.settingUtils.set('background', data.background);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.setConfig({
|
|
||||||
dialogOnDesktop: data.dialogOnDesktop,
|
|
||||||
analytics: data.analytics,
|
|
||||||
editorOptions: {
|
|
||||||
grid: data.grid,
|
|
||||||
background: color,
|
|
||||||
restorePosition: data.restorePosition,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await this.config.save();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
populateSettingMenu() {
|
populateSettingMenu() {
|
||||||
|
|
||||||
this.settingUtils = new SettingUtils({
|
this.settingUtils = new SettingUtils({
|
||||||
plugin: this.plugin,
|
plugin: this.plugin,
|
||||||
name: 'optionsUI',
|
|
||||||
callback: async (data) => {
|
callback: async (data) => {
|
||||||
await this.configSaveCallback(data);
|
this.config.setConfig({
|
||||||
|
grid: data.grid,
|
||||||
|
background: data.background,
|
||||||
|
dialogOnDesktop: data.dialogOnDesktop,
|
||||||
|
analytics: data.analytics,
|
||||||
|
});
|
||||||
|
await this.config.save();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
this.settingUtils.addItem({
|
||||||
key: "grid",
|
key: "grid",
|
||||||
title: this.plugin.i18n.settings.grid.title,
|
title: "Enable grid by default",
|
||||||
description: this.plugin.i18n.settings.grid.description,
|
description: "Enable to automatically turn on the grid on new drawings.",
|
||||||
value: this.config.options.editorOptions.grid,
|
value: this.config.options.grid,
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
|
||||||
key: 'backgroundDropdown',
|
|
||||||
title: this.plugin.i18n.settings.backgroundDropdown.title,
|
|
||||||
description: this.plugin.i18n.settings.backgroundDropdown.description,
|
|
||||||
type: 'select',
|
|
||||||
value: this.config.options.editorOptions.background in this.backgroundDropdownOptions ?
|
|
||||||
this.config.options.editorOptions.background : 'CUSTOM',
|
|
||||||
options: this.backgroundDropdownOptions,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
this.settingUtils.addItem({
|
||||||
key: "background",
|
key: "background",
|
||||||
title: this.plugin.i18n.settings.background.title,
|
title: "Default background Color",
|
||||||
description: this.plugin.i18n.settings.background.description,
|
description: "Default background color of the drawing area for new drawings in hexadecimal.",
|
||||||
value: this.config.options.editorOptions.background,
|
value: this.config.options.background,
|
||||||
type: 'textinput',
|
type: 'textarea',
|
||||||
});
|
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
|
||||||
key: "restorePosition",
|
|
||||||
title: this.plugin.i18n.settings.restorePosition.title,
|
|
||||||
description: this.plugin.i18n.settings.restorePosition.description,
|
|
||||||
value: this.config.options.editorOptions.restorePosition,
|
|
||||||
type: 'checkbox'
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
this.settingUtils.addItem({
|
||||||
key: "dialogOnDesktop",
|
key: "dialogOnDesktop",
|
||||||
title: this.plugin.i18n.settings.dialogOnDesktop.title,
|
title: "Open editor as dialog on desktop",
|
||||||
description: this.plugin.i18n.settings.dialogOnDesktop.description,
|
description: `
|
||||||
|
Dialog mode provides a larger drawing area, but it's not as handy to use as tabs (default).<br />
|
||||||
|
The editor will always open as a dialog on mobile.
|
||||||
|
`,
|
||||||
value: this.config.options.dialogOnDesktop,
|
value: this.config.options.dialogOnDesktop,
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settingUtils.addItem({
|
this.settingUtils.addItem({
|
||||||
key: "analytics",
|
key: "analytics",
|
||||||
title: this.plugin.i18n.settings.analytics.title,
|
title: "Analytics",
|
||||||
description: this.plugin.i18n.settings.analytics.description,
|
description: `
|
||||||
|
Enable to send anonymous usage data to the developer.
|
||||||
|
<a href='https://s.massive.box/jsdraw-plugin-privacy'>Privacy</a>
|
||||||
|
`,
|
||||||
value: this.config.options.analytics,
|
value: this.config.options.analytics,
|
||||||
type: 'checkbox'
|
type: 'checkbox'
|
||||||
});
|
});
|
||||||
|
|
144
src/editor.ts
144
src/editor.ts
|
@ -1,21 +1,12 @@
|
||||||
import {MaterialIconProvider} from "@js-draw/material-icons";
|
import {MaterialIconProvider} from "@js-draw/material-icons";
|
||||||
import {PluginAsset, PluginFile} from "@/file";
|
import {PluginAsset, PluginFile} from "@/file";
|
||||||
import {JSON_MIME, STORAGE_PATH, SVG_MIME, TOOLBAR_FILENAME} from "@/const";
|
import {JSON_MIME, STORAGE_PATH, SVG_MIME, TOOLBAR_FILENAME} from "@/const";
|
||||||
import Editor, {
|
import Editor, {BackgroundComponentBackgroundType, BaseWidget, Color4, EditorEventType} from "js-draw";
|
||||||
BackgroundComponentBackgroundType,
|
import {Dialog, getFrontend, openTab, Plugin} from "siyuan";
|
||||||
BaseWidget,
|
|
||||||
Color4,
|
|
||||||
EditorEventType,
|
|
||||||
Mat33,
|
|
||||||
Vec2,
|
|
||||||
Viewport
|
|
||||||
} from "js-draw";
|
|
||||||
import {Dialog, getFrontend, openTab, Plugin, showMessage} from "siyuan";
|
|
||||||
import {findSyncIDInProtyle, replaceSyncID} from "@/protyle";
|
import {findSyncIDInProtyle, replaceSyncID} from "@/protyle";
|
||||||
import DrawJSPlugin from "@/index";
|
import DrawJSPlugin from "@/index";
|
||||||
import {EditorOptions} from "@/config";
|
import {DefaultEditorOptions} from "@/config";
|
||||||
import 'js-draw/styles';
|
import 'js-draw/styles';
|
||||||
import {SyncIDNotFoundError, UnchangedProtyleError} from "@/errors";
|
|
||||||
|
|
||||||
export class PluginEditor {
|
export class PluginEditor {
|
||||||
|
|
||||||
|
@ -32,9 +23,8 @@ export class PluginEditor {
|
||||||
getEditor(): Editor { return this.editor; }
|
getEditor(): Editor { return this.editor; }
|
||||||
getFileID(): string { return this.fileID; }
|
getFileID(): string { return this.fileID; }
|
||||||
getSyncID(): string { return this.syncID; }
|
getSyncID(): string { return this.syncID; }
|
||||||
setSyncID(syncID: string) { this.syncID = syncID; }
|
|
||||||
|
|
||||||
private constructor(fileID: string) {
|
constructor(fileID: string, defaultEditorOptions: DefaultEditorOptions) {
|
||||||
|
|
||||||
this.fileID = fileID;
|
this.fileID = fileID;
|
||||||
|
|
||||||
|
@ -44,60 +34,29 @@ export class PluginEditor {
|
||||||
iconProvider: new MaterialIconProvider(),
|
iconProvider: new MaterialIconProvider(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const styleElement = document.createElement('style');
|
this.genToolbar().then(() => {
|
||||||
styleElement.innerHTML = `
|
|
||||||
canvas.wetInkCanvas {
|
|
||||||
cursor: url('/plugins/siyuan-jsdraw-plugin/webapp/cursor.png') 6 6, auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
this.element.appendChild(styleElement);
|
|
||||||
|
|
||||||
this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false);
|
this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false);
|
||||||
this.editor.getRootElement().style.height = '100%';
|
this.editor.getRootElement().style.height = '100%';
|
||||||
|
});
|
||||||
|
|
||||||
}
|
findSyncIDInProtyle(this.fileID).then(async (syncID) => {
|
||||||
|
|
||||||
static async create(fileID: string, defaultEditorOptions: EditorOptions): Promise<PluginEditor> {
|
|
||||||
|
|
||||||
const instance = new PluginEditor(fileID);
|
|
||||||
|
|
||||||
await instance.genToolbar();
|
|
||||||
let syncID = await findSyncIDInProtyle(fileID);
|
|
||||||
|
|
||||||
if(syncID == null) {
|
if(syncID == null) {
|
||||||
throw new SyncIDNotFoundError(fileID);
|
alert(
|
||||||
}
|
"Couldn't find SyncID in protyle for this file.\n" +
|
||||||
instance.setSyncID(syncID);
|
"Make sure the drawing you're trying to edit exists in a note.\n" +
|
||||||
await instance.restoreOrInitFile(defaultEditorOptions);
|
"Close this editor tab now, and try to open the editor again."
|
||||||
|
);
|
||||||
return instance;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async restoreOrInitFile(defaultEditorOptions: EditorOptions) {
|
this.syncID = syncID;
|
||||||
|
// restore drawing
|
||||||
this.drawingFile = new PluginAsset(this.fileID, this.syncID, SVG_MIME);
|
this.drawingFile = new PluginAsset(this.fileID, syncID, SVG_MIME);
|
||||||
await this.drawingFile.loadFromSiYuanFS();
|
await this.drawingFile.loadFromSiYuanFS();
|
||||||
const drawingContent = this.drawingFile.getContent();
|
|
||||||
|
|
||||||
if(drawingContent != null) {
|
|
||||||
|
|
||||||
await this.editor.loadFromSVG(drawingContent);
|
|
||||||
|
|
||||||
// restore position and zoom
|
|
||||||
const svgElem = new DOMParser().parseFromString(drawingContent, SVG_MIME).documentElement;
|
|
||||||
const editorViewStr = svgElem.getAttribute('editorView');
|
|
||||||
if(editorViewStr != null && defaultEditorOptions.restorePosition) {
|
|
||||||
try {
|
|
||||||
const [viewBoxOriginX, viewBoxOriginY, zoom] = editorViewStr.split(' ').map(x => parseFloat(x));
|
|
||||||
this.editor.dispatch(Viewport.transformBy(Mat33.scaling2D(zoom)));
|
|
||||||
this.editor.dispatch(Viewport.transformBy(Mat33.translation(Vec2.of(
|
|
||||||
- viewBoxOriginX,
|
|
||||||
- viewBoxOriginY
|
|
||||||
))));
|
|
||||||
}catch (e){}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if(this.drawingFile.getContent() != null) {
|
||||||
|
await this.editor.loadFromSVG(this.drawingFile.getContent());
|
||||||
}else{
|
}else{
|
||||||
// it's a new drawing
|
// it's a new drawing
|
||||||
this.editor.dispatch(this.editor.setBackgroundStyle({
|
this.editor.dispatch(this.editor.setBackgroundStyle({
|
||||||
|
@ -107,24 +66,29 @@ export class PluginEditor {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
alert("Error loading drawing: " + error);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async genToolbar() {
|
private async genToolbar() {
|
||||||
|
|
||||||
const toolbar = this.editor.addToolbar();
|
const toolbar = this.editor.addToolbar();
|
||||||
|
|
||||||
|
// restore toolbarFile state
|
||||||
|
this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME);
|
||||||
|
this.toolbarFile.loadFromSiYuanFS().then(() => {
|
||||||
|
if(this.toolbarFile.getContent() != null) {
|
||||||
|
toolbar.deserializeState(this.toolbarFile.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// save button
|
// save button
|
||||||
const saveButton = toolbar.addSaveButton(async () => {
|
const saveButton = toolbar.addSaveButton(async () => {
|
||||||
await this.saveCallback(saveButton);
|
await this.saveCallback(saveButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
// restore toolbarFile state
|
|
||||||
this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME);
|
|
||||||
await this.toolbarFile.loadFromSiYuanFS();
|
|
||||||
if(this.toolbarFile.getContent() != null) {
|
|
||||||
toolbar.deserializeState(this.toolbarFile.getContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
// save toolbar config on tool change (toolbar state is not saved in SVGs!)
|
// save toolbar config on tool change (toolbar state is not saved in SVGs!)
|
||||||
this.editor.notifier.on(EditorEventType.ToolUpdated, () => {
|
this.editor.notifier.on(EditorEventType.ToolUpdated, () => {
|
||||||
this.toolbarFile.setContent(toolbar.serializeState());
|
this.toolbarFile.setContent(toolbar.serializeState());
|
||||||
|
@ -139,17 +103,13 @@ export class PluginEditor {
|
||||||
let newSyncID: string;
|
let newSyncID: string;
|
||||||
const oldSyncID = this.syncID;
|
const oldSyncID = this.syncID;
|
||||||
|
|
||||||
const rect = this.editor.viewport.visibleRect;
|
|
||||||
const zoom = this.editor.viewport.getScaleFactor();
|
|
||||||
svgElem.setAttribute('editorView', `${rect.x} ${rect.y} ${zoom}`)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.drawingFile.setContent(svgElem.outerHTML);
|
this.drawingFile.setContent(svgElem.outerHTML);
|
||||||
await this.drawingFile.save();
|
await this.drawingFile.save();
|
||||||
newSyncID = this.drawingFile.getSyncID();
|
newSyncID = this.drawingFile.getSyncID();
|
||||||
if(newSyncID != oldSyncID) { // supposed to replace protyle
|
if(newSyncID != oldSyncID) { // supposed to replace protyle
|
||||||
const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); // try to change protyle
|
const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); // try to change protyle
|
||||||
if(!changed) throw new UnchangedProtyleError();
|
if(!changed) throw new Error("Couldn't replace old images in protyle");
|
||||||
await this.drawingFile.removeOld(oldSyncID);
|
await this.drawingFile.removeOld(oldSyncID);
|
||||||
}
|
}
|
||||||
saveButton.setDisabled(true);
|
saveButton.setDisabled(true);
|
||||||
|
@ -157,10 +117,7 @@ export class PluginEditor {
|
||||||
saveButton.setDisabled(false);
|
saveButton.setDisabled(false);
|
||||||
}, 500);
|
}, 500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage("Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there.", 0, 'error');
|
alert("Error saving! The current drawing has been copied to your clipboard. You may need to create a new drawing and paste it there.");
|
||||||
if(error instanceof UnchangedProtyleError) {
|
|
||||||
showMessage("Make sure the image you're trying to edit still exists in your documents.", 0, 'error');
|
|
||||||
}
|
|
||||||
await navigator.clipboard.writeText(svgElem.outerHTML);
|
await navigator.clipboard.writeText(svgElem.outerHTML);
|
||||||
console.error(error);
|
console.error(error);
|
||||||
console.log("Couldn't save SVG: ", svgElem.outerHTML)
|
console.log("Couldn't save SVG: ", svgElem.outerHTML)
|
||||||
|
@ -176,52 +133,31 @@ export class PluginEditor {
|
||||||
export class EditorManager {
|
export class EditorManager {
|
||||||
|
|
||||||
private editor: PluginEditor
|
private editor: PluginEditor
|
||||||
setEditor(editor: PluginEditor) { this.editor = editor;}
|
|
||||||
|
|
||||||
static async create(fileID: string, p: DrawJSPlugin) {
|
constructor(fileID: string, defaultEditorOptions: DefaultEditorOptions) {
|
||||||
let instance = new EditorManager();
|
this.editor = new PluginEditor(fileID, defaultEditorOptions);
|
||||||
try {
|
|
||||||
let editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
|
|
||||||
instance.setEditor(editor);
|
|
||||||
}catch (error) {
|
|
||||||
EditorManager.handleCreationError(error, p);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static registerTab(p: DrawJSPlugin) {
|
static registerTab(p: DrawJSPlugin) {
|
||||||
p.addTab({
|
p.addTab({
|
||||||
'type': "whiteboard",
|
'type': "whiteboard",
|
||||||
async init() {
|
init() {
|
||||||
const fileID = this.data.fileID;
|
const fileID = this.data.fileID;
|
||||||
if (fileID == null) {
|
if (fileID == null) {
|
||||||
alert(p.i18n.errNoFileID);
|
alert("File ID missing - couldn't open file.")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
const editor = new PluginEditor(fileID, p.config.getDefaultEditorOptions());
|
||||||
const editor = await PluginEditor.create(fileID, p.config.options.editorOptions);
|
|
||||||
this.element.appendChild(editor.getElement());
|
this.element.appendChild(editor.getElement());
|
||||||
}catch (error){
|
|
||||||
EditorManager.handleCreationError(error, p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static handleCreationError(error: any, p: DrawJSPlugin) {
|
|
||||||
console.error(error);
|
|
||||||
let errorTxt = p.i18n.errCreateUnknown;
|
|
||||||
if(error instanceof SyncIDNotFoundError) {
|
|
||||||
errorTxt = p.i18n.errSyncIDNotFound;
|
|
||||||
}
|
|
||||||
showMessage(errorTxt, 0, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
toTab(p: Plugin) {
|
toTab(p: Plugin) {
|
||||||
openTab({
|
openTab({
|
||||||
app: p.app,
|
app: p.app,
|
||||||
custom: {
|
custom: {
|
||||||
title: p.i18n.drawing,
|
title: 'Drawing',
|
||||||
icon: 'iconDraw',
|
icon: 'iconDraw',
|
||||||
id: "siyuan-jsdraw-pluginwhiteboard",
|
id: "siyuan-jsdraw-pluginwhiteboard",
|
||||||
data: {
|
data: {
|
||||||
|
@ -240,7 +176,7 @@ export class EditorManager {
|
||||||
dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement());
|
dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
open(p: DrawJSPlugin) {
|
async open(p: DrawJSPlugin) {
|
||||||
if(getFrontend() != "mobile" && !p.config.options.dialogOnDesktop) {
|
if(getFrontend() != "mobile" && !p.config.options.dialogOnDesktop) {
|
||||||
this.toTab(p);
|
this.toTab(p);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
export class SyncIDNotFoundError extends Error {
|
|
||||||
readonly fileID: string;
|
|
||||||
|
|
||||||
constructor(fileID: string) {
|
|
||||||
super(`SyncID not found for file ${fileID}`);
|
|
||||||
this.fileID = fileID;
|
|
||||||
Object.setPrototypeOf(this, new.target.prototype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnchangedProtyleError extends Error {}
|
|
|
@ -107,10 +107,9 @@ export function imgSrcToIDs(imgSrc: string | null): { fileID: string; syncID: st
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFirstDefined(...a) {
|
export function validateColor(hex: string) {
|
||||||
for(let i = 0; i < a.length; i++) {
|
hex = hex.replace('#', '');
|
||||||
if(a[i] !== undefined) {
|
return typeof hex === 'string'
|
||||||
return a[i];
|
&& hex.length === 6
|
||||||
}
|
&& !isNaN(Number('0x' + hex))
|
||||||
}
|
|
||||||
}
|
}
|
10
src/index.ts
10
src/index.ts
|
@ -29,12 +29,12 @@ export default class DrawJSPlugin extends Plugin {
|
||||||
id: "insert-drawing",
|
id: "insert-drawing",
|
||||||
filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"],
|
filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"],
|
||||||
html: getMenuHTML("iconDraw", this.i18n.insertDrawing),
|
html: getMenuHTML("iconDraw", this.i18n.insertDrawing),
|
||||||
callback: async (protyle: Protyle) => {
|
callback: (protyle: Protyle) => {
|
||||||
void this.analytics.sendEvent('create');
|
void this.analytics.sendEvent('create');
|
||||||
const fileID = generateRandomString();
|
const fileID = generateRandomString();
|
||||||
const syncID = generateTimeString() + '-' + generateRandomString();
|
const syncID = generateTimeString() + '-' + generateRandomString();
|
||||||
protyle.insert(getMarkdownBlock(fileID, syncID), true, false);
|
protyle.insert(getMarkdownBlock(fileID, syncID), true, false);
|
||||||
(await EditorManager.create(fileID, this)).open(this);
|
new EditorManager(fileID, this.config.getDefaultEditorOptions()).open(this);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
@ -43,10 +43,10 @@ export default class DrawJSPlugin extends Plugin {
|
||||||
if (ids === null) return;
|
if (ids === null) return;
|
||||||
e.detail.menu.addItem({
|
e.detail.menu.addItem({
|
||||||
icon: "iconDraw",
|
icon: "iconDraw",
|
||||||
label: this.i18n.editDrawing,
|
label: "Edit with js-draw",
|
||||||
click: async () => {
|
click: () => {
|
||||||
void this.analytics.sendEvent('edit');
|
void this.analytics.sendEvent('edit');
|
||||||
(await EditorManager.create(ids.fileID, this)).open(this);
|
new EditorManager(ids.fileID, this.config.getDefaultEditorOptions()).open(this);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue