mirror of
https://github.com/zigholding/obsidian-notesync-plugin.git
synced 2026-07-22 05:43:30 +00:00
v0.6.3
This commit is contained in:
parent
56f7156e2e
commit
58d58d13ce
8 changed files with 767 additions and 330 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "note-sync",
|
||||
"name": "Note Sync",
|
||||
"version": "0.6.2",
|
||||
"version": "0.6.3",
|
||||
"minAppVersion": "1.8.3",
|
||||
"description": "Sync notes or plugins between vaults.",
|
||||
"author": "ZigHolding",
|
||||
|
|
|
|||
258
src/commands.ts
258
src/commands.ts
|
|
@ -1,30 +1,31 @@
|
|||
import {
|
||||
Notice, TFile
|
||||
import {
|
||||
Notice, TFile,
|
||||
TFolder
|
||||
} from 'obsidian';
|
||||
|
||||
import NoteSyncPlugin from '../main';
|
||||
import { it } from 'node:test';
|
||||
|
||||
const cmd_export_current_note = (plugin:NoteSyncPlugin) => ({
|
||||
const cmd_export_current_note = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'export_current_note',
|
||||
name: plugin.strings.cmd_export_current_note,
|
||||
icon:'file-export',
|
||||
icon: 'file-export',
|
||||
callback: async () => {
|
||||
let tfile = plugin.app.workspace.getActiveFile();
|
||||
await plugin.export_readme(tfile,null);
|
||||
await plugin.export_readme(tfile, null);
|
||||
}
|
||||
});
|
||||
|
||||
const cmd_set_vexporter = (plugin:NoteSyncPlugin) => ({
|
||||
const cmd_set_vexporter = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'set_vexporter',
|
||||
name: plugin.strings.cmd_set_vexporter,
|
||||
icon:'settings',
|
||||
icon: 'settings',
|
||||
callback: async () => {
|
||||
let tfile = plugin.app.workspace.getActiveFile();
|
||||
if(!tfile){return}
|
||||
if (!tfile) { return }
|
||||
let dir = await plugin.dialog_prompt(plugin.strings.prompt_path_of_folder);
|
||||
let item: { [key: string]: any } = {};
|
||||
if(plugin.fsEditor.fs.existsSync(dir)){
|
||||
if (plugin.fsEditor.fs.existsSync(dir)) {
|
||||
item['Dir'] = dir;
|
||||
}
|
||||
item['Name'] = 'readMe';
|
||||
|
|
@ -35,176 +36,176 @@ const cmd_set_vexporter = (plugin:NoteSyncPlugin) => ({
|
|||
|
||||
await plugin.app.fileManager.processFrontMatter(
|
||||
tfile,
|
||||
async(fm) =>{
|
||||
async (fm) => {
|
||||
fm[plugin.yaml] = item
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
const cmd_export_plugin = (plugin:NoteSyncPlugin) => ({
|
||||
const cmd_export_plugin = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'export_plugin',
|
||||
name: plugin.strings.cmd_export_plugin,
|
||||
icon:'arrow-right-from-line',
|
||||
icon: 'arrow-right-from-line',
|
||||
callback: async () => {
|
||||
|
||||
|
||||
let plugins = Object.keys((plugin.app as any).plugins.plugins);
|
||||
let p = await plugin.dialog_suggest(plugins,plugins);
|
||||
let p = await plugin.dialog_suggest(plugins, plugins);
|
||||
let eplugin = (plugin.app as any).plugins.getPlugin(p);
|
||||
if(eplugin){
|
||||
if (eplugin) {
|
||||
let paths = plugin.settings.vaultDir.split("\n")
|
||||
let target = await plugin.fsEditor.select_valid_dir(
|
||||
paths
|
||||
)
|
||||
if(target){
|
||||
let items = plugin.fsEditor.list_dir(target,false)
|
||||
items = items.filter((x:string)=>x.startsWith('.') && x!='.git').filter(
|
||||
(x:string)=>{
|
||||
let path = plugin.fsEditor.path.join(target,x)
|
||||
if(!plugin.fsEditor.isdir(path)){
|
||||
if (target) {
|
||||
let items = plugin.fsEditor.list_dir(target, false)
|
||||
items = items.filter((x: string) => x.startsWith('.') && x != '.git').filter(
|
||||
(x: string) => {
|
||||
let path = plugin.fsEditor.path.join(target, x)
|
||||
if (!plugin.fsEditor.isdir(path)) {
|
||||
return false
|
||||
}
|
||||
let items = plugin.fsEditor.list_dir(path,false)
|
||||
let items = plugin.fsEditor.list_dir(path, false)
|
||||
return items.contains('plugins')
|
||||
}
|
||||
)
|
||||
if(items.length==1){
|
||||
target = plugin.fsEditor.path.join(target,items[0],'plugins')
|
||||
}else if(items.length>1){
|
||||
if (items.length == 1) {
|
||||
target = plugin.fsEditor.path.join(target, items[0], 'plugins')
|
||||
} else if (items.length > 1) {
|
||||
let item = await plugin.dialog_suggest(
|
||||
items,items,'config'
|
||||
items, items, 'config'
|
||||
)
|
||||
if(item){
|
||||
target = plugin.fsEditor.path.join(target,item,'plugins')
|
||||
if (item) {
|
||||
target = plugin.fsEditor.path.join(target, item, 'plugins')
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!plugin.fsEditor.fs.existsSync(target) ||
|
||||
plugin.fsEditor.path.basename(target)!='plugins'){
|
||||
if (!plugin.fsEditor.fs.existsSync(target) ||
|
||||
plugin.fsEditor.path.basename(target) != 'plugins') {
|
||||
target = await plugin.dialog_prompt(plugin.strings.prompt_path_of_folder);
|
||||
}
|
||||
|
||||
target = target.replace(/\\/g,'/');
|
||||
if(!target.endsWith('/' + p)){
|
||||
target = target.replace(/\\/g, '/');
|
||||
if (!target.endsWith('/' + p)) {
|
||||
target = target + '/' + p;
|
||||
}
|
||||
if(!plugin.fsEditor.fs.existsSync(target)){
|
||||
if (!plugin.fsEditor.fs.existsSync(target)) {
|
||||
plugin.fsEditor.fs.mkdirSync(target);
|
||||
}
|
||||
let items = ['main.js','manifest.json','styles.css'];
|
||||
let items = ['main.js', 'manifest.json', 'styles.css'];
|
||||
let dj = await plugin.dialog_suggest(
|
||||
[plugin.strings.item_skip_data_json,plugin.strings.item_copy_data_json],
|
||||
[false,true],
|
||||
[plugin.strings.item_skip_data_json, plugin.strings.item_copy_data_json],
|
||||
[false, true],
|
||||
''
|
||||
)
|
||||
if(dj){
|
||||
if (dj) {
|
||||
items.push('data.json')
|
||||
}
|
||||
for(let item of items){
|
||||
for (let item of items) {
|
||||
let src = `${plugin.fsEditor.root}/${eplugin.manifest.dir}/${item}`;
|
||||
let dst = `${target}/${item}`;
|
||||
let flag = plugin.fsEditor.copy_file(src,dst,'overwrite');
|
||||
if(flag){
|
||||
console.log(`Copy ${item} to ${target}`,5000)
|
||||
let flag = plugin.fsEditor.copy_file(src, dst, 'overwrite');
|
||||
if (flag) {
|
||||
console.log(`Copy ${item} to ${target}`, 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const cmd_download_git_repo = (plugin:NoteSyncPlugin) => ({
|
||||
const cmd_download_git_repo = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'cmd_download_git_repo',
|
||||
name: plugin.strings.cmd_download_git_repo,
|
||||
icon:'cloud-download',
|
||||
icon: 'cloud-download',
|
||||
callback: async () => {
|
||||
let repos = plugin.settings.git_repo.split('\n')
|
||||
let repo = await plugin.dialog_suggest(repos,repos);
|
||||
if(!repo){return}
|
||||
let repo = await plugin.dialog_suggest(repos, repos);
|
||||
if (!repo) { return }
|
||||
|
||||
let match = repo.match(/^https?:\/\/(.*)\.com\/([^/]*)\/([^/]*)\/tree\/([^/]*)\/?(.*)$/);
|
||||
if(!match){return}
|
||||
if (!match) { return }
|
||||
|
||||
let SOURCE = match[1]; // gitee
|
||||
let repoOwner = match[2]; // 开发者
|
||||
let repoName = match[3]; // 项目名称
|
||||
let branch = match[4]; // 分支名称
|
||||
let path = match[5]; // 初始路径
|
||||
|
||||
async function list_files_of_path(repoOwner:string, repoName:string, path:string, branch = 'master') {
|
||||
|
||||
async function list_files_of_path(repoOwner: string, repoName: string, path: string, branch = 'master') {
|
||||
let url;
|
||||
if(SOURCE=='github'){
|
||||
if (SOURCE == 'github') {
|
||||
url = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}?ref=${branch}`;
|
||||
}else{
|
||||
} else {
|
||||
url = `https://gitee.com/api/v5/repos/${repoOwner}/${repoName}/contents/${path}?ref=${branch}`
|
||||
}
|
||||
let req = await (window as any).requestUrl(url)
|
||||
req = JSON.parse(req.text)
|
||||
if(!Array.isArray(req)){
|
||||
if (!Array.isArray(req)) {
|
||||
req = [req]
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
async function download_file(url:string,folder_path:string,file_name:string){
|
||||
async function download_file(url: string, folder_path: string, file_name: string) {
|
||||
let req = await (window as any).requestUrl(url)
|
||||
// req = JSON.parse(req.text)
|
||||
// let ctx = atob(req.content)
|
||||
let ctx = req.text
|
||||
let tfile_path = `${folder_path}/${file_name}`
|
||||
// console.log(ctx)
|
||||
if(folder_path.startsWith('.')){
|
||||
if (folder_path.startsWith('.')) {
|
||||
let flag = (plugin.app.vault as any).exists(folder_path);
|
||||
if(!flag){
|
||||
if (!flag) {
|
||||
await plugin.app.vault.createFolder(folder_path)
|
||||
}
|
||||
flag = (plugin.app.vault as any).exists(tfile_path);
|
||||
if(flag){
|
||||
if (flag) {
|
||||
await (plugin.app.vault as any).adapter.remove(tfile_path)
|
||||
await plugin.app.vault.create(tfile_path,ctx)
|
||||
new Notice(`更新:${tfile_path}`,5000)
|
||||
}else{
|
||||
await plugin.app.vault.create(tfile_path,ctx)
|
||||
new Notice(`下载:${tfile_path}`,5000)
|
||||
await plugin.app.vault.create(tfile_path, ctx)
|
||||
new Notice(`更新:${tfile_path}`, 5000)
|
||||
} else {
|
||||
await plugin.app.vault.create(tfile_path, ctx)
|
||||
new Notice(`下载:${tfile_path}`, 5000)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
let folder = plugin.app.vault.getFolderByPath(folder_path)
|
||||
if(!folder){
|
||||
if (!folder) {
|
||||
folder = await plugin.app.vault.createFolder(folder_path)
|
||||
}
|
||||
|
||||
|
||||
let tfile = plugin.app.vault.getFileByPath(tfile_path)
|
||||
if(!tfile){
|
||||
await plugin.app.vault.create(tfile_path,ctx);
|
||||
new Notice(`下载:${tfile_path}`,5000)
|
||||
}else{
|
||||
await plugin.app.vault.modify(tfile,ctx)
|
||||
new Notice(`更新:${tfile.path}`,5000)
|
||||
if (!tfile) {
|
||||
await plugin.app.vault.create(tfile_path, ctx);
|
||||
new Notice(`下载:${tfile_path}`, 5000)
|
||||
} else {
|
||||
await plugin.app.vault.modify(tfile, ctx)
|
||||
new Notice(`更新:${tfile.path}`, 5000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function download_file_of_dir(repoOwner:string, repoName:string, path:string, branch:string){
|
||||
async function download_file_of_dir(repoOwner: string, repoName: string, path: string, branch: string) {
|
||||
let items = await list_files_of_path(repoOwner, repoName, path, branch)
|
||||
let nc = this.app.plugins.getPlugin('note-chain');
|
||||
let item = await nc.dialog_suggest(
|
||||
items.map((x:any)=>(x.type=='file'?'📃':'📁')+x.path),
|
||||
items,'',true
|
||||
items.map((x: any) => (x.type == 'file' ? '📃' : '📁') + x.path),
|
||||
items, '', true
|
||||
);
|
||||
if(!item){return}
|
||||
if(typeof(item)=='string' && item=='all'){
|
||||
for(let item of items){
|
||||
if(item.type=='file'){
|
||||
if (!item) { return }
|
||||
if (typeof (item) == 'string' && item == 'all') {
|
||||
for (let item of items) {
|
||||
if (item.type == 'file') {
|
||||
let file_name = item.path.split('/').last();
|
||||
let folder_path = item.path.slice(0,item.path.length-file_name.length-1);
|
||||
await download_file(item.download_url,folder_path,file_name)
|
||||
let folder_path = item.path.slice(0, item.path.length - file_name.length - 1);
|
||||
await download_file(item.download_url, folder_path, file_name)
|
||||
}
|
||||
}
|
||||
}else if(item.type=='file'){
|
||||
} else if (item.type == 'file') {
|
||||
let file_name = item.path.split('/').last();
|
||||
let folder_path = item.path.slice(0,item.path.length-file_name.length-1);
|
||||
await download_file(item.download_url,folder_path,file_name)
|
||||
}else if(item.type=='dir'){
|
||||
let folder_path = item.path.slice(0, item.path.length - file_name.length - 1);
|
||||
await download_file(item.download_url, folder_path, file_name)
|
||||
} else if (item.type == 'dir') {
|
||||
await download_file_of_dir(repoOwner, repoName, item.path, branch)
|
||||
}
|
||||
}
|
||||
|
|
@ -214,42 +215,103 @@ const cmd_download_git_repo = (plugin:NoteSyncPlugin) => ({
|
|||
}
|
||||
});
|
||||
|
||||
const cmd_export_wxmp = (plugin:NoteSyncPlugin) => ({
|
||||
const cmd_export_wxmp = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'cmd_export_wxmp',
|
||||
name: plugin.strings.cmd_export_wxmp,
|
||||
icon:'aperture',
|
||||
icon: 'aperture',
|
||||
hotkeys: [{ modifiers: ['Alt', 'Shift'], key: 'P' }],
|
||||
callback: async () => {
|
||||
if(!plugin.easyapi.cfile){return}
|
||||
if (!plugin.easyapi.cfile) { return }
|
||||
let ctx = plugin.easyapi.ceditor.getSelection();
|
||||
if(!ctx){
|
||||
if (!ctx) {
|
||||
plugin.wxmp.tfile_to_wxmp(plugin.easyapi.cfile);
|
||||
}else{
|
||||
} else {
|
||||
await plugin.wxmp.selection_to_wxmp();
|
||||
}
|
||||
new Notice(`${plugin.strings.cmd_export_wxmp}: OK`,5000)
|
||||
new Notice(`${plugin.strings.cmd_export_wxmp}: OK`, 5000)
|
||||
}
|
||||
});
|
||||
|
||||
const cmd_export_as_single_note = (plugin: NoteSyncPlugin) => ({
|
||||
id: 'cmd_export_as_single_note',
|
||||
name: plugin.strings.cmd_export_as_single_note,
|
||||
icon: 'aperture',
|
||||
// hotkeys: [{ modifiers: ['Alt', 'Shift'], key: 'P' }],
|
||||
callback: async () => {
|
||||
if (!plugin.easyapi.cfile) { return }
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { dialog } = require("electron").remote;
|
||||
|
||||
// 1. 当前文件
|
||||
let cfile = plugin.easyapi.cfile;
|
||||
if (!cfile) {
|
||||
new Notice("未找到当前文件");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 读取内容
|
||||
let n = 0;
|
||||
if(cfile.parent && cfile.parent.children.filter(x=> x instanceof TFolder).length>0){
|
||||
n = await plugin.easyapi.dialog_suggest([`-1 - All`,`0 - Brother`,`1 - Subfolder`],[-1,0,1]);
|
||||
if(n == null){n=0}
|
||||
}
|
||||
|
||||
let cfiles = plugin.easyapi.file.get_tfiles_of_folder(cfile.parent,n)
|
||||
|
||||
if(plugin.easyapi.nc){
|
||||
cfiles = plugin.easyapi.nc.chain.sort_tfiles_by_chain(cfiles);
|
||||
}
|
||||
|
||||
// 3. 打开「另存为」对话框
|
||||
const result = await dialog.showSaveDialog({
|
||||
title: plugin.strings.cmd_export_as_single_note,
|
||||
defaultPath: (cfile.parent?.name || cfile.basename+'_parent') + ".md",
|
||||
filters: [
|
||||
{ name: "Markdown", extensions: ["md"] },
|
||||
{ name: "All Files", extensions: ["*"] }
|
||||
]
|
||||
});
|
||||
|
||||
if (result.canceled || !result.filePath) {
|
||||
new Notice("已取消另存为");
|
||||
return;
|
||||
}
|
||||
|
||||
let content = '';
|
||||
for(let cfile of cfiles){
|
||||
let tmp = await plugin.app.vault.read(cfile);
|
||||
content = content + `=====\n${cfile.name}\n=====\n\n${tmp}\n\n`
|
||||
}
|
||||
// 4. 写入外部文件
|
||||
fs.writeFileSync(result.filePath, content, "utf-8");
|
||||
|
||||
new Notice(`已保存到:${result.filePath}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const commandBuilders:Array<Function> = [
|
||||
|
||||
const commandBuilders: Array<Function> = [
|
||||
cmd_export_wxmp,
|
||||
|
||||
|
||||
];
|
||||
|
||||
const commandBuildersDesktop:Array<Function> = [
|
||||
const commandBuildersDesktop: Array<Function> = [
|
||||
cmd_export_current_note,
|
||||
cmd_set_vexporter,
|
||||
cmd_export_plugin,
|
||||
cmd_download_git_repo,
|
||||
cmd_export_wxmp
|
||||
cmd_export_wxmp,
|
||||
cmd_export_as_single_note
|
||||
];
|
||||
|
||||
export function addCommands(plugin:NoteSyncPlugin) {
|
||||
commandBuilders.forEach((c) => {
|
||||
plugin.addCommand(c(plugin));
|
||||
});
|
||||
if((plugin.app as any).isMobile==false){
|
||||
export function addCommands(plugin: NoteSyncPlugin) {
|
||||
commandBuilders.forEach((c) => {
|
||||
plugin.addCommand(c(plugin));
|
||||
});
|
||||
if ((plugin.app as any).isMobile == false) {
|
||||
commandBuildersDesktop.forEach((c) => {
|
||||
plugin.addCommand(c(plugin));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {Random } from './random';
|
|||
import { Waiter } from './waiter';
|
||||
import { Templater } from './templater';
|
||||
import {Time} from './time'
|
||||
import { Web } from './web';
|
||||
|
||||
export class EasyAPI {
|
||||
app: App;
|
||||
|
|
@ -21,6 +22,7 @@ export class EasyAPI {
|
|||
waiter: Waiter
|
||||
tpl: Templater
|
||||
time: Time
|
||||
web: Web
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
|
|
@ -31,7 +33,8 @@ export class EasyAPI {
|
|||
this.waiter = new Waiter(app,this);
|
||||
this.random = new Random(app,this);
|
||||
this.tpl = new Templater(app,this);
|
||||
this.time = new Time(app,this)
|
||||
this.time = new Time(app,this);
|
||||
this.web = new Web(app);
|
||||
}
|
||||
|
||||
get_plugin(name:string){
|
||||
|
|
@ -50,6 +53,10 @@ export class EasyAPI {
|
|||
return this.get_plugin('note-sync');
|
||||
}
|
||||
|
||||
get wv(){
|
||||
return this.get_plugin('webview-llm');
|
||||
}
|
||||
|
||||
get qa(){
|
||||
return this.get_plugin('quickadd')?.api;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
|
||||
|
||||
|
||||
import { App, View, WorkspaceLeaf,TFile } from 'obsidian';
|
||||
import { App, View, WorkspaceLeaf, TFile } from 'obsidian';
|
||||
|
||||
import {EasyAPI} from 'src/easyapi/easyapi'
|
||||
import { EasyAPI } from 'src/easyapi/easyapi'
|
||||
|
||||
export class EasyEditor {
|
||||
yamljs = require('js-yaml');
|
||||
app: App;
|
||||
ea: EasyAPI;
|
||||
|
||||
constructor(app: App, api:EasyAPI) {
|
||||
constructor(app: App, api: EasyAPI) {
|
||||
this.app = app;
|
||||
this.ea = api;
|
||||
}
|
||||
|
||||
cn2num(chinese:string) {
|
||||
cn2num(chinese: string) {
|
||||
let v = parseFloat(chinese);
|
||||
if(!Number.isNaN(v)){return v}
|
||||
|
||||
if (!Number.isNaN(v)) { return v }
|
||||
|
||||
chinese = chinese.trim()
|
||||
const cnNumbers:{[key:string]:number} = {
|
||||
const cnNumbers: { [key: string]: number } = {
|
||||
"零": 0, "一": 1, "二": 2, "三": 3, "四": 4,
|
||||
"五": 5, "六": 6, "七": 7, "八": 8, "九": 9,
|
||||
"十": 10, "百": 100, "千": 1000, "万": 10000
|
||||
};
|
||||
|
||||
|
||||
let sign = 1.0;
|
||||
let i = 0;
|
||||
|
||||
|
||||
// 处理负号(JavaScript中汉字是双字节字符)
|
||||
if (i + 1 <= chinese.length && chinese[i] === "负") {
|
||||
sign = -1.0;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
|
||||
let integer_total = 0;
|
||||
let decimal_total = 0.0;
|
||||
let temp = 0;
|
||||
let processing_decimal = false;
|
||||
let decimal_factor = 0.1;
|
||||
|
||||
|
||||
while (i < chinese.length) {
|
||||
const c = chinese[i];
|
||||
i += 1;
|
||||
|
||||
|
||||
// 处理小数点
|
||||
if (c === "点") {
|
||||
processing_decimal = true;
|
||||
|
|
@ -52,14 +52,14 @@ export class EasyEditor {
|
|||
temp = 0;
|
||||
continue;
|
||||
}
|
||||
if(!(c in cnNumbers)){
|
||||
if (!(c in cnNumbers)) {
|
||||
return parseFloat('-')
|
||||
}
|
||||
if (!processing_decimal) {
|
||||
// 整数部分处理
|
||||
if (cnNumbers.hasOwnProperty(c)) {
|
||||
const num = cnNumbers[c];
|
||||
|
||||
|
||||
if (num >= 10) { // 处理单位
|
||||
if (temp === 0 && num === 10) {
|
||||
integer_total += 1 * num; // 特殊处理"十"前无数字的情况
|
||||
|
|
@ -79,33 +79,33 @@ export class EasyEditor {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理最后的临时值
|
||||
integer_total += temp;
|
||||
|
||||
|
||||
return sign * (integer_total + decimal_total);
|
||||
}
|
||||
|
||||
slice_by_position(ctx:string,pos:any){
|
||||
if(pos.position){
|
||||
slice_by_position(ctx: string, pos: any) {
|
||||
if (pos.position) {
|
||||
pos = pos.position
|
||||
}
|
||||
return ctx.slice(pos.start.offset,pos.end.offset);
|
||||
return ctx.slice(pos.start.offset, pos.end.offset);
|
||||
}
|
||||
|
||||
parse_list_regx(aline:string,regx:RegExp,field:{[key:string]:number}={}){
|
||||
|
||||
parse_list_regx(aline: string, regx: RegExp, field: { [key: string]: number } = {}) {
|
||||
let match = aline.match(regx);
|
||||
if(!match){return null}
|
||||
let res:{[key:string]:string} = {src:aline}
|
||||
for(let k in field){
|
||||
if (!match) { return null }
|
||||
let res: { [key: string]: string } = { src: aline }
|
||||
for (let k in field) {
|
||||
res[k] = match[field[k]]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
parse_list_dataview(aline:string,src='_src_'){
|
||||
let res:{[key:string]:string} = {};
|
||||
if(src){
|
||||
|
||||
parse_list_dataview(aline: string, src = '_src_') {
|
||||
let res: { [key: string]: string } = {};
|
||||
if (src) {
|
||||
res[src] = aline;
|
||||
}
|
||||
let regex = /[($$](.*?)::(.*?)[)$$]/g;
|
||||
|
|
@ -117,203 +117,295 @@ export class EasyEditor {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
keys_in(keys:Array<string>,obj:object){
|
||||
for(let k of keys){
|
||||
if(!(k in obj)){
|
||||
|
||||
keys_in(keys: Array<string>, obj: object) {
|
||||
for (let k of keys) {
|
||||
if (!(k in obj)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async extract_code_block(tfile:TFile|string,btype:string){
|
||||
|
||||
async extract_code_block(tfile: TFile | string, btype: string) {
|
||||
let xfile = this.ea.file.get_tfile(tfile);
|
||||
if(xfile){
|
||||
tfile = await this.app.vault.cachedRead(xfile);
|
||||
}
|
||||
if(typeof(tfile)!='string'){return []}
|
||||
if (xfile) {
|
||||
tfile = await this.app.vault.cachedRead(xfile);
|
||||
}
|
||||
if (typeof (tfile) != 'string') { return [] }
|
||||
|
||||
let blocks = [];
|
||||
let reg = new RegExp(`\`\`\`${btype}\\n([\\s\\S]*?)\n\`\`\``,'g');;
|
||||
let matches;
|
||||
while ((matches = reg.exec(tfile)) !== null) {
|
||||
blocks.push(matches[1].trim());
|
||||
}
|
||||
let blocks = [];
|
||||
let reg = new RegExp(`\`\`\`${btype}\\n([\\s\\S]*?)\n\`\`\``, 'g');;
|
||||
let matches;
|
||||
while ((matches = reg.exec(tfile)) !== null) {
|
||||
blocks.push(matches[1].trim());
|
||||
}
|
||||
|
||||
reg = new RegExp(`~~~${btype}\\n([\\s\\S]*?)\n~~~`,'g');;
|
||||
while ((matches = reg.exec(tfile)) !== null) {
|
||||
blocks.push(matches[1].trim());
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
reg = new RegExp(`~~~${btype}\\n([\\s\\S]*?)\n~~~`, 'g');;
|
||||
while ((matches = reg.exec(tfile)) !== null) {
|
||||
blocks.push(matches[1].trim());
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
async get_selection(cancel_selection=false){
|
||||
async get_selection(cancel_selection = false) {
|
||||
let editor = (this.app.workspace as any).getActiveFileView()?.editor;
|
||||
if(editor){
|
||||
if (editor) {
|
||||
let sel = editor.getSelection();
|
||||
if(cancel_selection){
|
||||
let cursor = editor.getCursor();
|
||||
if (cancel_selection) {
|
||||
let cursor = editor.getCursor();
|
||||
await editor.setSelection(cursor, cursor);
|
||||
}
|
||||
return sel;
|
||||
}else{
|
||||
return '';
|
||||
if (sel) {
|
||||
return sel;
|
||||
}
|
||||
}
|
||||
|
||||
let selection = window.getSelection();
|
||||
if (selection && !selection.isCollapsed) {
|
||||
const selectedText = selection.toString();
|
||||
if (selectedText) {
|
||||
return selectedText;
|
||||
}
|
||||
}
|
||||
let areas = document.querySelectorAll('textarea');
|
||||
for (let area of Array.from(areas)) {
|
||||
let sel = area.value.slice(area.selectionStart, area.selectionEnd);
|
||||
|
||||
if (sel) {
|
||||
area.selectionStart = area.selectionEnd;
|
||||
area.blur();
|
||||
return sel
|
||||
}
|
||||
}
|
||||
return ''
|
||||
|
||||
}
|
||||
|
||||
async get_code_section(tfile:TFile,ctype='',idx=0,as_simple=true){
|
||||
async get_code_section(tfile: TFile, ctype = '', idx = 0, as_simple = true) {
|
||||
let dvmeta = this.app.metadataCache.getFileCache(tfile);
|
||||
let ctx = await this.app.vault.cachedRead(tfile);
|
||||
let section = dvmeta?.sections?.filter(x=>x.type=='code').filter(x=>{
|
||||
let c = ctx.slice(x.position.start.offset,x.position.end.offset).trim();
|
||||
return c.startsWith('```'+ctype) || c.startsWith('~~~'+ctype);
|
||||
})[idx]
|
||||
if(section){
|
||||
let c = ctx.slice(
|
||||
section.position.start.offset,
|
||||
section.position.end.offset
|
||||
|
||||
let sections = dvmeta?.sections
|
||||
?.filter(x => x.type == 'code')
|
||||
.filter(x => {
|
||||
let c = ctx.slice(x.position.start.offset, x.position.end.offset).trim();
|
||||
return c.startsWith('```' + ctype) || c.startsWith('~~~' + ctype);
|
||||
});
|
||||
|
||||
if (!sections || sections.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let selected: any;
|
||||
|
||||
if(sections.length==1){
|
||||
selected = sections[0];
|
||||
}else if (idx >= 2 && sections[idx]) {
|
||||
selected = sections[idx];
|
||||
} else {
|
||||
let sel = await this.ea.dialog_suggest(
|
||||
sections.map(x => ctx.slice(x.position.start.offset, x.position.end.offset)),
|
||||
[...Array(sections.length).keys()]
|
||||
);
|
||||
|
||||
if(as_simple){
|
||||
return c.slice(4+ctype.length,c.length-4)
|
||||
}else{
|
||||
let res = {
|
||||
code:c,
|
||||
section:section,
|
||||
ctx:ctx
|
||||
}
|
||||
return res;
|
||||
}
|
||||
if (sel == null) return;
|
||||
selected = sections[sel];
|
||||
}
|
||||
|
||||
// Now safely use selected
|
||||
let c = ctx.slice(
|
||||
selected.position.start.offset,
|
||||
selected.position.end.offset
|
||||
);
|
||||
|
||||
if (as_simple) {
|
||||
return c.slice(4 + ctype.length, c.length - 4);
|
||||
} else {
|
||||
return {
|
||||
code: c,
|
||||
section: selected,
|
||||
ctx: ctx
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async get_heading_section(tfile:TFile,heading:string,idx=0, with_heading=true){
|
||||
get_heading_ctx(ctx: string, headings: any[], heading: any, with_heading = true) {
|
||||
// 找到 heading 在 headings 中的位置
|
||||
if(!heading){return ''}
|
||||
let idx = headings.indexOf(heading);
|
||||
let nextIdx = idx + 1;
|
||||
|
||||
// 找下一个同级或更高的 heading(level <= target.level)
|
||||
while (nextIdx < headings.length) {
|
||||
if (headings[nextIdx].level <= heading.level) {
|
||||
break;
|
||||
}
|
||||
nextIdx++;
|
||||
}
|
||||
|
||||
// 起点
|
||||
let start = with_heading
|
||||
? heading.position.start.offset
|
||||
: heading.position.end.offset;
|
||||
|
||||
// 终点:如果找到下一个 heading
|
||||
if (nextIdx < headings.length) {
|
||||
let nextSec = headings[nextIdx];
|
||||
return ctx.slice(start, nextSec.position.start.offset);
|
||||
}
|
||||
|
||||
// 没有更下一级 heading,截到文末
|
||||
return ctx.slice(start);
|
||||
}
|
||||
|
||||
|
||||
async get_heading_section(tfile: TFile, heading: string, idx = 0, with_heading = true) {
|
||||
let dvmeta = this.app.metadataCache.getFileCache(tfile);
|
||||
let ctx = await this.app.vault.cachedRead(tfile);
|
||||
if(!dvmeta?.headings){
|
||||
|
||||
if (!dvmeta?.headings) {
|
||||
return '';
|
||||
}
|
||||
let section = dvmeta?.headings?.filter(x=>x.heading==heading)[idx]
|
||||
if(section){
|
||||
let idx = dvmeta.headings.indexOf(section)+1;
|
||||
while(idx<dvmeta.headings.length){
|
||||
let csec = dvmeta.headings[idx];
|
||||
if(csec.level<=section.level){
|
||||
break
|
||||
}
|
||||
idx = idx+1;
|
||||
}
|
||||
if(idx<dvmeta.headings.length){
|
||||
let csec = dvmeta.headings[idx];
|
||||
let c = ctx.slice(
|
||||
with_heading?section.position.start.offset : section.position.end.offset,
|
||||
csec.position.start.offset
|
||||
);
|
||||
return c;
|
||||
}else{
|
||||
let c = ctx.slice(
|
||||
with_heading?section.position.start.offset : section.position.end.offset,
|
||||
);
|
||||
return c;
|
||||
}
|
||||
|
||||
// 找到所有匹配开头的 headings
|
||||
let sections = dvmeta.headings.filter(x => x.heading==heading);
|
||||
|
||||
if (sections.length === 0) return '';
|
||||
|
||||
let selected: any;
|
||||
|
||||
// idx >= 0 时直接取
|
||||
if(sections.length==1){
|
||||
selected = sections[0];
|
||||
}else if (idx >= 2 && sections[idx]) {
|
||||
selected = sections[idx];
|
||||
} else {
|
||||
// 弹窗选择
|
||||
const choices = sections.map(x =>
|
||||
this.get_heading_ctx(ctx,dvmeta?.headings??[],x,with_heading)
|
||||
);
|
||||
|
||||
const nums = [...Array(sections.length).keys()];
|
||||
let sel = await this.ea.dialog_suggest(choices, nums);
|
||||
|
||||
if (sel == null) return;
|
||||
selected = sections[sel];
|
||||
}
|
||||
return this.get_heading_ctx(ctx,dvmeta.headings??[],selected,with_heading).trim();
|
||||
}
|
||||
|
||||
async get_current_section(){
|
||||
let editor = this.ea.ceditor;
|
||||
let tfile = this.ea.cfile;
|
||||
if(!editor || !tfile){return null}
|
||||
let cursor = editor.getCursor();
|
||||
let cache = this.app.metadataCache.getFileCache(tfile)
|
||||
if(!cache){return null}
|
||||
if(cursor){
|
||||
let section = cache?.sections?.filter(
|
||||
x=>{return x.position.start.line<=cursor.line && x.position.end.line>=cursor.line}
|
||||
)[0]
|
||||
|
||||
async get_current_section(with_section = false) {
|
||||
let editor = this.ea.ceditor;
|
||||
let tfile = this.ea.cfile;
|
||||
if (!editor || !tfile) { return null }
|
||||
let cursor = editor.getCursor();
|
||||
let cache = this.app.metadataCache.getFileCache(tfile)
|
||||
if (!cache || !cache?.sections) { return null }
|
||||
if (cursor) {
|
||||
let section = cache?.sections?.filter(
|
||||
x => { return x.position.start.line <= cursor.line && x.position.end.line >= cursor.line }
|
||||
)[0]
|
||||
if (!section && cursor.line > cache.sections[cache.sections.length - 1].position.end.line) {
|
||||
section = cache.sections[cache.sections.length - 1]
|
||||
}
|
||||
if (!section && cursor.line < cache.sections[0].position.start.line) {
|
||||
section = cache.sections[0]
|
||||
}
|
||||
if (!section) {
|
||||
return null;
|
||||
}
|
||||
let ctx = await this.app.vault.cachedRead(tfile);
|
||||
if(!section){return ''}
|
||||
return ctx.slice(
|
||||
ctx = ctx.slice(
|
||||
section.position.start.offset,
|
||||
section.position.end.offset
|
||||
)
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (with_section) {
|
||||
return {
|
||||
'section': section,
|
||||
'sec': ctx
|
||||
}
|
||||
} else {
|
||||
return ctx;
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
set_obj_value(data:any,key:string,value:any){
|
||||
let items = key.trim().split('.')
|
||||
if(!items){return}
|
||||
let curr = data
|
||||
for(let item of items.slice(0,items.length-1)){
|
||||
let kv = item.match(/^(.*?)(\[-?\d+\])?$/) // 匹配数组索引, 如 key[0] 或 key
|
||||
if(!kv){return}
|
||||
let k = kv[1] // 键名
|
||||
if(kv[2]){ // 有索引
|
||||
let i = parseInt(kv[2].slice(1,kv[2].length-1)) // 索引
|
||||
if(!(k in curr)){ // 键不存在
|
||||
curr[k] = [{}] // 创建空数组
|
||||
curr = curr[k][0]
|
||||
}else{
|
||||
if(Array.isArray(curr[k])){
|
||||
let tmp = {}
|
||||
if(i<0){
|
||||
curr[k].splice(-i-1,0,tmp)
|
||||
}else if(i<curr[k].length){
|
||||
curr[k][i]=tmp
|
||||
}else{
|
||||
curr[k].push(tmp)
|
||||
}
|
||||
curr = tmp
|
||||
}else{
|
||||
curr[k] = [{}]
|
||||
curr = curr[k][0]
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(!(k in curr)){
|
||||
curr[k] = {}
|
||||
curr = curr[k]
|
||||
}else{
|
||||
if(typeof(curr[k])!='object'){
|
||||
curr[k] = {}
|
||||
curr = curr[k]
|
||||
}else{
|
||||
curr = curr[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let kv = items[items.length-1].match(/^(.*?)(\[-?\d+\])?$/)
|
||||
if(!kv){return}
|
||||
let k = kv[1]
|
||||
if(kv[2]){
|
||||
let i = parseInt(kv[2].slice(1,kv[2].length-1))
|
||||
if(k in curr){
|
||||
if(Array.isArray(curr[k])){
|
||||
if(i<0){
|
||||
curr[k].splice(-i-1,0,value)
|
||||
}else if(i<curr[k].length){
|
||||
curr[k][i] = value
|
||||
}else{
|
||||
curr[k].push(value)
|
||||
}
|
||||
}else{
|
||||
curr[k] = value
|
||||
}
|
||||
}else{
|
||||
curr[k] = [value]
|
||||
}
|
||||
}else{
|
||||
curr[k] = value
|
||||
}
|
||||
}
|
||||
set_obj_value(data: any, key: string, value: any) {
|
||||
let items = key.trim().split('.')
|
||||
if (!items) { return }
|
||||
let curr = data
|
||||
for (let item of items.slice(0, items.length - 1)) {
|
||||
let kv = item.match(/^(.*?)(\[-?\d+\])?$/) // 匹配数组索引, 如 key[0] 或 key
|
||||
if (!kv) { return }
|
||||
let k = kv[1] // 键名
|
||||
if (kv[2]) { // 有索引
|
||||
let i = parseInt(kv[2].slice(1, kv[2].length - 1)) // 索引
|
||||
if (!(k in curr)) { // 键不存在
|
||||
curr[k] = [{}] // 创建空数组
|
||||
curr = curr[k][0]
|
||||
} else {
|
||||
if (Array.isArray(curr[k])) {
|
||||
let tmp = {}
|
||||
if (i < 0) {
|
||||
curr[k].splice(-i - 1, 0, tmp)
|
||||
} else if (i < curr[k].length) {
|
||||
curr[k][i] = tmp
|
||||
} else {
|
||||
curr[k].push(tmp)
|
||||
}
|
||||
curr = tmp
|
||||
} else {
|
||||
curr[k] = [{}]
|
||||
curr = curr[k][0]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!(k in curr)) {
|
||||
curr[k] = {}
|
||||
curr = curr[k]
|
||||
} else {
|
||||
if (typeof (curr[k]) != 'object') {
|
||||
curr[k] = {}
|
||||
curr = curr[k]
|
||||
} else {
|
||||
curr = curr[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let kv = items[items.length - 1].match(/^(.*?)(\[-?\d+\])?$/)
|
||||
if (!kv) { return }
|
||||
let k = kv[1]
|
||||
if (kv[2]) {
|
||||
let i = parseInt(kv[2].slice(1, kv[2].length - 1))
|
||||
if (k in curr) {
|
||||
if (Array.isArray(curr[k])) {
|
||||
if (i < 0) {
|
||||
curr[k].splice(-i - 1, 0, value)
|
||||
} else if (i < curr[k].length) {
|
||||
curr[k][i] = value
|
||||
} else {
|
||||
curr[k].push(value)
|
||||
}
|
||||
} else {
|
||||
curr[k] = value
|
||||
}
|
||||
} else {
|
||||
curr[k] = [value]
|
||||
}
|
||||
} else {
|
||||
curr[k] = value
|
||||
}
|
||||
}
|
||||
|
||||
get_obj_value(data:any,key:string):any{
|
||||
try {
|
||||
get_obj_value(data: any, key: string): any {
|
||||
try {
|
||||
// key 直接在对象中
|
||||
if(data[key]){
|
||||
if (data[key]) {
|
||||
return data[key]
|
||||
}
|
||||
|
||||
|
|
@ -321,59 +413,61 @@ export class EasyEditor {
|
|||
let left = keys[0];
|
||||
let right = keys.slice(1).join('.');
|
||||
|
||||
if(left){
|
||||
if (left) {
|
||||
// key[3],key[-3]
|
||||
let items = left.match(/^(.*?)(\[-?\d+\])?$/)
|
||||
if(!items){return null}
|
||||
if(items[1]){
|
||||
if (!items) { return null }
|
||||
if (items[1]) {
|
||||
data = data[items[1]]
|
||||
}
|
||||
if(!data){return null}
|
||||
if(items[2]){
|
||||
if(Array.isArray(data)){
|
||||
if(data.length == 0){
|
||||
if (!data) { return null }
|
||||
if (items[2]) {
|
||||
if (Array.isArray(data)) {
|
||||
if (data.length == 0) {
|
||||
data = null;
|
||||
}else{
|
||||
let i = parseInt(items[2].slice(1,items[2].length-1))
|
||||
} else {
|
||||
let i = parseInt(items[2].slice(1, items[2].length - 1))
|
||||
i = ((i % data.length) + data.length) % data.length;
|
||||
data = data[i]
|
||||
}
|
||||
}else if(typeof data=='object'){
|
||||
} else if (typeof data == 'object') {
|
||||
let keys = Object.keys(data).sort();
|
||||
if(keys.length==0){
|
||||
if (keys.length == 0) {
|
||||
data = null;
|
||||
}else{
|
||||
let i = parseInt(items[2].slice(1,items[2].length-1))
|
||||
} else {
|
||||
let i = parseInt(items[2].slice(1, items[2].length - 1))
|
||||
i = ((i % keys.length) + keys.length) % keys.length;
|
||||
data = data[keys[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!right){
|
||||
if (!right) {
|
||||
return data;
|
||||
}else{
|
||||
return this.get_obj_value(data,right);
|
||||
} else {
|
||||
return this.get_obj_value(data, right);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// LINE 存在时在其之后插件,不存在在末尾
|
||||
async insert_after_line(tfile:TFile,aline:string,LINE:string, tail=true,suffix='\n\n'){
|
||||
if(!tfile){return false}
|
||||
async insert_after_line(tfile: TFile, aline: string, LINE: string, tail = true, suffix = '\n\n') {
|
||||
if (!tfile) { return false }
|
||||
let ctx = await this.ea.app.vault.cachedRead(tfile)
|
||||
|
||||
let idx = ctx.indexOf(LINE)
|
||||
|
||||
if(idx==-1 && tail){
|
||||
if (idx == -1 && tail) {
|
||||
ctx = `${ctx}${suffix}${aline}`
|
||||
}else{
|
||||
ctx = `${ctx.slice(0,idx+LINE.length)}\n${aline}${ctx.slice(idx+LINE.length)}`
|
||||
} else {
|
||||
ctx = `${ctx.slice(0, idx + LINE.length)}\n${aline}${ctx.slice(idx + LINE.length)}`
|
||||
}
|
||||
await this.ea.app.vault.modify(tfile,ctx)
|
||||
await this.ea.app.vault.modify(tfile, ctx)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,11 @@ export class File {
|
|||
this.api = api;
|
||||
}
|
||||
|
||||
get_tfile(path:string|TFile,only_first=true){
|
||||
get_tfile(path:string|TFile|null,only_first=true){
|
||||
try{
|
||||
if(!path){
|
||||
return null;
|
||||
}
|
||||
if(path instanceof TFile){
|
||||
return path;
|
||||
}
|
||||
|
|
@ -57,6 +60,11 @@ export class File {
|
|||
}
|
||||
}
|
||||
|
||||
get_all_tfiles(){
|
||||
let files = this.app.vault.getMarkdownFiles();
|
||||
return files;
|
||||
}
|
||||
|
||||
get_tfiles_of_folder(tfolder:TFolder|null,n=0):any{
|
||||
if(!tfolder){return [];}
|
||||
let notes = [];
|
||||
|
|
@ -73,6 +81,30 @@ export class File {
|
|||
return notes;
|
||||
}
|
||||
|
||||
get_all_tfiles_of_tags(tags:string|Array<string>,sort_mode=''){
|
||||
if(!Array.isArray(tags)){
|
||||
tags = [tags]
|
||||
}
|
||||
|
||||
tags = tags.map(x=>{
|
||||
if(x.startsWith('#')){
|
||||
return x;
|
||||
}else{
|
||||
return '#'+x;
|
||||
}
|
||||
})
|
||||
|
||||
let tfiles = this.get_all_tfiles().filter(x=>{
|
||||
let ttags = this.get_tags(x);
|
||||
for(let tag of tags){
|
||||
if(ttags.contains(tag)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
})
|
||||
return tfiles;
|
||||
}
|
||||
|
||||
generate_structure(tfolder:TFolder, depth = 0, isRoot = true,only_folder=false,only_md=true) {
|
||||
let structure = '';
|
||||
const indentUnit = ' '; // 关键修改点:每层缩进 4 空格
|
||||
|
|
@ -102,5 +134,35 @@ export class File {
|
|||
});
|
||||
return structure;
|
||||
}
|
||||
|
||||
get_tags(tfile:TFile){
|
||||
if(!tfile){return []}
|
||||
let mcache= this.app.metadataCache.getFileCache(tfile);
|
||||
let tags:Array<string> = []
|
||||
if(mcache?.tags){
|
||||
for(let curr of mcache.tags){
|
||||
if(!tags.contains(curr.tag)){
|
||||
tags.push(curr.tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(mcache?.frontmatter?.tags){
|
||||
if(Array.isArray(mcache.frontmatter.tags)){
|
||||
for(let curr of mcache.frontmatter.tags){
|
||||
let tag = '#'+curr;
|
||||
if(!tags.contains(tag)){
|
||||
tags.push(tag)
|
||||
}
|
||||
}
|
||||
}else if(typeof mcache.frontmatter.tags === 'string'){
|
||||
let tag = `#`+mcache.frontmatter.tags
|
||||
if(!tags.contains(tag)){
|
||||
tags.push(tag)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return tags
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
199
src/easyapi/web.ts
Normal file
199
src/easyapi/web.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { App } from 'obsidian';
|
||||
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const url = require('url');
|
||||
|
||||
export interface WebRequestOptions {
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
headers?: { [key: string]: string };
|
||||
body?: any;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface WebResponse {
|
||||
statusCode: number;
|
||||
headers: { [key: string]: string };
|
||||
body: any;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class Web {
|
||||
app: App;
|
||||
|
||||
constructor(app: App) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 HTTP 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param options 请求选项
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async request(urlStr: string, options: WebRequestOptions = {}): Promise<WebResponse> {
|
||||
// 解析 URL 并正确处理编码
|
||||
const parsed = url.parse(urlStr);
|
||||
const isHttps = parsed.protocol === 'https:';
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
// 处理路径和查询字符串的编码
|
||||
let requestPath = parsed.pathname || '/';
|
||||
|
||||
// 处理查询字符串,确保中文字符被正确编码
|
||||
if (parsed.query || parsed.search) {
|
||||
let queryString = '';
|
||||
if (parsed.search) {
|
||||
// 解析查询字符串并重新编码
|
||||
const queryParams = new URLSearchParams(parsed.search.substring(1));
|
||||
queryString = '?' + queryParams.toString();
|
||||
} else if (parsed.query) {
|
||||
// 手动处理查询字符串
|
||||
const parts: string[] = [];
|
||||
const queryPairs = parsed.query.split('&');
|
||||
for (const pair of queryPairs) {
|
||||
const [key, ...valueParts] = pair.split('=');
|
||||
const value = valueParts.join('=');
|
||||
// 对键和值进行编码(如果还没有编码)
|
||||
const encodedKey = encodeURIComponent(decodeURIComponent(key || ''));
|
||||
const encodedValue = encodeURIComponent(decodeURIComponent(value || ''));
|
||||
parts.push(`${encodedKey}=${encodedValue}`);
|
||||
}
|
||||
queryString = '?' + parts.join('&');
|
||||
}
|
||||
requestPath = requestPath + queryString;
|
||||
}
|
||||
|
||||
const method = options.method || 'GET';
|
||||
const headers = options.headers || {};
|
||||
const timeout = options.timeout || 30000; // 默认 30 秒
|
||||
|
||||
// 如果没有设置 Content-Type,根据 body 类型自动设置
|
||||
if (method !== 'GET' && options.body && !headers['Content-Type']) {
|
||||
if (typeof options.body === 'string') {
|
||||
headers['Content-Type'] = 'text/plain; charset=utf-8';
|
||||
} else if (options.body instanceof Buffer) {
|
||||
headers['Content-Type'] = 'application/octet-stream';
|
||||
} else {
|
||||
headers['Content-Type'] = 'application/json; charset=utf-8';
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
}
|
||||
|
||||
const requestOptions: any = {
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port || (isHttps ? 443 : 80),
|
||||
path: requestPath,
|
||||
method: method,
|
||||
headers: headers,
|
||||
timeout: timeout
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.request(requestOptions, (res: any) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
let body: any = data;
|
||||
|
||||
// 尝试解析 JSON
|
||||
const contentType = res.headers['content-type'] || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
try {
|
||||
body = JSON.parse(data);
|
||||
} catch {
|
||||
// 解析失败,保持原样
|
||||
}
|
||||
}
|
||||
|
||||
const response: WebResponse = {
|
||||
statusCode: res.statusCode || 200,
|
||||
headers: res.headers,
|
||||
body: body,
|
||||
text: data
|
||||
};
|
||||
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error: Error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
});
|
||||
|
||||
// 发送请求体
|
||||
if (method !== 'GET' && options.body) {
|
||||
if (typeof options.body === 'string' || options.body instanceof Buffer) {
|
||||
req.write(options.body);
|
||||
} else {
|
||||
req.write(JSON.stringify(options.body));
|
||||
}
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param options 请求选项(可选)
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async get(urlStr: string, options: Omit<WebRequestOptions, 'method'> = {}): Promise<WebResponse> {
|
||||
return this.request(urlStr, { ...options, method: 'GET' });
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param body 请求体(可选)
|
||||
* @param options 请求选项(可选)
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async post(urlStr: string, body?: any, options: Omit<WebRequestOptions, 'method' | 'body'> = {}): Promise<WebResponse> {
|
||||
return this.request(urlStr, { ...options, method: 'POST', body: body });
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param body 请求体(可选)
|
||||
* @param options 请求选项(可选)
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async put(urlStr: string, body?: any, options: Omit<WebRequestOptions, 'method' | 'body'> = {}): Promise<WebResponse> {
|
||||
return this.request(urlStr, { ...options, method: 'PUT', body: body });
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param options 请求选项(可选)
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async delete(urlStr: string, options: Omit<WebRequestOptions, 'method'> = {}): Promise<WebResponse> {
|
||||
return this.request(urlStr, { ...options, method: 'DELETE' });
|
||||
}
|
||||
|
||||
/**
|
||||
* PATCH 请求
|
||||
* @param urlStr 请求 URL
|
||||
* @param body 请求体(可选)
|
||||
* @param options 请求选项(可选)
|
||||
* @returns Promise<WebResponse>
|
||||
*/
|
||||
async patch(urlStr: string, body?: any, options: Omit<WebRequestOptions, 'method' | 'body'> = {}): Promise<WebResponse> {
|
||||
return this.request(urlStr, { ...options, method: 'PATCH', body: body });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +50,14 @@ export class Strings{
|
|||
}
|
||||
}
|
||||
|
||||
get cmd_export_as_single_note(){
|
||||
if(this.language=='zh'){
|
||||
return '导出目录文件';
|
||||
}else{
|
||||
return 'Export folder';
|
||||
}
|
||||
}
|
||||
|
||||
get prompt_path_of_folder(){
|
||||
if(this.language=='zh'){
|
||||
return '输入文件夹路径'
|
||||
|
|
|
|||
33
src/wxmp.ts
33
src/wxmp.ts
|
|
@ -56,8 +56,10 @@ export class Wxmp {
|
|||
if (!config['section@cards-album']) {
|
||||
config['section@cards-album'] = this.format_code_block_cards_album.bind(this);
|
||||
}
|
||||
|
||||
|
||||
let tfiles = this.plugin.easyapi.file.get_all_tfiles_of_tags('NoteSyncWxmp');
|
||||
for(let i in tfiles){
|
||||
config[`section@${i}`] = tfiles[i].basename;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
@ -127,27 +129,27 @@ export class Wxmp {
|
|||
console.warn(`图片 ${src} 转换失败:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
return new XMLSerializer().serializeToString(doc.body);
|
||||
}
|
||||
|
||||
async section_to_wxmp(section:any,sec:string){
|
||||
async section_to_wxmp(section:any,sec:string,ctx:string){
|
||||
if (section.type == 'yaml') {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rhtml:any = null;
|
||||
|
||||
for(let k in this.ctx_map){
|
||||
let ctx_map = this.ctx_map;
|
||||
for(let k in ctx_map){
|
||||
if(k.startsWith('section@')){
|
||||
let tpl = this.ctx_map[k];
|
||||
let tpl = ctx_map[k];
|
||||
if (typeof tpl == 'function') {
|
||||
rhtml = await tpl(section,sec);
|
||||
} else {
|
||||
let rendered = await this.plugin.easyapi.tpl.parse_templater(
|
||||
tpl, true,{section:section,sec:sec},[0]
|
||||
tpl, true,{section:section,sec:sec,ctx:ctx},[0]
|
||||
);
|
||||
if (rendered.length > 0 && rendered[0].trim() != '') {
|
||||
rendered = rendered.filter(x=>x);
|
||||
if (rendered && rendered.length > 0 && rendered[0].trim() != '') {
|
||||
rhtml = rendered[0];
|
||||
}
|
||||
}
|
||||
|
|
@ -191,7 +193,9 @@ export class Wxmp {
|
|||
let sec = ctx.slice(from,to).trim();
|
||||
|
||||
if(section.type=='code'){
|
||||
let items = this.plugin.easyapi.editor.slice_by_position(ctx, section.position).trim().split('\n')
|
||||
let items = this.plugin.easyapi.editor.slice_by_position(
|
||||
ctx, section.position
|
||||
).trim().split('\n')
|
||||
if(!sec.startsWith(items[0])){
|
||||
sec = items[0]+'\n'+sec;
|
||||
}
|
||||
|
|
@ -201,7 +205,7 @@ export class Wxmp {
|
|||
}
|
||||
|
||||
if(sec==''){continue}
|
||||
let rhtml = await this.section_to_wxmp(section,sec);
|
||||
let rhtml = await this.section_to_wxmp(section,sec,sec);
|
||||
if(!rhtml){continue}
|
||||
|
||||
if(Array.isArray(rhtml)){
|
||||
|
|
@ -222,7 +226,7 @@ export class Wxmp {
|
|||
continue
|
||||
}
|
||||
let sec = this.plugin.easyapi.editor.slice_by_position(ctx, section.position);
|
||||
let rhtml = await this.section_to_wxmp(section,sec);
|
||||
let rhtml = await this.section_to_wxmp(section,sec,ctx);
|
||||
if(!rhtml){continue}
|
||||
|
||||
if(Array.isArray(rhtml)){
|
||||
|
|
@ -250,9 +254,10 @@ export class Wxmp {
|
|||
rhtml = this.html_replace_code(rhtml)
|
||||
|
||||
// 替换标题
|
||||
for (let k in this.ctx_map) {
|
||||
let ctx_map = this.ctx_map;
|
||||
for (let k in ctx_map) {
|
||||
if(k.contains('@')){continue;}
|
||||
rhtml = await this.set_tag_with_tpl(rhtml, k, this.ctx_map[k]);
|
||||
rhtml = await this.set_tag_with_tpl(rhtml, k, ctx_map[k]);
|
||||
}
|
||||
|
||||
// [[格式化图片链接]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue