mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
fix: guard regeneration and scroll sync
Change-Id: I08e2c55d5e88a51bad9a007e57962ebe48dc7c64
This commit is contained in:
parent
5c2c97fdbe
commit
206a50f8d0
6 changed files with 59 additions and 22 deletions
38
main.js
38
main.js
File diff suppressed because one or more lines are too long
20
main.ts
20
main.ts
|
|
@ -5,7 +5,7 @@ import os from 'os';
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { findLineForAnchor } from './src/anchor';
|
||||
import { serializeCacheFile, touchCacheEntry } from './src/cache';
|
||||
import { serializeCacheFile, shouldConfirmRegenerate, touchCacheEntry } from './src/cache';
|
||||
import { activeIndexAfterCardDelete, removeCardAt, updateCardAt } from './src/cards';
|
||||
import { translate } from './src/i18n';
|
||||
import { cardToMarkdown, cardToPlain, cardsToMarkdown } from './src/markdown';
|
||||
|
|
@ -17,7 +17,7 @@ import {
|
|||
normalizeCardsPayload,
|
||||
parseCardsJson,
|
||||
} from './src/schema';
|
||||
import { createRafThrottledHandler } from './src/scroll';
|
||||
import { createRafThrottledHandler, visibleTopProbeY } from './src/scroll';
|
||||
import {
|
||||
buildAnthropicMessagesBody,
|
||||
buildGeminiBody,
|
||||
|
|
@ -1065,6 +1065,14 @@ class ParallelReaderPlugin extends Plugin {
|
|||
else new Notice(this.t('noCancelableJob'));
|
||||
}
|
||||
|
||||
confirmRegenerateEditedCards() {
|
||||
const message = this.t('confirmRegenerateEditedCards');
|
||||
if (typeof window !== 'undefined' && typeof window.confirm === 'function') {
|
||||
return window.confirm(message);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
addFileMenuItems(menu, file) {
|
||||
if (!(file instanceof TFile) || !file.path.endsWith('.md')) return;
|
||||
menu.addSeparator();
|
||||
|
|
@ -1176,6 +1184,10 @@ class ParallelReaderPlugin extends Plugin {
|
|||
new Notice(this.t('alreadyGenerating'));
|
||||
return;
|
||||
}
|
||||
if (shouldConfirmRegenerate(this.cacheGet(file.path), force) && !this.confirmRegenerateEditedCards()) {
|
||||
new Notice(this.t('regenerateCancelled'));
|
||||
return false;
|
||||
}
|
||||
|
||||
let view = null;
|
||||
return this.jobs.start(runningKey, async job => {
|
||||
|
|
@ -1337,7 +1349,7 @@ class ParallelReaderPlugin extends Plugin {
|
|||
|
||||
// Find visible top line
|
||||
const rect = scrollDom.getBoundingClientRect();
|
||||
const topY = rect.top + 80; // offset to pick line just under the header
|
||||
const topY = visibleTopProbeY(rect);
|
||||
let topLine = 0;
|
||||
try {
|
||||
const pos = cm.posAtCoords({ x: rect.left + 20, y: topY });
|
||||
|
|
@ -1770,9 +1782,11 @@ export const __test = {
|
|||
pruneCacheEntries,
|
||||
removeCardAt,
|
||||
serializeCacheFile,
|
||||
shouldConfirmRegenerate,
|
||||
summarizeViaApi,
|
||||
touchCacheEntry,
|
||||
translate,
|
||||
tokenLimitFieldForOpenAiChat,
|
||||
updateCardAt,
|
||||
visibleTopProbeY,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,3 +12,7 @@ export function serializeCacheFile(entries) {
|
|||
entries: entries || {},
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldConfirmRegenerate(entry, force) {
|
||||
return !!force && !!entry && typeof entry.updatedAt === 'string' && entry.updatedAt.trim().length > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ export const STRINGS = {
|
|||
noExportContent: '当前没有可导出的对照笔记',
|
||||
noCopyContent: '当前没有可复制的对照笔记',
|
||||
noActiveCard: '当前没有可跳转的摘要卡片',
|
||||
confirmRegenerateEditedCards: '这篇笔记的对照卡片已被手动编辑。重新生成会覆盖这些修改,是否继续?',
|
||||
regenerateCancelled: '已取消重新生成',
|
||||
cardDeleted: '已删除此卡片',
|
||||
cardSaved: '已保存此卡片',
|
||||
editCardTitle: '编辑摘要卡片',
|
||||
|
|
@ -194,6 +196,8 @@ export const STRINGS = {
|
|||
noExportContent: 'No parallel notes to export',
|
||||
noCopyContent: 'No parallel notes to copy',
|
||||
noActiveCard: 'No active summary card to jump',
|
||||
confirmRegenerateEditedCards: 'These parallel-reader cards were edited manually. Regenerating will overwrite those edits. Continue?',
|
||||
regenerateCancelled: 'Regeneration cancelled',
|
||||
cardDeleted: 'Deleted this card',
|
||||
cardSaved: 'Saved this card',
|
||||
editCardTitle: 'Edit summary card',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
export type RafThrottledHandler = (() => void) & { cancel: () => void };
|
||||
|
||||
export function visibleTopProbeY(rect, maxOffset = 80, ratio = 0.1) {
|
||||
const top = Number(rect?.top) || 0;
|
||||
const height = Math.max(0, Number(rect?.height) || 0);
|
||||
const cappedOffset = Math.min(Number(maxOffset) || 0, height * ratio);
|
||||
return top + Math.max(0, cappedOffset);
|
||||
}
|
||||
|
||||
function defaultSchedule(callback: FrameRequestCallback): number {
|
||||
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback);
|
||||
return setTimeout(() => callback(Date.now()), 16) as unknown as number;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@ assert.strictEqual(typeof t.pruneCacheEntries, 'function');
|
|||
assert.strictEqual(typeof t.removeCardAt, 'function');
|
||||
assert.strictEqual(typeof t.activeIndexAfterCardDelete, 'function');
|
||||
assert.strictEqual(typeof t.createRafThrottledHandler, 'function');
|
||||
assert.strictEqual(typeof t.visibleTopProbeY, 'function');
|
||||
assert.strictEqual(typeof t.serializeCacheFile, 'function');
|
||||
assert.strictEqual(typeof t.shouldConfirmRegenerate, 'function');
|
||||
assert.strictEqual(typeof t.translate, 'function');
|
||||
assert.strictEqual(typeof t.updateCardAt, 'function');
|
||||
|
||||
|
|
@ -246,6 +248,8 @@ scheduledFrames.shift()(0);
|
|||
assert.strictEqual(throttledCalls, 1);
|
||||
throttled();
|
||||
assert.strictEqual(scheduledFrames.length, 1, 'scroll handler should be schedulable after the frame runs');
|
||||
assert.strictEqual(t.visibleTopProbeY({ top: 100, height: 300 }), 130);
|
||||
assert.strictEqual(t.visibleTopProbeY({ top: 100, height: 1200 }), 180);
|
||||
const cardList = [{ title: 'A' }, { title: 'B' }, { title: 'C' }];
|
||||
assert.deepStrictEqual(t.removeCardAt(cardList, 1), [{ title: 'A' }, { title: 'C' }]);
|
||||
assert.deepStrictEqual(t.removeCardAt(cardList, -1), cardList);
|
||||
|
|
@ -270,6 +274,10 @@ const untouchedCacheEntry = { generatedAt: '2024-01-01T00:00:00.000Z' };
|
|||
assert.strictEqual(t.touchCacheEntry(null), null);
|
||||
assert.strictEqual(t.touchCacheEntry(untouchedCacheEntry, '2024-01-05T00:00:00.000Z'), untouchedCacheEntry);
|
||||
assert.strictEqual(untouchedCacheEntry.lastAccessedAt, '2024-01-05T00:00:00.000Z');
|
||||
assert.strictEqual(t.shouldConfirmRegenerate({ updatedAt: '2024-01-05T00:00:00.000Z' }, true), true);
|
||||
assert.strictEqual(t.shouldConfirmRegenerate({ updatedAt: '2024-01-05T00:00:00.000Z' }, false), false);
|
||||
assert.strictEqual(t.shouldConfirmRegenerate({ generatedAt: '2024-01-01T00:00:00.000Z' }, true), false);
|
||||
assert.strictEqual(t.shouldConfirmRegenerate(null, true), false);
|
||||
const serializedCache = t.serializeCacheFile({
|
||||
'note.md': { generatedAt: '2024-01-01T00:00:00.000Z', cards: [{ title: 'A' }] },
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue