diff --git a/package-lock.json b/package-lock.json index 92e0efe..9fa9e04 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "git-file-sync", - "version": "1.5.9", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "git-file-sync", - "version": "1.5.9", + "version": "1.6.0", "license": "MIT", "dependencies": { "ignore": "^7.0.6", diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index 975d2c9..b55243d 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -178,7 +178,6 @@ const en = { 'sourceControl.filter.changes': 'Changes', 'sourceControl.filter.local': 'Local', 'sourceControl.filter.remote': 'Incoming', - 'sourceControl.filter.conflict': 'Conflict', 'sourceControl.filter.readyToPush': 'Ready to Push', 'sourceControl.filter.remoteChanges': 'Incoming', 'sourceControl.filter.conflicts': 'Conflicts', @@ -216,10 +215,8 @@ const en = { 'sourceControl.detail.back': 'Back', 'sourceControl.mobile.filesSelected': '{count} files selected', 'sourceControl.mobile.sync': 'Sync', - 'sourceControl.info.lastSync': 'Last sync: {time}', 'sourceControl.info.lastChecked': 'Last checked: {time}', 'sourceControl.info.justChecked': 'Last checked: just now', - 'sourceControl.info.neverSynced': 'Never synced', 'sourceControl.search.placeholder': 'Filter by path…', 'sourceControl.search.clear': 'Clear filter', 'sourceControl.folder.selectAll': 'Select all in folder', diff --git a/src/i18n/locales/zh-cn.ts b/src/i18n/locales/zh-cn.ts index 6b1ef3f..21126ae 100644 --- a/src/i18n/locales/zh-cn.ts +++ b/src/i18n/locales/zh-cn.ts @@ -180,7 +180,6 @@ const zhCn: Partial> = { 'sourceControl.filter.changes': '更改', 'sourceControl.filter.local': '本地', 'sourceControl.filter.remote': '传入', - 'sourceControl.filter.conflict': '冲突', 'sourceControl.filter.readyToPush': '待推送', 'sourceControl.filter.remoteChanges': '传入', 'sourceControl.filter.conflicts': '冲突', @@ -218,10 +217,8 @@ const zhCn: Partial> = { 'sourceControl.detail.back': '返回', 'sourceControl.mobile.filesSelected': '已选 {count} 个文件', 'sourceControl.mobile.sync': '同步', - 'sourceControl.info.lastSync': '上次同步:{time}', 'sourceControl.info.lastChecked': '上次检查:{time}', 'sourceControl.info.justChecked': '上次检查:刚刚', - 'sourceControl.info.neverSynced': '尚未同步', 'sourceControl.search.placeholder': '按路径过滤…', 'sourceControl.search.clear': '清除过滤', 'sourceControl.folder.selectAll': '选取文件夹内全部项目', diff --git a/src/i18n/locales/zh-tw.ts b/src/i18n/locales/zh-tw.ts index 800c56a..71973a7 100644 --- a/src/i18n/locales/zh-tw.ts +++ b/src/i18n/locales/zh-tw.ts @@ -180,7 +180,6 @@ const zhTw: Partial> = { 'sourceControl.filter.changes': '變更', 'sourceControl.filter.local': '本地', 'sourceControl.filter.remote': '傳入', - 'sourceControl.filter.conflict': '衝突', 'sourceControl.filter.readyToPush': '待推送', 'sourceControl.filter.remoteChanges': '傳入', 'sourceControl.filter.conflicts': '衝突', @@ -218,10 +217,8 @@ const zhTw: Partial> = { 'sourceControl.detail.back': '返回', 'sourceControl.mobile.filesSelected': '已選 {count} 個檔案', 'sourceControl.mobile.sync': '同步', - 'sourceControl.info.lastSync': '上次同步:{time}', 'sourceControl.info.lastChecked': '上次檢查:{time}', 'sourceControl.info.justChecked': '上次檢查:剛剛', - 'sourceControl.info.neverSynced': '尚未同步', 'sourceControl.search.placeholder': '以路徑過濾…', 'sourceControl.search.clear': '清除過濾', 'sourceControl.folder.selectAll': '選取資料夾內全部項目', diff --git a/src/ui/source-control/FilterMenu.ts b/src/ui/source-control/FilterMenu.ts index 6208dec..1abd5c7 100644 --- a/src/ui/source-control/FilterMenu.ts +++ b/src/ui/source-control/FilterMenu.ts @@ -15,7 +15,7 @@ import type { SourceControlCounts } from '../../logic/source-control/SourceContr * bucket in the view (see `SourceControlView`) rather than via a domain * change. Surfaced as an opt-in overview; the default stays on Needs Sync * so a quiet workspace stays quiet. - * - **Incoming / Conflict / Synced** — the matching domain filters (chip id stays `remote`; the label reads "Incoming" — a file only on the remote, or changed only on the remote, is something coming *in*). + * - **Incoming / Synced** — the matching domain filters (chip id stays `remote`; the label reads "Incoming" — a file only on the remote, or changed only on the remote, is something coming *in*). * * "Local" (domain `changes`) is intentionally not a chip: Needs Sync already * covers local-side changes, and a standalone local-only view added a @@ -30,12 +30,11 @@ export interface FilterChip { count: (counts: SourceControlCounts) => number; } -/** The five filter chips, in display order. */ +/** The four filter chips, in display order. */ export const FILTER_CHIPS: readonly FilterChip[] = [ { id: 'all', filter: 'all', showSynced: true, labelKey: 'sourceControl.filter.all', count: c => c.all + c.synced }, { id: 'needsSync', filter: 'all', showSynced: false, labelKey: 'sourceControl.filter.needsSync', count: c => c.all }, { id: 'remote', filter: 'remote-changes', showSynced: false, labelKey: 'sourceControl.filter.remote', count: c => c['remote-changes'] }, - { id: 'conflict', filter: 'conflicts', showSynced: false, labelKey: 'sourceControl.filter.conflict', count: c => c.conflicts }, { id: 'synced', filter: 'synced', showSynced: true, labelKey: 'sourceControl.filter.synced', count: c => c.synced }, ]; @@ -55,8 +54,8 @@ export interface FilterMenuOptions { } /** - * Renders the Source Control filter row: five chips — All / Needs Sync / - * Incoming / Conflict / Synced. On mobile a single `` dropdown * replaces the chips (same chip ids, counts inline as "Label (N)"). * * Per-filter counts come straight from the ViewModel's single-source counts diff --git a/src/ui/source-control/SourceControlHeader.ts b/src/ui/source-control/SourceControlHeader.ts index 7d7431a..9ef6cdd 100644 --- a/src/ui/source-control/SourceControlHeader.ts +++ b/src/ui/source-control/SourceControlHeader.ts @@ -8,8 +8,6 @@ export interface SourceControlWorkspaceInfo { serviceName: string; branch: string; vaultFolder: string; - /** Epoch ms of the most recent successful push/pull, or 0 if nothing has synced yet. */ - lastSyncTime: number; /** * Epoch ms the Source Control view last completed a status refresh * (any reason — manual, startup, local-change), or 0 if it hasn't @@ -104,14 +102,6 @@ function renderInfoStrip(container: HTMLElement, info: SourceControlWorkspaceInf folder.createSpan({ text: ` ${info.vaultFolder}` }); } - strip.createSpan({ cls: 'scv-info-sep', text: '·' }); - strip.createSpan({ - cls: 'scv-info-time', - text: info.lastSyncTime > 0 - ? t('sourceControl.info.lastSync', { time: new Date(info.lastSyncTime).toLocaleTimeString() }) - : t('sourceControl.info.neverSynced'), - }); - if (info.lastCheckedAt > 0) { strip.createSpan({ cls: 'scv-info-sep', text: '·' }); const elapsed = Date.now() - info.lastCheckedAt; diff --git a/src/ui/source-control/SourceControlItemView.ts b/src/ui/source-control/SourceControlItemView.ts index 6a583b4..a808987 100644 --- a/src/ui/source-control/SourceControlItemView.ts +++ b/src/ui/source-control/SourceControlItemView.ts @@ -188,10 +188,8 @@ export class SourceControlItemView extends ItemView { private getWorkspaceInfo(): SourceControlWorkspaceInfo { const info = this.plugin.syncWorkspace.getInfo(); - const lastSyncTime = Object.values(this.plugin.settings.syncMetadata) - .reduce((latest, metadata) => Math.max(latest, metadata.lastSyncedAt), 0); const lastCheckedAt = this.plugin.refreshState.getLastCheckedAt(); - return { ...info, lastSyncTime, lastCheckedAt }; + return { ...info, lastCheckedAt }; } getViewType(): string { return SOURCE_CONTROL_VIEW_TYPE; } diff --git a/tests/ui/source-control/FilterMenu.test.ts b/tests/ui/source-control/FilterMenu.test.ts index 02339e4..c604cc8 100644 --- a/tests/ui/source-control/FilterMenu.test.ts +++ b/tests/ui/source-control/FilterMenu.test.ts @@ -19,18 +19,18 @@ describe('renderFilterMenu', () => { callbacks = { onFilterChange: vi.fn() }; }); - it('renders the five chips (All/Needs Sync/Incoming/Conflict/Synced)', () => { + it('renders the four chips (All/Needs Sync/Incoming/Synced)', () => { renderFilterMenu(container, { filter: 'all', showSynced: false }, zeroCounts, callbacks); const chips = Array.from(container.querySelectorAll('.scv-filter-option')).map(el => el.getAttribute('data-filter')); - expect(chips).toEqual(['all', 'needsSync', 'remote', 'conflict', 'synced']); + expect(chips).toEqual(['all', 'needsSync', 'remote', 'synced']); }); it('labels the chips with their display names', () => { renderFilterMenu(container, { filter: 'all', showSynced: false }, zeroCounts, callbacks); const labels = Array.from(container.querySelectorAll('.scv-filter-option .scv-filter-label')).map(el => el.textContent); - expect(labels).toEqual(['All', 'Needs Sync', 'Incoming', 'Conflict', 'Synced']); + expect(labels).toEqual(['All', 'Needs Sync', 'Incoming', 'Synced']); }); it('marks the current (filter, showSynced) chip as active', () => { @@ -66,6 +66,6 @@ describe('renderFilterMenu', () => { expect(container.querySelector('.scv-filter-dropdown')).not.toBeNull(); expect(container.querySelector('.scv-filter-option')).toBeNull(); const options = Array.from(container.querySelectorAll('.scv-filter-dropdown option')).map(o => (o as HTMLOptionElement).value); - expect(options).toEqual(['all', 'needsSync', 'remote', 'conflict', 'synced']); + expect(options).toEqual(['all', 'needsSync', 'remote', 'synced']); }); }); \ No newline at end of file diff --git a/tests/ui/source-control/SourceControlView.test.ts b/tests/ui/source-control/SourceControlView.test.ts index 221e1c0..29a9892 100644 --- a/tests/ui/source-control/SourceControlView.test.ts +++ b/tests/ui/source-control/SourceControlView.test.ts @@ -25,7 +25,6 @@ function buildView(changes: SyncChange[], callbacks: Partial { it('shows a flat tree (no sections) once a specific filter is selected', () => { const { view } = buildView([ - { id: toChangeId('c-1'), path: 'a.md', kind: 'conflict' }, + { id: toChangeId('c-1'), path: 'a.md', kind: 'remote-only' }, { id: toChangeId('c-2'), path: 'b.md', kind: 'local-only' }, ]); view.render(container); - (container.querySelector('.scv-filter-option[data-filter="conflict"]') as HTMLButtonElement).click(); + (container.querySelector('.scv-filter-option[data-filter="remote"]') as HTMLButtonElement).click(); expect(container.querySelectorAll('.scv-section')).toHaveLength(0); expect(container.querySelectorAll('.scv-change-item')).toHaveLength(1); - expect(view.getFilter()).toBe('conflicts'); + expect(view.getFilter()).toBe('remote-changes'); }); it('shows the empty state when the active filter has no items', () => { const { view } = buildView([{ id: toChangeId('c-1'), path: 'a.md', kind: 'local-only' }]); view.render(container); - (container.querySelector('.scv-filter-option[data-filter="conflict"]') as HTMLButtonElement).click(); + (container.querySelector('.scv-filter-option[data-filter="remote"]') as HTMLButtonElement).click(); expect(container.querySelector('.scv-empty')).not.toBeNull(); }); @@ -1284,7 +1282,7 @@ describe('SourceControlView', () => { const view = new SourceControlView( viewModel, { onSync: vi.fn(), onRefresh: vi.fn() }, - () => ({ serviceName: 'GitHub', branch: 'main', vaultFolder: '', lastSyncTime: 0, lastCheckedAt }), + () => ({ serviceName: 'GitHub', branch: 'main', vaultFolder: '', lastCheckedAt }), ); return { view, refreshState }; } @@ -1294,9 +1292,7 @@ describe('SourceControlView', () => { view.render(container); const infoTimes = container.querySelectorAll('.scv-info-time'); - // Only the "Never synced" line is present; no "Last checked" line. - expect(infoTimes).toHaveLength(1); - expect(infoTimes[0]?.textContent).toBe('Never synced'); + expect(infoTimes).toHaveLength(0); }); it('shows "Last checked: just now" when the last refresh was within a minute', () => { @@ -1304,8 +1300,8 @@ describe('SourceControlView', () => { view.render(container); const infoTimes = container.querySelectorAll('.scv-info-time'); - expect(infoTimes).toHaveLength(2); - expect(infoTimes[1]?.textContent).toBe('Last checked: just now'); + expect(infoTimes).toHaveLength(1); + expect(infoTimes[0]?.textContent).toBe('Last checked: just now'); }); it('shows "Last checked: