mirror of
https://github.com/fancive/obsidian-parallel-reader.git
synced 2026-07-22 06:53:43 +00:00
feat: validate batch folder input
Change-Id: I137fa046e77781e063ee782af4321b7277d64d51
This commit is contained in:
parent
d8dc82362b
commit
9098de6884
6 changed files with 93 additions and 27 deletions
52
main.js
52
main.js
File diff suppressed because one or more lines are too long
15
main.ts
15
main.ts
|
|
@ -6,6 +6,7 @@ import {
|
|||
batchProgressVars,
|
||||
createBatchRunState,
|
||||
createBatchStats,
|
||||
hasUnsafeBatchFolderSegments,
|
||||
markBatchFileRunning,
|
||||
normalizeBatchFolderInput,
|
||||
recordBatchProcessed,
|
||||
|
|
@ -13,6 +14,7 @@ import {
|
|||
requestBatchCancel,
|
||||
selectBatchFiles,
|
||||
shouldSkipBatchFile,
|
||||
validateBatchFolderInput,
|
||||
} from './src/batch';
|
||||
import { serializeCacheFile, shouldConfirmRegenerate, touchCacheEntry } from './src/cache';
|
||||
import { CacheManager } from './src/cache-manager';
|
||||
|
|
@ -585,7 +587,16 @@ class ParallelReaderPlugin extends Plugin {
|
|||
new FolderPromptModal(plugin.app).open();
|
||||
});
|
||||
if (folderPath === null) return;
|
||||
const allFiles = selectBatchFiles(this.app.vault.getMarkdownFiles(), folderPath);
|
||||
const validation = validateBatchFolderInput(folderPath, (path) => {
|
||||
const target = this.app.vault.getAbstractFileByPath(path);
|
||||
return !!target && !(target instanceof TFile);
|
||||
});
|
||||
if (!validation.valid) {
|
||||
const noticeKey = validation.reason === 'unsafe' ? 'batchInvalidFolderInput' : 'batchFolderNotFound';
|
||||
new Notice(this.t(noticeKey, { path: validation.folderPath || folderPath }));
|
||||
return;
|
||||
}
|
||||
const allFiles = selectBatchFiles(this.app.vault.getMarkdownFiles(), validation.folderPath);
|
||||
if (allFiles.length === 0) {
|
||||
new Notice(this.t('batchNoMarkdown'));
|
||||
return;
|
||||
|
|
@ -772,6 +783,7 @@ export const __test = {
|
|||
createBatchStats,
|
||||
generationFingerprint,
|
||||
getApiBaseUrl,
|
||||
hasUnsafeBatchFolderSegments,
|
||||
markBatchFileRunning,
|
||||
modelForApi,
|
||||
normalizeBatchFolderInput,
|
||||
|
|
@ -793,6 +805,7 @@ export const __test = {
|
|||
translate,
|
||||
tokenLimitFieldForOpenAiChat,
|
||||
updateCardAt,
|
||||
validateBatchFolderInput,
|
||||
visibleTopProbeY,
|
||||
supportsStreaming,
|
||||
deltaExtractorForFormat,
|
||||
|
|
|
|||
25
src/batch.ts
25
src/batch.ts
|
|
@ -19,6 +19,14 @@ export interface BatchRunState {
|
|||
currentPath: string;
|
||||
}
|
||||
|
||||
export type BatchFolderValidationReason = 'ok' | 'unsafe' | 'missing';
|
||||
|
||||
export interface BatchFolderValidation {
|
||||
valid: boolean;
|
||||
reason: BatchFolderValidationReason;
|
||||
folderPath: string;
|
||||
}
|
||||
|
||||
export function normalizeBatchFolderInput(input: string): string {
|
||||
return (input || '')
|
||||
.trim()
|
||||
|
|
@ -28,6 +36,23 @@ export function normalizeBatchFolderInput(input: string): string {
|
|||
.join('/');
|
||||
}
|
||||
|
||||
export function hasUnsafeBatchFolderSegments(input: string): boolean {
|
||||
return (input || '')
|
||||
.split('/')
|
||||
.map((part) => part.trim())
|
||||
.some((part) => part === '.' || part === '..');
|
||||
}
|
||||
|
||||
export function validateBatchFolderInput(
|
||||
input: string,
|
||||
folderExists: (folderPath: string) => boolean,
|
||||
): BatchFolderValidation {
|
||||
const folderPath = normalizeBatchFolderInput(input);
|
||||
if (hasUnsafeBatchFolderSegments(input)) return { valid: false, reason: 'unsafe', folderPath };
|
||||
if (folderPath && !folderExists(folderPath)) return { valid: false, reason: 'missing', folderPath };
|
||||
return { valid: true, reason: 'ok', folderPath };
|
||||
}
|
||||
|
||||
export function isFileInBatchFolder(file: BatchFileLike, folderPath: string): boolean {
|
||||
if (!folderPath) return !file.path.includes('/');
|
||||
return file.parent?.path === folderPath;
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ export const STRINGS: Record<string, Record<string, string>> = {
|
|||
batchFolderPrompt: '文件夹路径',
|
||||
batchAlreadyRunning: '批量生成正在运行',
|
||||
batchCancelRequested: '已请求取消批量生成;当前文件结束后停止',
|
||||
batchInvalidFolderInput: '文件夹路径无效:{path}',
|
||||
batchFolderNotFound: '文件夹不存在:{path}',
|
||||
batchNoMarkdown: '该文件夹下没有 Markdown 文件',
|
||||
batchProgress: '批量生成:{current}/{total}',
|
||||
batchDone: '批量生成完成:共处理 {processed}/{total} 篇,跳过缓存 {skipped} 篇',
|
||||
|
|
@ -311,6 +313,8 @@ export const STRINGS: Record<string, Record<string, string>> = {
|
|||
batchFolderPrompt: 'Folder path',
|
||||
batchAlreadyRunning: 'Batch generation is already running',
|
||||
batchCancelRequested: 'Batch cancellation requested; it will stop after the current note',
|
||||
batchInvalidFolderInput: 'Invalid folder path: {path}',
|
||||
batchFolderNotFound: 'Folder not found: {path}',
|
||||
batchNoMarkdown: 'No Markdown files found in that folder',
|
||||
batchProgress: 'Batch generating: {current}/{total}',
|
||||
batchDone: 'Batch complete: processed {processed}/{total}, skipped {skipped} (cached)',
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ assert.strictEqual(typeof t.findLineForAnchor, 'function');
|
|||
assert.strictEqual(typeof t.folderPathsForTarget, 'function');
|
||||
assert.strictEqual(typeof t.getApiBaseUrl, 'function');
|
||||
assert.strictEqual(typeof t.generationFingerprint, 'function');
|
||||
assert.strictEqual(typeof t.hasUnsafeBatchFolderSegments, 'function');
|
||||
assert.strictEqual(typeof t.CacheManager, 'function');
|
||||
assert.strictEqual(typeof t.GenerationJobManager, 'function');
|
||||
assert.strictEqual(typeof t.createBatchRunState, 'function');
|
||||
|
|
@ -90,6 +91,7 @@ assert.strictEqual(typeof t.serializeCacheFile, 'function');
|
|||
assert.strictEqual(typeof t.shouldConfirmRegenerate, 'function');
|
||||
assert.strictEqual(typeof t.translate, 'function');
|
||||
assert.strictEqual(typeof t.updateCardAt, 'function');
|
||||
assert.strictEqual(typeof t.validateBatchFolderInput, 'function');
|
||||
|
||||
const baseSettings = {
|
||||
backend: 'api',
|
||||
|
|
|
|||
|
|
@ -226,6 +226,28 @@ assert.deepStrictEqual(t.folderPathsForTarget('a/../../b'), ['a', 'a/b'], 'strip
|
|||
|
||||
assert.strictEqual(t.normalizeBatchFolderInput('/Reading//Articles/'), 'Reading/Articles', 'normalizes folder input');
|
||||
assert.strictEqual(t.normalizeBatchFolderInput('../Inbox/./Daily'), 'Inbox/Daily', 'strips unsafe folder segments');
|
||||
assert.strictEqual(t.hasUnsafeBatchFolderSegments('../Inbox/./Daily'), true, 'unsafe folder segments are detected');
|
||||
assert.strictEqual(t.hasUnsafeBatchFolderSegments('/Reading//Articles/'), false, 'normal folder paths are safe');
|
||||
assert.deepStrictEqual(
|
||||
t.validateBatchFolderInput('', () => false),
|
||||
{ valid: true, reason: 'ok', folderPath: '' },
|
||||
'empty batch folder targets vault root',
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
t.validateBatchFolderInput('Reading', (path) => path === 'Reading'),
|
||||
{ valid: true, reason: 'ok', folderPath: 'Reading' },
|
||||
'existing folder input is valid',
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
t.validateBatchFolderInput('../Reading', () => true),
|
||||
{ valid: false, reason: 'unsafe', folderPath: 'Reading' },
|
||||
'unsafe folder input is invalid even if normalized target exists',
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
t.validateBatchFolderInput('Missing', () => false),
|
||||
{ valid: false, reason: 'missing', folderPath: 'Missing' },
|
||||
'missing folder input is invalid',
|
||||
);
|
||||
const batchFiles = [
|
||||
{ path: 'root.md', parent: null },
|
||||
{ path: 'Reading/note.md', parent: { path: 'Reading' } },
|
||||
|
|
|
|||
Loading…
Reference in a new issue