From e56a479a4541774322c40b6f60b0be328b81fa99 Mon Sep 17 00:00:00 2001 From: wood chen Date: Sat, 12 Jul 2025 01:54:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=BF=9C=E7=A8=8B=E5=9B=BE?= =?UTF-8?q?=E7=89=87URL=E6=9B=BF=E6=8D=A2=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E7=94=A8=E6=88=B7=E5=9C=A8=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E5=90=8E=E5=B0=86=E6=9C=AC=E5=9C=B0=E5=9B=BE=E7=89=87=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E6=9B=BF=E6=8D=A2=E4=B8=BADiscourse=E4=B8=8A=E7=9A=84?= =?UTF-8?q?=E8=BF=9C=E7=A8=8BURL=E3=80=82=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E6=8C=81markdown=E6=A0=BC=E5=BC=8F=E5=BC=95=E7=94=A8=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E5=9B=BE=E7=89=87=E7=9A=84=E4=B8=8A=E4=BC=A0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.ts | 22 ++++++++++++++++-- src/config.ts | 14 ++++++++++++ src/embed-handler.ts | 52 +++++++++++++++++++++++++++++++++---------- src/i18n/en.ts | 4 +++- src/i18n/zh-CN.ts | 4 +++- src/main.ts | 53 +++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 132 insertions(+), 17 deletions(-) diff --git a/src/api.ts b/src/api.ts index ef07fb2..cefc5b5 100644 --- a/src/api.ts +++ b/src/api.ts @@ -15,7 +15,7 @@ export class DiscourseAPI { ) {} // 上传图片到Discourse - async uploadImage(file: TFile): Promise { + async uploadImage(file: TFile): Promise<{shortUrl: string, fullUrl?: string} | null> { try { const imgfile = await this.app.vault.readBinary(file); const boundary = genBoundary(); @@ -53,7 +53,25 @@ export class DiscourseAPI { if (response.status == 200) { const jsonResponse = response.json; - return jsonResponse.short_url; + let fullUrl: string | undefined; + + // 处理完整URL的拼接 + if (jsonResponse.url) { + // 如果返回的url已经是完整URL(包含http/https),直接使用 + if (jsonResponse.url.startsWith('http://') || jsonResponse.url.startsWith('https://')) { + fullUrl = jsonResponse.url; + } else { + // 如果是相对路径,需要与baseUrl拼接 + const baseUrl = this.settings.baseUrl.replace(/\/$/, ''); // 移除尾部斜杠 + const urlPath = jsonResponse.url.startsWith('/') ? jsonResponse.url : `/${jsonResponse.url}`; + fullUrl = `${baseUrl}${urlPath}`; + } + } + + return { + shortUrl: jsonResponse.short_url, + fullUrl: fullUrl + }; } else { new NotifyUser(this.app, `Error uploading image: ${response.status}`).open(); return null; diff --git a/src/config.ts b/src/config.ts index 2a63427..ead6ae9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ export interface DiscourseSyncSettings { baseUrl: string; category: number; skipH1: boolean; + useRemoteImageUrl: boolean; userApiKey: string; lastNotifiedVersion?: string; // 记录上次显示更新通知的版本 } @@ -14,6 +15,7 @@ export const DEFAULT_SETTINGS: DiscourseSyncSettings = { baseUrl: "https://yourforum.example.com", category: 1, skipH1: false, + useRemoteImageUrl: false, userApiKey: "" }; @@ -230,5 +232,17 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab { await this.plugin.saveSettings(); }) ); + + new Setting(publishSection) + .setName(t('USE_REMOTE_IMAGE_URL')) + .setDesc(t('USE_REMOTE_IMAGE_URL_DESC')) + .addToggle((toggle) => + toggle + .setValue(this.plugin.settings.useRemoteImageUrl) + .onChange(async (value) => { + this.plugin.settings.useRemoteImageUrl = value; + await this.plugin.saveSettings(); + }) + ); } } diff --git a/src/embed-handler.ts b/src/embed-handler.ts index c0bf710..d7c7620 100644 --- a/src/embed-handler.ts +++ b/src/embed-handler.ts @@ -11,17 +11,30 @@ export class EmbedHandler { // 提取嵌入引用 extractEmbedReferences(content: string): string[] { - const regex = /!\[\[(.*?)\]\]/g; - const matches = []; + const references: string[] = []; + + // 匹配 ![[...]] 格式 (Wiki格式) + const wikiRegex = /!\[\[(.*?)\]\]/g; let match; - while ((match = regex.exec(content)) !== null) { - matches.push(match[1]); + while ((match = wikiRegex.exec(content)) !== null) { + references.push(match[1]); } - return matches; + + // 匹配 ![](path) 格式 (Markdown格式) + const markdownRegex = /!\[.*?\]\(([^)]+)\)/g; + while ((match = markdownRegex.exec(content)) !== null) { + // 过滤掉网络URL,只处理本地文件路径 + const path = match[1]; + if (!path.startsWith('http://') && !path.startsWith('https://') && !path.startsWith('upload://')) { + references.push(path); + } + } + + return references; } // 处理嵌入内容 - async processEmbeds(embedReferences: string[], activeFileName: string): Promise { + async processEmbeds(embedReferences: string[], activeFileName: string, useRemoteUrl = false): Promise { const uploadedUrls: string[] = []; for (const ref of embedReferences) { // 处理带有#的文件路径,分离文件名和标题部分 @@ -37,8 +50,14 @@ export class EmbedHandler { if (abstractFile instanceof TFile) { // 检查是否为图片或PDF文件 if (isImageFile(abstractFile)) { - const imageUrl = await this.api.uploadImage(abstractFile); - uploadedUrls.push(imageUrl || ""); + const imageResult = await this.api.uploadImage(abstractFile); + if (imageResult) { + // 根据配置选择使用短URL还是完整URL + const urlToUse = useRemoteUrl && imageResult.fullUrl ? imageResult.fullUrl : imageResult.shortUrl; + uploadedUrls.push(urlToUse); + } else { + uploadedUrls.push(""); + } } else { // 非图片文件,返回空字符串 uploadedUrls.push(""); @@ -58,14 +77,23 @@ export class EmbedHandler { // 替换内容中的嵌入引用为Markdown格式 replaceEmbedReferences(content: string, embedReferences: string[], uploadedUrls: string[]): string { let processedContent = content; + embedReferences.forEach((ref, index) => { - const obsRef = `![[${ref}]]`; - // 只有当上传URL不为空时(即为图片)才替换为Markdown格式的图片链接 if (uploadedUrls[index]) { - const discoRef = `![${ref}](${uploadedUrls[index]})`; - processedContent = processedContent.replace(obsRef, discoRef); + // 处理 ![[...]] 格式 (Wiki格式) + const wikiRef = `![[${ref}]]`; + const wikiReplacement = `![${ref}](${uploadedUrls[index]})`; + processedContent = processedContent.replace(wikiRef, wikiReplacement); + + // 处理 ![](path) 格式 (Markdown格式) + // 创建正则表达式来匹配具体的路径 + const escapedRef = ref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const markdownRegex = new RegExp(`!\\[([^\\]]*)\\]\\(${escapedRef}\\)`, 'g'); + const markdownReplacement = `![$1](${uploadedUrls[index]})`; + processedContent = processedContent.replace(markdownRegex, markdownReplacement); } }); + return processedContent; } } \ No newline at end of file diff --git a/src/i18n/en.ts b/src/i18n/en.ts index fbab349..15dcfcd 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -5,6 +5,8 @@ export default { 'SKIP_H1': 'Skip First Heading', 'SKIP_H1_DESC': 'Skip the first heading (H1) when publishing to Discourse', + 'USE_REMOTE_IMAGE_URL': 'Use Remote Image URLs', + 'USE_REMOTE_IMAGE_URL_DESC': 'Replace local image links with remote URLs from Discourse after publishing', 'TEST_API_KEY': 'Test Connection', 'TESTING': 'Testing...', 'API_TEST_SUCCESS': 'Connection successful! API key is valid', @@ -17,7 +19,7 @@ export default { 'CATEGORY': 'Category', 'TAGS': 'Tags', 'ENTER_TAG': 'Enter tag name (press Enter to add)', - 'ENTER_TAG_WITH_CREATE': 'Enter tag name (can create new tags)', + 'ENTER_TAG_WITH_CREATE': 'Enter tag name (press Enter to add) (can create new tags)', 'PUBLISHING': 'Publishing...', 'UPDATING': 'Updating...', 'PUBLISH': 'Publish', diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts index 34b6d66..be7da7b 100644 --- a/src/i18n/zh-CN.ts +++ b/src/i18n/zh-CN.ts @@ -5,6 +5,8 @@ export default { 'SKIP_H1': '跳过一级标题', 'SKIP_H1_DESC': '发布到 Discourse 时跳过笔记中的一级标题', + 'USE_REMOTE_IMAGE_URL': '使用远程图片URL', + 'USE_REMOTE_IMAGE_URL_DESC': '发布后用Discourse上的远程图片URL替换本地文章中的图片链接', 'TEST_API_KEY': '测试连接', 'TESTING': '测试中...', 'API_TEST_SUCCESS': '连接成功!API密钥有效', @@ -17,7 +19,7 @@ export default { 'CATEGORY': '分类', 'TAGS': '标签', 'ENTER_TAG': '输入标签名称(回车添加)', - 'ENTER_TAG_WITH_CREATE': '输入标签名称(可创建新标签)', + 'ENTER_TAG_WITH_CREATE': '输入标签名称(回车添加)(可创建新标签)', 'PUBLISHING': '发布中...', 'UPDATING': '更新中...', 'PUBLISH': '发布', diff --git a/src/main.ts b/src/main.ts index 85e07c6..3b11831 100644 --- a/src/main.ts +++ b/src/main.ts @@ -239,7 +239,7 @@ export default class PublishToDiscourse extends Plugin implements PluginInterfac const embedReferences = this.embedHandler.extractEmbedReferences(content); // 处理嵌入内容 - const uploadedUrls = await this.embedHandler.processEmbeds(embedReferences, this.activeFile.name); + const uploadedUrls = await this.embedHandler.processEmbeds(embedReferences, this.activeFile.name, this.settings.useRemoteImageUrl); // 替换嵌入引用为Markdown格式 content = this.embedHandler.replaceEmbedReferences(content, embedReferences, uploadedUrls); @@ -276,6 +276,11 @@ export default class PublishToDiscourse extends Plugin implements PluginInterfac // 如果更新成功,更新Front Matter if (result.success) { await this.updateFrontMatter(postId, topicId, currentTags); + + // 如果启用了远程URL替换,更新本地文件中的图片链接 + if (this.settings.useRemoteImageUrl) { + await this.updateLocalImageLinks(embedReferences, uploadedUrls); + } } } else { // 创建新帖子 @@ -289,6 +294,11 @@ export default class PublishToDiscourse extends Plugin implements PluginInterfac // 如果创建成功,更新Front Matter if (result.success && result.postId && result.topicId) { await this.updateFrontMatter(result.postId, result.topicId, currentTags); + + // 如果启用了远程URL替换,更新本地文件中的图片链接 + if (this.settings.useRemoteImageUrl) { + await this.updateLocalImageLinks(embedReferences, uploadedUrls); + } } } @@ -362,6 +372,47 @@ export default class PublishToDiscourse extends Plugin implements PluginInterfac } } + // 更新本地文件中的图片链接为远程URL + private async updateLocalImageLinks(embedReferences: string[], uploadedUrls: string[]) { + try { + const activeFile = this.app.workspace.getActiveFile(); + if (!activeFile) { + return; + } + + let content = await this.app.vault.read(activeFile); + let hasChanges = false; + + embedReferences.forEach((ref, index) => { + if (uploadedUrls[index]) { + // 替换 ![[...]] 格式 (Wiki格式) + const wikiRef = `![[${ref}]]`; + const wikiReplacement = `![${ref}](${uploadedUrls[index]})`; + if (content.includes(wikiRef)) { + content = content.replace(wikiRef, wikiReplacement); + hasChanges = true; + } + + // 替换 ![](path) 格式 (Markdown格式) + const escapedRef = ref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const markdownRegex = new RegExp(`!\\[([^\\]]*)\\]\\(${escapedRef}\\)`, 'g'); + const markdownReplacement = `![$1](${uploadedUrls[index]})`; + if (markdownRegex.test(content)) { + content = content.replace(markdownRegex, markdownReplacement); + hasChanges = true; + } + } + }); + + // 只有在有变更时才保存文件 + if (hasChanges) { + await this.app.vault.modify(activeFile, content); + } + } catch (error) { + console.error('Failed to update local image links:', error); + } + } + onunload() {} }