mirror of
https://github.com/prncc/obsidian-repeat-plugin.git
synced 2026-07-22 06:50:25 +00:00
Add flag to allow enqueueing non-repeating notes (#41)
* Add command to repeat: never * Add a default repeat value to the settings * Add comments to repeat types * Remove unused utility * Use the default repeat property when inferring values * Use a one day default default period after all * Update README to mention the new default interval setting * Reword new repeat property setting * Prepare release of version 1.8.0 * Add toggle to enqueue notes without repeat (for setup) * Show the Never button if non-repeating notes are enqueued * Fix merge conflict after adding the never repeat setting * Prepare release of version 1.9.0
This commit is contained in:
parent
a8bc2b0bfd
commit
1c4d77bf3e
15 changed files with 252 additions and 72 deletions
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "repeat-plugin",
|
||||
"name": "Repeat",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"minAppVersion": "1.1.0",
|
||||
"description": "Review notes using periodic or spaced repetition.",
|
||||
"author": "Andre Perunicic",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-repeat-plugin",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"description": "An Obsidian plugin to review notes using periodic or spaced repetition.",
|
||||
"main": "./src/main.ts",
|
||||
"scripts": {
|
||||
|
|
|
|||
40
src/main.ts
40
src/main.ts
|
|
@ -77,7 +77,11 @@ export default class RepeatPlugin extends Plugin {
|
|||
this.statusBarItem = this.addStatusBarItem();
|
||||
}
|
||||
const dueNoteCount = getNotesDue(
|
||||
getAPI(this.app), this.settings.ignoreFolderPath)?.length;
|
||||
getAPI(this.app),
|
||||
this.settings.ignoreFolderPath,
|
||||
undefined,
|
||||
this.settings.enqueueNonRepeatingNotes,
|
||||
this.settings.defaultRepeat)?.length;
|
||||
if (dueNoteCount != undefined && this.statusBarItem) {
|
||||
this.statusBarItem.setText(
|
||||
`${dueNoteCount} repeat notes due`);
|
||||
|
|
@ -191,6 +195,7 @@ export default class RepeatPlugin extends Plugin {
|
|||
...repeat,
|
||||
hidden: parseHiddenFieldFromMarkdown(content),
|
||||
repeatDueAt,
|
||||
virtual: false,
|
||||
}));
|
||||
this.app.vault.modify(file, newContent);
|
||||
}
|
||||
|
|
@ -222,6 +227,28 @@ export default class RepeatPlugin extends Plugin {
|
|||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'repeat-never',
|
||||
name: 'Never repeat this note',
|
||||
checkCallback: (checking: boolean) => {
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView && !!markdownView.file) {
|
||||
if (!checking) {
|
||||
const { editor, file } = markdownView;
|
||||
const content = editor.getValue();
|
||||
const newContent = updateRepetitionMetadata(content, {
|
||||
repeat: 'never',
|
||||
due_at: undefined,
|
||||
hidden: undefined,
|
||||
});
|
||||
this.app.vault.modify(file, newContent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onload() {
|
||||
|
|
@ -319,7 +346,6 @@ class RepeatPluginSettingTab extends PluginSettingTab {
|
|||
.setName('Default `repeat` property')
|
||||
.setDesc('Used to populate "Repeat this note..." command\'s modal. Ignored if the supplied value is not parsable.')
|
||||
.addText((component) => {
|
||||
console.log(this.plugin.settings.defaultRepeat);
|
||||
return component
|
||||
.setValue(serializeRepeat(this.plugin.settings.defaultRepeat))
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -329,5 +355,15 @@ class RepeatPluginSettingTab extends PluginSettingTab {
|
|||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Enqueue non-repeating notes')
|
||||
.setDesc('Add notes without a repeat field to the end of the queue. Useful to quickly make new notes repeating during reviews.')
|
||||
.addToggle(component => component
|
||||
.setValue(this.plugin.settings.enqueueNonRepeatingNotes)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enqueueNonRepeatingNotes = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
getRepeatChoices,
|
||||
DISMISS_BUTTON_TEXT,
|
||||
SKIP_BUTTON_TEXT,
|
||||
NEVER_BUTTON_TEXT,
|
||||
} from './choices';
|
||||
import { parseTime } from './parsers';
|
||||
|
||||
|
|
@ -39,6 +40,11 @@ const invalidRepetition = {
|
|||
repeatDueAt: dueAt,
|
||||
};
|
||||
|
||||
// Helper function to check if nextRepetition is a Repetition object
|
||||
function isRepetition(nextRepetition: Repetition | 'DISMISS' | 'NEVER'): nextRepetition is Repetition {
|
||||
return typeof nextRepetition === 'object' && nextRepetition !== null;
|
||||
}
|
||||
|
||||
test.concurrent.each([
|
||||
...['HOUR', 'DAY', 'MONTH', 'YEAR'].map((repeatPeriodUnit) => ({
|
||||
...periodicRepetition,
|
||||
|
|
@ -55,22 +61,22 @@ test.concurrent.each([
|
|||
expect(choices).toHaveLength(1);
|
||||
expect(choices[0]).toStrictEqual({
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
} as RepeatChoice);
|
||||
return;
|
||||
}
|
||||
expect(choices).toHaveLength(2);
|
||||
choices.forEach((choice) => {
|
||||
expect(choice.nextRepetition?.repeatDueAt).not.toBeNull();
|
||||
// @ts-ignore
|
||||
expect(choice.nextRepetition?.repeatDueAt > now).toBe(true);
|
||||
if (choice.text !== SKIP_BUTTON_TEXT) {
|
||||
// @ts-ignore
|
||||
expect(choice.nextRepetition?.repeatDueAt.hour).toBe(
|
||||
(repetition.repeatTimeOfDay === 'AM')
|
||||
? parseTime(mockPluginSettings.morningReviewTime).hour
|
||||
: parseTime(mockPluginSettings.eveningReviewTime).hour,
|
||||
);
|
||||
if (isRepetition(choice.nextRepetition)) {
|
||||
expect(choice.nextRepetition.repeatDueAt).not.toBeNull();
|
||||
expect(choice.nextRepetition.repeatDueAt > now).toBe(true);
|
||||
if (choice.text !== SKIP_BUTTON_TEXT) {
|
||||
expect(choice.nextRepetition.repeatDueAt.hour).toBe(
|
||||
(repetition.repeatTimeOfDay === 'AM')
|
||||
? parseTime(mockPluginSettings.morningReviewTime).hour
|
||||
: parseTime(mockPluginSettings.eveningReviewTime).hour,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -91,7 +97,7 @@ test.concurrent.each([
|
|||
expect(choices).toHaveLength(1);
|
||||
expect(choices[0]).toStrictEqual({
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -99,16 +105,18 @@ test.concurrent.each([
|
|||
|
||||
// Skip button.
|
||||
const firstChoice = choices.shift();
|
||||
expect(firstChoice?.nextRepetition?.repeatPeriodUnit).toBe(
|
||||
repetition.repeatPeriodUnit);
|
||||
expect(firstChoice?.nextRepetition?.repeatPeriod).toBe(
|
||||
repetition.repeatPeriod);
|
||||
if (firstChoice && isRepetition(firstChoice.nextRepetition)) {
|
||||
expect(firstChoice.nextRepetition.repeatPeriodUnit).toBe(
|
||||
repetition.repeatPeriodUnit);
|
||||
expect(firstChoice.nextRepetition.repeatPeriod).toBe(
|
||||
repetition.repeatPeriod);
|
||||
}
|
||||
|
||||
choices.forEach((choice) => {
|
||||
expect(choice.nextRepetition).not.toBeNull();
|
||||
// @ts-ignore
|
||||
expect(choice.nextRepetition?.repeatDueAt > now).toBe(true);
|
||||
expect(choice.nextRepetition?.repeatPeriodUnit).toBe('HOUR');
|
||||
if (isRepetition(choice.nextRepetition)) {
|
||||
expect(choice.nextRepetition.repeatDueAt > now).toBe(true);
|
||||
expect(choice.nextRepetition.repeatPeriodUnit).toBe('HOUR');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -119,6 +127,72 @@ test('a note with invalid repetition gets only a skip choice', () => {
|
|||
expect(choices).toHaveLength(1);
|
||||
expect(choices[0]).toStrictEqual({
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
});
|
||||
});
|
||||
|
||||
test('periodic repetition includes NEVER button when enqueueNonRepeatingNotes is true', () => {
|
||||
const settingsWithEnqueue = {
|
||||
...mockPluginSettings,
|
||||
enqueueNonRepeatingNotes: true,
|
||||
};
|
||||
|
||||
const choices = getRepeatChoices(periodicRepetition, settingsWithEnqueue as any);
|
||||
|
||||
// Should have 3 choices: skip, next repetition, and never
|
||||
expect(choices).toHaveLength(3);
|
||||
|
||||
// Check that the NEVER button is present
|
||||
const neverChoice = choices.find(choice => choice.nextRepetition === 'NEVER');
|
||||
expect(neverChoice).toBeDefined();
|
||||
expect(neverChoice?.text).toBe(NEVER_BUTTON_TEXT);
|
||||
});
|
||||
|
||||
test('spaced repetition includes NEVER button when enqueueNonRepeatingNotes is true', () => {
|
||||
const settingsWithEnqueue = {
|
||||
...mockPluginSettings,
|
||||
enqueueNonRepeatingNotes: true,
|
||||
};
|
||||
|
||||
const choices = getRepeatChoices(spacedRepetition, settingsWithEnqueue as any);
|
||||
|
||||
// Should have 6 choices: skip, 4 multiplier choices, and never
|
||||
expect(choices).toHaveLength(6);
|
||||
|
||||
// Check that the NEVER button is present
|
||||
const neverChoice = choices.find(choice => choice.nextRepetition === 'NEVER');
|
||||
expect(neverChoice).toBeDefined();
|
||||
expect(neverChoice?.text).toBe(NEVER_BUTTON_TEXT);
|
||||
});
|
||||
|
||||
test('periodic repetition excludes NEVER button when enqueueNonRepeatingNotes is false', () => {
|
||||
const settingsWithoutEnqueue = {
|
||||
...mockPluginSettings,
|
||||
enqueueNonRepeatingNotes: false,
|
||||
};
|
||||
|
||||
const choices = getRepeatChoices(periodicRepetition, settingsWithoutEnqueue as any);
|
||||
|
||||
// Should have 2 choices: skip and next repetition (no never)
|
||||
expect(choices).toHaveLength(2);
|
||||
|
||||
// Check that the NEVER button is not present
|
||||
const neverChoice = choices.find(choice => choice.nextRepetition === 'NEVER');
|
||||
expect(neverChoice).toBeUndefined();
|
||||
});
|
||||
|
||||
test('spaced repetition excludes NEVER button when enqueueNonRepeatingNotes is false', () => {
|
||||
const settingsWithoutEnqueue = {
|
||||
...mockPluginSettings,
|
||||
enqueueNonRepeatingNotes: false,
|
||||
};
|
||||
|
||||
const choices = getRepeatChoices(spacedRepetition, settingsWithoutEnqueue as any);
|
||||
|
||||
// Should have 5 choices: skip and 4 multiplier choices (no never)
|
||||
expect(choices).toHaveLength(5);
|
||||
|
||||
// Check that the NEVER button is not present
|
||||
const neverChoice = choices.find(choice => choice.nextRepetition === 'NEVER');
|
||||
expect(neverChoice).toBeUndefined();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { uniqByField } from '../utils';
|
|||
import { RepeatPluginSettings } from '../settings';
|
||||
import { parseTime } from './parsers';
|
||||
|
||||
export const DISMISS_BUTTON_TEXT = 'dismiss';
|
||||
export const DISMISS_BUTTON_TEXT = 'Dismiss';
|
||||
export const NEVER_BUTTON_TEXT = 'Never';
|
||||
|
||||
export const SKIP_PERIOD_MINUTES = 5;
|
||||
export const SKIP_BUTTON_TEXT = `${SKIP_PERIOD_MINUTES} minutes (skip)`;
|
||||
|
|
@ -79,11 +80,11 @@ function getPeriodicRepeatChoices(
|
|||
if ((repeatDueAt > now) || !repeatDueAt) {
|
||||
return [{
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
}];
|
||||
}
|
||||
const nextRepeatDueAt = incrementRepeatDueAt({ ...repetition }, settings);
|
||||
return [{
|
||||
const choices: RepeatChoice[] = [{
|
||||
text: SKIP_BUTTON_TEXT,
|
||||
nextRepetition: {
|
||||
...repetition,
|
||||
|
|
@ -96,6 +97,15 @@ function getPeriodicRepeatChoices(
|
|||
repeatDueAt: nextRepeatDueAt,
|
||||
},
|
||||
}];
|
||||
|
||||
if (settings.enqueueNonRepeatingNotes) {
|
||||
choices.push({
|
||||
text: NEVER_BUTTON_TEXT,
|
||||
nextRepetition: 'NEVER',
|
||||
});
|
||||
}
|
||||
|
||||
return choices;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,12 +129,12 @@ function getSpacedRepeatChoices(
|
|||
if ((repeatDueAt > now) || !repeatDueAt) {
|
||||
return [{
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
}];
|
||||
}
|
||||
const morningReviewTime = parseTime(settings.morningReviewTime);
|
||||
const eveningReviewTime = parseTime(settings.eveningReviewTime);
|
||||
const multiplierChoices = [0.5, 1.0, 1.5, 2.0].map((multiplier) => {
|
||||
const multiplierChoices: RepeatChoice[] = [0.5, 1.0, 1.5, 2.0].map((multiplier) => {
|
||||
let nextRepeatDueAt = now.plus({
|
||||
[repeatPeriodUnit]: multiplier * repeatPeriod,
|
||||
});
|
||||
|
|
@ -160,7 +170,7 @@ function getSpacedRepeatChoices(
|
|||
}
|
||||
};
|
||||
});
|
||||
return uniqByField([
|
||||
const choices: RepeatChoice[] = [
|
||||
{
|
||||
text: SKIP_BUTTON_TEXT,
|
||||
nextRepetition: {
|
||||
|
|
@ -169,7 +179,14 @@ function getSpacedRepeatChoices(
|
|||
},
|
||||
},
|
||||
...multiplierChoices,
|
||||
], 'text');
|
||||
];
|
||||
if (settings.enqueueNonRepeatingNotes) {
|
||||
choices.push({
|
||||
text: NEVER_BUTTON_TEXT,
|
||||
nextRepetition: 'NEVER',
|
||||
});
|
||||
}
|
||||
return uniqByField(choices, 'text');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -195,6 +212,6 @@ export function getRepeatChoices(
|
|||
}
|
||||
return [{
|
||||
text: DISMISS_BUTTON_TEXT,
|
||||
nextRepetition: null,
|
||||
nextRepetition: 'DISMISS',
|
||||
}];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,9 @@ class RepeatView extends ItemView {
|
|||
const page = getNextDueNote(
|
||||
this.dv,
|
||||
this.settings.ignoreFolderPath,
|
||||
ignoreFilePath);
|
||||
ignoreFilePath,
|
||||
this.settings.enqueueNonRepeatingNotes,
|
||||
this.settings.defaultRepeat);
|
||||
if (!page) {
|
||||
this.setMessage('All done for now!');
|
||||
this.buttonsContainer.createEl('button', {
|
||||
|
|
@ -253,11 +255,6 @@ class RepeatView extends ItemView {
|
|||
(buttonElement) => {
|
||||
buttonElement.onclick = async () => {
|
||||
this.resetView();
|
||||
if (!choice.nextRepetition) {
|
||||
// TODO: Handle case of null nextRepetition properly.
|
||||
this.setPage();
|
||||
return;
|
||||
}
|
||||
const markdown = await this.app.vault.read(file);
|
||||
const newMarkdown = updateRepetitionMetadata(
|
||||
markdown, serializeRepetition(choice.nextRepetition));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ describe('parseRepetitionFields', () => {
|
|||
repeatTimeOfDay: 'AM',
|
||||
repeatDueAt: DateTime.fromISO(referenceRepeatDueAt),
|
||||
hidden: false,
|
||||
virtual: false,
|
||||
};
|
||||
test.concurrent.each([
|
||||
{
|
||||
|
|
@ -100,6 +101,7 @@ describe('parseRepetitionFields', () => {
|
|||
repeatTimeOfDay: 'AM',
|
||||
repeatDueAt: DateTime.fromISO(referenceRepeatDueAt),
|
||||
hidden: false,
|
||||
virtual: false,
|
||||
};
|
||||
const makeUnitCases = (unit: string) => ([{
|
||||
repeat: `every ${unit}`,
|
||||
|
|
@ -168,9 +170,10 @@ test('spaced without period specified', () => {
|
|||
delete repetition.repeatDueAt;
|
||||
expect(repetition).toEqual({
|
||||
repeatStrategy: 'SPACED',
|
||||
repeatPeriod: 24,
|
||||
repeatPeriodUnit: 'HOUR',
|
||||
repeatPeriod: 1,
|
||||
repeatPeriodUnit: 'DAY',
|
||||
repeatTimeOfDay: 'AM',
|
||||
hidden: false,
|
||||
virtual: false,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
Strategy,
|
||||
TimeOfDay,
|
||||
} from './repeatTypes';
|
||||
import { DEFAULT_SETTINGS } from 'src/settings';
|
||||
import { DEFAULT_SETTINGS } from '../settings';
|
||||
|
||||
const joinedUnits = 'hour|day|week|month|year';
|
||||
|
||||
|
|
@ -138,6 +138,25 @@ export function parseYamlBoolean(
|
|||
return booleanRegex.test(value);
|
||||
}
|
||||
|
||||
export function formRepetition(
|
||||
parsedRepeat: Repeat,
|
||||
repeatDueAt: string | undefined,
|
||||
hidden?: string | undefined,
|
||||
referenceDateTime?: DateTime | undefined,
|
||||
virtual?: boolean | undefined,
|
||||
): Repetition {
|
||||
return {
|
||||
...parsedRepeat,
|
||||
hidden: parseYamlBoolean(hidden),
|
||||
virtual: virtual || false,
|
||||
repeatDueAt: parseRepeatDueAt(
|
||||
repeatDueAt,
|
||||
parsedRepeat,
|
||||
referenceDateTime || DateTime.now(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export function parseRepetitionFields(
|
||||
repeat: string,
|
||||
repeatDueAt: string | undefined,
|
||||
|
|
@ -145,15 +164,7 @@ export function parseRepetitionFields(
|
|||
referenceDateTime?: DateTime | undefined,
|
||||
): Repetition {
|
||||
const parsedRepeat = parseRepeat(repeat);
|
||||
return {
|
||||
...parsedRepeat,
|
||||
hidden: parseYamlBoolean(hidden),
|
||||
repeatDueAt: parseRepeatDueAt(
|
||||
repeatDueAt,
|
||||
parsedRepeat,
|
||||
referenceDateTime || DateTime.now(),
|
||||
),
|
||||
}
|
||||
return formRepetition(parsedRepeat, repeatDueAt, hidden, referenceDateTime);
|
||||
}
|
||||
|
||||
export function parseRepetitionFromMarkdown(
|
||||
|
|
|
|||
|
|
@ -1,43 +1,63 @@
|
|||
import { DateTime } from 'luxon';
|
||||
import { Literal, DataviewApi, DataArray } from 'obsidian-dataview';
|
||||
|
||||
import { isRepeatDisabled, parseRepetitionFields } from './parsers';
|
||||
import { isRepeatDisabled, formRepetition, parseRepetitionFields } from './parsers';
|
||||
|
||||
export function getNotesDue(
|
||||
dv: DataviewApi | undefined,
|
||||
ignoreFolderPath: string,
|
||||
ignoreFilePath?: string | undefined,
|
||||
enqueueNonRepeatingNotes?: boolean,
|
||||
defaultRepeat?: any,
|
||||
): DataArray<Record<string, Literal>> | undefined {
|
||||
const now = DateTime.now();
|
||||
return dv?.pages()
|
||||
.mutate((page: any) => {
|
||||
const { repeat, due_at, hidden } = page.file.frontmatter || {};
|
||||
if (!repeat || isRepeatDisabled(repeat)) {
|
||||
if (isRepeatDisabled(repeat)) {
|
||||
page.repetition = undefined;
|
||||
return page;
|
||||
}
|
||||
page.repetition = parseRepetitionFields(
|
||||
repeat,
|
||||
due_at,
|
||||
hidden,
|
||||
page.file.ctime);
|
||||
return page;
|
||||
else if (!repeat) {
|
||||
if (enqueueNonRepeatingNotes) {
|
||||
page.repetition = formRepetition(
|
||||
defaultRepeat,
|
||||
undefined,
|
||||
undefined,
|
||||
page.file.ctime,
|
||||
true,
|
||||
);
|
||||
return page;
|
||||
} else {
|
||||
page.repetition = undefined;
|
||||
return page;
|
||||
}
|
||||
} else {
|
||||
page.repetition = parseRepetitionFields(
|
||||
repeat,
|
||||
due_at,
|
||||
hidden,
|
||||
page.file.ctime);
|
||||
return page;
|
||||
}
|
||||
})
|
||||
.where((page: any) => {
|
||||
const { repetition } = page;
|
||||
if (!repetition) {
|
||||
return false;
|
||||
}
|
||||
if (ignoreFolderPath && page.file.folder.startsWith(ignoreFolderPath)) {
|
||||
else if (ignoreFolderPath && page.file.folder.startsWith(ignoreFolderPath)) {
|
||||
return false;
|
||||
}
|
||||
if (ignoreFilePath && (page.file.path === ignoreFilePath)) {
|
||||
else if (ignoreFilePath && (page.file.path === ignoreFilePath)) {
|
||||
return false;
|
||||
}
|
||||
return repetition.repeatDueAt <= now;
|
||||
else {
|
||||
return repetition.repeatDueAt <= now;
|
||||
}
|
||||
})
|
||||
.sort((page: any) => {
|
||||
return page.repetition.repeatDueAt;
|
||||
return [page.repetition.virtual ? 1 : 0, page.repetition.repeatDueAt];
|
||||
}, 'asc')
|
||||
}
|
||||
|
||||
|
|
@ -45,8 +65,10 @@ export function getNextDueNote(
|
|||
dv: DataviewApi | undefined,
|
||||
ignoreFolderPath: string,
|
||||
ignoreFilePath?: string | undefined,
|
||||
enqueueNonRepeatingNotes?: boolean,
|
||||
defaultRepeat?: any,
|
||||
): Record<string, Literal> | undefined {
|
||||
const page = getNotesDue(dv, ignoreFolderPath, ignoreFilePath)?.first();
|
||||
const page = getNotesDue(dv, ignoreFolderPath, ignoreFilePath, enqueueNonRepeatingNotes, defaultRepeat)?.first();
|
||||
if (!page) { return; }
|
||||
return page;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@ export type Repeat = {
|
|||
export interface Repetition extends Repeat {
|
||||
repeatDueAt: DateTime,
|
||||
hidden: boolean,
|
||||
virtual: boolean,
|
||||
}
|
||||
|
||||
// A next-repeat choice shown in the review interface.
|
||||
export type RepeatChoice = {
|
||||
text: string,
|
||||
nextRepetition: Repetition | null,
|
||||
nextRepetition: Repetition | 'DISMISS' | 'NEVER',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ const makeTestRepetitions = () => {
|
|||
repeatTimeOfDay: 'AM',
|
||||
repeatDueAt: referenceRepeatDueAt,
|
||||
hidden: false,
|
||||
virtual: false,
|
||||
}));
|
||||
return [
|
||||
...basicRepetitions,
|
||||
|
|
@ -56,7 +57,7 @@ describe('serializeRepeat round trip', () => {
|
|||
'retains $repeatStrategy, $repeatPeriod, $repeatPeriodUnit, $repeatTimeOfDay',
|
||||
(repetition) => {
|
||||
const { repeat, due_at, hidden } = serializeRepetition(repetition as any);
|
||||
const serializedUnserializedRepetition = parseRepetitionFields(repeat, due_at, hidden, undefined);
|
||||
const serializedUnserializedRepetition = parseRepetitionFields(String(repeat), String(due_at ?? ""), String(hidden), undefined);
|
||||
expect(serializedUnserializedRepetition).toEqual(repetition);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,10 +44,24 @@ export function serializeRepeat({
|
|||
].join(' ');
|
||||
}
|
||||
|
||||
export function serializeRepetition(repetition: Repetition) {
|
||||
return {
|
||||
repeat: serializeRepeat(repetition),
|
||||
due_at: repetition.repeatDueAt.toISO(),
|
||||
hidden: repetition.hidden ? SERIALIZED_TRUE : SERIALIZED_FALSE,
|
||||
export function serializeRepetition(repetition: Repetition | 'DISMISS' | 'NEVER') {
|
||||
if (repetition === 'NEVER') {
|
||||
return {
|
||||
repeat: 'never',
|
||||
due_at: undefined,
|
||||
hidden: undefined,
|
||||
}
|
||||
} else if (repetition === 'DISMISS') {
|
||||
return {
|
||||
repeat: undefined,
|
||||
due_at: undefined,
|
||||
hidden: undefined,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
repeat: serializeRepeat(repetition),
|
||||
due_at: repetition.repeatDueAt.toISO(),
|
||||
hidden: repetition.hidden ? SERIALIZED_TRUE : SERIALIZED_FALSE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface RepeatPluginSettings {
|
|||
morningReviewTime: string;
|
||||
eveningReviewTime: string;
|
||||
defaultRepeat: Repeat;
|
||||
enqueueNonRepeatingNotes: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: RepeatPluginSettings = {
|
||||
|
|
@ -21,4 +22,5 @@ export const DEFAULT_SETTINGS: RepeatPluginSettings = {
|
|||
repeatPeriodUnit: 'DAY',
|
||||
repeatTimeOfDay: 'AM',
|
||||
},
|
||||
enqueueNonRepeatingNotes: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
|
|
|
|||
|
|
@ -19,5 +19,6 @@
|
|||
"1.7.0": "1.1.0",
|
||||
"1.7.1": "1.1.0",
|
||||
"1.7.2": "1.1.0",
|
||||
"1.8.0": "1.1.0"
|
||||
"1.8.0": "1.1.0",
|
||||
"1.9.0": "1.1.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue