feat: reimplement

This commit is contained in:
codeonquer 2024-12-16 17:49:37 +08:00
parent e792ff380c
commit e48051e0ed
11 changed files with 5436 additions and 5422 deletions

View file

@ -1,27 +1,27 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"args": "none"
}],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {
"args": "none"
}],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

View file

@ -12,37 +12,37 @@ if you want to view the source, please visit the github repository of this plugi
const prod = (process.argv[2] === "production");
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
});
if (prod) {
await context.rebuild();
process.exit(0);
await context.rebuild();
process.exit(0);
} else {
await context.watch();
await context.watch();
}

View file

@ -1,10 +1,10 @@
{
"id": "sync-config-folder-to-common-folder",
"name": "Sync config folder to common folder",
"description": "Sync contents from config folder to common folder for backup or other purposes",
"author": "codeonquer",
"authorUrl": "https://github.com/codeonquer",
"isDesktopOnly": true,
"version": "1.0.2",
"minAppVersion": "1.5.0"
"id": "sync-config-folder-to-common-folder",
"name": "Sync config folder to common folder",
"description": "Sync contents from config folder to common folder for backup or other purposes",
"author": "codeonquer",
"authorUrl": "https://github.com/codeonquer",
"isDesktopOnly": true,
"version": "1.0.3",
"minAppVersion": "1.5.0"
}

View file

@ -1,66 +1,51 @@
import { App, Modal, Notice } from 'obsidian';
import { App, Modal, Notice, normalizePath } from 'obsidian';
import { SyncPlugin } from './SyncPlugin';
export enum SyncModel {
None,
Sync,
Restore
None,
Sync,
Restore
}
enum StatType {
Folder = 'folder',
File = 'file'
}
export class SyncModal extends Modal {
syncModel: SyncModel;
plugin: SyncPlugin;
syncModel: SyncModel;
plugin: SyncPlugin;
constructor(app: App, syncModel: SyncModel, plugin: SyncPlugin) {
super(app);
constructor(app: App, syncModel: SyncModel, plugin: SyncPlugin) {
super(app);
this.syncModel = syncModel;
this.plugin = plugin;
}
this.syncModel = syncModel;
this.plugin = plugin;
}
get fs() {
// @ts-ignore
return this.app.vault.adapter.fs;
}
get configFolder() {
const p = normalizePath(this.plugin.settings.configFolderPath || this.app.vault.configDir);
return p.endsWith('/') ? p.substring(0, p.length - 1) : p;
}
get path() {
// @ts-ignore
return this.app.vault.adapter.path;
}
get commonFolder() {
const p = normalizePath(this.plugin.settings.commonFolderPath);
return p.endsWith('/') ? p.substring(0, p.length - 1) : p;
}
get basePath() {
// @ts-ignore
return this.app.vault.adapter.basePath;
}
get configFolder() {
const configDir = this.plugin.settings.configFolderPath || this.app.vault.configDir;
return this.path.join(this.basePath, configDir);
}
get commonFolderPath() {
return this.plugin.settings.commonFolderPath;
}
get commonFolder() {
return this.path.isAbsolute(this.commonFolderPath)
? this.commonFolderPath
: this.path.join(this.basePath, this.commonFolderPath)
}
logNormal (text: string, contentEl?: HTMLElement, ) {
if (contentEl) {
contentEl.createDiv({ text })
} else {
logNormal (text: string, contentEl?: HTMLElement, ) {
if (contentEl) {
contentEl.createDiv({ text })
} else {
new Notice(text, 3000);
}
}
}
logSuccess (text: string, contentEl?: HTMLElement) {
if (contentEl) {
contentEl.createDiv({ text, attr: { style: 'color: green;' } })
} else {
logSuccess (text: string, contentEl?: HTMLElement) {
if (contentEl) {
contentEl.createDiv({ text, attr: { style: 'color: green;' } })
} else {
const fragment = document.createDocumentFragment();
fragment.appendChild(
fragment.createSpan({
@ -72,12 +57,12 @@ export class SyncModal extends Modal {
);
new Notice(fragment, 3000);
}
}
}
logFail (text: string, contentEl?: HTMLElement) {
if (contentEl) {
contentEl.createDiv({ text, attr: { style: 'color: red;' } })
} else {
logFail (text: string, contentEl?: HTMLElement) {
if (contentEl) {
contentEl.createDiv({ text, attr: { style: 'color: red;' } })
} else {
const fragment = document.createDocumentFragment();
fragment.appendChild(
fragment.createSpan({
@ -89,73 +74,101 @@ export class SyncModal extends Modal {
);
new Notice(fragment, 0);
}
}
}
syncConfig(contentEl?: HTMLElement) {
try {
this.logNormal('Check if ConfigFolder exists (检查 ConfigFolder 是否存在) ...', contentEl);
const isConfigFolderExisted = this.fs.existsSync(this.configFolder);
if (!isConfigFolderExisted) {
this.logSuccess('ConfigFolder does not exist, no need to back up (ConfigFolder 不存在,无需备份) .', contentEl);
return;
}
async syncConfig(contentEl?: HTMLElement) {
try {
this.logNormal('Check if ConfigFolder exists (检查 ConfigFolder 是否存在) ...', contentEl);
// const isConfigFolderExisted = this.fs.existsSync(this.configFolder);
const isConfigFolderExisted = await this.app.vault.adapter.exists(this.configFolder, true);
if (!isConfigFolderExisted) {
this.logSuccess('ConfigFolder does not exist, no need to back up (ConfigFolder 不存在,无需备份) .', contentEl);
return;
}
this.logNormal('Reset CommonFolder (重置 CommonFolder 中) ...', contentEl);
const isCommonFolderExisted = this.fs.existsSync(this.commonFolder);
if (isCommonFolderExisted) {
this.fs.rmSync(this.commonFolder, { force: true, recursive: true });
}
this.fs.mkdirSync(this.commonFolder, { recursive: true });
this.logSuccess('Reset CommonFolder successfully (重置 CommonFolder 成功) .', contentEl);
this.logNormal('Reset CommonFolder (重置 CommonFolder 中) ...', contentEl);
// const isCommonFolderExisted = this.fs.existsSync(this.commonFolder);
const isCommonFolderExisted = await this.app.vault.adapter.exists(this.commonFolder, true);
if (isCommonFolderExisted) {
// this.fs.rmSync(this.commonFolder, { force: true, recursive: true });
await this.app.vault.adapter.rmdir(this.commonFolder, true);
}
// this.fs.mkdirSync(this.commonFolder, { recursive: true });
await this.app.vault.adapter.mkdir(this.commonFolder);
this.logSuccess('Reset CommonFolder successfully (重置 CommonFolder 成功) .', contentEl);
this.logNormal('Back up ConfigFolder (备份 ConfigFolder 中) ...', contentEl);
this.fs.cpSync(this.configFolder, this.commonFolder, { recursive: true });
this.logSuccess('Backup ConfigFolder successful (备份 ConfigFolder 成功) .', contentEl);
} catch (e) {
this.logFail(e.message, contentEl);
}
}
this.logNormal('Back up ConfigFolder (备份 ConfigFolder 中) ...', contentEl);
// this.fs.cpSync(this.configFolder, this.commonFolder, { recursive: true });
await this.recursiveCopy(this.configFolder, this.commonFolder);
this.logSuccess('Backup ConfigFolder successful (备份 ConfigFolder 成功) .', contentEl);
} catch (e) {
this.logFail(e.message, contentEl);
}
}
restoreConfig(contentEl: HTMLElement) {
try {
this.logNormal('Check if CommonFolder exists (检查 CommonFolder 是否存在) ...', contentEl);
const isCommonFolderExisted = this.fs.existsSync(this.commonFolder);
if (!isCommonFolderExisted) {
this.logSuccess('CommonFolder does not exist and cannot be restored (CommonFolder 不存在,无法恢复) .', contentEl);
return;
}
async restoreConfig(contentEl: HTMLElement) {
try {
this.logNormal('Check if CommonFolder exists (检查 CommonFolder 是否存在) ...', contentEl);
// const isCommonFolderExisted = this.fs.existsSync(this.commonFolder);
const isCommonFolderExisted = await this.app.vault.adapter.exists(this.commonFolder, true);
if (!isCommonFolderExisted) {
this.logSuccess('CommonFolder does not exist and cannot be restored (CommonFolder 不存在,无法恢复) .', contentEl);
return;
}
this.logNormal('Reset ConfigFolder (重置 ConfigFolder 中) ...', contentEl);
const isConfigFolderExisted = this.fs.existsSync(this.configFolder);
if (isConfigFolderExisted) {
this.fs.rmSync(this.configFolder, { force: true, recursive: true });
}
this.fs.mkdirSync(this.configFolder, { recursive: true });
this.logSuccess('Reset ConfigFolder successfully (重置 ConfigFolder 成功) .', contentEl);
this.logNormal('Reset ConfigFolder (重置 ConfigFolder 中) ...', contentEl);
// const isConfigFolderExisted = this.fs.existsSync(this.configFolder);
const isConfigFolderExisted = await this.app.vault.adapter.exists(this.configFolder, true);
if (isConfigFolderExisted) {
// this.fs.rmSync(this.configFolder, { force: true, recursive: true });
await this.app.vault.adapter.rmdir(this.configFolder, true);
}
// this.fs.mkdirSync(this.configFolder, { recursive: true });
await this.app.vault.adapter.mkdir(this.configFolder);
this.logSuccess('Reset ConfigFolder successfully (重置 ConfigFolder 成功) .', contentEl);
this.logNormal('Restore ConfigFolder (恢复 ConfigFolder 中) ...', contentEl);
this.fs.cpSync(this.commonFolder, this.configFolder, { recursive: true });
this.logSuccess('Restore ConfigFolder successfully (恢复 ConfigFolder 成功) .', contentEl);
} catch (e) {
this.logFail(e.message, contentEl);
}
}
this.logNormal('Restore ConfigFolder (恢复 ConfigFolder 中) ...', contentEl);
// this.fs.cpSync(this.commonFolder, this.configFolder, { recursive: true });
await this.recursiveCopy(this.commonFolder, this.configFolder);
this.logSuccess('Restore ConfigFolder successfully (恢复 ConfigFolder 成功) .', contentEl);
} catch (e) {
this.logFail(e.message, contentEl);
}
}
onOpen() {
window.setTimeout(() => {
const { contentEl } = this;
onOpen() {
window.setTimeout(() => {
const { contentEl } = this;
if (this.syncModel === SyncModel.Restore) {
this.restoreConfig(contentEl);
return;
}
if (this.syncModel === SyncModel.Restore) {
this.restoreConfig(contentEl);
return;
}
this.syncConfig(contentEl);
}, 300);
}
this.syncConfig(contentEl);
}, 300);
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
async recursiveCopy(src: string, dest: string) {
const stat = await this.app.vault.adapter.stat(src);
if (stat?.type === StatType.Folder) {
if (!(await this.app.vault.adapter.exists(dest, true))) {
await this.app.vault.adapter.mkdir(dest);
}
const { folders, files } = await this.app.vault.adapter.list(src);
await Promise.all([...folders, ...files].map(async (item) => {
await this.recursiveCopy(item, item.replace(src, dest));
}));
return;
}
await this.app.vault.adapter.copy(src, dest);
}
}

View file

@ -5,50 +5,50 @@ import { SyncModal, SyncModel } from './SyncModal';
import { SyncSettingTab } from './SyncSettingTab';
export class SyncPlugin extends Plugin {
settings: ISyncPluginSettings;
settings: ISyncPluginSettings;
async onload() {
await this.loadSettings();
async onload() {
await this.loadSettings();
this.addCommand({
id: 'sync-config-folder-to-common-folder',
name: 'Sync',
callback: () => {
new SyncModal(this.app, SyncModel.Sync, this).open();
}
});
this.addCommand({
id: 'sync-config-folder-to-common-folder',
name: 'Sync',
callback: () => {
new SyncModal(this.app, SyncModel.Sync, this).open();
}
});
this.addCommand({
id: 'restore-config-folder-from-common-folder',
name: 'Restore',
callback: () => {
new SyncModal(this.app, SyncModel.Restore, this).open();
}
});
this.addCommand({
id: 'restore-config-folder-from-common-folder',
name: 'Restore',
callback: () => {
new SyncModal(this.app, SyncModel.Restore, this).open();
}
});
this.addSettingTab(new SyncSettingTab(this.app, this));
this.addSettingTab(new SyncSettingTab(this.app, this));
if (this.settings.autoSaveInterval) {
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(
if (this.settings.autoSaveInterval) {
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(
() => {
new SyncModal(this.app, SyncModel.None, this).syncConfig();
},
this.settings.autoSaveInterval * 1000
));
}
}
}
}
onunload() {
onunload() {
}
}
async loadSettings() {
const originData = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, originData);
}
async loadSettings() {
const originData = await this.loadData();
this.settings = Object.assign({}, DEFAULT_SETTINGS, originData);
}
async saveSettings() {
await this.saveData(this.settings);
}
async saveSettings() {
await this.saveData(this.settings);
}
}

View file

@ -4,49 +4,49 @@ import { DEFAULT_CONFIG_FOLDER_PATH, DEFAULT_COMMON_FOLDER_PATH, DEFAULT_INTERVA
import { SyncPlugin } from './SyncPlugin';
export class SyncSettingTab extends PluginSettingTab {
plugin: SyncPlugin;
plugin: SyncPlugin;
constructor(app: App, plugin: SyncPlugin) {
super(app, plugin);
this.plugin = plugin;
}
constructor(app: App, plugin: SyncPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.empty();
new Setting(containerEl)
.setName('Config folder path')
.setDesc('Customize the path filled in ConfigFolder, which needs to be consistent with the one in Files and links->Override config folder (自定义填入 ConfigFolder 的路径,需要和 文件与链接->切换配置文件夹 中的保持一致)')
.addText(text => text
.setPlaceholder(DEFAULT_CONFIG_FOLDER_PATH)
.setValue(this.plugin.settings.configFolderPath)
.onChange(async (value) => {
this.plugin.settings.configFolderPath = value || DEFAULT_CONFIG_FOLDER_PATH;
await this.plugin.saveSettings();
}));
.setName('Config folder path')
.setDesc('Customize the path filled in ConfigFolder, which needs to be consistent with the one in Files and links->Override config folder (自定义填入 ConfigFolder 的路径,需要和 文件与链接->切换配置文件夹 中的保持一致)')
.addText(text => text
.setPlaceholder(DEFAULT_CONFIG_FOLDER_PATH)
.setValue(this.plugin.settings.configFolderPath)
.onChange(async (value) => {
this.plugin.settings.configFolderPath = value || DEFAULT_CONFIG_FOLDER_PATH;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Common folder path')
.setDesc('Customize the path to be filled in ConfigFolder for synchronization. The relative path needs to start with ./ or ../ (自定义填入 ConfigFolder 同步的路径,相对路径需要以 ./ 或者 ../ 开头)')
.addText(text => text
.setPlaceholder(DEFAULT_COMMON_FOLDER_PATH)
.setValue(this.plugin.settings.commonFolderPath)
.onChange(async (value) => {
this.plugin.settings.commonFolderPath = value || DEFAULT_COMMON_FOLDER_PATH;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Common folder path')
.setDesc('Customize the path to be filled in ConfigFolder for synchronization. The relative path needs to start with ./ or ../ (自定义填入 ConfigFolder 同步的路径,相对路径需要以 ./ 或者 ../ 开头)')
.addText(text => text
.setPlaceholder(DEFAULT_COMMON_FOLDER_PATH)
.setValue(this.plugin.settings.commonFolderPath)
.onChange(async (value) => {
this.plugin.settings.commonFolderPath = value || DEFAULT_COMMON_FOLDER_PATH;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Auto save interval')
.setDesc('Customize the auto save time interval in seconds, 0 means off (自定义自动保存的时间间隔单位是秒0 代表关闭). Need to be restarted to take effect (修改都需要重启生)')
.addText(text => text
.setPlaceholder(String(DEFAULT_INTERVAL))
.setValue(String(this.plugin.settings.autoSaveInterval))
.onChange(async (value) => {
this.plugin.settings.autoSaveInterval = Number(value) || DEFAULT_INTERVAL;
await this.plugin.saveSettings();
}));
}
new Setting(containerEl)
.setName('Auto save interval')
.setDesc('Customize the auto save time interval in seconds, 0 means off (自定义自动保存的时间间隔单位是秒0 代表关闭). Need to be restarted to take effect (修改都需要重启生)')
.addText(text => text
.setPlaceholder(String(DEFAULT_INTERVAL))
.setValue(String(this.plugin.settings.autoSaveInterval))
.onChange(async (value) => {
this.plugin.settings.autoSaveInterval = Number(value) || DEFAULT_INTERVAL;
await this.plugin.saveSettings();
}));
}
}

View file

@ -1,7 +1,7 @@
export interface ISyncPluginSettings {
configFolderPath: string;
commonFolderPath: string;
autoSaveInterval: number;
commonFolderPath: string;
autoSaveInterval: number;
}
export const DEFAULT_CONFIG_FOLDER_PATH = '.obsidian';
@ -11,5 +11,5 @@ export const DEFAULT_INTERVAL = 0;
export const DEFAULT_SETTINGS: ISyncPluginSettings = {
configFolderPath: DEFAULT_CONFIG_FOLDER_PATH,
commonFolderPath: DEFAULT_COMMON_FOLDER_PATH,
autoSaveInterval: DEFAULT_INTERVAL
autoSaveInterval: DEFAULT_INTERVAL
};

10246
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,30 +1,30 @@
{
"name": "obsidian-sync-config-folder-to-common-folder",
"version": "1.0.2",
"description": "Sync contents from config folder to common folder for backup or other purposes",
"keywords": [],
"author": "codeonquer",
"repository": {
"url": "https://github.com/codeonquer/obsidian-sync-config-folder-to-common-folder"
},
"license": "MIT",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"editorconfig": "2.0.0",
"esbuild": "0.17.3",
"eslint": "8.57.0",
"obsidian": "latest",
"name": "obsidian-sync-config-folder-to-common-folder",
"version": "1.0.3",
"description": "Sync contents from config folder to common folder for backup or other purposes",
"keywords": [],
"author": "codeonquer",
"repository": {
"url": "https://github.com/codeonquer/obsidian-sync-config-folder-to-common-folder"
},
"license": "MIT",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"editorconfig": "2.0.0",
"esbuild": "0.17.3",
"eslint": "8.57.0",
"obsidian": "latest",
"stylelint": "16.3.0",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

View file

@ -1,24 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

View file

@ -1,5 +1,6 @@
{
"1.0.0": "0.15.0",
"1.0.1": "0.15.0",
"1.0.2": "1.5.0"
"1.0.0": "0.15.0",
"1.0.1": "0.15.0",
"1.0.2": "1.5.0",
"1.0.3": "1.5.0"
}