knowledge-vault/discussions/project-thinking/AI/开发Obsidian插件.md

33 KiB
Raw Permalink Blame History

wantsong: 我想为Obsidian开发一个插件这个用什么语言要做什么环境准备。

Workspace: Sure, here's a proposed directory structure for a Obsidian plugin project

创建工作区...

wantsong: 我想在Obsidian中当用户粘贴图片时自动将图片上传至我的S3服务器上而不是用本地文件夹。在这个工作环境里应该增加哪些文件代码如何写。

Workspace: Sure, here's a proposed directory structure for a

创建工作区...

wantsong: @terminal 我执行“npm install aws-sdk”时报错无法将“node.exe”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写如果包括路径请确保路径正确 然后再试一次。

Terminal: 看起来你的 Node.js 环境变量配置有问题。请尝试以下命令来修复:

$env:PATH += ";C:\Tools\Nodejs"

然后重新运行:

npm install aws-sdk

如果问题仍然存在,请检查 Node.js 是否正确安装:

where.exe node

wantsong: 这个服务器是我自行搭建的与S3兼容的服务器所以我需要指定自己的服务器地址Endpoint),代码中好像没有这个位置

GitHub Copilot: 你说得对,需要添加自定义的 endpoint 配置。请更新代码如下:

import AWS from 'aws-sdk';

export class S3Uploader {
    private s3: AWS.S3;
    private bucketName: string;

    constructor(bucketName: string, accessKeyId: string, secretAccessKey: string, endpoint?: string, region?: string) {
        this.bucketName = bucketName;
        this.s3 = new AWS.S3({
            accessKeyId: accessKeyId,
            secretAccessKey: secretAccessKey,
            endpoint: endpoint || 'https://s3.amazonaws.com', // 自定义端点
            region: region || 'us-east-1',
            s3ForcePathStyle: true, // 对于自建S3兼容服务很重要
            signatureVersion: 'v4'
        });
    }

    public async uploadImage(file: File): Promise<string> {
        const params = {
            Bucket: this.bucketName,
            Key: file.name,
            Body: file,
            ContentType: file.type,
            ACL: 'public-read' // Change as needed
        };

        try {
            const data = await this.s3.upload(params).promise();
            return data.Location; // Return the URL of the uploaded image
        } catch (error) {
            console.error('Error uploading image to S3:', error);
            throw new Error('Upload failed');
        }
    }
}

主要改动:

  • 添加了 endpointregion 参数
  • 设置 s3ForcePathStyle: true这对自建S3兼容服务很重要
  • 添加了 signatureVersion: 'v4' 确保兼容性

wantsong: 代码里我修改了实际的数据,请帮我检查下,看是否修改的正确

GitHub Copilot: 检查你的代码,有几个需要注意的地方:

import AWS from 'aws-sdk';

export class S3Uploader {
    private s3: AWS.S3;
    private bucketName: string;

    constructor(bucketName: string, accessKeyId: string, secretAccessKey: string, endpoint?: string, region?: string) {
        // 建议使用传入的参数而不是硬编码
        this.bucketName = bucketName || 'attachments';
        this.s3 = new AWS.S3({
            accessKeyId: accessKeyId,
            secretAccessKey: secretAccessKey,
            endpoint: endpoint || 'https://s3.wantsong.life',
            region: region || 'us-east-1',
            s3ForcePathStyle: true,
            signatureVersion: 'v4'
        });
    }

    public async uploadImage(file: File): Promise<string> {
        // 建议添加时间戳避免文件名冲突
        const timestamp = Date.now();
        const fileName = `${timestamp}-${file.name}`;
        
        const params = {
            Bucket: this.bucketName,
            Key: fileName,
            Body: file,
            ContentType: file.type,
            ACL: 'public-read'
        };

        try {
            const data = await this.s3.upload(params).promise();
            return data.Location;
        } catch (error) {
            console.error('Error uploading image to S3:', error);
            throw new Error('Upload failed');
        }
    }
}

主要建议:

  1. 避免在代码中硬编码凭据,建议从配置文件或环境变量读取
  2. 使用传入的 bucketName 参数
  3. 添加时间戳到文件名避免重复文件覆盖

