mirror of
https://github.com/woodchen-ink/obsidian-publish-to-discourse.git
synced 2025-07-18 05:42:05 +08:00
实现更新帖子的功能
This commit is contained in:
parent
c4513b5abc
commit
95e541e1a7
15
README.md
15
README.md
@ -1,17 +1,30 @@
|
|||||||
# Publish to Discourse
|
# Publish to Discourse
|
||||||
|
|
||||||
## How to use
|
## 配置key
|
||||||
|
|
||||||
下载插件, 在设置页面添加"论坛地址", "API密钥(需要管理员创建)", "用户名".
|
下载插件, 在设置页面添加"论坛地址", "API密钥(需要管理员创建)", "用户名".
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 发布帖子
|
||||||
|
|
||||||
在文档页面的右上角, 展开菜单, 选择"发布到discourse", 选择类别即可.
|
在文档页面的右上角, 展开菜单, 选择"发布到discourse", 选择类别即可.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
在发布帖子成功后, 会在笔记属性里添加一个"discourse_post_id"属性, 用于更新帖子.
|
||||||
|
|
||||||
|
|
||||||
|
## 更新帖子
|
||||||
|
|
||||||
|
在文档页面的右上角, 展开菜单, 选择"发布到discourse", 点击更新即可.
|
||||||
|
|
||||||
|
> 更新的前提是帖子本身是通过obsidian发布的.
|
||||||
|
|
||||||
|

