fix(updater,i18n): 对抗性审查修复(1中 + 3低)
- [中] 手动检查不再丢弃「已下载」事实:runCheck('manual') 原来无条件清
抑制记录,导致下载完成后点「检查更新」会把关于页从「重启安装」退回
「下载」(包已在磁盘)。改为保留 downloaded 记录,只清 announcement
记账;测试 §19 补 manual-check 保持 downloaded 的口径(153 断言)
- website 首帧脚本:?lang= 的 decodeURIComponent 包 try/catch(畸形转义
会在 IIFE 内抛 URIError,跳过 html[lang] 与防闪标记,指定语言被静默
忽略);zh-Hant-TW/zh-Hant-HK/zh-Hant 按 BCP-47 语义判为繁体
(normalize 与 i18n-apply.js 双处同步)
- ZmodemOffers 是 App 兄弟节点,语言切换时父级 children bailout 不重渲染:
订阅 language 并以 tFor 显式翻译(事件回调内的 t() 保留)
- ROADMAP 补录对抗性审查推迟项(周期检查可被设置写入推迟、启动开关
即时生效语义、下载无整体超时、--channel-only/--skip-gitea 矛盾)
This commit is contained in:
1 parent
7c2044fdf4
commit
2027fa3310
6 files changed
+58
-11
No files matched your search
@@ -164,6 +164,10 @@
|
||||
- [x] ~~首启动空屏~~(v0.2.1:启动自动打开一个终端)
|
||||
- [ ] zh-TW 用语统一(i18n 质量专项):`命令/指令` 全文件混用(panels/ssh/settings/terminal,台湾惯用「指令」)、`口令`→`密碼`(ssh.secret.passphrase)、顺带通读 服务器→伺服器 / 默认→預設 / 网络→網路 / 软件→軟體。需要一次全局连贯的专项,零散改会制造新的不一致
|
||||
- [ ] ja 片假名长音符号统一:`フォルダ`(6) / `フォルダー`(7) 混用,二选一全局统一
|
||||
- [ ] 周期检查可被高频设置写入推迟(updater:每次 mutateSettings 重排 4h 定时器,字体快捷键连按/ColorPicker 拖动会重置倒计时;改为「仅 interval 值变化时重排」或记录上次触发时间)
|
||||
- [ ] 「启动时自动检查」运行中途拨开会在 ~5s 后立即检查(startupArmed 语义应为下次启动生效)
|
||||
- [ ] 下载无整体超时:检查有 withTimeout,下载没有——慢滴流可让 downloading 卡死且自动下载不再重试(需设计,不能简单套超时杀死慢速下载)
|
||||
- [ ] release.cjs `--channel-only` 与 `--skip-gitea` 组合矛盾(channel-only 先 return,警告永不打印,Gitea 通道仍会上传)
|
||||
- [ ] autoWrap=false 的固定列宽模式(xterm.js 无原生 wrapMode,需自定义列数控制)
|
||||
- [ ] 终端标题跟随 OSC 序列(窗格标题显示当前目录/命令)
|
||||
- [ ] 内置等宽字体打包(OFL 许可:JetBrains Mono / Fira Code 等)
|
||||
|
||||
+11
-5
@@ -323,11 +323,17 @@ async function performCheck(source: CheckSource): Promise<UpdateState> {
|
||||
*/
|
||||
export function runCheck(source: CheckSource = 'manual'): Promise<UpdateState> {
|
||||
// A manual check is an explicit act, so whatever it finds counts as news even
|
||||
// if this run has already dealt with that version: the suppression record is
|
||||
// dropped here — before the in-flight guard, so a click that joins a check
|
||||
// already running gets the same treatment. (A manual DOWNLOAD needs no
|
||||
// equivalent: its entry point never consults the record.)
|
||||
if (source === 'manual') actedRelease = undefined
|
||||
// if this run has already announced that version: the announcement half of the
|
||||
// record is dropped here — before the in-flight guard, so a click that joins a
|
||||
// check already running gets the same treatment. The DOWNLOADED half is kept:
|
||||
// for a release whose package sits on disk, "downloaded" *is* the truthful
|
||||
// answer, and dropping it made update-available report `available` again —
|
||||
// folding the About tab's install button back into "download" for a manual
|
||||
// check on an already-fetched release. (A manual DOWNLOAD needs no equivalent:
|
||||
// its entry point never consults the record.)
|
||||
if (source === 'manual') {
|
||||
actedRelease = actedRelease?.downloaded ? actedRelease : undefined
|
||||
}
|
||||
if (checkInFlight) return checkInFlight
|
||||
const task = performCheck(source)
|
||||
checkInFlight = task
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { App as AntdApp, Button, Modal, Space, Typography } from 'antd'
|
||||
import type { ZmodemOfferEvent, ZmodemResponse } from '@shared/ipc'
|
||||
import { t } from '@shared/i18n'
|
||||
import { DEFAULT_LANGUAGE, t, tFor } from '@shared/i18n'
|
||||
import { useSettingsStore } from '../settings/store'
|
||||
|
||||
/**
|
||||
* M6 ZMODEM offer/done UX: renders one dialog per active zmodem offer.
|
||||
@@ -15,6 +16,10 @@ import { t } from '@shared/i18n'
|
||||
*/
|
||||
export default function ZmodemOffers(): null | React.JSX.Element {
|
||||
const { message } = AntdApp.useApp()
|
||||
// Rendered as App's sibling: a language switch re-renders App, but the parent's
|
||||
// children element is identical, so this subtree bails out and would keep the
|
||||
// old language. Subscribe here and translate with the explicit language.
|
||||
const language = useSettingsStore((s) => s.settings.system.language ?? DEFAULT_LANGUAGE)
|
||||
const [offers, setOffers] = useState<Map<string, ZmodemOfferEvent>>(new Map())
|
||||
const busyRef = useRef<Set<string>>(new Set())
|
||||
|
||||
@@ -117,21 +122,21 @@ export default function ZmodemOffers(): null | React.JSX.Element {
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
keyboard={false}
|
||||
title={isReceive ? t('ssh.zmodem.titleReceive') : t('ssh.zmodem.titleSend')}
|
||||
title={isReceive ? tFor(language, 'ssh.zmodem.titleReceive') : tFor(language, 'ssh.zmodem.titleSend')}
|
||||
footer={
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => (isReceive ? void handlePickDir(first.id) : void handlePickFiles(first.id))}
|
||||
>
|
||||
{isReceive ? t('ssh.zmodem.pickDir') : t('ssh.zmodem.pickFiles')}
|
||||
{isReceive ? tFor(language, 'ssh.zmodem.pickDir') : tFor(language, 'ssh.zmodem.pickFiles')}
|
||||
</Button>
|
||||
<Button onClick={() => handleCancel(first.id)}>{t('common.cancel')}</Button>
|
||||
<Button onClick={() => handleCancel(first.id)}>{tFor(language, 'common.cancel')}</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Typography.Text>
|
||||
{isReceive ? t('ssh.zmodem.receiveDesc') : t('ssh.zmodem.sendDesc')}
|
||||
{isReceive ? tFor(language, 'ssh.zmodem.receiveDesc') : tFor(language, 'ssh.zmodem.sendDesc')}
|
||||
</Typography.Text>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -986,6 +986,28 @@ console.log('a finished download stays finished')
|
||||
ok(s.status === 'available', `a different release starts from 'available' (got '${s.status}')`)
|
||||
ok(s.version === '4.4.2', `and is named (${s.version})`)
|
||||
}
|
||||
|
||||
// A manual check after a finished download must ALSO keep the downloaded
|
||||
// state — the package is on disk, so "downloaded" is the truthful answer, and
|
||||
// folding the install button back into "download" would make the user fetch
|
||||
// what they already have. The manual record drop only discards announcement
|
||||
// bookkeeping, never the on-disk fact.
|
||||
live.emit('update-downloaded', { version: '4.4.2' })
|
||||
await tick()
|
||||
ok(call(Ipc.UPDATE_STATE_GET).status === 'downloaded', 'the new release finishes too')
|
||||
ctl.checkForUpdates = () => {
|
||||
live.emit('checking-for-update')
|
||||
live.emit('update-available', { version: '4.4.2' })
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
{
|
||||
const state = await call(Ipc.UPDATE_CHECK)
|
||||
ok(
|
||||
state.status === 'downloaded' && state.version === '4.4.2',
|
||||
`a manual check keeps a finished download finished (got '${state.status}')`
|
||||
)
|
||||
ok(state.status !== 'checking', 'and is not parked on the spinner')
|
||||
}
|
||||
}
|
||||
|
||||
console.log('balloons respect the visible-window guard')
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
if (!tag) return null;
|
||||
var t = String(tag).replace(/_/g, '-');
|
||||
if (/^zh-?(tw|hk|mo)/i.test(t)) return 'zh-TW';
|
||||
// zh-Hant-* is Traditional by definition (kept in sync with the head script).
|
||||
if (/^zh-hant/i.test(t)) return 'zh-TW';
|
||||
if (/^zh/i.test(t)) return 'zh-CN';
|
||||
if (/^ja/i.test(t)) return 'ja';
|
||||
if (/^en/i.test(t)) return 'en';
|
||||
|
||||
+9
-1
@@ -27,6 +27,9 @@
|
||||
if (!tag) return null;
|
||||
var t = String(tag).replace(/_/g, '-');
|
||||
if (/^zh-?(tw|hk|mo)/i.test(t)) return 'zh-TW';
|
||||
// BCP-47 can carry a script subtag (zh-Hant-TW, zh-Hant-HK, zh-Hant):
|
||||
// some browsers report those forms, and they are Traditional by definition.
|
||||
if (/^zh-hant/i.test(t)) return 'zh-TW';
|
||||
if (/^zh/i.test(t)) return 'zh-CN';
|
||||
if (/^ja/i.test(t)) return 'ja';
|
||||
if (/^en/i.test(t)) return 'en';
|
||||
@@ -35,7 +38,12 @@
|
||||
function pickLang() {
|
||||
var m = /[?&]lang=([^&]+)/.exec(location.search);
|
||||
if (m) {
|
||||
var fromUrl = normalize(decodeURIComponent(m[1]));
|
||||
// A truncated % escape in a shared link must not throw inside this
|
||||
// first-frame IIFE: a throw here would skip setting html[lang] AND the
|
||||
// pending flag, leaving the page stuck on the default language.
|
||||
var raw = null;
|
||||
try { raw = decodeURIComponent(m[1]); } catch (e) {}
|
||||
var fromUrl = normalize(raw);
|
||||
if (fromUrl) return fromUrl;
|
||||
}
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user