wantsong: accessKeyId和secretAccessKey我在哪里指定呢

GitHub Copilot: 你应该通过插件的设置界面来配置这些敏感信息,而不是硬编码在代码中。需要创建以下文件:

export interface S3PluginSettings {
    accessKeyId: string;
    secretAccessKey: string;
    bucketName: string;
    endpoint: string;
    region: string;
}

export const DEFAULT_SETTINGS: S3PluginSettings = {
    accessKeyId: '',
    secretAccessKey: '',
    bucketName: 'attachments',
    endpoint: 'https://s3.amazonaws.com',
    region: 'us-east-1'
}
import { App, PluginSettingTab, Setting } from 'obsidian';
import S3UploadPlugin from '../main';

export class S3SettingTab extends PluginSettingTab {
    plugin: S3UploadPlugin;

    constructor(app: App, plugin: S3UploadPlugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display(): void {
        const { containerEl } = this;
        containerEl.empty();
        containerEl.createEl('h2', { text: 'S3 Upload Settings' });

        new Setting(containerEl)
            .setName('Access Key ID')
            .setDesc('Your S3 Access Key ID')
            .addText(text => text
                .setPlaceholder('Enter your access key ID')
                .setValue(this.plugin.settings.accessKeyId)
                .onChange(async (value) => {
                    this.plugin.settings.accessKeyId = value;
                    await this.plugin.saveSettings();
                }));

        new Setting(containerEl)
            .setName('Secret Access Key')
            .setDesc('Your S3 Secret Access Key')
            .addText(text => text
                .setPlaceholder('Enter your secret access key')
                .setValue(this.plugin.settings.secretAccessKey)
                .onChange(async (value) => {
                    this.plugin.settings.secretAccessKey = value;
                    await this.plugin.saveSettings();
                }));

        new Setting(containerEl)
            .setName('Bucket Name')
            .setDesc('S3 bucket name for storing images')
            .addText(text => text
                .setPlaceholder('Enter bucket name')
                .setValue(this.plugin.settings.bucketName)
                .onChange(async (value) => {
                    this.plugin.settings.bucketName = value;
                    await this.plugin.saveSettings();
                }));

        new Setting(containerEl)
            .setName('Endpoint')
            .setDesc('S3 endpoint URL')
            .addText(text => text
                .setPlaceholder('https://s3.amazonaws.com')
                .setValue(this.plugin.settings.endpoint)
                .onChange(async (value) => {
                    this.plugin.settings.endpoint = value;
                    await this.plugin.saveSettings();
                }));

        new Setting(containerEl)
            .setName('Region')
            .setDesc('S3 region')
            .addText(text => text
                .setPlaceholder('us-east-1')
                .setValue(this.plugin.settings.region)
                .onChange(async (value) => {
                    this.plugin.settings.region = value;
                    await this.plugin.saveSettings();
                }));
    }
}

然后修改 s3Uploader.ts移除硬编码的凭据

// filepath: d:\AI\obsidian-s3-upload-plugin\src\s3Uploader.ts
import AWS from 'aws-sdk';

export class S3Uploader {
    private s3: AWS.S3;
    private bucketName: string;

    constructor(bucketName: string, accessKeyId: string, secretAccessKey: string, endpoint?: string, region?: string) {
        this.bucketName = bucketName;
        this.s3 = new AWS.S3({
            accessKeyId: accessKeyId,
            secretAccessKey: secretAccessKey,
            endpoint: endpoint,
            region: region,
            s3ForcePathStyle: true,
            signatureVersion: 'v4'
        });
    }

    // ...existing code...
}

这样用户就可以在插件设置中安全地配置自己的 S3 凭据了。

找到具有 2 个许可证类型的类似代码

wantsong: @workspace 你能否看到我所有的工程,看应该改文件还是增加文件,每个文件应该怎么修改

Workspace: 正在收集工作区信息我已经仔细查看了你的工程结构。你的代码有一些不一致的地方需要修复。以下是需要修改的文件:

1. 修复 settings.ts

import { Plugin } from 'obsidian';

export interface S3UploadSettings {
    awsAccessKeyId: string;
    awsSecretAccessKey: string;
    bucketName: string;
    region: string;
    endpoint?: string;
}

const DEFAULT_SETTINGS: S3UploadSettings = {
    awsAccessKeyId: '',
    awsSecretAccessKey: '',
    bucketName: '',
    region: 'us-east-1',
    endpoint: ''
};

export default class Settings {
    private plugin: Plugin;
    public awsAccessKeyId: string;
    public awsSecretAccessKey: string;
    public bucketName: string;
    public region: string;
    public endpoint?: string;

    constructor(plugin: Plugin) {
        this.plugin = plugin;
        this.awsAccessKeyId = DEFAULT_SETTINGS.awsAccessKeyId;
        this.awsSecretAccessKey = DEFAULT_SETTINGS.awsSecretAccessKey;
        this.bucketName = DEFAULT_SETTINGS.bucketName;
        this.region = DEFAULT_SETTINGS.region;
        this.endpoint = DEFAULT_SETTINGS.endpoint;
    }

    async load(): Promise<void> {
        const data = await this.plugin.loadData();
        if (data) {
            this.awsAccessKeyId = data.awsAccessKeyId || DEFAULT_SETTINGS.awsAccessKeyId;
            this.awsSecretAccessKey = data.awsSecretAccessKey || DEFAULT_SETTINGS.awsSecretAccessKey;
            this.bucketName = data.bucketName || DEFAULT_SETTINGS.bucketName;
            this.region = data.region || DEFAULT_SETTINGS.region;
            this.endpoint = data.endpoint || DEFAULT_SETTINGS.endpoint;
        }
    }

    async save(): Promise<void> {
        await this.plugin.saveData({
            awsAccessKeyId: this.awsAccessKeyId,
            awsSecretAccessKey: this.awsSecretAccessKey,
            bucketName: this.bucketName,
            region: this.region,
            endpoint: this.endpoint
        });
    }
}

2. 修复 s3Uploader.ts

import AWS from 'aws-sdk';
import Settings from './settings';

export default class S3Uploader {
    private s3: AWS.S3;
    private settings: Settings;

    constructor(settings: Settings) {
        this.settings = settings;
        this.initializeS3();
    }

    private initializeS3(): void {
        const config: AWS.S3.ClientConfiguration = {
            accessKeyId: this.settings.awsAccessKeyId,
            secretAccessKey: this.settings.awsSecretAccessKey,
            region: this.settings.region,
            signatureVersion: 'v4'
        };

        if (this.settings.endpoint) {
            config.endpoint = this.settings.endpoint;
            config.s3ForcePathStyle = true;
        }

        this.s3 = new AWS.S3(config);
    }

    public async uploadImage(file: File): Promise<string> {
        if (!this.settings.bucketName || !this.settings.awsAccessKeyId || !this.settings.awsSecretAccessKey) {
            throw new Error('S3 credentials not configured');
        }

        // 重新初始化S3客户端以确保使用最新的设置
        this.initializeS3();

        const fileName = `${Date.now()}-${file.name}`;
        
        const params: AWS.S3.PutObjectRequest = {
            Bucket: this.settings.bucketName,
            Key: fileName,
            Body: file,
            ContentType: file.type,
            ACL: 'public-read'
        };

        try {
            const data = await this.s3.upload(params).promise();
            return data.Location;
        } catch (error) {
            console.error('Error uploading image to S3:', error);
            throw new Error(`Upload failed: ${error.message}`);
        }
    }
}

3. 修复 main.ts

import { Plugin, Notice, ClipboardManager } from 'obsidian';
import S3Uploader from './s3Uploader';
import Settings from './settings';
import SettingTab from './ui/settingTab';

export default class S3UploadPlugin extends Plugin {
    private s3Uploader: S3Uploader;
    private settings: Settings;

    async onload() {
        console.log('Loading S3 Upload Plugin');

        this.settings = new Settings(this);
        await this.settings.load();

        this.s3Uploader = new S3Uploader(this.settings);

        this.addSettingTab(new SettingTab(this.app, this.s3Uploader, this.settings));

        // 注册粘贴事件处理器
        this.registerDomEvent(document, 'paste', this.handlePaste.bind(this));
    }

    private async handlePaste(event: ClipboardEvent): Promise<void> {
        const clipboardData = event.clipboardData;
        if (!clipboardData) return;

        const items = Array.from(clipboardData.items);
        const imageItem = items.find(item => item.type.startsWith('image/'));

        if (imageItem) {
            event.preventDefault();
            const file = imageItem.getAsFile();
            if (file) {
                try {
                    new Notice('正在上传图片到S3...');
                    const imageUrl = await this.s3Uploader.uploadImage(file);
                    
                    // 获取当前活动的编辑器
                    const activeView = this.app.workspace.getActiveViewOfType(require('obsidian').MarkdownView);
                    if (activeView) {
                        const editor = activeView.editor;
                        const cursor = editor.getCursor();
                        const markdownLink = `![${file.name}](${imageUrl})`;
                        editor.replaceRange(markdownLink, cursor);
                    }
                    
                    new Notice('图片上传成功!');
                } catch (error) {
                    console.error('Upload error:', error);
                    new Notice(`图片上传失败: ${error.message}`);
                }
            }
        }
    }

    onunload() {
        console.log('Unloading S3 Upload Plugin');
    }
}

4. 修复 settingTab.ts

import { App, PluginSettingTab, Setting } from 'obsidian';
import S3Uploader from '../s3Uploader';
import Settings from '../settings';

export default class SettingTab extends PluginSettingTab {
    private uploader: S3Uploader;
    private settings: Settings;

    constructor(app: App, uploader: S3Uploader, settings: Settings) {
        super(app, app.plugins.getPlugin('obsidian-s3-upload-plugin'));
        this.uploader = uploader;
        this.settings = settings;
    }

