1064 lines
33 KiB
Markdown
1064 lines
33 KiB
Markdown
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 环境变量配置有问题。请尝试以下命令来修复:
|
||
|
||
```pwsh
|
||
$env:PATH += ";C:\Tools\Nodejs"
|
||
```
|
||
|
||
然后重新运行:
|
||
|
||
```pwsh
|
||
npm install aws-sdk
|
||
```
|
||
|
||
如果问题仍然存在,请检查 Node.js 是否正确安装:
|
||
|
||
```pwsh
|
||
where.exe node
|
||
```
|
||
|
||
wantsong: 这个服务器是我自行搭建的与S3兼容的服务器,所以我需要指定自己的服务器地址(Endpoint),代码中好像没有这个位置
|
||
|
||
GitHub Copilot: 你说得对,需要添加自定义的 endpoint 配置。请更新代码如下:
|
||
|
||
````typescript
|
||
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');
|
||
}
|
||
}
|
||
}
|
||
````
|
||
|
||
主要改动:
|
||
- 添加了 `endpoint` 和 `region` 参数
|
||
- 设置 `s3ForcePathStyle: true`,这对自建S3兼容服务很重要
|
||
- 添加了 `signatureVersion: 'v4'` 确保兼容性
|
||
|
||
wantsong: 代码里我修改了实际的数据,请帮我检查下,看是否修改的正确
|
||
|
||
GitHub Copilot: 检查你的代码,有几个需要注意的地方:
|
||
|
||
````typescript
|
||
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: 你应该通过插件的设置界面来配置这些敏感信息,而不是硬编码在代码中。需要创建以下文件:
|
||
|
||
````typescript
|
||
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'
|
||
}
|
||
````
|
||
|
||
````typescript
|
||
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,移除硬编码的凭据:
|
||
|
||
````typescript
|
||
// 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
|
||
|
||
````typescript
|
||
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
|
||
|
||
````typescript
|
||
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
|
||
|
||
````typescript
|
||
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 = ``;
|
||
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
|
||
|
||
````typescript
|
||
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
|
||
|
||
````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 的导入问题
|
||
|
||
````typescript
|
||
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 = ``;
|
||
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 的构造函数
|
||
|
||
````typescript
|
||
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 依赖包
|
||
|
||
````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
|
||
|
||
````javascript
|
||
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. 需要安装的依赖包
|
||
|
||
运行以下命令来安装所有必要的依赖包:
|
||
|
||
```bash
|
||
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 编译错误。需要修复这些类型问题:
|
||
|
||
```pwsh
|
||
code src/main.ts
|
||
```
|
||
|
||
在 `main.ts` 中修复以下问题:
|
||
1. 在第7行和第8行的属性后添加 `!` 断言符:
|
||
```typescript
|
||
private s3Uploader!: S3Uploader;
|
||
private settings!: Settings;
|
||
```
|
||
|
||
2. 在第51行将 `error` 转换为 `Error` 类型:
|
||
```typescript
|
||
new Notice(`图片上传失败: ${(error as Error).message}`);
|
||
```
|
||
|
||
然后修复 `s3Uploader.ts`:
|
||
|
||
```pwsh
|
||
code src/s3Uploader.ts
|
||
```
|
||
|
||
在第52行将 `error` 转换为 `Error` 类型:
|
||
```typescript
|
||
throw new Error(`Upload failed: ${(error as Error).message}`);
|
||
```
|
||
|
||
修复完成后重新构建:
|
||
|
||
```pwsh
|
||
npm run build
|
||
```
|
||
|
||
wantsong: @terminal build应该是完成了,这个插件build完成后在哪里?我怎么在Obsidian中用它
|
||
|
||
Terminal: 构建完成后,插件文件在当前目录下。你需要将插件复制到 Obsidian 的插件目录:
|
||
|
||
```pwsh
|
||
$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 文件,只复制前两个文件:
|
||
|
||
```pwsh
|
||
Copy-Item -Path ".\main.js", ".\manifest.json" -Destination $obsidianPluginsPath
|
||
```
|
||
|
||
wantsong: 当我在 Obsidian 中启用这个插件时,提示我启用失败。我在哪里可以看到错误原因
|
||
|
||
GitHub Copilot: 你可以通过以下几种方式查看错误原因:
|
||
|
||
## 1. 查看 Obsidian 开发者控制台
|
||
按 `Ctrl+Shift+I` 打开开发者工具,然后查看 Console 标签页中的错误信息。
|
||
|
||
## 2. 检查插件文件是否完整
|
||
确认这些文件都存在于插件目录中:
|
||
|
||
```pwsh
|
||
$pluginPath = "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin"
|
||
Get-ChildItem $pluginPath
|
||
```
|
||
|
||
应该至少包含:
|
||
- main.js
|
||
- manifest.json
|
||
|
||
## 3. 检查 manifest.json 是否正确
|
||
查看 manifest.json 内容:
|
||
|
||
```pwsh
|
||
Get-Content "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin\manifest.json"
|
||
```
|
||
|
||
确保内容类似:
|
||
```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. **重新编译插件**:
|
||
```pwsh
|
||
npm run build
|
||
```
|
||
3. **重新复制文件**:
|
||
```pwsh
|
||
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`:
|
||
|
||
```pwsh
|
||
npm uninstall aws-sdk
|
||
npm install @aws-sdk/client-s3 @aws-sdk/lib-storage
|
||
```
|
||
|
||
然后更新 [`src/s3Uploader.ts`](src/s3Uploader.ts ):
|
||
|
||
````typescript
|
||
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`](package.json ) 的依赖:
|
||
|
||
````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"
|
||
}
|
||
}
|
||
````
|
||
|
||
然后重新构建:
|
||
|
||
```pwsh
|
||
npm install
|
||
npm run build
|
||
```
|
||
|
||
重新复制插件文件:
|
||
|
||
```pwsh
|
||
Copy-Item -Path ".\main.js", ".\manifest.json" -Destination "$env:APPDATA\Obsidian\plugins\obsidian-s3-upload-plugin" -Force
|
||
```
|
||
|
||
新的 AWS SDK v3 是专门为现代浏览器和 Electron 环境设计的,应该能解决 `buffer` 模块的问题。
|
||
|
||
找到具有 1 个许可证类型的类似代码 |