justin-arnold_Journalyst/views/Settings.ts
2026-04-25 20:23:51 -04:00

829 lines
30 KiB
TypeScript

import { App, DropdownComponent, PluginSettingTab, Setting, TFolder } from 'obsidian';
import { JournalNotePropertyBackfillItem } from "../bases";
import { JournalCadenceType } from "../cadence";
import { JournalPromptSettings, PromptDeliveryMode, PromptSelectionMode, PromptSourceType } from "../prompts";
import { SidebarMode } from "../review/types";
import { TemplateEngine } from "../templates/types";
import JournalystPlugin from "../main";
export class JournalystSettingsTab extends PluginSettingTab {
plugin: JournalystPlugin;
private noteDateFormatDraft: string | null = null;
private migrationPreviewFormat: string | null = null;
private readonly migrationPreviewLimit = 24;
private customPromptListDrafts: Record<string, { name: string; body: string }> = {};
private basesBackfillPreview: JournalNotePropertyBackfillItem[] | null = null;
private readonly basesPreviewLimit = 24;
constructor(app: App, plugin: JournalystPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
void this.displayAsync();
}
private async displayAsync() {
const { containerEl } = this;
if (this.noteDateFormatDraft === null) {
this.noteDateFormatDraft = this.plugin.settings.noteDateFormat;
}
const noteDateFormatDraft = this.noteDateFormatDraft;
containerEl.empty();
this.plugin.refreshJournals();
this.renderSectionHeading(containerEl, 'Workspace', 'Choose where Journalyst lives in your vault and how its optional sidebar companion behaves.');
this.renderWorkspaceSettings(containerEl);
this.renderSectionHeading(containerEl, 'Notes and files', 'Control how Journalyst names entry files and handles filename migrations.');
this.renderNoteSettings(containerEl, noteDateFormatDraft);
this.renderSectionHeading(containerEl, 'Journal cadence', 'Define how often each journal is expected so status, review, and reminders stay honest.');
this.renderCadenceSettings(containerEl);
this.renderSectionHeading(containerEl, 'Prompts', 'Keep prompts optional, but use them when you want a little freshness or structure in a journal entry.');
this.renderSubsectionIntro(
containerEl,
'Prompt libraries',
'Create custom prompt lists here, or use Journalyst built-in libraries and markdown note-backed lists when you assign prompts to journals.'
);
await this.renderCustomPromptLists(containerEl);
this.renderSubsectionIntro(
containerEl,
'Journal prompt assignments',
'Choose which journals use prompts, where those prompts come from, and how Journalyst rotates and delivers them.'
);
await this.renderJournalPromptSettings(containerEl);
this.renderSectionHeading(containerEl, 'Templates', 'Pick the journal template engine, choose fallback behavior, and assign per-journal templates.');
await this.renderTemplateSettings(containerEl);
this.renderSectionHeading(containerEl, 'Bases', 'Opt into Journalyst properties and managed starter Bases files for your journals.');
await this.renderBasesSettings(containerEl);
}
private renderSectionHeading(containerEl: HTMLElement, title: string, description?: string) {
if (containerEl.children.length > 0) {
containerEl.createEl('hr');
}
const section = containerEl.createDiv({ cls: 'journalyst-settings-section' });
section.createEl('h3', { text: title });
if (description) {
section.createEl('p', {
text: description,
cls: 'journalyst-settings-section-description',
});
}
}
private renderSubsectionIntro(containerEl: HTMLElement, title: string, description: string) {
const section = containerEl.createDiv({ cls: 'journalyst-settings-subsection' });
section.createEl('h4', { text: title });
section.createEl('p', {
text: description,
cls: 'journalyst-settings-subsection-description',
});
}
private renderWorkspaceSettings(containerEl: HTMLElement) {
new Setting(containerEl)
.setName('Journalyst home directory')
.setDesc('The directory where Journalyst will look for your journals.')
.addDropdown(dropdown => {
this.app.vault.getAllLoadedFiles()
.filter(file => file instanceof TFolder)
.forEach(folder => {
dropdown.addOption(folder.path, folder.path);
});
dropdown.setValue(this.plugin.settings.rootDirectory)
.onChange(async (value) => {
this.plugin.settings.rootDirectory = value;
await this.plugin.saveSettings();
this.plugin.refreshJournals();
this.display();
});
});
new Setting(containerEl)
.setName('Sidebar mode')
.setDesc('Choose whether the optional Journalyst sidebar shows a quick home summary or the journal heatmap launcher.')
.addDropdown(dropdown => {
dropdown.addOption('home-mini', 'Home mini view');
dropdown.addOption('journals-mini', 'Journals mini view');
dropdown.setValue(this.plugin.getSidebarMode())
.onChange(async value => {
await this.plugin.updateSidebarMode(value as SidebarMode);
this.display();
});
});
}
private renderNoteSettings(containerEl: HTMLElement, noteDateFormatDraft: string) {
const noteNameExample = noteDateFormatDraft.trim()
? this.plugin.formatJournalNoteFileNameForFormat('2026-04-25', noteDateFormatDraft.trim())
: 'YYYY-MM-DD.md';
new Setting(containerEl)
.setName('Journal note date format')
.setDesc(`Use Moment-style date tokens for note filenames. Example output: ${noteNameExample}`)
.addText(text => {
text.setPlaceholder('YYYY-MM-DD')
.setValue(noteDateFormatDraft)
.onChange((value) => {
this.noteDateFormatDraft = value;
});
})
.addButton(button => {
button.setButtonText('Preview renames')
.setDisabled(!noteDateFormatDraft.trim())
.onClick(() => {
this.migrationPreviewFormat = this.noteDateFormatDraft?.trim() || null;
this.display();
});
})
.addButton(button => {
button.setButtonText('Save format')
.setCta()
.setDisabled(!noteDateFormatDraft.trim() || noteDateFormatDraft.trim() === this.plugin.settings.noteDateFormat)
.onClick(async () => {
const nextFormat = this.noteDateFormatDraft?.trim() || '';
if (!nextFormat) {
return;
}
await this.plugin.updateNoteDateFormat(nextFormat);
this.noteDateFormatDraft = this.plugin.settings.noteDateFormat;
if (this.migrationPreviewFormat) {
this.migrationPreviewFormat = nextFormat;
}
this.display();
});
});
if (!this.migrationPreviewFormat) {
return;
}
const migrationPlan = this.plugin.getJournalMigrationPlanForFormat(this.migrationPreviewFormat);
const conflictingMigrationItems = migrationPlan.filter(item => item.hasConflict);
const safeMigrationItems = migrationPlan.filter(item => !item.hasConflict);
const visibleMigrationItems = migrationPlan.slice(0, this.migrationPreviewLimit);
const hiddenCount = Math.max(0, migrationPlan.length - visibleMigrationItems.length);
new Setting(containerEl)
.setName('Migration preview')
.setDesc(
migrationPlan.length === 0
? `No journal notes need renaming for ${this.migrationPreviewFormat}.`
: conflictingMigrationItems.length > 0
? `Found ${migrationPlan.length} rename candidates with ${conflictingMigrationItems.length} conflicts to resolve first.`
: `Found ${migrationPlan.length} journal notes that can be renamed to match ${this.migrationPreviewFormat}.`
)
.addButton(button => {
button.setButtonText(`Rename ${safeMigrationItems.length} notes`)
.setDisabled(migrationPlan.length === 0 || safeMigrationItems.length === 0 || conflictingMigrationItems.length > 0)
.onClick(async () => {
await this.plugin.applyJournalMigrationPlan(safeMigrationItems);
this.display();
});
})
.addExtraButton(button => {
button.setIcon('cross')
.setTooltip('Hide migration preview')
.onClick(() => {
this.migrationPreviewFormat = null;
this.display();
});
});
if (migrationPlan.length > 0) {
const migrationList = containerEl.createDiv({ cls: 'journalyst-migration-list' });
visibleMigrationItems.forEach(item => {
const row = migrationList.createDiv({ cls: 'journalyst-migration-row' });
row.createEl('code', { text: item.currentPath });
row.createEl('span', { text: '->', cls: 'journalyst-migration-arrow' });
row.createEl('code', { text: item.targetPath });
if (item.hasConflict) {
row.createEl('span', {
text: `Conflict with ${item.conflictPath}`,
cls: 'journalyst-migration-conflict',
});
}
});
if (hiddenCount > 0) {
containerEl.createEl('p', {
text: `Showing ${visibleMigrationItems.length} examples out of ${migrationPlan.length} rename candidates.`,
cls: 'journalyst-migration-summary',
});
}
}
}
private renderCadenceSettings(containerEl: HTMLElement) {
for (const journal of this.plugin.journals) {
const cadence = this.plugin.getJournalCadence(journal.path);
new Setting(containerEl)
.setName(journal.name)
.addDropdown(dropdown => {
dropdown.addOption('daily', 'Daily');
dropdown.addOption('weekdays', 'Weekdays only');
dropdown.addOption('weekly-days', 'Specific weekdays');
dropdown.addOption('interval', 'Custom interval');
dropdown.addOption('adhoc', 'Ad hoc / no tracking');
dropdown.setValue(cadence.type)
.onChange(async (value: JournalCadenceType) => {
const nextCadence = value === 'weekly-days'
? { type: value, weekdays: cadence.weekdays ?? [1] }
: value === 'interval'
? { type: value, intervalDays: cadence.intervalDays ?? 3, startDate: cadence.startDate }
: { type: value };
await this.plugin.updateJournalCadence(journal.path, nextCadence);
this.display();
});
});
if (cadence.type === 'weekly-days') {
const detail = containerEl.createDiv({ cls: 'journalyst-cadence-detail' });
detail.createEl('span', { text: 'Expected weekdays', cls: 'journalyst-cadence-label' });
const buttonRow = detail.createDiv({ cls: 'journalyst-cadence-weekdays' });
['S', 'M', 'T', 'W', 'T', 'F', 'S'].forEach((label, day) => {
const button = buttonRow.createEl('button', { text: label, cls: 'journalyst-cadence-day' });
button.type = 'button';
if (cadence.weekdays?.includes(day)) {
button.addClass('is-selected');
}
button.addEventListener('click', async () => {
const nextWeekdays = new Set(cadence.weekdays ?? []);
if (nextWeekdays.has(day)) {
nextWeekdays.delete(day);
} else {
nextWeekdays.add(day);
}
await this.plugin.updateJournalCadence(journal.path, {
type: 'weekly-days',
weekdays: Array.from(nextWeekdays).sort((left, right) => left - right),
});
this.display();
});
});
}
if (cadence.type === 'interval') {
const detail = containerEl.createDiv({ cls: 'journalyst-cadence-detail' });
const fieldGrid = detail.createDiv({ cls: 'journalyst-cadence-fields' });
const intervalField = fieldGrid.createDiv({ cls: 'journalyst-cadence-field' });
intervalField.createEl('span', { text: 'Every how many days', cls: 'journalyst-cadence-label' });
const intervalInput = intervalField.createEl('input', { type: 'number' });
intervalInput.min = '1';
intervalInput.step = '1';
intervalInput.value = String(cadence.intervalDays ?? 3);
intervalInput.addClass('journalyst-cadence-input');
intervalInput.addEventListener('change', async () => {
const intervalDays = Math.max(1, Number.parseInt(intervalInput.value, 10) || 1);
intervalInput.value = String(intervalDays);
await this.plugin.updateJournalCadence(journal.path, {
type: 'interval',
intervalDays,
startDate: cadence.startDate,
});
});
const anchorField = fieldGrid.createDiv({ cls: 'journalyst-cadence-field' });
anchorField.createEl('span', { text: 'Anchor date', cls: 'journalyst-cadence-label' });
const anchorInput = anchorField.createEl('input', { type: 'date' });
anchorInput.value = cadence.startDate ?? '';
anchorInput.addClass('journalyst-cadence-input');
anchorInput.addEventListener('change', async () => {
await this.plugin.updateJournalCadence(journal.path, {
type: 'interval',
intervalDays: cadence.intervalDays ?? 3,
startDate: anchorInput.value,
});
});
}
}
}
private async renderTemplateSettings(containerEl: HTMLElement) {
new Setting(containerEl)
.setName('Template engine')
.setDesc('Choose which template system Journalyst should use when creating journal entries.')
.addDropdown(dropdown => {
dropdown.addOption('none', 'None');
dropdown.addOption('core', 'Obsidian Templates');
dropdown.addOption('templater', 'Templater');
dropdown.setValue(this.plugin.settings.templateEngine)
.onChange(async (value: TemplateEngine) => {
this.plugin.settings.templateEngine = value;
await this.plugin.saveSettings();
this.display();
});
});
new Setting(containerEl)
.setName('Template failure behavior')
.setDesc('Choose whether Journalyst should create its default note when a configured template cannot be applied.')
.addDropdown(dropdown => {
dropdown.addOption('fallback-default', 'Create default note');
dropdown.addOption('abort', 'Abort with notice');
dropdown.setValue(this.plugin.settings.templateFailureBehavior)
.onChange(async (value) => {
this.plugin.settings.templateFailureBehavior = value as typeof this.plugin.settings.templateFailureBehavior;
await this.plugin.saveSettings();
});
});
if (this.plugin.settings.templateEngine === 'none') {
this.addTemplateNotice('Journalyst will create entries with its default note content.');
return;
}
await this.renderJournalTemplateSettings(containerEl);
}
private addTemplateNotice(message: string) {
new Setting(this.containerEl)
.setDesc(message);
}
private async renderJournalTemplateSettings(containerEl: HTMLElement) {
const templateEngine = this.plugin.settings.templateEngine;
const templateAvailability = await this.plugin.getTemplateAvailability(templateEngine);
const templateFiles = await this.plugin.getTemplateFiles(templateEngine);
if (templateEngine === 'core') {
this.addTemplateNotice('Core Templates supports plain placeholder substitution such as {{title}}, {{date}}, and {{time}}.');
} else {
this.addTemplateNotice('Templater templates are executed by the Templater plugin, so Journalyst defers rendering to Templater.');
}
if (templateAvailability === 'not-installed') {
this.addTemplateNotice('Templater is not installed. Install and enable Templater to choose templates for Journalyst journals.');
return;
}
if (templateAvailability === 'disabled') {
if (templateEngine === 'core') {
this.addTemplateNotice('The core Templates plugin is not enabled. Enable it in Core plugins to choose templates for Journalyst journals.');
} else {
this.addTemplateNotice('Templater is installed but not enabled. Enable Templater to choose templates for Journalyst journals.');
}
return;
}
if (templateAvailability === 'no-template-folder') {
if (templateEngine === 'core') {
this.addTemplateNotice('The core Templates plugin does not have a template folder configured. Set "Template folder location" in Templates settings first.');
} else {
this.addTemplateNotice('Templater does not have a template folder configured. Set "Template folder location" in Templater settings first.');
}
return;
}
if (templateFiles.length === 0) {
const templateFolder = await this.plugin.getTemplateFolder(templateEngine);
this.addTemplateNotice(`No markdown templates were found in ${templateFolder}. Add templates there to assign them to Journalyst journals.`);
return;
}
this.renderSubsectionIntro(
containerEl,
'Journal template assignments',
'Choose which template file each journal should use when Journalyst creates a new entry.'
);
for (const journal of this.plugin.journals) {
const templatePath = this.plugin.getJournalTemplatePath(templateEngine, journal.path);
const templateStatus = await this.plugin.getJournalTemplateStatus(templateEngine, journal.path);
const warningMessage = this.getTemplateWarningMessage(templateStatus, templatePath);
const setting = new Setting(containerEl)
.setName(journal.name)
.addDropdown(dropdown => {
dropdown.addOption('', 'None');
templateFiles.forEach(file => {
dropdown.addOption(file.path, file.path);
});
dropdown.setValue(this.plugin.getJournalTemplatePath(templateEngine, journal.path) ?? '')
.onChange(async (value) => {
if (value) {
this.plugin.setJournalTemplatePath(templateEngine, journal.path, value);
} else {
this.plugin.clearJournalTemplatePath(templateEngine, journal.path);
}
await this.plugin.saveSettings();
this.display();
});
});
if (warningMessage) {
setting.setDesc(`Warning: ${warningMessage}.`);
}
}
}
private getTemplateWarningMessage(templateStatus: string, templatePath?: string) {
if (templateStatus === 'missing') {
return `saved template is missing: ${templatePath}`;
}
if (templateStatus === 'outside-folder') {
return `saved template is outside the configured template folder: ${templatePath}`;
}
return null;
}
private async renderBasesSettings(containerEl: HTMLElement) {
new Setting(containerEl)
.setName('Enable Bases integration')
.setDesc('Write Journalyst properties into notes and generate plugin-managed .base files for each journal.')
.addToggle(toggle => {
toggle.setValue(this.plugin.isBasesIntegrationEnabled())
.onChange(async value => {
await this.plugin.updateBasesIntegrationEnabled(value);
if (!value) {
this.basesBackfillPreview = null;
}
this.display();
});
});
if (!this.plugin.isBasesIntegrationEnabled()) {
new Setting(containerEl)
.setDesc('Bases stays completely out of the way until you opt in here.');
return;
}
new Setting(containerEl)
.setName('Managed Bases files')
.setDesc('Generate Journalyst-owned starter Bases for your journals. Existing managed files are updated in place.')
.addButton(button => {
button.setButtonText('Generate all')
.setCta()
.onClick(async () => {
await this.plugin.generateBasesForAllJournals();
});
})
.addButton(button => {
button.setButtonText('Preview backfill')
.onClick(async () => {
this.basesBackfillPreview = await this.plugin.getJournalNotePropertyBackfillPreview();
this.display();
});
});
for (const journal of this.plugin.journals) {
new Setting(containerEl)
.setName(`${journal.name} Bases`)
.setDesc(`Generate or regenerate Journalyst starter Bases in ${journal.path}.`)
.addButton(button => {
button.setButtonText('Generate')
.onClick(async () => {
await this.plugin.generateBasesForJournal(journal);
});
});
}
if (!this.basesBackfillPreview) {
return;
}
const backfillPreview = this.basesBackfillPreview;
const visibleItems = backfillPreview.slice(0, this.basesPreviewLimit);
const hiddenCount = Math.max(0, backfillPreview.length - visibleItems.length);
new Setting(containerEl)
.setName('Property backfill preview')
.setDesc(
backfillPreview.length === 0
? 'All recognized journal notes already have the current Journalyst Bases properties.'
: `Journalyst found ${backfillPreview.length} notes that need missing or updated properties.`
)
.addButton(button => {
button.setButtonText(`Apply to ${backfillPreview.length} notes`)
.setDisabled(backfillPreview.length === 0)
.onClick(async () => {
await this.plugin.applyJournalNotePropertyBackfill(backfillPreview);
this.basesBackfillPreview = await this.plugin.getJournalNotePropertyBackfillPreview();
this.display();
});
})
.addExtraButton(button => {
button.setIcon('cross')
.setTooltip('Hide backfill preview')
.onClick(() => {
this.basesBackfillPreview = null;
this.display();
});
});
if (backfillPreview.length === 0) {
return;
}
const previewList = containerEl.createDiv({ cls: 'journalyst-migration-list' });
visibleItems.forEach(item => {
const row = previewList.createDiv({ cls: 'journalyst-migration-row' });
row.createEl('code', { text: item.filePath });
const details = [
item.entryType,
item.missingKeys.length > 0 ? `missing: ${item.missingKeys.join(', ')}` : null,
item.changedKeys.length > 0 ? `update: ${item.changedKeys.join(', ')}` : null,
].filter(Boolean).join(' | ');
row.createEl('span', { text: details, cls: 'journalyst-migration-summary' });
});
if (hiddenCount > 0) {
containerEl.createEl('p', {
text: `Showing ${visibleItems.length} examples out of ${backfillPreview.length} notes needing backfill.`,
cls: 'journalyst-migration-summary',
});
}
}
private async renderCustomPromptLists(containerEl: HTMLElement) {
const customLists = Object.values(this.plugin.getCustomPromptLists());
new Setting(containerEl)
.setName('Custom prompt lists')
.setDesc(customLists.length === 0
? 'You do not have any custom prompt lists yet.'
: 'Settings-managed prompt lists use one prompt per line.')
.addButton(button => {
button.setButtonText(customLists.length === 0 ? 'Add first list' : 'Add list')
.setCta()
.onClick(async () => {
const listId = this.plugin.createCustomPromptListId();
await this.plugin.upsertCustomPromptList(listId, 'New Prompt List', '');
this.customPromptListDrafts[listId] = { name: 'New Prompt List', body: '' };
this.display();
});
});
if (customLists.length === 0) {
containerEl.createEl('p', {
text: 'Built-in prompt libraries are always available when you assign prompts to a journal. Add a custom list here only if you want your own reusable bank.',
cls: 'journalyst-settings-empty-note',
});
return;
}
customLists.forEach(list => {
const draft = this.getCustomPromptListDraft(list.id, list.name, list.prompts.join('\n'));
new Setting(containerEl)
.setName(list.name)
.setDesc('One prompt per line. Save after editing the list name or body.')
.addText(text => {
text.setPlaceholder('Prompt list name')
.setValue(draft.name)
.onChange(value => {
this.customPromptListDrafts[list.id] = {
...draft,
name: value,
};
});
})
.addButton(button => {
button.setButtonText('Save')
.onClick(async () => {
const nextDraft = this.customPromptListDrafts[list.id] ?? draft;
await this.plugin.upsertCustomPromptList(list.id, nextDraft.name, nextDraft.body);
this.display();
});
})
.addExtraButton(button => {
button.setIcon('trash')
.setTooltip('Delete prompt list')
.onClick(async () => {
delete this.customPromptListDrafts[list.id];
await this.plugin.deleteCustomPromptList(list.id);
this.display();
});
});
const textarea = containerEl.createEl('textarea', { cls: 'journalyst-prompt-textarea' });
textarea.value = draft.body;
textarea.rows = Math.max(4, Math.min(10, draft.body.split('\n').length || 4));
textarea.placeholder = 'Write one prompt per line';
textarea.addEventListener('input', () => {
this.customPromptListDrafts[list.id] = {
...draft,
body: textarea.value,
};
});
});
}
private async renderJournalPromptSettings(containerEl: HTMLElement) {
for (const journal of this.plugin.journals) {
const promptSettings = this.plugin.getJournalPromptSettings(journal.path);
const promptStatus = await this.plugin.getPromptSettingsStatus(journal.path);
const resolvedList = await this.plugin.getResolvedPromptList(journal.path);
const promptOptions = resolvedList?.prompts ?? [];
const journalSetting = new Setting(containerEl)
.setName(journal.name)
.addToggle(toggle => {
toggle.setValue(promptSettings.enabled)
.onChange(async value => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
enabled: value,
});
this.display();
});
});
if (promptStatus) {
journalSetting.setDesc(promptStatus);
}
if (!promptSettings.enabled) {
continue;
}
const detailsContainer = containerEl.createDiv({ cls: 'journalyst-prompt-journal-details' });
new Setting(detailsContainer)
.setName(`${journal.name} source`)
.setDesc('Choose where this journal should pull prompts from.')
.addDropdown(dropdown => {
dropdown.addOption('built-in', 'Built-in library');
dropdown.addOption('custom', 'Custom prompt list');
dropdown.addOption('file', 'Markdown note');
dropdown.setValue(promptSettings.sourceType)
.onChange(async (value: PromptSourceType) => {
const nextSettings: JournalPromptSettings = {
...promptSettings,
sourceType: value,
selectedListId: value === 'built-in'
? promptSettings.selectedListId ?? Object.keys(this.plugin.getBuiltInPromptLists())[0]
: value === 'custom'
? promptSettings.selectedListId ?? Object.keys(this.plugin.getCustomPromptLists())[0]
: undefined,
selectedFilePath: value === 'file' ? promptSettings.selectedFilePath : undefined,
staticPromptId: undefined,
weekdayOverrides: {},
};
await this.plugin.updateJournalPromptSettings(journal.path, nextSettings);
this.display();
});
});
if (promptSettings.sourceType === 'built-in' || promptSettings.sourceType === 'custom') {
const listOptions = promptSettings.sourceType === 'built-in'
? this.plugin.getBuiltInPromptLists()
: this.plugin.getCustomPromptLists();
new Setting(detailsContainer)
.setName(`${journal.name} prompt list`)
.addDropdown(dropdown => {
Object.values(listOptions).forEach(list => {
dropdown.addOption(list.id, list.name);
});
const defaultListId = promptSettings.selectedListId ?? Object.keys(listOptions)[0] ?? '';
dropdown.setValue(defaultListId)
.onChange(async value => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
selectedListId: value,
staticPromptId: undefined,
weekdayOverrides: {},
});
this.display();
});
});
}
if (promptSettings.sourceType === 'file') {
new Setting(detailsContainer)
.setName(`${journal.name} prompt note`)
.setDesc('Journalyst reads one markdown list item per prompt from the selected note.')
.addDropdown(dropdown => {
this.app.vault.getMarkdownFiles().forEach(file => {
dropdown.addOption(file.path, file.path);
});
dropdown.setValue(promptSettings.selectedFilePath ?? '')
.onChange(async value => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
selectedFilePath: value || undefined,
staticPromptId: undefined,
weekdayOverrides: {},
});
this.display();
});
});
}
new Setting(detailsContainer)
.setName(`${journal.name} selection`)
.addDropdown(dropdown => {
dropdown.addOption('static', 'Static prompt');
dropdown.addOption('random', 'Random');
dropdown.addOption('random-no-repeat', 'Random, no repeats until exhausted');
dropdown.setValue(promptSettings.selectionMode)
.onChange(async (value: PromptSelectionMode) => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
selectionMode: value,
staticPromptId: value === 'static' ? promptSettings.staticPromptId : undefined,
});
this.display();
});
})
.addDropdown(dropdown => {
dropdown.addOption('append-body', 'Append into note');
dropdown.addOption('template-variables', 'Use template variables');
dropdown.setValue(promptSettings.deliveryMode)
.onChange(async (value: PromptDeliveryMode) => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
deliveryMode: value,
});
this.display();
});
});
if (promptSettings.selectionMode === 'static' && promptOptions.length > 0) {
new Setting(detailsContainer)
.setName(`${journal.name} static prompt`)
.addDropdown(dropdown => {
promptOptions.forEach(prompt => {
dropdown.addOption(prompt.id, prompt.title);
});
dropdown.setValue(promptSettings.staticPromptId ?? promptOptions[0]?.id ?? '')
.onChange(async value => {
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
staticPromptId: value,
});
});
});
}
if (promptOptions.length > 0) {
const weekdaysContainer = detailsContainer.createDiv({ cls: 'journalyst-prompt-weekdays' });
weekdaysContainer.createEl('span', { text: 'Weekday overrides', cls: 'journalyst-cadence-label' });
['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].forEach((label, index) => {
new Setting(weekdaysContainer)
.setName(label)
.addDropdown(dropdown => {
dropdown.addOption('', 'None');
promptOptions.forEach(prompt => {
dropdown.addOption(prompt.id, prompt.title);
});
dropdown.setValue(promptSettings.weekdayOverrides[index.toString()] ?? '')
.onChange(async value => {
const nextOverrides = { ...promptSettings.weekdayOverrides };
if (value) {
nextOverrides[index.toString()] = value;
} else {
delete nextOverrides[index.toString()];
}
await this.plugin.updateJournalPromptSettings(journal.path, {
...promptSettings,
weekdayOverrides: nextOverrides,
});
this.display();
});
});
});
}
}
}
private getCustomPromptListDraft(listId: string, defaultName: string, defaultBody: string) {
const existingDraft = this.customPromptListDrafts[listId];
if (existingDraft) {
return existingDraft;
}
const nextDraft = {
name: defaultName,
body: defaultBody,
};
this.customPromptListDrafts[listId] = nextDraft;
return nextDraft;
}
}