Native browser pages could cover application dialogs and overlapping menus or notifications. Suspend only the viewport while covered and restore the same active page afterward, including nested dialogs and terminal prompts. Keep knowledge source controls at their full height, constrain long library cards and titles, and update automatic names when switching remote targets without overwriting edited or saved names. Restore packaged release-note loading now that the history exceeds its previous size limit. Add focused regressions and real Electron UI evidence. Clean up temporary Harness fixture directories at test completion instead of accumulating them. Release note: 修复内嵌浏览器遮挡弹窗和浮层、知识库来源选项被压缩及长名称显示异常;切换远端知识库时自动名称正确更新,并恢复更新说明显示。
100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { open } from 'node:fs/promises'
|
|
import {
|
|
releaseNotesAcknowledgeSchema,
|
|
releaseNotesFileSchema,
|
|
type ReleaseNote,
|
|
type ReleaseNotesSnapshot
|
|
} from '../shared/release-notes-contracts'
|
|
import type { ApplicationSettingsStore } from './application-settings-store'
|
|
import { compareStrictSemVer } from './version-checker'
|
|
|
|
const maximumReleaseNotesBytes = 1024 * 1024
|
|
|
|
export class ReleaseNotesService {
|
|
private releases?: ReleaseNote[]
|
|
private releaseLoad?: Promise<ReleaseNote[]>
|
|
|
|
constructor(
|
|
private readonly dependencies: {
|
|
currentVersion: string
|
|
filePath: string
|
|
settingsStore: ApplicationSettingsStore
|
|
}
|
|
) {}
|
|
|
|
private async loadReleases(): Promise<ReleaseNote[]> {
|
|
if (this.releases) {
|
|
return this.releases
|
|
}
|
|
if (!this.releaseLoad) {
|
|
this.releaseLoad = this.readReleases().finally(() => {
|
|
this.releaseLoad = undefined
|
|
})
|
|
}
|
|
return this.releaseLoad
|
|
}
|
|
|
|
private async readReleases(): Promise<ReleaseNote[]> {
|
|
const handle = await open(this.dependencies.filePath, 'r')
|
|
try {
|
|
const buffer = Buffer.alloc(maximumReleaseNotesBytes + 1)
|
|
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0)
|
|
if (bytesRead > maximumReleaseNotesBytes) {
|
|
throw new Error('Release notes exceed the size limit')
|
|
}
|
|
const parsed = releaseNotesFileSchema.parse(
|
|
JSON.parse(buffer.toString('utf8', 0, bytesRead)) as unknown
|
|
)
|
|
this.releases = [...parsed.releases].sort((left, right) =>
|
|
compareStrictSemVer(right.version, left.version)
|
|
)
|
|
return this.releases
|
|
} finally {
|
|
await handle.close()
|
|
}
|
|
}
|
|
|
|
async getPending(): Promise<ReleaseNotesSnapshot> {
|
|
const releases = await this.loadReleases()
|
|
const currentVersion = this.dependencies.currentVersion
|
|
if (!releases.some((release) => release.version === currentVersion)) {
|
|
return {
|
|
currentVersion,
|
|
releases: []
|
|
}
|
|
}
|
|
const lastSeenVersion =
|
|
await this.dependencies.settingsStore.getLastSeenReleaseNotesVersion()
|
|
const pending = releases.filter((release) => {
|
|
const comparedWithCurrent = compareStrictSemVer(
|
|
release.version,
|
|
currentVersion
|
|
)
|
|
if (comparedWithCurrent > 0) {
|
|
return false
|
|
}
|
|
return lastSeenVersion
|
|
? compareStrictSemVer(release.version, lastSeenVersion) > 0
|
|
: comparedWithCurrent === 0
|
|
})
|
|
return {
|
|
currentVersion,
|
|
releases: pending
|
|
}
|
|
}
|
|
|
|
async acknowledge(input: unknown): Promise<void> {
|
|
const { version } = releaseNotesAcknowledgeSchema.parse(input)
|
|
if (version !== this.dependencies.currentVersion) {
|
|
throw new Error('Only the current release notes can be acknowledged')
|
|
}
|
|
const releases = await this.loadReleases()
|
|
if (!releases.some((release) => release.version === version)) {
|
|
throw new Error('Current release notes are unavailable')
|
|
}
|
|
await this.dependencies.settingsStore.setLastSeenReleaseNotesVersion(
|
|
version
|
|
)
|
|
}
|
|
}
|