ckelsoe_obsidian-rss-importer/duplicate-prompt-modal.ts
Charles Kelsoe 9e563d8ffe Phase 5: apply adversarial-review fixes
Correctness: resolver classifies /p/ as Substack only on a substack.com host;
redirect cap aligned to 5; extractFeedItemId unescapes in a single pass (round
trips literal backslash escapes). Feature: podcast/media notes now record
media-url frontmatter and append an [Episode audio]/[Media] body link. Safety:
the import command guards synchronous NoteWriter construction (a bad destination
folder now surfaces a Notice, not a silent throw); loadSettings validates
per-feed literal fields from data.json. Diagnosability: empty-body imports and
skipped image downloads are logged. Structure: FeedPickerModal and
DuplicatePromptModal moved to their own files; id non-empty guard at the mapping
boundary; doc + type-dedup cleanups. 244 tests; build + lint green.

Deferred (low/cosmetic): shared header helper, Atom updated-vs-published date
precedence, the sanctioned settings-tab cast.
2026-06-14 15:26:45 -04:00

66 lines
1.6 KiB
TypeScript

// Asks the user whether to overwrite, skip, or cancel when a same-item note
// already exists and the duplicate policy is "prompt".
import { App, Modal, Setting } from "obsidian";
import type {
DuplicatePromptContext,
DuplicatePromptDecision,
} from "./note-writer";
export class DuplicatePromptModal extends Modal {
private readonly context: DuplicatePromptContext;
private readonly resolve: (decision: DuplicatePromptDecision) => void;
private decided = false;
constructor(
app: App,
context: DuplicatePromptContext,
resolve: (decision: DuplicatePromptDecision) => void,
) {
super(app);
this.context = context;
this.resolve = resolve;
}
onOpen(): void {
this.setTitle("Note already exists");
const { contentEl } = this;
contentEl.createEl("p", {
text: `A note for "${this.context.itemTitle}" already exists at ${this.context.targetPath}. Overwrite it?`,
});
new Setting(contentEl)
.addButton((btn) =>
btn
.setButtonText("Overwrite")
.setDestructive()
.onClick(() => {
this.settle("overwrite");
}),
)
.addButton((btn) =>
btn.setButtonText("Skip").onClick(() => {
this.settle("skip");
}),
)
.addButton((btn) =>
btn.setButtonText("Cancel import").onClick(() => {
this.settle("cancel");
}),
);
}
onClose(): void {
// Dismissing the modal without a choice cancels the import.
this.settle("cancel");
this.contentEl.empty();
}
private settle(decision: DuplicatePromptDecision): void {
if (this.decided) {
return;
}
this.decided = true;
this.resolve(decision);
this.close();
}
}