cmd导出插件

This commit is contained in:
zigholding 2024-04-25 22:32:43 +08:00
parent 452e1e4f8b
commit ee8a08e5b4
5 changed files with 444 additions and 209 deletions

View file

@ -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` 的元数据。
![](./assets/Pasted image 20240422220412.png)
![Pasted image 20240422220412.png](./assets/Pasted image 20240422220412.png)
`Vault Exporter: Export readMe`:将当前笔记输出到 readme笔记引用的图和文件复制到 `LocalGitProject/assets` 下。复制 readme 时,会更换链接。
元数据和附录可以在设置页面更改。
![](./assets/Pasted image 20240422213449.png)
![Pasted image 20240422213449.png](./assets/Pasted image 20240422213449.png)

View file

@ -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

File diff suppressed because one or more lines are too long

180
main.ts
View file

@ -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
* overwritemtime
*/
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 `![](./assets/${filename})`;
(match:any, filename:string) => {
return `![${filename}](./assets/${filename})`;
});
// 异步写入新内容到文件
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
View 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
* overwritemtime
*/
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.');
}
});
}
);
}
}