|
||||||
|
19
package-lock.json
generated
19
package-lock.json
generated
@ -1,13 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "obsidian-sample-plugin",
|
"name": "obsidian-publish-to-discourse",
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "obsidian-sample-plugin",
|
"name": "obsidian-publish-to-discourse",
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.7.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||||
@ -2364,6 +2367,18 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
@ -24,5 +24,8 @@
|
|||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
"tslib": "2.4.0",
|
"tslib": "2.4.0",
|
||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"yaml": "^2.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
pics/20250124-000738.gif
Normal file
BIN
pics/20250124-000738.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 322 KiB |
BIN
pics/20250124-001000.gif
Normal file
BIN
pics/20250124-001000.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 817 KiB |
154
src/main.ts
154
src/main.ts
@ -1,9 +1,14 @@
|
|||||||
import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile } from 'obsidian';
|
import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile } from 'obsidian';
|
||||||
import { DEFAULT_SETTINGS, DiscourseSyncSettings, DiscourseSyncSettingsTab } from './config';
|
import { DEFAULT_SETTINGS, DiscourseSyncSettings, DiscourseSyncSettingsTab } from './config';
|
||||||
|
import * as yaml from 'yaml';
|
||||||
|
|
||||||
export default class DiscourseSyncPlugin extends Plugin {
|
export default class DiscourseSyncPlugin extends Plugin {
|
||||||
settings: DiscourseSyncSettings;
|
settings: DiscourseSyncSettings;
|
||||||
activeFile: { name: string; content: string };
|
activeFile: {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
postId?: number; // 添加帖子ID字段
|
||||||
|
};
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
this.addSettingTab(new DiscourseSyncSettingsTab(this.app, this));
|
this.addSettingTab(new DiscourseSyncSettingsTab(this.app, this));
|
||||||
@ -113,6 +118,10 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
let content = this.activeFile.content;
|
let content = this.activeFile.content;
|
||||||
|
|
||||||
|
// 检查是否包含帖子ID的frontmatter
|
||||||
|
const frontMatter = this.getFrontMatter(content);
|
||||||
|
const postId = frontMatter?.discourse_post_id;
|
||||||
|
|
||||||
// 过滤掉笔记属性部分
|
// 过滤掉笔记属性部分
|
||||||
content = content.replace(/^---[\s\S]*?---\n/, '');
|
content = content.replace(/^---[\s\S]*?---\n/, '');
|
||||||
|
|
||||||
@ -125,7 +134,14 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
content = content.replace(obsRef, discoRef);
|
content = content.replace(obsRef, discoRef);
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const isUpdate = postId !== undefined;
|
||||||
|
const endpoint = isUpdate ? `${this.settings.baseUrl}/posts/${postId}` : url;
|
||||||
|
const method = isUpdate ? "PUT" : "POST";
|
||||||
|
|
||||||
|
const body = JSON.stringify(isUpdate ? {
|
||||||
|
raw: content,
|
||||||
|
edit_reason: "Updated from Obsidian"
|
||||||
|
} : {
|
||||||
title: this.activeFile.name,
|
title: this.activeFile.name,
|
||||||
raw: content,
|
raw: content,
|
||||||
category: this.settings.category
|
category: this.settings.category
|
||||||
@ -133,28 +149,44 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await requestUrl({
|
const response = await requestUrl({
|
||||||
url: url,
|
url: endpoint,
|
||||||
method: "POST",
|
method: method,
|
||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body,
|
body,
|
||||||
headers,
|
headers,
|
||||||
throw: false // 设置为 false 以获取错误响应
|
throw: false
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
|
if (!isUpdate) {
|
||||||
|
try {
|
||||||
|
// 获取新帖子的ID
|
||||||
|
const responseData = response.json;
|
||||||
|
if (responseData && responseData.id) {
|
||||||
|
await this.updateFrontMatter(responseData.id);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
message: "Error",
|
||||||
|
error: "发布成功但无法获取帖子ID"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
message: "Error",
|
||||||
|
error: "发布成功但无法保存帖子ID"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
return { message: "Success" };
|
return { message: "Success" };
|
||||||
} else {
|
} else {
|
||||||
// 尝试从响应中获取错误信息
|
|
||||||
try {
|
try {
|
||||||
const errorResponse = response.json;
|
const errorResponse = response.json;
|
||||||
// Discourse 通常会在 errors 数组中返回错误信息
|
|
||||||
if (errorResponse.errors && errorResponse.errors.length > 0) {
|
if (errorResponse.errors && errorResponse.errors.length > 0) {
|
||||||
return {
|
return {
|
||||||
message: "Error",
|
message: "Error",
|
||||||
error: errorResponse.errors.join('\n')
|
error: errorResponse.errors.join('\n')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 有些错误可能在 error 字段中
|
|
||||||
if (errorResponse.error) {
|
if (errorResponse.error) {
|
||||||
return {
|
return {
|
||||||
message: "Error",
|
message: "Error",
|
||||||
@ -162,20 +194,63 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
// 如果无法解析错误响应
|
|
||||||
return {
|
return {
|
||||||
message: "Error",
|
message: "Error",
|
||||||
error: `发布失败 (${response.status})`
|
error: `${isUpdate ? '更新' : '发布'}失败 (${response.status})`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
message: "Error",
|
message: "Error",
|
||||||
error: `发布失败: ${error.message || '未知错误'}`
|
error: `${isUpdate ? '更新' : '发布'}失败: ${error.message || '未知错误'}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { message: "Error", error: "发布失败,请重试" };
|
return { message: "Error", error: `${isUpdate ? '更新' : '发布'}失败,请重试` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取frontmatter数据
|
||||||
|
private getFrontMatter(content: string): any {
|
||||||
|
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||||
|
if (fmMatch) {
|
||||||
|
try {
|
||||||
|
return yaml.parse(fmMatch[1]);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新frontmatter,添加帖子ID
|
||||||
|
private async updateFrontMatter(postId: number) {
|
||||||
|
try {
|
||||||
|
// 获取当前活动文件
|
||||||
|
const activeFile = this.app.workspace.getActiveFile();
|
||||||
|
if (!activeFile) {
|
||||||
|
console.error('No active file found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await this.app.vault.read(activeFile);
|
||||||
|
const fm = this.getFrontMatter(content);
|
||||||
|
|
||||||
|
let newContent: string;
|
||||||
|
if (fm) {
|
||||||
|
// 更新现有的frontmatter
|
||||||
|
const updatedFm = { ...fm, discourse_post_id: postId };
|
||||||
|
newContent = content.replace(/^---\n[\s\S]*?\n---\n/, `---\n${yaml.stringify(updatedFm)}---\n`);
|
||||||
|
} else {
|
||||||
|
// 添加新的frontmatter
|
||||||
|
const newFm = { discourse_post_id: postId };
|
||||||
|
newContent = `---\n${yaml.stringify(newFm)}---\n${content}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.app.vault.modify(activeFile, newContent);
|
||||||
|
console.log('Successfully updated frontmatter with post ID:', postId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating frontmatter:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchCategories() {
|
private async fetchCategories() {
|
||||||
@ -217,9 +292,12 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
const syncDiscourse = (item: MenuItem) => {
|
const syncDiscourse = (item: MenuItem) => {
|
||||||
item.setTitle("发布到 Discourse");
|
item.setTitle("发布到 Discourse");
|
||||||
item.onClick(async () => {
|
item.onClick(async () => {
|
||||||
|
const content = await this.app.vault.read(file);
|
||||||
|
const fm = this.getFrontMatter(content);
|
||||||
this.activeFile = {
|
this.activeFile = {
|
||||||
name: file.basename,
|
name: file.basename,
|
||||||
content: await this.app.vault.read(file)
|
content: content,
|
||||||
|
postId: fm?.discourse_post_id
|
||||||
};
|
};
|
||||||
await this.syncToDiscourse();
|
await this.syncToDiscourse();
|
||||||
});
|
});
|
||||||
@ -298,21 +376,23 @@ export class SelectCategoryModal extends Modal {
|
|||||||
contentEl.empty();
|
contentEl.empty();
|
||||||
contentEl.addClass('discourse-sync-modal');
|
contentEl.addClass('discourse-sync-modal');
|
||||||
|
|
||||||
contentEl.createEl("h1", { text: '选择发布分类' });
|
const isUpdate = this.plugin.activeFile.postId !== undefined;
|
||||||
|
contentEl.createEl("h1", { text: isUpdate ? '更新帖子' : '发布到 Discourse' });
|
||||||
|
|
||||||
// 创建选择器容器
|
// 创建选择器容器
|
||||||
const selectContainer = contentEl.createEl('div', { cls: 'select-container' });
|
const selectContainer = contentEl.createEl('div', { cls: 'select-container' });
|
||||||
selectContainer.createEl('label', { text: '分类' });
|
if (!isUpdate) {
|
||||||
|
// 只在新建帖子时显示分类选择
|
||||||
const selectEl = selectContainer.createEl('select');
|
selectContainer.createEl('label', { text: '分类' });
|
||||||
|
const selectEl = selectContainer.createEl('select');
|
||||||
this.categories.forEach(category => {
|
this.categories.forEach(category => {
|
||||||
const option = selectEl.createEl('option', { text: category.name });
|
const option = selectEl.createEl('option', { text: category.name });
|
||||||
option.value = category.id.toString();
|
option.value = category.id.toString();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const submitButton = contentEl.createEl('button', {
|
const submitButton = contentEl.createEl('button', {
|
||||||
text: '发布',
|
text: isUpdate ? '更新' : '发布',
|
||||||
cls: 'submit-button'
|
cls: 'submit-button'
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -320,13 +400,19 @@ export class SelectCategoryModal extends Modal {
|
|||||||
const noticeContainer = contentEl.createEl('div');
|
const noticeContainer = contentEl.createEl('div');
|
||||||
|
|
||||||
submitButton.onclick = async () => {
|
submitButton.onclick = async () => {
|
||||||
const selectedCategoryId = selectEl.value;
|
if (!isUpdate) {
|
||||||
this.plugin.settings.category = parseInt(selectedCategoryId);
|
const selectEl = contentEl.querySelector('select') as HTMLSelectElement;
|
||||||
await this.plugin.saveSettings();
|
if (!selectEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedCategoryId = selectEl.value;
|
||||||
|
this.plugin.settings.category = parseInt(selectedCategoryId);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
// 禁用提交按钮,显示加载状态
|
// 禁用提交按钮,显示加载状态
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
submitButton.textContent = '发布中...';
|
submitButton.textContent = isUpdate ? '更新中...' : '发布中...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const reply = await this.plugin.postTopic();
|
const reply = await this.plugin.postTopic();
|
||||||
@ -336,7 +422,7 @@ export class SelectCategoryModal extends Modal {
|
|||||||
if (reply.message === 'Success') {
|
if (reply.message === 'Success') {
|
||||||
noticeContainer.createEl('div', {
|
noticeContainer.createEl('div', {
|
||||||
cls: 'notice success',
|
cls: 'notice success',
|
||||||
text: '✓ 发布成功!'
|
text: isUpdate ? '✓ 更新成功!' : '✓ 发布成功!'
|
||||||
});
|
});
|
||||||
// 成功后延迟关闭
|
// 成功后延迟关闭
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -346,13 +432,13 @@ export class SelectCategoryModal extends Modal {
|
|||||||
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
||||||
errorContainer.createEl('div', {
|
errorContainer.createEl('div', {
|
||||||
cls: 'error-title',
|
cls: 'error-title',
|
||||||
text: '发布失败'
|
text: isUpdate ? '更新失败' : '发布失败'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 显示 Discourse 返回的具体错误信息
|
// 显示 Discourse 返回的具体错误信息
|
||||||
errorContainer.createEl('div', {
|
errorContainer.createEl('div', {
|
||||||
cls: 'error-message',
|
cls: 'error-message',
|
||||||
text: reply.error || '发布失败,请重试'
|
text: reply.error || (isUpdate ? '更新失败,请重试' : '发布失败,请重试')
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加重试按钮
|
// 添加重试按钮
|
||||||
@ -363,7 +449,7 @@ export class SelectCategoryModal extends Modal {
|
|||||||
retryButton.onclick = () => {
|
retryButton.onclick = () => {
|
||||||
noticeContainer.empty();
|
noticeContainer.empty();
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
submitButton.textContent = '发布';
|
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -371,7 +457,7 @@ export class SelectCategoryModal extends Modal {
|
|||||||
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
||||||
errorContainer.createEl('div', {
|
errorContainer.createEl('div', {
|
||||||
cls: 'error-title',
|
cls: 'error-title',
|
||||||
text: '发布出错'
|
text: isUpdate ? '更新出错' : '发布出错'
|
||||||
});
|
});
|
||||||
errorContainer.createEl('div', {
|
errorContainer.createEl('div', {
|
||||||
cls: 'error-message',
|
cls: 'error-message',
|
||||||
@ -386,14 +472,14 @@ export class SelectCategoryModal extends Modal {
|
|||||||
retryButton.onclick = () => {
|
retryButton.onclick = () => {
|
||||||
noticeContainer.empty();
|
noticeContainer.empty();
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
submitButton.textContent = '发布';
|
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果发生错误,重置按钮状态
|
// 如果发生错误,重置按钮状态
|
||||||
if (submitButton.disabled) {
|
if (submitButton.disabled) {
|
||||||
submitButton.disabled = false;
|
submitButton.disabled = false;
|
||||||
submitButton.textContent = '发布';
|
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user