    display(): void {
        const { containerEl } = this;

        containerEl.empty();
        containerEl.createEl('h2', { text: 'S3上传插件设置' });

        new Setting(containerEl)
            .setName('AWS Access Key ID')
            .setDesc('输入你的AWS Access Key ID')
            .addText(text => text
                .setPlaceholder('输入Access Key ID')
                .setValue(this.settings.awsAccessKeyId)
                .onChange(async (value) => {
                    this.settings.awsAccessKeyId = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('AWS Secret Access Key')
            .setDesc('输入你的AWS Secret Access Key')
            .addText(text => {
                text.inputEl.type = 'password';
                return text
                    .setPlaceholder('输入Secret Access Key')
                    .setValue(this.settings.awsSecretAccessKey)
                    .onChange(async (value) => {
                        this.settings.awsSecretAccessKey = value;
                        await this.settings.save();
                    });
            });

        new Setting(containerEl)
            .setName('S3存储桶名称')
            .setDesc('输入你的S3存储桶名称')
            .addText(text => text
                .setPlaceholder('输入存储桶名称')
                .setValue(this.settings.bucketName)
                .onChange(async (value) => {
                    this.settings.bucketName = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('区域')
            .setDesc('输入你的S3存储桶区域')
            .addText(text => text
                .setPlaceholder('如: us-east-1')
                .setValue(this.settings.region)
                .onChange(async (value) => {
                    this.settings.region = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('自定义端点 (可选)')
            .setDesc('如果使用自建S3兼容服务请输入端点URL')
            .addText(text => text
                .setPlaceholder('如: https://s3.example.com')
                .setValue(this.settings.endpoint || '')
                .onChange(async (value) => {
                    this.settings.endpoint = value;
                    await this.settings.save();
                }));
    }
}

5. 修复 manifest.json

{
  "id": "obsidian-s3-upload-plugin",
  "name": "S3图片上传插件",
  "version": "1.0.0",
  "description": "自动将粘贴的图片上传到S3存储服务",
  "author": "Your Name",
  "minAppVersion": "0.15.0",
  "main": "main.js"
}

主要修复内容:

  1. 统一了设置接口和类:所有文件现在都使用相同的 Settings
  2. 实现了正确的Obsidian设置存储:使用 loadData()saveData() 方法
  3. 修复了导入导出问题:确保所有的导入都使用 default 导出
  4. 改进了粘贴事件处理使用更可靠的DOM事件监听
  5. 添加了错误处理和用户反馈:显示上传状态通知
  6. 支持自定义S3端点允许使用自建的S3兼容服务

现在你的插件应该能够正常工作了。记得运行 npm run build 来编译插件。

wantsong: @workspace 第一句话报错:模块“"obsidian"”没有导出的成员“ClipboardManager”。在整个工程里我应该安装哪些依赖包

Workspace: 正在收集工作区信息我看到你的项目中有几个问题需要修复。首先,ClipboardManager 不是 Obsidian API 的导出成员,而且你需要安装正确的依赖包。

1. 修复 main.ts 的导入问题

import { Plugin, Notice, MarkdownView } from 'obsidian';
import S3Uploader from './s3Uploader';
import Settings from './settings';
import SettingTab from './ui/settingTab';

export default class S3UploadPlugin extends Plugin {
    private s3Uploader: S3Uploader;
    private settings: Settings;

    async onload() {
        console.log('Loading S3 Upload Plugin');

        this.settings = new Settings(this);
        await this.settings.load();

        this.s3Uploader = new S3Uploader(this.settings);

        this.addSettingTab(new SettingTab(this.app, this, this.s3Uploader, this.settings));

        // 注册粘贴事件处理器
        this.registerDomEvent(document, 'paste', this.handlePaste.bind(this));
    }

    private async handlePaste(event: ClipboardEvent): Promise<void> {
        const clipboardData = event.clipboardData;
        if (!clipboardData) return;

        const items = Array.from(clipboardData.items);
        const imageItem = items.find(item => item.type.startsWith('image/'));

        if (imageItem) {
            event.preventDefault();
            const file = imageItem.getAsFile();
            if (file) {
                try {
                    new Notice('正在上传图片到S3...');
                    const imageUrl = await this.s3Uploader.uploadImage(file);
                    
                    // 获取当前活动的编辑器
                    const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
                    if (activeView) {
                        const editor = activeView.editor;
                        const cursor = editor.getCursor();
                        const markdownLink = `![${file.name}](${imageUrl})`;
                        editor.replaceRange(markdownLink, cursor);
                    }
                    
                    new Notice('图片上传成功!');
                } catch (error) {
                    console.error('Upload error:', error);
                    new Notice(`图片上传失败: ${error.message}`);
                }
            }
        }
    }

    onunload() {
        console.log('Unloading S3 Upload Plugin');
    }
}

2. 修复 settingTab.ts 的构造函数

import { App, PluginSettingTab, Setting } from 'obsidian';
import S3Uploader from '../s3Uploader';
import Settings from '../settings';
import S3UploadPlugin from '../main';

export default class SettingTab extends PluginSettingTab {
    private uploader: S3Uploader;
    private settings: Settings;

    constructor(app: App, plugin: S3UploadPlugin, uploader: S3Uploader, settings: Settings) {
        super(app, plugin);
        this.uploader = uploader;
        this.settings = settings;
    }

    display(): void {
        const { containerEl } = this;

        containerEl.empty();
        containerEl.createEl('h2', { text: 'S3上传插件设置' });

        new Setting(containerEl)
            .setName('AWS Access Key ID')
            .setDesc('输入你的AWS Access Key ID')
            .addText(text => text
                .setPlaceholder('输入Access Key ID')
                .setValue(this.settings.awsAccessKeyId)
                .onChange(async (value) => {
                    this.settings.awsAccessKeyId = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('AWS Secret Access Key')
            .setDesc('输入你的AWS Secret Access Key')
            .addText(text => {
                text.inputEl.type = 'password';
                return text
                    .setPlaceholder('输入Secret Access Key')
                    .setValue(this.settings.awsSecretAccessKey)
                    .onChange(async (value) => {
                        this.settings.awsSecretAccessKey = value;
                        await this.settings.save();
                    });
            });

        new Setting(containerEl)
            .setName('S3存储桶名称')
            .setDesc('输入你的S3存储桶名称')
            .addText(text => text
                .setPlaceholder('输入存储桶名称')
                .setValue(this.settings.bucketName)
                .onChange(async (value) => {
                    this.settings.bucketName = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('区域')
            .setDesc('输入你的S3存储桶区域')
            .addText(text => text
                .setPlaceholder('如: us-east-1')
                .setValue(this.settings.region)
                .onChange(async (value) => {
                    this.settings.region = value;
                    await this.settings.save();
                }));

        new Setting(containerEl)
            .setName('自定义端点 (可选)')
            .setDesc('如果使用自建S3兼容服务请输入端点URL')
            .addText(text => text
                .setPlaceholder('如: https://s3.example.com')
                .setValue(this.settings.endpoint || '')
                .onChange(async (value) => {
                    this.settings.endpoint = value;
                    await this.settings.save();
                }));
    }
}

3. 更新 package.json 依赖包

{
  "name": "obsidian-s3-upload-plugin",
  "version": "1.0.0",
  "description": "An Obsidian plugin that uploads images to an S3 server when pasted.",
  "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"
  },
  "keywords": [],
  "author": "Wantsong",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^16.11.6",
    "@typescript-eslint/eslint-plugin": "5.29.0",
    "@typescript-eslint/parser": "5.29.0",
    "builtin-modules": "3.3.0",
    "esbuild": "0.17.3",
    "obsidian": "latest",
    "tslib": "2.4.0",
    "typescript": "4.7.4"
  },
  "dependencies": {
    "aws-sdk": "^2.1692.0"
  }
}

4. 更新 esbuild.config.mjs

import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";

const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;

const prod = (process.argv[2] === "production");

const context = await esbuild.context({
	banner: {
		js: banner,
	},
	entryPoints: ["src/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);
} else {
	await context.watch();
}

5. 需要安装的依赖包

运行以下命令来安装所有必要的依赖包:

npm install --save aws-sdk
npm install --save-dev @types/node @typescript-eslint/eslint-plugin @typescript-eslint/parser builtin-modules esbuild obsidian tslib typescript

主要修复内容:

  1. 移除了不存在的 ClipboardManager 导入
  2. 正确导入了 MarkdownView
  3. 修复了 SettingTab 构造函数参数
  4. 更新了 package.json 依赖配置
  5. 改进了 esbuild.config.mjs 配置

现在你可以运行 npm install 然后 npm run build 来编译你的插件了。

找到具有 2 个许可证类型的类似代码

wantsong: @terminal 你能看见终端上的报错么,报了四个锁雾

Terminal:

wantsong: @terminal 你能看见终端上的报错么执行“npm run build”时报了四个错误

Terminal: 是的,我看到了 TypeScript 编译错误。需要修复这些类型问题:

code src/main.ts

main.ts 中修复以下问题:

  1. 在第7行和第8行的属性后添加 ! 断言符:

    private s3Uploader!: S3Uploader;
    private settings!: Settings;
    
  2. 在第51行将 error 转换为 Error 类型:

    new Notice(`图片上传失败: ${(error as Error).message}`);
    

然后修复 s3Uploader.ts

code src/s3Uploader.ts

在第52行将 error 转换为 Error 类型:

throw new Error(`Upload failed: ${(error as Error).message}`);

修复完成后重新构建:

npm run build

wantsong: @terminal build应该是完成了这个插件build完成后在哪里我怎么在Obsidian中用它

Terminal: 构建完成后,插件文件在当前目录下。你需要将插件复制到 Obsidian 的插件目录:

$obsidianPluginsPath = "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin"
New-Item -ItemType Directory -Path $obsidianPluginsPath -Force
Copy-Item -Path ".\main.js", ".\manifest.json", ".\styles.css" -Destination $obsidianPluginsPath

然后在 Obsidian 中:

  1. 打开设置 (Ctrl+,)
  2. 转到"社区插件"
  3. 关闭安全模式(如果已开启)
  4. 在已安装插件列表中找到"S3 Upload Plugin"并启用它

如果没有 styles.css 文件,只复制前两个文件:

Copy-Item -Path ".\main.js", ".\manifest.json" -Destination $obsidianPluginsPath

wantsong: 当我在 Obsidian 中启用这个插件时,提示我启用失败。我在哪里可以看到错误原因

GitHub Copilot: 你可以通过以下几种方式查看错误原因:

1. 查看 Obsidian 开发者控制台

Ctrl+Shift+I 打开开发者工具,然后查看 Console 标签页中的错误信息。

2. 检查插件文件是否完整

确认这些文件都存在于插件目录中:

$pluginPath = "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin"
Get-ChildItem $pluginPath

应该至少包含:

  • main.js
  • manifest.json

3. 检查 manifest.json 是否正确

查看 manifest.json 内容:

Get-Content "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin\manifest.json"

确保内容类似:

{
  "id": "obsidian-s3-upload-plugin",
  "name": "S3图片上传插件",
  "version": "1.0.0",
  "description": "自动将粘贴的图片上传到S3存储服务",
  "author": "Your Name",
  "minAppVersion": "0.15.0",
  "main": "main.js"
}

4. 常见问题排查

  1. 检查 main.js 文件大小如果文件很小几KB说明编译可能有问题
  2. 重新编译插件
    npm run build
    
  3. 重新复制文件
    Copy-Item -Path ".\main.js", ".\manifest.json" -Destination "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin" -Force
    

请先打开开发者控制台查看具体的错误信息,然后告诉我看到了什么错误。

wantsong: 我把错误日志下载了下来,你可以看见。这个怎么解决

GitHub Copilot: 这个错误是因为 aws-sdk 在 Electron 环境中需要 Node.js 的 buffer 模块,但 Obsidian 插件环境中没有这个模块。我们需要改用浏览器兼容的 S3 客户端。

首先卸载 aws-sdk 并安装 @aws-sdk/client-s3

npm uninstall aws-sdk
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage

然后更新 src/s3Uploader.ts

import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
import Settings from './settings';

export default class S3Uploader {
    private s3Client: S3Client;
    private settings: Settings;

    constructor(settings: Settings) {
        this.settings = settings;
        this.initializeS3();
    }

    private initializeS3(): void {
        const config: any = {
            region: this.settings.region,
            credentials: {
                accessKeyId: this.settings.awsAccessKeyId,
                secretAccessKey: this.settings.awsSecretAccessKey,
            },
        };

        if (this.settings.endpoint) {
            config.endpoint = this.settings.endpoint;
            config.forcePathStyle = true;
        }

        this.s3Client = new S3Client(config);
    }

    public async uploadImage(file: File): Promise<string> {
        if (!this.settings.bucketName || !this.settings.awsAccessKeyId || !this.settings.awsSecretAccessKey) {
            throw new Error('S3 credentials not configured');
        }

        // 重新初始化S3客户端以确保使用最新的设置
        this.initializeS3();

        const fileName = `${Date.now()}-${file.name}`;
        
        try {
            const upload = new Upload({
                client: this.s3Client,
                params: {
                    Bucket: this.settings.bucketName,
                    Key: fileName,
                    Body: file,
                    ContentType: file.type,
                    ACL: 'public-read'
                }
            });

            const result = await upload.done();
            
            // 构建文件 URL
            const baseUrl = this.settings.endpoint || `https://s3.${this.settings.region}.amazonaws.com`;
            return `${baseUrl}/${this.settings.bucketName}/${fileName}`;
            
        } catch (error) {
            console.error('Error uploading image to S3:', error);
            throw new Error(`Upload failed: ${(error as Error).message}`);
        }
    }
}

更新 package.json 的依赖:

{
  "name": "obsidian-s3-upload-plugin",
  "version": "1.0.0",
  "description": "An Obsidian plugin that uploads images to an S3 server when pasted.",
  "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"
  },
  "keywords": [],
  "author": "Wantsong",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^16.11.6",
    "@typescript-eslint/eslint-plugin": "5.29.0",
    "@typescript-eslint/parser": "5.29.0",
    "builtin-modules": "3.3.0",
    "esbuild": "0.17.3",
    "obsidian": "latest",
    "tslib": "2.4.0",
    "typescript": "4.7.4"
  },
  "dependencies": {
    "@aws-sdk/client-s3": "^3.0.0",
    "@aws-sdk/lib-storage": "^3.0.0"
  }
}

然后重新构建:

npm install
npm run build

重新复制插件文件:

Copy-Item -Path ".\main.js", ".\manifest.json" -Destination "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin" -Force

新的 AWS SDK v3 是专门为现代浏览器和 Electron 环境设计的,应该能解决 buffer 模块的问题。

找到具有 1 个许可证类型的类似代码