mirror of
https://github.com/zigholding/obsidian-notesync-plugin.git
synced 2026-07-22 05:43:30 +00:00
cmd导出插件
This commit is contained in:
parent
452e1e4f8b
commit
ee8a08e5b4
5 changed files with 444 additions and 209 deletions
49
README.md
49
README.md
|
|
@ -1,58 +1,13 @@
|
|||
---
|
||||
title: obsidian-vaultexporter-plugin
|
||||
ctime: 2024-04-22 20:10
|
||||
aliases:
|
||||
-
|
||||
tags:
|
||||
-
|
||||
NID:
|
||||
NIW:
|
||||
drafts: true
|
||||
publish: false
|
||||
PrevNote: "[[obsidian-sample-plugin]]"
|
||||
NextNote: "[[vexporter readMe]]"
|
||||
LocalGitProject: D:\iLanix\isync\Obsidian\.obsidian\plugins\obsidian-vaultexporter-plugin
|
||||
---
|
||||
|
||||
下载 [obsidian-sample-plugin](https://github.com/obsidianmd/obsidian-sample-plugin),
|
||||
|
||||
```bash
|
||||
cd Your_Vault_DIR/.obsidian/plugins
|
||||
git clone https://github.com/obsidianmd/obsidian-sample-plugin.git
|
||||
```
|
||||
|
||||
修改 `manifest.json`:
|
||||
|
||||
```jsn
|
||||
{
|
||||
"id": "vault-exporter",
|
||||
"name": "Vault Exporter",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Export current note to another Vault.",
|
||||
"author": "ZigHolding",
|
||||
"isDesktopOnly": true
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
cd obsidian-sample-plugin
|
||||
npm i # 下载依赖包
|
||||
npm run dev # 将 main.ts 编译为 main.js
|
||||
```
|
||||
|
||||
开发完成后:将`main.js`、`styles.css`、`mainfest.json` 复制到 :`VaultFolder/.obsidian/plugins/your-plugin-id/`。
|
||||
|
||||
|
||||
需要先安装 [[obsidian-notechain-plugin]] 和 [[Templater]] 插件。
|
||||
|
||||
`Vault Exporter: Set Git Project`:将当前笔记绑定项目笔记。输入目录,生成 `LocalGitProject` 的元数据。
|
||||
|
||||

|
||||

|
||||
|
||||
`Vault Exporter: Export readMe`:将当前笔记输出到 readme,笔记引用的图和文件复制到 `LocalGitProject/assets` 下。复制 readme 时,会更换链接。
|
||||
|
||||
元数据和附录可以在设置页面更改。
|
||||
|
||||

|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"nameLocalGitProject": "LocalGitProject",
|
||||
"readmeRemoveFrontmatter": true,
|
||||
"assetsLocalGitProject": "assets",
|
||||
"pluginDirExporter": "D:\\iLanix\\isync\\Obsidian\\.obsidian\\plugins",
|
||||
"mySetting": "D:/iLanix/isync/test"
|
||||
}
|
||||
302
main.js
302
main.js
File diff suppressed because one or more lines are too long
180
main.ts
180
main.ts
|
|
@ -1,25 +1,28 @@
|
|||
import * as Module from 'module';
|
||||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
const fs = require('fs');
|
||||
import { FsEditor } from 'src/fseditor';
|
||||
|
||||
interface VExporterSettings {
|
||||
nameLocalGitProject: string;
|
||||
assetsLocalGitProject:string;
|
||||
readmeRemoveFrontmatter:boolean;
|
||||
pluginDirExporter:string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: VExporterSettings = {
|
||||
nameLocalGitProject: 'LocalGitProject',
|
||||
assetsLocalGitProject: 'assets'
|
||||
readmeRemoveFrontmatter:true,
|
||||
assetsLocalGitProject: 'assets',
|
||||
pluginDirExporter:''
|
||||
}
|
||||
|
||||
|
||||
const cmd_export_readme = (plugin:VaultExpoterPlugin) => ({
|
||||
id: 'export_readme',
|
||||
name: 'Export readMe',
|
||||
callback: async () => {
|
||||
let nc = await plugin.app.plugins.getPlugin('note-chain');
|
||||
const nc = plugin.notechain;
|
||||
let tfile = nc.chain.current_note;
|
||||
await plugin.export_readme(
|
||||
tfile,null,true,plugin.settings.assetsLocalGitProject
|
||||
|
|
@ -31,9 +34,9 @@ const cmd_set_git_project = (plugin:VaultExpoterPlugin) => ({
|
|||
id: 'set_git_project',
|
||||
name: 'Set Git Project',
|
||||
callback: async () => {
|
||||
let nc = await plugin.app.plugins.getPlugin('note-chain');
|
||||
const nc = plugin.notechain;
|
||||
let dir = await nc.chain.tp_prompt('输入文件夹');
|
||||
if(!dir || !fs.existsSync(dir)){
|
||||
if(!dir || !plugin.fsEditor.fs.existsSync(dir)){
|
||||
return;
|
||||
}
|
||||
await nc.editor.set_frontmatter(
|
||||
|
|
@ -44,9 +47,42 @@ const cmd_set_git_project = (plugin:VaultExpoterPlugin) => ({
|
|||
}
|
||||
});
|
||||
|
||||
const cmd_export_plugin = (plugin:VaultExpoterPlugin) => ({
|
||||
id: 'cmd_export_plugin',
|
||||
name: 'Export Plugin',
|
||||
callback: async () => {
|
||||
const nc = plugin.notechain;
|
||||
|
||||
let plugins = Object.keys((plugin.app as any).plugins.plugins);
|
||||
let p = await nc.chain.tp_suggester(plugins,plugins);
|
||||
let eplugin = (plugin.app as any).plugins.getPlugin(p);
|
||||
if(eplugin){
|
||||
let target = plugin.settings.pluginDirExporter;
|
||||
if(!plugin.fsEditor.fs.existsSync(target)){
|
||||
target = await nc.chain.tp_prompt('输出目录');
|
||||
}
|
||||
target = target.replace(/\\/g,'/');
|
||||
if(!target.endsWith('/' + p)){
|
||||
target = target + '/' + p;
|
||||
}
|
||||
if(!plugin.fsEditor.fs.existsSync(target)){
|
||||
plugin.fsEditor.fs.mkdirSync(target);
|
||||
}
|
||||
let items = ['main.js','manifest.json','styles.css','data.json'];
|
||||
for(let item of items){
|
||||
let src = `${plugin.fsEditor.root}/${plugin.manifest.dir}/${item}`;
|
||||
let dst = `${target}/${item}`;
|
||||
plugin.fsEditor.copy_file_by_path(src,dst,'overwrite');
|
||||
new Notice(`导出:${p}/${item}`,3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const commandBuilders = [
|
||||
cmd_export_readme,
|
||||
cmd_set_git_project
|
||||
cmd_set_git_project,
|
||||
cmd_export_plugin
|
||||
];
|
||||
|
||||
function addCommands(plugin:VaultExpoterPlugin) {
|
||||
|
|
@ -57,13 +93,11 @@ function addCommands(plugin:VaultExpoterPlugin) {
|
|||
|
||||
export default class VaultExpoterPlugin extends Plugin {
|
||||
settings: VExporterSettings;
|
||||
root : string;
|
||||
|
||||
fsEditor : FsEditor;
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
this.app.vt = this;
|
||||
this.fs = fs;
|
||||
this.root = this.app.vault.adapter.basePath;
|
||||
this.fsEditor = new FsEditor(this);
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new VExporterSettingTab(this.app, this));
|
||||
|
||||
|
|
@ -74,6 +108,10 @@ export default class VaultExpoterPlugin extends Plugin {
|
|||
|
||||
}
|
||||
|
||||
get notechain(){
|
||||
return (this.app as any).plugins.getPlugin('note-chain');
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
|
@ -81,50 +119,9 @@ export default class VaultExpoterPlugin extends Plugin {
|
|||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件 src 到 dst,不在 vault 中,需要绝对路径
|
||||
* overwrite,复盖;mtime,新文件;
|
||||
*/
|
||||
copy_file_by_path(src:string, dst:string,mode='overwrite>mtime>pass') {
|
||||
mode = mode.split('>')[0]
|
||||
if(!fs.existsSync(src)){
|
||||
return;
|
||||
}
|
||||
if(fs.existsSync(dst)){
|
||||
if(mode==='overwrite'){
|
||||
fs.unlinkSync(dst);
|
||||
fs.copyFileSync(src,dst);
|
||||
}else if(mode==='mtime'){
|
||||
// dst 更新时间小于 src
|
||||
if(fs.statSync(dst).mtimeMs<fs.statSync(src).mtimeMs){
|
||||
fs.unlinkSync(dst);
|
||||
fs.copyFileSync(src,dst);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
fs.copyFileSync(src,dst);
|
||||
}
|
||||
}
|
||||
|
||||
abspath(tfile:TFile){
|
||||
if(tfile){
|
||||
return this.root+'/'+tfile.path;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
copy_tfile(tfile:TFile, dst:string) {
|
||||
if(tfile){
|
||||
let src = this.abspath(tfile);
|
||||
src && this.copy_file_by_path(src,dst);
|
||||
}
|
||||
}
|
||||
|
||||
async export_readme(tfile:TFile,dst:string|null,as_readme=true,assets='assets'){
|
||||
|
||||
let nc = this.app.plugins.getPlugin('note-chain');
|
||||
let nc = (this.app as any).plugins.getPlugin('note-chain');
|
||||
if(!tfile){tfile = nc.chain.current_note;}
|
||||
|
||||
if(!dst){
|
||||
|
|
@ -139,7 +136,7 @@ export default class VaultExpoterPlugin extends Plugin {
|
|||
if(!dst){return;}
|
||||
}
|
||||
dst = dst.replace(/\\/g,'/');
|
||||
if(!fs.existsSync(dst)){
|
||||
if(!this.fsEditor.fs.existsSync(dst)){
|
||||
console.log('No Dir:',dst);
|
||||
}
|
||||
console.log(dst);
|
||||
|
|
@ -152,52 +149,67 @@ export default class VaultExpoterPlugin extends Plugin {
|
|||
tmp = dst+'/'+tfile.basename+'.md';
|
||||
}
|
||||
|
||||
this.copy_tfile(tfile,tmp);
|
||||
this.fsEditor.copy_tfile(tfile,tmp);
|
||||
await this.replace_readme(tmp);
|
||||
|
||||
let adir = dst+'/'+assets;
|
||||
if(!fs.existsSync(adir)){
|
||||
fs.mkdirSync(adir);
|
||||
if(!this.fsEditor.fs.existsSync(adir)){
|
||||
this.fsEditor.fs.mkdirSync(adir);
|
||||
}
|
||||
for(let f of olinks){
|
||||
if(!(f.extension==='md')){
|
||||
this.copy_tfile(f,adir+'/'+f.basename+'.'+f.extension);
|
||||
this.fsEditor.copy_tfile(f,adir+'/'+f.basename+'.'+f.extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replace_readme(path:string){
|
||||
fs.readFile(path, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
const replacedContent = data.replace(
|
||||
const ufunc = (path:string,data:string)=>{
|
||||
let replacedContent = data.replace(
|
||||
/\!\[\[(.*?)\]\]/g,
|
||||
(match, filename) => {
|
||||
return ``;
|
||||
(match:any, filename:string) => {
|
||||
return ``;
|
||||
});
|
||||
|
||||
// 异步写入新内容到文件
|
||||
fs.writeFile(path, replacedContent, 'utf8', (err) => {
|
||||
if (err) {
|
||||
console.error('Error writing file:', err);
|
||||
} else {
|
||||
console.log('File content updated successfully.');
|
||||
}
|
||||
});
|
||||
});
|
||||
if(this.settings.readmeRemoveFrontmatter){
|
||||
replacedContent = replacedContent.replace(
|
||||
/---[\n(\r\n)][\s\S]*?---[\n(\r\n)]/,''
|
||||
)
|
||||
}
|
||||
return replacedContent;
|
||||
}
|
||||
this.fsEditor.modify(path,ufunc,'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
class VExporterSettingTab extends PluginSettingTab {
|
||||
plugin: VaultExpoterPlugin;
|
||||
|
||||
|
||||
constructor(app: App, plugin: VaultExpoterPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
getSettingValue(field: keyof VExporterSettings) {
|
||||
return this.plugin.settings[field];
|
||||
}
|
||||
|
||||
add_toggle(name:string,desc:string,field:keyof VExporterSettings){
|
||||
const {containerEl} = this;
|
||||
let value = this.plugin.settings[field] as boolean;
|
||||
let item = new Setting(containerEl)
|
||||
.setName(name)
|
||||
.setDesc(desc)
|
||||
.addToggle(text => text
|
||||
.setValue(value)
|
||||
.onChange(async (value:never) => {
|
||||
this.plugin.settings[field] = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
return item;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
|
||||
|
|
@ -223,5 +235,19 @@ class VExporterSettingTab extends PluginSettingTab {
|
|||
this.plugin.settings.assetsLocalGitProject = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
this.add_toggle(
|
||||
'Remove Frontmatter?','','readmeRemoveFrontmatter'
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Plugin Dir To Export')
|
||||
.addText(text => text
|
||||
.setValue(this.plugin.settings.pluginDirExporter)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.pluginDirExporter = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
120
src/fseditor.ts
Normal file
120
src/fseditor.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile } from 'obsidian';
|
||||
|
||||
export class FsEditor{
|
||||
fs;
|
||||
path;
|
||||
plugin :Plugin;
|
||||
|
||||
constructor(plugin:Plugin){
|
||||
this.plugin = plugin;
|
||||
this.fs = require('fs');
|
||||
this.path = require('path');
|
||||
}
|
||||
|
||||
get root(){
|
||||
let a = this.plugin.app.vault.adapter as any;
|
||||
return a.basePath.replace(/\\/g,'/');
|
||||
}
|
||||
|
||||
abspath(tfile:TFile){
|
||||
if(tfile){
|
||||
return this.root+'/'+tfile.path;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
isfile(path:string){
|
||||
return this.fs.existsSync(path) && this.fs.statSync(path).isFile();
|
||||
}
|
||||
|
||||
isdir(path:string){
|
||||
return this.fs.existsSync(path) && this.fs.statSync(path).isDirectory();
|
||||
}
|
||||
|
||||
mkdirRecursiveSync(path:string){
|
||||
if(this.isdir(path)){return true;}
|
||||
const parent = this.path.dirname(path);
|
||||
if(!this.isdir(parent)){
|
||||
this.mkdirRecursiveSync(parent);
|
||||
}
|
||||
this.fs.mkdirSync(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 附件 src 到 dst,不在 vault 中,需要绝对路径
|
||||
* overwrite,复盖;mtime,新文件;
|
||||
*/
|
||||
copy_file_by_path(src:string,dst:string,mode='pass>overwrite>mtime') {
|
||||
const fs = this.fs;
|
||||
|
||||
mode = mode.split('>')[0]
|
||||
if(!fs.existsSync(src)){
|
||||
return;
|
||||
}
|
||||
if(fs.existsSync(dst)){
|
||||
if(mode==='overwrite'){
|
||||
fs.unlinkSync(dst);
|
||||
fs.copyFileSync(src,dst);
|
||||
}else if(mode==='mtime'){
|
||||
// dst 更新时间小于 src
|
||||
if(fs.statSync(dst).mtimeMs<fs.statSync(src).mtimeMs){
|
||||
fs.unlinkSync(dst);
|
||||
fs.copyFileSync(src,dst);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
fs.copyFileSync(src,dst);
|
||||
}
|
||||
}
|
||||
|
||||
copy_tfile(tfile:TFile, dst:string,mode='mtime') {
|
||||
if(tfile){
|
||||
let src = this.abspath(tfile);
|
||||
src && this.copy_file_by_path(src,dst,mode);
|
||||
}
|
||||
}
|
||||
|
||||
mirror_tfile(tfile:TFile,vault_root:string,mode='mtime',attachment=true,outlink=false){
|
||||
// 将笔记镜像移动到别的库中,文件结构与当前库相同
|
||||
if(tfile){
|
||||
vault_root = vault_root.replace(/\\g/,'/');
|
||||
let src = this.root + '/' + tfile.path;
|
||||
let dst = vault_root+'/'+tfile.path;
|
||||
this.mkdirRecursiveSync(this.path.dirname);
|
||||
this.copy_file_by_path(src,dst,mode);
|
||||
if(attachment){
|
||||
let nc = (this.plugin.app as any).plugins.getPlugin('note-chain');
|
||||
let tfiles = nc.chain.get_outlinks(tfile);
|
||||
for(let t of tfiles){
|
||||
if(!(t.extension==='md')){
|
||||
this.mirror_tfile(t,vault_root,mode,false);
|
||||
}else if(outlink){
|
||||
this.mirror_tfile(t,vault_root,mode,false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modify(path:string,callback:Function,encoding='utf8'){
|
||||
const fs = this.fs;
|
||||
if(!fs.existsSync(path)){return};
|
||||
|
||||
fs.readFile(path, encoding, (err:Error, data:string) => {
|
||||
if(err){
|
||||
console.error('Error reading file:', err);;
|
||||
}
|
||||
let rs = callback(path,data);
|
||||
fs.writeFile(path, rs, encoding, (err:Error) => {
|
||||
if (err) {
|
||||
console.error('Error writing file:', err);
|
||||
} else {
|
||||
console.log('File content updated successfully.');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue