From b24bf09efcc583ca7c294737c95ab1dde24c608c Mon Sep 17 00:00:00 2001 From: wood chen Date: Fri, 20 Jun 2025 21:23:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=9C=A8=E6=9C=AC=E5=9C=B0=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E4=B8=8E=E8=BF=9C=E7=A8=8B=E5=88=86=E7=B1=BB=E4=B8=8D=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E9=80=89=E6=8B=A9=E4=BD=BF=E7=94=A8=E5=93=AA?= =?UTF-8?q?=E4=B8=80=E5=88=86=E7=B1=BB=E3=80=82=E6=9B=B4=E6=96=B0=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E4=BB=A5=E6=94=AF=E6=8C=81=E5=88=86=E7=B1=BB=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E7=A1=AE=E8=AE=A4=E5=AF=B9=E8=AF=9D=E6=A1=86=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E5=9C=A8=E5=9B=BD=E9=99=85=E5=8C=96=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0=E7=9B=B8=E5=85=B3=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E3=80=82=E6=9B=B4=E6=96=B0=20GitHub=20Actions=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E4=BB=A5=E7=94=9F=E6=88=90=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 62 +++++++++++++++-- .gitignore | 1 + src/i18n/en.ts | 10 ++- src/i18n/zh-CN.ts | 10 ++- src/main.ts | 50 ++++++++++++-- src/ui.ts | 98 ++++++++++++++++++++++++++ styles.css | 125 ++++++++++++++++++++++++++++++++++ 7 files changed, 343 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d201c4..2eac2a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,11 +25,56 @@ jobs: npm install npm run build + - name: Generate release notes + id: release_notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + tag="${GITHUB_REF#refs/tags/}" + + # 获取上一个标签 + previous_tag=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + + # 生成发布说明 + # 获取上一个标签 + if [ -n "$previous_tag" ]; then + echo "## 🚀 更新内容 / What's Changed" > release_notes.md + echo "" >> release_notes.md + + # 分析提交类型并分类显示 + echo "### ✨ 新增功能 / New Features:" >> release_notes.md + git log --pretty=format:"%s" "$previous_tag"..HEAD | grep -i "^feat\|^add\|^新增\|^功能" | sed 's/^/- /' >> release_notes.md || echo "- 暂无" >> release_notes.md + echo "" >> release_notes.md + + echo "### 🐛 问题修复 / Bug Fixes:" >> release_notes.md + git log --pretty=format:"%s" "$previous_tag"..HEAD | grep -i "^fix\|^bug\|^修复\|^修正" | sed 's/^/- /' >> release_notes.md || echo "- 暂无" >> release_notes.md + echo "" >> release_notes.md + + echo "### 🔧 改进优化 / Improvements:" >> release_notes.md + git log --pretty=format:"%s" "$previous_tag"..HEAD | grep -i "^improve\|^update\|^优化\|^改进\|^更新" | sed 's/^/- /' >> release_notes.md || echo "- 暂无" >> release_notes.md + echo "" >> release_notes.md + + echo "### 📝 所有提交 / All Commits:" >> release_notes.md + git log --pretty=format:"- %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" "$previous_tag"..HEAD >> release_notes.md + echo "" >> release_notes.md + + echo "### 📂 变更文件 / Changed Files:" >> release_notes.md + git diff --name-only "$previous_tag"..HEAD | sed 's/^/- `/' | sed 's/$/`/' >> release_notes.md + + echo "" >> release_notes.md + echo "---" >> release_notes.md + echo "**完整变更日志**: https://github.com/${{ github.repository }}/compare/$previous_tag...$tag" >> release_notes.md + else + # 首次发布时使用 GitHub 自动生成的发布说明 + echo "" > release_notes.md + fi + - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | tag="${GITHUB_REF#refs/tags/}" + previous_tag=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") files=() for file in main.js manifest.json styles.css; do @@ -41,7 +86,16 @@ jobs: fi done - gh release create "$tag" \ - --title="$tag" \ - --draft \ - "${files[@]}" + if [ -n "$previous_tag" ]; then + # 有上一个标签,使用自定义发布说明 + gh release create "$tag" \ + --title="Release $tag" \ + --notes-file release_notes.md \ + "${files[@]}" + else + # 首次发布,使用 GitHub 自动生成的发布说明 + gh release create "$tag" \ + --title="Release $tag" \ + --generate-notes \ + "${files[@]}" + fi diff --git a/.gitignore b/.gitignore index e09a007..42606f6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ data.json # Exclude macOS Finder (System Explorer) View States .DS_Store +.cursor/rules/discourse.mdc diff --git a/src/i18n/en.ts b/src/i18n/en.ts index d3223a6..4d6d444 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -45,5 +45,13 @@ export default { // Open in Discourse 'OPEN_IN_DISCOURSE': 'Open in Discourse', 'NO_ACTIVE_FILE': 'No active file', - 'NO_TOPIC_ID': 'This note has not been published to Discourse yet' + 'NO_TOPIC_ID': 'This note has not been published to Discourse yet', + + // Category conflict + 'CATEGORY_CONFLICT_TITLE': 'Category Conflict', + 'CATEGORY_CONFLICT_DESC': 'The local category setting differs from the remote category on Discourse. Please choose which category to use:', + 'LOCAL_CATEGORY': 'Local Category (set in frontmatter)', + 'REMOTE_CATEGORY': 'Remote Category (from Discourse)', + 'KEEP_LOCAL_CATEGORY': 'Keep Local Category', + 'USE_REMOTE_CATEGORY': 'Use Remote Category' } \ No newline at end of file diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts index 47125f1..dc78498 100644 --- a/src/i18n/zh-CN.ts +++ b/src/i18n/zh-CN.ts @@ -45,5 +45,13 @@ export default { // Open in Discourse 'OPEN_IN_DISCOURSE': '在 Discourse 中打开', 'NO_ACTIVE_FILE': '没有打开的文件', - 'NO_TOPIC_ID': '此笔记尚未发布到 Discourse' + 'NO_TOPIC_ID': '此笔记尚未发布到 Discourse', + + // 分类冲突 + 'CATEGORY_CONFLICT_TITLE': '分类冲突', + 'CATEGORY_CONFLICT_DESC': '检测到本地设置的分类与 Discourse 上的分类不同,请选择要使用的分类:', + 'LOCAL_CATEGORY': '本地分类(frontmatter中设置)', + 'REMOTE_CATEGORY': '远程分类(Discourse上的分类)', + 'KEEP_LOCAL_CATEGORY': '保持本地分类', + 'USE_REMOTE_CATEGORY': '使用远程分类' } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 1b61b42..8e78e10 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import { t, setLocale } from './i18n'; import { expandEmbeds } from './expand-embeds'; import { DiscourseAPI } from './api'; import { EmbedHandler } from './embed-handler'; -import { SelectCategoryModal } from './ui'; +import { SelectCategoryModal, CategoryConflictModal } from './ui'; import { NotifyUser } from './notification'; import { getFrontMatter, removeFrontMatter } from './utils'; import { ActiveFile, PluginInterface } from './types'; @@ -124,13 +124,49 @@ export default class PublishToDiscourse extends Plugin implements PluginInterfac console.log(`Updated tags from Discourse: ${topicInfo.tags.join(', ')}`); } - // 如果获取到了分类ID,更新设置中的分类 + // 处理分类冲突 if (topicInfo.categoryId) { - // 查找分类名称 - const category = categories.find(c => c.id === topicInfo.categoryId); - if (category) { - this.settings.category = category.id; - console.log(`Updated category from Discourse: ${category.name} (${category.id})`); + const localCategoryId = fm?.discourse_category_id; + const remoteCategoryId = topicInfo.categoryId; + + // 如果本地有设置分类ID且与远程不同,询问用户 + if (localCategoryId && localCategoryId !== remoteCategoryId) { + const localCategory = categories.find(c => c.id === localCategoryId); + const remoteCategory = categories.find(c => c.id === remoteCategoryId); + + if (localCategory && remoteCategory) { + const conflictModal = new CategoryConflictModal( + this.app, + this, + localCategoryId, + localCategory.name, + remoteCategoryId, + remoteCategory.name + ); + + const useRemote = await conflictModal.showAndWait(); + if (useRemote) { + this.settings.category = remoteCategoryId; + console.log(`User chose remote category: ${remoteCategory.name} (${remoteCategoryId})`); + } else { + this.settings.category = localCategoryId; + console.log(`User kept local category: ${localCategory.name} (${localCategoryId})`); + } + } else { + // 如果找不到分类信息,使用远程分类 + this.settings.category = remoteCategoryId; + } + } else if (localCategoryId) { + // 如果本地有设置且与远程相同,使用本地设置 + this.settings.category = localCategoryId; + console.log(`Using local category: ${localCategoryId}`); + } else { + // 如果本地没有设置,使用远程分类 + const category = categories.find(c => c.id === remoteCategoryId); + if (category) { + this.settings.category = category.id; + console.log(`Using remote category: ${category.name} (${category.id})`); + } } } } catch (error) { diff --git a/src/ui.ts b/src/ui.ts index 26b456b..3cd1639 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -290,4 +290,102 @@ export class SelectCategoryModal extends Modal { const { contentEl } = this; contentEl.empty(); } +} + +// 分类冲突确认对话框 +export class CategoryConflictModal extends Modal { + plugin: PluginInterface; + localCategoryId: number; + localCategoryName: string; + remoteCategoryId: number; + remoteCategoryName: string; + resolve: (useRemote: boolean) => void; + + constructor( + app: App, + plugin: PluginInterface, + localCategoryId: number, + localCategoryName: string, + remoteCategoryId: number, + remoteCategoryName: string + ) { + super(app); + this.plugin = plugin; + this.localCategoryId = localCategoryId; + this.localCategoryName = localCategoryName; + this.remoteCategoryId = remoteCategoryId; + this.remoteCategoryName = remoteCategoryName; + } + + // 返回一个Promise,让调用者等待用户选择 + showAndWait(): Promise { + return new Promise((resolve) => { + this.resolve = resolve; + this.open(); + }); + } + + onOpen() { + const { contentEl } = this; + contentEl.empty(); + contentEl.addClass('discourse-category-conflict-modal'); + + // 标题 + contentEl.createEl('h2', { text: t('CATEGORY_CONFLICT_TITLE') }); + + // 说明文字 + const description = contentEl.createEl('div', { cls: 'conflict-description' }); + description.createEl('p', { text: t('CATEGORY_CONFLICT_DESC') }); + + // 分类对比 + const comparisonContainer = contentEl.createEl('div', { cls: 'category-comparison' }); + + // 本地分类 + const localContainer = comparisonContainer.createEl('div', { cls: 'category-option local' }); + localContainer.createEl('h3', { text: t('LOCAL_CATEGORY') }); + localContainer.createEl('div', { + cls: 'category-name', + text: `${this.localCategoryName} (ID: ${this.localCategoryId})` + }); + + // 远程分类 + const remoteContainer = comparisonContainer.createEl('div', { cls: 'category-option remote' }); + remoteContainer.createEl('h3', { text: t('REMOTE_CATEGORY') }); + remoteContainer.createEl('div', { + cls: 'category-name', + text: `${this.remoteCategoryName} (ID: ${this.remoteCategoryId})` + }); + + // 按钮区域 + const buttonArea = contentEl.createEl('div', { cls: 'button-area' }); + + // 保持本地分类按钮 + const keepLocalButton = buttonArea.createEl('button', { + cls: 'keep-local-button', + text: t('KEEP_LOCAL_CATEGORY') + }); + keepLocalButton.onclick = () => { + this.resolve(false); + this.close(); + }; + + // 使用远程分类按钮 + const useRemoteButton = buttonArea.createEl('button', { + cls: 'use-remote-button', + text: t('USE_REMOTE_CATEGORY') + }); + useRemoteButton.onclick = () => { + this.resolve(true); + this.close(); + }; + } + + onClose() { + const { contentEl } = this; + contentEl.empty(); + // 如果用户直接关闭对话框,默认保持本地设置 + if (this.resolve) { + this.resolve(false); + } + } } \ No newline at end of file diff --git a/styles.css b/styles.css index 3eb927a..3000c1e 100644 --- a/styles.css +++ b/styles.css @@ -420,3 +420,128 @@ If your plugin does not need CSS, delete this file. opacity: 0; } } + +/* Category Conflict Modal Styles */ +.discourse-category-conflict-modal { + padding: 24px; + max-width: 600px; + background-color: var(--background-primary); + border-radius: var(--radius-l); +} + +.discourse-category-conflict-modal h2 { + margin: 0 0 16px 0; + font-size: 1.4em; + font-weight: 600; + color: var(--text-normal); + text-align: center; +} + +.discourse-category-conflict-modal .conflict-description { + margin-bottom: 24px; + text-align: center; +} + +.discourse-category-conflict-modal .conflict-description p { + margin: 0; + color: var(--text-muted); + line-height: 1.5; +} + +.discourse-category-conflict-modal .category-comparison { + display: flex; + gap: 16px; + margin-bottom: 24px; +} + +.discourse-category-conflict-modal .category-option { + flex: 1; + padding: 16px; + border: 2px solid var(--background-modifier-border); + border-radius: var(--radius-m); + text-align: center; + background-color: var(--background-secondary); +} + +.discourse-category-conflict-modal .category-option.local { + border-color: var(--color-blue); + background-color: var(--color-blue-hover); +} + +.discourse-category-conflict-modal .category-option.remote { + border-color: var(--color-orange); + background-color: var(--color-orange-hover); +} + +.discourse-category-conflict-modal .category-option h3 { + margin: 0 0 8px 0; + font-size: 1.1em; + font-weight: 600; + color: var(--text-normal); +} + +.discourse-category-conflict-modal .category-name { + font-size: 14px; + color: var(--text-muted); + word-break: break-all; +} + +.discourse-category-conflict-modal .button-area { + display: flex; + gap: 12px; + justify-content: center; +} + +.discourse-category-conflict-modal .keep-local-button, +.discourse-category-conflict-modal .use-remote-button { + flex: 1; + max-width: 180px; + padding: 10px 16px; + border-radius: var(--radius-m); + cursor: pointer; + font-weight: 500; + font-size: 14px; + transition: all 0.2s ease; + min-height: 42px; + box-shadow: var(--shadow-s); +} + +.discourse-category-conflict-modal .keep-local-button { + background-color: var(--color-blue); + color: white; + border: 1px solid var(--color-blue); +} + +.discourse-category-conflict-modal .keep-local-button:hover { + background-color: var(--color-blue-hover); + border-color: var(--color-blue-hover); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +} + +.discourse-category-conflict-modal .use-remote-button { + background-color: var(--color-orange); + color: white; + border: 1px solid var(--color-orange); +} + +.discourse-category-conflict-modal .use-remote-button:hover { + background-color: var(--color-orange-hover); + border-color: var(--color-orange-hover); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); +} + +/* 响应式设计 */ +@media (max-width: 600px) { + .discourse-category-conflict-modal .category-comparison { + flex-direction: column; + } + + .discourse-category-conflict-modal .button-area { + flex-direction: column; + } + + .discourse-category-conflict-modal .keep-local-button, + .discourse-category-conflict-modal .use-remote-button { + max-width: none; + } +}