diff --git a/.playwright/scripts/__tests__/updates.e2e.mjs b/.playwright/scripts/__tests__/updates.e2e.mjs index 6989a8ed..689f84cb 100644 --- a/.playwright/scripts/__tests__/updates.e2e.mjs +++ b/.playwright/scripts/__tests__/updates.e2e.mjs @@ -8,7 +8,7 @@ const result = { latestVersion: version, status: 'update_available', releaseUrl: `https://github.com/useLexora/Lexora/releases/tag/v${version}`, - releaseNotes: '## 更新亮点\n- 改善任务恢复\n- 修复文件操作\n- 优化资源使用\n![external](https://example.invalid/image.png)', + releaseNotes: '## 更新亮点\n- feat(buddy): 改善任务恢复 by @contributor in https://github.com/useLexora/Lexora/pull/301\n- 修复文件操作\n- 优化资源使用\n![external](https://example.invalid/image.png)', } async function seedUpdate(instance, enabled = true) { @@ -34,6 +34,11 @@ async function about(page) { await page.locator('.desktop-settings-sidebar').getByRole('link', { name: '关于', exact: true }).click() } +async function checkFromApplicationMenu(page) { + await page.locator('.desktop-window-menu').getByRole('button', { name: 'Lexora Buddy', exact: true }).click() + await page.getByRole('menuitem').filter({ hasText: '检查更新…' }).click() +} + function toggle(page) { return page.locator('.desktop-application-toggle').filter({ hasText: '更新通知' }).getByRole('switch') } @@ -65,6 +70,8 @@ test('updates stay discoverable after dismissal, persist skips and opt-out, and const dialog = () => desktop.page.locator('.desktop-update-dialog') await expect(dialog()).toBeVisible() await expect(dialog()).toContainText('改善任务恢复') + await expect(dialog()).not.toContainText('@contributor') + await expect(dialog()).not.toContainText('/pull/301') await expect(dialog().locator('img')).toHaveCount(0) await expect.poll(() => decisions(instance)).toMatchObject({ seenVersion: version }) await desktop.app.evaluate(({ shell }) => { @@ -95,10 +102,29 @@ test('updates stay discoverable after dismissal, persist skips and opt-out, and ipcMain.removeHandler('lexora:app:check-for-updates') ipcMain.handle('lexora:app:check-for-updates', () => ({ ...result, currentVersion: app.getVersion() })) }, result) - await desktop.page.getByRole('button', { name: '检查更新', exact: true }).click() + await desktop.page.locator('.desktop-app-sidebar').getByRole('button', { name: '任务', exact: true }).click() + await checkFromApplicationMenu(desktop.page) await expect(dialog()).toContainText(version) await expect(reminder()).toHaveCount(0) - await desktop.page.screenshot({ path: path.join(instance.artifactDirectory, 'update-details.png'), animations: 'disabled' }) + await desktop.page.screenshot({ path: path.join(instance.artifactDirectory, 'update-available.png'), animations: 'disabled' }) + await dialog().getByRole('button', { name: '关闭', exact: true }).click() + await expect(dialog()).toHaveCount(0) + await desktop.app.evaluate(({ ipcMain, app }) => { + const currentVersion = app.getVersion() + ipcMain.removeHandler('lexora:app:check-for-updates') + ipcMain.handle('lexora:app:check-for-updates', () => ({ + currentVersion, + latestVersion: currentVersion, + status: 'up_to_date', + releaseUrl: `https://github.com/useLexora/Lexora/releases/tag/v${currentVersion}`, + releaseNotes: '', + })) + }) + await checkFromApplicationMenu(desktop.page) + await expect(dialog()).toContainText('已是最新版本') + await expect(dialog().getByRole('button', { name: '忽略此版本', exact: true })).toHaveCount(0) + await expect(dialog().getByRole('button', { name: '前往下载', exact: true })).toHaveCount(0) + await desktop.page.screenshot({ path: path.join(instance.artifactDirectory, 'up-to-date.png'), animations: 'disabled' }) expect(desktop.diagnostics.console.filter(entry => entry.type === 'pageerror')).toEqual([]) }) diff --git a/apps/buddy/src/i18n/locales/en-US/app.ts b/apps/buddy/src/i18n/locales/en-US/app.ts index f15d957a..68d5595f 100644 --- a/apps/buddy/src/i18n/locales/en-US/app.ts +++ b/apps/buddy/src/i18n/locales/en-US/app.ts @@ -61,17 +61,20 @@ export default { 'desktop.window.minimize': 'Minimize', 'desktop.window.restore': 'Restore window', 'desktop.update.available': 'A new version is available', + 'desktop.update.availableDescription': 'Review what changed, then open the release page when you are ready to download.', 'desktop.update.currentVersion': 'Current version', 'desktop.update.failed': 'Unable to check for updates', 'desktop.update.latest': 'Lexora Buddy is up to date', + 'desktop.update.latestDescription': 'You are using the latest version of Lexora Buddy.', 'desktop.update.latestTag': 'Up to date', 'desktop.update.latestVersion': 'Latest version', - 'desktop.update.openRelease': 'View release', + 'desktop.update.newTag': 'New', + 'desktop.update.openRelease': 'Full changelog', 'desktop.update.title': 'Check for updates', 'desktop.update.view': 'View update', 'desktop.update.ignore': 'Skip this version', 'desktop.update.download': 'Go to download', - 'desktop.update.highlights': 'Release notes excerpt', + 'desktop.update.highlights': 'What\'s New', 'desktop.update.notesUnavailable': 'No update summary is available. See the release page for details.', 'desktop.update.notifications': 'Update notifications', 'desktop.update.notificationsDescription': 'Check for new versions automatically and notify you when tasks are idle. Turning this off stops automatic checks; manual checks remain available.', diff --git a/apps/buddy/src/i18n/locales/zh-CN/app.ts b/apps/buddy/src/i18n/locales/zh-CN/app.ts index 7acec7f0..cef98977 100644 --- a/apps/buddy/src/i18n/locales/zh-CN/app.ts +++ b/apps/buddy/src/i18n/locales/zh-CN/app.ts @@ -59,17 +59,20 @@ export default { 'desktop.window.minimize': '最小化', 'desktop.window.restore': '还原窗口', 'desktop.update.available': '发现新版本', + 'desktop.update.availableDescription': '新版本已准备好,可查看更新内容后前往发布页下载。', 'desktop.update.currentVersion': '当前版本', 'desktop.update.failed': '暂时无法检查更新', 'desktop.update.latest': '已是最新版本', + 'desktop.update.latestDescription': '你正在使用 Lexora Buddy 的最新版本。', 'desktop.update.latestTag': '最新版', 'desktop.update.latestVersion': '最新版本', - 'desktop.update.openRelease': '查看发布页', + 'desktop.update.newTag': '新版本', + 'desktop.update.openRelease': '完整更新日志', 'desktop.update.title': '检查更新', 'desktop.update.view': '查看更新', 'desktop.update.ignore': '忽略此版本', 'desktop.update.download': '前往下载', - 'desktop.update.highlights': '发布说明摘录', + 'desktop.update.highlights': '本次更新内容', 'desktop.update.notesUnavailable': '此版本暂无更新摘要,可在发布页查看详情。', 'desktop.update.notifications': '更新通知', 'desktop.update.notificationsDescription': '自动检查新版本,在任务空闲时提醒。关闭后停止自动检查,仍可手动检查更新。', diff --git a/apps/buddy/src/modules/updates/model/__tests__/updateReleaseNotes.spec.ts b/apps/buddy/src/modules/updates/model/__tests__/updateReleaseNotes.spec.ts index b37af14f..2853fe7e 100644 --- a/apps/buddy/src/modules/updates/model/__tests__/updateReleaseNotes.spec.ts +++ b/apps/buddy/src/modules/updates/model/__tests__/updateReleaseNotes.spec.ts @@ -3,6 +3,11 @@ import { updateReleaseHighlights } from '../updateReleaseNotes' it('shows a bounded plain-text excerpt without loading Markdown images or executing release content', () => { expect(updateReleaseHighlights('## Highlights\n\n- **Restore tasks**\n- [Fix files](https://example.invalid)\n![image](https://example.invalid/pixel)\n```sh\ncommand\n```\n- Keep settings\n- More changes')).toEqual(['Restore tasks', 'Fix files', 'Keep settings']) + expect(updateReleaseHighlights('- refactor: 事件嵌入服务架构 by @shanyuhai123 in https://github.com/useLexora/Lexora/pull/216\n- feat(buddy): 新增新任务默认权限设置 by @QAyong in https://github.com/useLexora/Lexora/pull/213\n- fix!: 修复升级流程 (#219)\n**Full Changelog**: https://github.com/useLexora/Lexora/compare/v0.9.3...v0.9.4')).toEqual([ + '事件嵌入服务架构', + '新增新任务默认权限设置', + '修复升级流程', + ]) expect(updateReleaseHighlights('')).toEqual([]) expect(updateReleaseHighlights(`- ${'x'.repeat(10000)}`)[0]?.length).toBe(400) }) diff --git a/apps/buddy/src/modules/updates/model/updateReleaseNotes.ts b/apps/buddy/src/modules/updates/model/updateReleaseNotes.ts index d45fc05b..da54b5e1 100644 --- a/apps/buddy/src/modules/updates/model/updateReleaseNotes.ts +++ b/apps/buddy/src/modules/updates/model/updateReleaseNotes.ts @@ -1,12 +1,31 @@ +const conventionalCommitPrefix = /^(?:build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(?:\([^)]*\))?!?:\s*/i + +function cleanReleaseHighlight(line: string): string { + return line + .replace(/^(?:[-*+] |\d+\. )/, '') + .replace(/\*\*|__|`/g, '') + .replace(/\s+by\s+@?[\w-]+\s+in\s+(?:https?:\/\/\S+|#\d+)\s*$/i, '') + .replace(/\s+\(#\d+\)\s*$/, '') + .replace(/https?:\/\/\S+/g, '') + .replace(/^Full Changelog:\s*/i, '') + .replace(conventionalCommitPrefix, '') + .replace(/<[^>]*>/g, '') + .replace(/\s+/g, ' ') + .trim() + .slice(0, 400) +} + export function updateReleaseHighlights(notes: string): string[] { const text = notes.slice(0, 8_000) .replace(//g, '') .replace(/```[\s\S]*?(?:```|$)/g, '') .replace(/!\[[^\]]*\]\([^)]*\)/g, '') .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') - return text.split(/\r?\n/) + const highlights = text.split(/\r?\n/) .map(line => line.trim()) .filter(line => line && !/^(?:#{1,6}\s|https?:\/\/|[-*_]{3,}$|<)/.test(line)) - .map(line => line.replace(/^(?:[-*+] |\d+\. )/, '').replace(/\*\*|__|`/g, '').slice(0, 400)) - .slice(0, 3) + .map(cleanReleaseHighlight) + .filter(Boolean) + + return [...new Set(highlights)].slice(0, 3) } diff --git a/apps/buddy/src/modules/updates/widgets/DesktopUpdateDialog.vue b/apps/buddy/src/modules/updates/widgets/DesktopUpdateDialog.vue index 6de00454..c091fc57 100644 --- a/apps/buddy/src/modules/updates/widgets/DesktopUpdateDialog.vue +++ b/apps/buddy/src/modules/updates/widgets/DesktopUpdateDialog.vue @@ -1,9 +1,11 @@ @@ -28,41 +31,98 @@ const highlights = computed(() => updateReleaseHighlights(props.result?.releaseN class="desktop-update-dialog" :style="{ width: 'min(32rem, calc(100vw - 2rem))' }" :title="t('desktop.update.title')" + :auto-focus="false" @update:show="emit('update:show', $event)" > +