修复了重复日历第一次不显示的问题。
This commit is contained in:
@@ -153,11 +153,14 @@ export class CalendarDataService {
|
||||
rows.push(m);
|
||||
}
|
||||
}
|
||||
// 单次覆盖实例(RECURRENCE-ID 独立行):展开时跳过对应发生,避免重复
|
||||
// 单次覆盖实例(RECURRENCE-ID 独立行):展开时跳过对应发生,避免重复。
|
||||
// 注意:①排除键必须带 calKey(同一系列可能出现在多个日历本);
|
||||
// ②只有"可见日历本"的覆盖行才参与排除——历史孤儿行(旧账号迁移残留)
|
||||
// 自己不显示,却会把主事件的对应发生排除掉,导致"第一次不显示"
|
||||
const overrideKeys: string[] = [];
|
||||
for (const e of rows) {
|
||||
if (e.rrule === '' && e.recurring) {
|
||||
overrideKeys.push(`${e.uid}_${e.startTime}`);
|
||||
if (e.rrule === '' && e.recurring && visibleKeys.includes(e.calKey)) {
|
||||
overrideKeys.push(`${e.calKey}_${e.uid}_${e.startTime}`);
|
||||
}
|
||||
}
|
||||
let occTotal: number = 0;
|
||||
@@ -181,7 +184,8 @@ export class CalendarDataService {
|
||||
}
|
||||
const occs: number[] = RruleUtil.expand(e.rrule, e.startTime, start, end, exNums, 1500);
|
||||
if (occs.length > 0) {
|
||||
times = occs.filter((occ: number): boolean => !overrideKeys.includes(`${e.uid}_${occ}`));
|
||||
times = occs.filter((occ: number): boolean =>
|
||||
!overrideKeys.includes(`${e.calKey}_${e.uid}_${occ}`));
|
||||
}
|
||||
}
|
||||
for (const occ of times) {
|
||||
|
||||
@@ -121,13 +121,15 @@ export class CardDataService {
|
||||
const end: number = start + 60 * 86400000;
|
||||
const sources = await CalendarDataService.loadSources(context);
|
||||
const events: DisplayEvent[] = await CalendarDataService.loadEvents(context, start, end, sources);
|
||||
// 1) 过滤已结束超过 1 小时的;2) 按天分组,组内全天在前、有时间按开始时间排序;
|
||||
// 1) 过滤:保留"今天 0 点以来"的日程(今天已结束的也显示,否则重复日程的
|
||||
// 第一次发生会被隐藏,表现为"第一次不显示、第二次及以后显示");
|
||||
// 2) 按天分组,组内全天在前、有时间按开始时间排序;
|
||||
// 3) 平铺输出并给每组第一条打 showDate 标记(日期头只跟在自己日期前面)
|
||||
// 跨天日程:已开始未结束的归到"今天"、按全天显示,不再从开始那天重复显示
|
||||
const upcoming: DisplayEvent[] = events
|
||||
.filter((e: DisplayEvent): boolean => e.endTime >= now.getTime() - 3600000);
|
||||
|
||||
const todayKey: number = CardDataService.startOfDay(now.getTime());
|
||||
const upcoming: DisplayEvent[] = events
|
||||
.filter((e: DisplayEvent): boolean => e.endTime >= todayKey);
|
||||
|
||||
const dayKey = (ms: number): number => CardDataService.startOfDay(ms);
|
||||
const spansDays = (e: DisplayEvent): boolean =>
|
||||
dayKey(e.endTime) > dayKey(e.startTime);
|
||||
|
||||
@@ -368,8 +368,8 @@ export class EventDb {
|
||||
await store.insert('events', EventDb.toBucket(e));
|
||||
added++;
|
||||
} else if (found.etag !== r.etag || found.rrule !== r.rrule || found.exdate !== r.exdate
|
||||
|| found.reminder !== r.reminder) {
|
||||
// 更新(rrule/exdate 变化也更新,兼容老数据回填)
|
||||
|| found.reminder !== r.reminder || found.recurring !== r.recurring) {
|
||||
// 更新(rrule/exdate/recurring 变化也更新,兼容老数据回填)
|
||||
found.title = r.title;
|
||||
found.description = r.description;
|
||||
found.location = r.location;
|
||||
@@ -417,6 +417,26 @@ export class EventDb {
|
||||
await store.delete(predicates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理孤儿行:calKey 既不是本机日历(local)、也不属于任何现有账号(accId_ 前缀)的历史残留数据。
|
||||
* 典型来源:账号删除重建、日历重选后序号变化遗留的旧 calKey(如 `_13`、`acc1789274273124_0_13`)。
|
||||
* 这些行会参与重复日程的 override 排除计算,导致重复日程的第一次发生不显示,必须物理删除。
|
||||
* 匹配规则为前缀包含(NOT LIKE 'accId_%' 的 AND 组合),宁可少删不会误删有效数据。
|
||||
*/
|
||||
static async pruneOrphanCalKeys(context: common.Context, accIds: string[]): Promise<number> {
|
||||
const store = await EventDb.getDb(context);
|
||||
const predicates = new relationalStore.RdbPredicates('events');
|
||||
predicates.notEqualTo('cal_key', 'local');
|
||||
for (const id of accIds) {
|
||||
if (id === '') {
|
||||
continue;
|
||||
}
|
||||
predicates.and().notLike('cal_key', `${id}_%`);
|
||||
}
|
||||
const removed: number = await store.delete(predicates);
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新勾选日历本后清理失效数据:
|
||||
* 删除该账号下 calKey 不在有效列表中的本地日程/待办(calKey = accId_序号,重选后序号会变)
|
||||
|
||||
@@ -91,7 +91,7 @@ export class IcsUtil {
|
||||
current.description = IcsUtil.unescape(value);
|
||||
} else if (propName === 'LOCATION') {
|
||||
current.location = IcsUtil.unescape(value);
|
||||
} else if (propName === 'RRULE' || propName === 'RECURREIENCE-ID') {
|
||||
} else if (propName === 'RRULE' || propName === 'RECURRENCE-ID') {
|
||||
current.recurring = true;
|
||||
if (propName === 'RRULE') {
|
||||
current.rrule = value;
|
||||
|
||||
@@ -108,6 +108,13 @@ export class RruleUtil {
|
||||
if (targets.length === 0) {
|
||||
targets.push(base.getDay());
|
||||
}
|
||||
// RFC 5545:DTSTART 始终是第一次发生——即使它的星期不在 BYDAY 中
|
||||
// (例如周二创建的"每周一重复"日程,第一次是周二那次,必须显示)
|
||||
if (byday.length > 0 && !targets.includes(base.getDay())) {
|
||||
if (!accept(dtstart)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
const anchorWeek: number = RruleUtil.weekStartMs(dtstart);
|
||||
let dayMs: number = baseZeroMs;
|
||||
let guard: number = 0;
|
||||
@@ -129,6 +136,13 @@ export class RruleUtil {
|
||||
let y: number = base.getFullYear();
|
||||
let m: number = base.getMonth();
|
||||
let guard: number = 0;
|
||||
// RFC 5545:DTSTART 始终是第一次发生——即使它的日期不在 BYMONTHDAY 中
|
||||
if (bymonthday.length > 0 &&
|
||||
!bymonthday.some((v: number): boolean => v === base.getDate())) {
|
||||
if (!accept(dtstart)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
while (guard < 1500) {
|
||||
guard++;
|
||||
const monthFirst: number = new Date(y, m, 1).getTime();
|
||||
|
||||
@@ -201,4 +201,23 @@ export class SyncEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理孤儿日程行:calKey 不属于任何现有账号(accId_ 前缀)也不是本机日历(local)的历史残留。
|
||||
* 典型如账号重建/日历重选后遗留的 `_13`、`acc1789274273124_0_13` 等,
|
||||
* 这些行会参与重复日程的 override 排除计算,导致重复日程第一次发生不显示。
|
||||
* 建议在所有账号同步完成后调用。
|
||||
*/
|
||||
static async pruneOrphanRows(context: common.Context, accounts: DavAccount[]): Promise<void> {
|
||||
try {
|
||||
const accIds: string[] = accounts.map((a: DavAccount): string => a.id);
|
||||
const removed: number = await EventDb.pruneOrphanCalKeys(context, accIds);
|
||||
if (removed > 0) {
|
||||
LogUtil.write(`清理孤儿日程行:${removed} 条(calKey 不属于任何现有账号)`);
|
||||
}
|
||||
} catch (err) {
|
||||
const e = err as BusinessError;
|
||||
LogUtil.write(`清理孤儿日程行失败(忽略):${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user