feat: support tag or folder flow editor

This commit is contained in:
quorafind 2025-02-26 21:32:08 +08:00
parent 89073a5306
commit 2eb78f227c
6 changed files with 784 additions and 187 deletions

View file

@ -5,149 +5,68 @@
import { TFile, moment } from "obsidian";
import DailyNote from "./DailyNote.svelte";
import { inview } from "svelte-inview";
import {
getAllDailyNotes,
getDailyNote,
createDailyNote,
getDateFromFile,
getDailyNoteSettings,
DEFAULT_DAILY_NOTE_FORMAT
} from 'obsidian-daily-notes-interface';
import { TimeRange, SelectionMode, TimeField } from "../types/time";
import { onMount } from "svelte";
import { TimeRange } from "../types/time";
import { FileManager, FileManagerOptions } from "../utils/fileManager";
export let plugin: DailyNoteViewPlugin;
export let leaf: WorkspaceLeaf;
export let selectedRange: TimeRange = "all";
export let customRange: { start: Date; end: Date } | null = null;
export let selectionMode: SelectionMode = "daily";
export let target: string = "";
export let timeField: TimeField = "mtime"; // 默认使用修改时间
const size = 1;
let intervalId;
let cacheDailyNotes: Record<string, any>;
let allDailyNotes: TFile[] = [];
let renderedDailyNotes: TFile[] = [];
let filteredDailyNotes: TFile[] = [];
let renderedFiles: TFile[] = [];
let filteredFiles: TFile[] = [];
let hasMore = true;
let hasFetch = false;
let hasCurrentDay: boolean = true;
let firstLoaded = true;
let loaderRef: HTMLDivElement;
$: if (hasMore && !hasFetch) {
cacheDailyNotes = getAllDailyNotes();
// Build notes list by date in descending order.
for (const string of Object.keys(cacheDailyNotes).sort().reverse()) {
allDailyNotes.push(<TFile>cacheDailyNotes[string]);
}
hasFetch = true;
checkDailyNote();
filterNotesByRange();
}
// Create the file manager
let fileManager: FileManager;
$: fileManagerOptions = {
mode: selectionMode,
target: target,
timeRange: selectedRange,
customRange: customRange,
app: plugin.app,
timeField: timeField
} as FileManagerOptions;
$: if (selectedRange && hasFetch) {
filterNotesByRange();
}
onMount(() => {
checkDailyNote();
startFillViewport();
});
function filterNotesByRange() {
const now = moment();
const fileFormat = getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
$: if (fileManager && (selectedRange !== fileManager.options.timeRange ||
customRange !== fileManager.options.customRange ||
selectionMode !== fileManager.options.mode ||
target !== fileManager.options.target ||
timeField !== fileManager.options.timeField)) {
fileManager.updateOptions({
timeRange: selectedRange,
customRange: customRange,
mode: selectionMode,
target: target,
timeField: timeField
});
// Reset filtered notes
filteredDailyNotes = [];
// Filter notes based on selected range
if (selectedRange === 'all') {
filteredDailyNotes = [...allDailyNotes];
} else {
filteredDailyNotes = allDailyNotes.filter(file => {
const fileDate = moment(file.basename, fileFormat);
switch (selectedRange) {
case 'week':
return fileDate.isSame(now, 'week');
case 'month':
return fileDate.isSame(now, 'month');
case 'year':
return fileDate.isSame(now, 'year');
case 'last-week':
return fileDate.isBetween(
moment().subtract(1, 'week').startOf('week'),
moment().subtract(1, 'week').endOf('week'),
null,
'[]'
);
case 'last-month':
return fileDate.isBetween(
moment().subtract(1, 'month').startOf('month'),
moment().subtract(1, 'month').endOf('month'),
null,
'[]'
);
case 'last-year':
return fileDate.isBetween(
moment().subtract(1, 'year').startOf('year'),
moment().subtract(1, 'year').endOf('year'),
null,
'[]'
);
case 'quarter':
return fileDate.isSame(now, 'quarter');
case 'last-quarter':
return fileDate.isBetween(
moment().subtract(1, 'quarter').startOf('quarter'),
moment().subtract(1, 'quarter').endOf('quarter'),
null,
'[]'
);
case 'custom':
if (customRange) {
const startDate = moment(customRange.start);
const endDate = moment(customRange.end);
return fileDate.isBetween(startDate, endDate, null, '[]');
}
return false;
default:
return true;
}
});
}
// Reset rendered notes and start filling viewport again
renderedDailyNotes = [];
hasMore = filteredDailyNotes.length > 0;
// Reset rendered files and start filling viewport again
renderedFiles = [];
filteredFiles = fileManager.getFilteredFiles();
hasMore = filteredFiles.length > 0;
firstLoaded = true;
startFillViewport();
}
function checkDailyNote() {
// @ts-ignore
const currentDate = moment();
const currentDailyNote = getDailyNote(currentDate, cacheDailyNotes);
// console.log(currentDate, cacheDailyNotes);
if (!currentDailyNote) {
hasCurrentDay = false;
}
}
async function createNewDailyNote() {
const currentDate = moment();
if (!hasCurrentDay) {
const currentDailyNote: any = await createDailyNote(currentDate);
renderedDailyNotes.push(currentDailyNote);
renderedDailyNotes = sortDailyNotes(renderedDailyNotes);
hasCurrentDay = true;
}
}
onMount(() => {
fileManager = new FileManager(fileManagerOptions);
filteredFiles = fileManager.getFilteredFiles();
hasMore = filteredFiles.length > 0;
startFillViewport();
});
function startFillViewport() {
if (!intervalId) {
@ -161,13 +80,13 @@
}
function infiniteHandler() {
if (!hasFetch || !hasMore) return;
if (filteredDailyNotes.length === 0 && hasFetch) {
if (!fileManager || !hasMore) return;
if (filteredFiles.length === 0) {
hasMore = false;
} else {
renderedDailyNotes = [
...renderedDailyNotes,
...filteredDailyNotes.splice(0, size)
renderedFiles = [
...renderedFiles,
...filteredFiles.splice(0, size)
];
if (firstLoaded) {
window.setTimeout(() => {
@ -184,71 +103,51 @@
}
}
async function createNewDailyNote() {
const newNote = await fileManager.createNewDailyNote();
if (newNote) {
renderedFiles = [newNote, ...renderedFiles];
}
}
export function tick() {
renderedDailyNotes = renderedDailyNotes;
renderedFiles = renderedFiles;
}
export function check() {
checkDailyNote();
}
function sortDailyNotes(notes: TFile[]): TFile[] {
const fileFormat = getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
return notes.sort((a, b) => {
return moment(b.basename, fileFormat).valueOf() - moment(a.basename, fileFormat).valueOf();
});
fileManager.checkDailyNote();
}
export function fileCreate(file: TFile) {
const fileDate = getDateFromFile(file as any, 'day');
const fileFormat = getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
if (!fileDate) return;
if (renderedDailyNotes.length === 0) {
allDailyNotes.push(file);
allDailyNotes = sortDailyNotes(allDailyNotes);
filterNotesByRange();
return;
fileManager.fileCreate(file);
// Update the rendered files if needed
if (selectionMode === "daily") {
// For daily notes, we need to check if the file should be added to the rendered files
const filteredFiles = fileManager.getFilteredFiles();
if (filteredFiles.some(f => f.basename === file.basename) &&
!renderedFiles.some(f => f.basename === file.basename)) {
renderedFiles = [file, ...renderedFiles];
}
} else {
// For folder and tag modes, we can simply update the rendered files
renderedFiles = fileManager.getFilteredFiles().slice(0, renderedFiles.length);
}
const lastRenderedDailyNote = renderedDailyNotes[renderedDailyNotes.length - 1];
const firstRenderedDailyNote = renderedDailyNotes[0];
const lastRenderedDailyNoteDate = moment(lastRenderedDailyNote.basename, fileFormat);
const firstRenderedDailyNoteDate = moment(firstRenderedDailyNote.basename, fileFormat);
if (fileDate.isBetween(lastRenderedDailyNoteDate, firstRenderedDailyNoteDate)) {
renderedDailyNotes.push(file);
renderedDailyNotes = sortDailyNotes(renderedDailyNotes);
} else if (fileDate.isBefore(lastRenderedDailyNoteDate)) {
allDailyNotes.push(file);
allDailyNotes = sortDailyNotes(allDailyNotes);
filterNotesByRange();
} else if (fileDate.isAfter(firstRenderedDailyNoteDate)) {
renderedDailyNotes.push(file);
renderedDailyNotes = sortDailyNotes(renderedDailyNotes);
}
if (fileDate.isSame(moment(), 'day')) hasCurrentDay = true;
}
export function fileDelete(file: TFile) {
if (!getDateFromFile(file as any, 'day')) return;
renderedDailyNotes = renderedDailyNotes.filter((dailyNote) => {
fileManager.fileDelete(file);
// Remove the file from rendered files if it exists
renderedFiles = renderedFiles.filter((dailyNote) => {
return dailyNote.basename !== file.basename;
});
allDailyNotes = allDailyNotes.filter((dailyNote) => {
return dailyNote.basename !== file.basename;
});
filterNotesByRange();
checkDailyNote();
}
</script>
<div class="daily-note-view">
<div class="dn-range-indicator">
{#if selectedRange !== 'all'}
{#if selectionMode === "daily" && selectedRange !== 'all'}
<div class="dn-range-text">
{#if selectedRange === 'custom' && customRange}
Showing notes from: {moment(customRange.start).format('YYYY-MM-DD')} to {moment(customRange.end).format('YYYY-MM-DD')}
@ -256,19 +155,37 @@
Showing notes for: {selectedRange}
{/if}
</div>
{:else if selectionMode === "folder"}
<div class="dn-range-text">
Showing files from folder: {target}
{#if selectedRange !== 'all'}
<span class="dn-time-field">
({timeField === 'ctime' ? 'created' : 'modified'} {selectedRange})
</span>
{/if}
</div>
{:else if selectionMode === "tag"}
<div class="dn-range-text">
Showing files with tag: {target}
{#if selectedRange !== 'all'}
<span class="dn-time-field">
({timeField === 'ctime' ? 'created' : 'modified'} {selectedRange})
</span>
{/if}
</div>
{/if}
</div>
{#if renderedDailyNotes.length === 0}
{#if renderedFiles.length === 0}
<div class="dn-stock"></div>
{/if}
{#if !hasCurrentDay && (selectedRange === 'all' || selectedRange === 'week' || selectedRange === 'month' || selectedRange === 'year' || selectedRange === 'quarter')}
{#if selectionMode === "daily" && !fileManager?.hasCurrentDayNote() && (selectedRange === 'all' || selectedRange === 'week' || selectedRange === 'month' || selectedRange === 'year' || selectedRange === 'quarter')}
<div class="dn-blank-day" on:click={createNewDailyNote} aria-hidden="true">
<div class="dn-blank-day-text">
Create a daily note for today ✍
</div>
</div>
{/if}
{#each renderedDailyNotes as file (file)}
{#each renderedFiles as file (file)}
<DailyNote file={file} plugin={plugin} leaf={leaf}/>
{/each}
<div bind:this={loaderRef} class="dn-view-loader" use:inview={{
@ -329,4 +246,9 @@
text-align: center;
font-style: italic;
}
.dn-time-field {
font-size: 0.9em;
color: var(--color-base-40);
}
</style>

View file

@ -25,7 +25,7 @@ import {
DailyNoteSettingTab,
DEFAULT_SETTINGS,
} from "./dailyNoteSettings";
import { TimeRange } from "./types/time";
import { TimeRange, TimeField } from "./types/time";
import {
getAllDailyNotes,
getDailyNote,
@ -45,6 +45,9 @@ class DailyNoteView extends ItemView {
scope: Scope;
selectedDaysRange: TimeRange = "all";
selectionMode: "daily" | "folder" | "tag" = "daily";
target: string = "";
timeField: TimeField = "mtime"; // 默认使用修改时间
customRange: {
start: Date;
@ -63,11 +66,25 @@ class DailyNoteView extends ItemView {
}
getDisplayText(): string {
return "Daily Note";
if (this.selectionMode === "daily") {
return "Daily Notes";
} else if (this.selectionMode === "folder") {
return `Folder: ${this.target}`;
} else if (this.selectionMode === "tag") {
return `Tag: ${this.target}`;
}
return "Notes";
}
getIcon(): string {
return "daily-note";
if (this.selectionMode === "daily") {
return "daily-note";
} else if (this.selectionMode === "folder") {
return "folder";
} else if (this.selectionMode === "tag") {
return "tag";
}
return "document";
}
onFileCreate = (file: TAbstractFile) => {
@ -92,6 +109,25 @@ class DailyNoteView extends ItemView {
}
}
setSelectionMode(mode: "daily" | "folder" | "tag", target: string = "") {
this.selectionMode = mode;
this.target = target;
if (this.view) {
this.view.$set({
selectionMode: mode,
target: target,
});
}
}
setTimeField(field: TimeField) {
this.timeField = field;
if (this.view) {
this.view.$set({ timeField: field });
}
}
openDailyNoteEditor() {
this.plugin.openDailyNoteEditor();
}
@ -100,6 +136,65 @@ class DailyNoteView extends ItemView {
this.scope.register(["Mod"], "f", (e) => {
// do-nothing
});
// Add action for selecting view mode
this.addAction("layers-2", "Select view mode", (e) => {
const menu = new Menu();
// Add mode selection options
const addModeOption = (
title: string,
mode: "daily" | "folder" | "tag"
) => {
menu.addItem((item) => {
item.setTitle(title);
item.setChecked(this.selectionMode === mode);
item.onClick(() => {
if (mode === "daily") {
this.setSelectionMode(mode);
} else {
// For folder and tag modes, we need to prompt for the target
const modal = new SelectTargetModal(
this.plugin.app,
mode,
(target: string) => {
this.setSelectionMode(mode, target);
}
);
modal.open();
}
});
});
};
addModeOption("Daily Notes", "daily");
addModeOption("Folder", "folder");
addModeOption("Tag", "tag");
menu.showAtMouseEvent(e);
});
// Add action for selecting time field (for folder and tag modes)
this.addAction("clock", "Select time field", (e) => {
const menu = new Menu();
// Add time field selection options
const addTimeFieldOption = (title: string, field: TimeField) => {
menu.addItem((item) => {
item.setTitle(title);
item.setChecked(this.timeField === field);
item.onClick(() => {
this.setTimeField(field);
});
});
};
addTimeFieldOption("Creation Time", "ctime");
addTimeFieldOption("Modification Time", "mtime");
menu.showAtMouseEvent(e);
});
this.addAction("calendar-range", "Select date range", (e) => {
const menu = new Menu();
// Add range selection options
@ -146,6 +241,9 @@ class DailyNoteView extends ItemView {
leaf: this.leaf,
selectedRange: this.selectedDaysRange,
customRange: this.customRange,
selectionMode: this.selectionMode,
target: this.target,
timeField: this.timeField,
},
});
this.app.vault.on("create", this.onFileCreate);
@ -239,6 +337,106 @@ class CustomRangeModal extends Modal {
}
}
class SelectTargetModal extends Modal {
saveCallback: (target: string) => void;
mode: "folder" | "tag";
targetInput: HTMLInputElement;
constructor(
app: App,
mode: "folder" | "tag",
saveCallback: (target: string) => void
) {
super(app);
this.mode = mode;
this.saveCallback = saveCallback;
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", {
text: this.mode === "folder" ? "Select Folder" : "Select Tag",
});
const form = contentEl.createEl("form");
form.addEventListener("submit", (e) => {
e.preventDefault();
this.save();
});
const targetSetting = form.createDiv();
targetSetting.addClass("setting-item");
const targetSettingInfo = targetSetting.createDiv();
targetSettingInfo.addClass("setting-item-info");
targetSettingInfo.createEl("div", {
text: this.mode === "folder" ? "Folder Path" : "Tag Name",
cls: "setting-item-name",
});
targetSettingInfo.createEl("div", {
text:
this.mode === "folder"
? "Enter the path to the folder (e.g., 'folder/subfolder')"
: "Enter the tag name without the '#' (e.g., 'tag')",
cls: "setting-item-description",
});
const targetSettingControl = targetSetting.createDiv();
targetSettingControl.addClass("setting-item-control");
this.targetInput = targetSettingControl.createEl("input", {
type: "text",
value: "",
});
this.targetInput.addClass("target-input");
const footerEl = contentEl.createDiv();
footerEl.addClass("modal-button-container");
footerEl
.createEl("button", {
text: "Cancel",
cls: "mod-warning",
attr: {
type: "button",
},
})
.addEventListener("click", () => {
this.close();
});
footerEl
.createEl("button", {
text: "Save",
cls: "mod-cta",
attr: {
type: "submit",
},
})
.addEventListener("click", (e) => {
e.preventDefault();
this.save();
});
}
save() {
const target = this.targetInput.value.trim();
if (target) {
this.saveCallback(target);
this.close();
}
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
export default class DailyNoteViewPlugin extends Plugin {
private view: DailyNoteView;
lastActiveFile: TFile;
@ -295,6 +493,32 @@ export default class DailyNoteViewPlugin extends Plugin {
workspace.revealLeaf(leaf);
}
async openFolderView(folderPath: string, timeField: TimeField = "mtime") {
const workspace = this.app.workspace;
const leaf = workspace.getLeaf(true);
await leaf.setViewState({ type: DAILY_NOTE_VIEW_TYPE });
// Get the view and set the selection mode to folder
const view = leaf.view as DailyNoteView;
view.setSelectionMode("folder", folderPath);
view.setTimeField(timeField);
workspace.revealLeaf(leaf);
}
async openTagView(tagName: string, timeField: TimeField = "mtime") {
const workspace = this.app.workspace;
const leaf = workspace.getLeaf(true);
await leaf.setViewState({ type: DAILY_NOTE_VIEW_TYPE });
// Get the view and set the selection mode to tag
const view = leaf.view as DailyNoteView;
view.setSelectionMode("tag", tagName);
view.setTimeField(timeField);
workspace.revealLeaf(leaf);
}
async ensureTodaysDailyNoteExists() {
try {
const currentDate = moment();
@ -353,7 +577,7 @@ export default class DailyNoteViewPlugin extends Plugin {
// 0.14.x doesn't have WorkspaceContainer; this can just be an instanceof check once 15.x is mandatory:
if (
parent === app.workspace.rootSplit ||
parent === this.app.workspace.rootSplit ||
(WorkspaceContainer &&
parent instanceof WorkspaceContainer)
) {

View file

@ -42,7 +42,6 @@
padding: 0 !important;
overflow-y: clip;
}
.daily-note-view .daily-note-container::before {
content: "";
display: block;
@ -54,7 +53,11 @@
background-color: var(--color-base-25);
}
.daily-note-view .daily-note-container:first-child::before {
.daily-note-view .daily-note-container:first-of-type::before {
height: 0px;
}
.daily-note-view .dn-range-indicator + .daily-note-container::before {
height: 0px;
}

16
src/types/time.d.ts vendored
View file

@ -1 +1,15 @@
export type TimeRange = "week" | "month" | "year" | "all" | "last-week" | "last-month" | "last-year" | "quarter" | "last-quarter" | "custom";
export type TimeRange =
| "week"
| "month"
| "year"
| "all"
| "last-week"
| "last-month"
| "last-year"
| "quarter"
| "last-quarter"
| "custom";
export type SelectionMode = "daily" | "folder" | "tag";
export type TimeField = "ctime" | "mtime";

431
src/utils/fileManager.ts Normal file
View file

@ -0,0 +1,431 @@
import { TFile, moment, App } from "obsidian";
import {
getAllDailyNotes,
getDailyNote,
createDailyNote,
getDateFromFile,
getDailyNoteSettings,
DEFAULT_DAILY_NOTE_FORMAT,
} from "obsidian-daily-notes-interface";
import { TimeRange, TimeField } from "../types/time";
export interface FileManagerOptions {
mode: "daily" | "folder" | "tag";
target?: string;
timeRange?: TimeRange;
customRange?: { start: Date; end: Date } | null;
app?: App;
timeField?: TimeField;
}
export class FileManager {
private allFiles: TFile[] = [];
private filteredFiles: TFile[] = [];
private hasFetched: boolean = false;
private hasCurrentDay: boolean = true;
private cacheDailyNotes: Record<string, any> = {};
// Make options public so it can be accessed from outside
public options: FileManagerOptions;
constructor(options: FileManagerOptions) {
this.options = options;
this.fetchFiles();
}
public fetchFiles(): void {
if (this.hasFetched) return;
switch (this.options.mode) {
case "daily":
this.fetchDailyNotes();
break;
case "folder":
this.fetchFolderFiles();
break;
case "tag":
this.fetchTaggedFiles();
break;
}
this.hasFetched = true;
this.checkDailyNote();
this.filterFilesByRange();
}
private fetchDailyNotes(): void {
this.cacheDailyNotes = getAllDailyNotes();
// Build notes list by date in descending order.
for (const string of Object.keys(this.cacheDailyNotes)
.sort()
.reverse()) {
this.allFiles.push(<TFile>this.cacheDailyNotes[string]);
}
}
private fetchFolderFiles(): void {
if (!this.options.target || !this.options.app) return;
// Get all files in the vault
const allFiles = this.options.app.vault.getFiles();
// Filter files by folder path
this.allFiles = allFiles.filter((file) => {
const folderPath = file.parent?.path || "";
return (
folderPath === this.options.target ||
folderPath.startsWith(this.options.target + "/")
);
});
// Sort files by modification time (newest first)
this.allFiles = this.allFiles.sort((a, b) => {
return b.stat.mtime - a.stat.mtime;
});
}
private fetchTaggedFiles(): void {
if (!this.options.target || !this.options.app) return;
// Get all files with the specified tag
const allFiles = this.options.app.vault.getFiles();
const targetTag = this.options.target.startsWith("#")
? this.options.target
: "#" + this.options.target;
this.allFiles = allFiles.filter((file) => {
// Check if the file has the target tag in its cache
const fileCache =
this.options.app?.metadataCache.getFileCache(file);
if (!fileCache || !fileCache.tags) return false;
return fileCache.tags.some((tag) => tag.tag === targetTag);
});
// Sort files by modification time (newest first)
this.allFiles = this.allFiles.sort((a, b) => {
return b.stat.mtime - a.stat.mtime;
});
}
public filterFilesByRange(): TFile[] {
// If no time range is specified, return all files
if (!this.options.timeRange) {
this.filteredFiles = [...this.allFiles];
return this.filteredFiles;
}
// Reset the filtered files list
this.filteredFiles = [];
// If the time range is "all", return all files
if (this.options.timeRange === "all") {
this.filteredFiles = [...this.allFiles];
return this.filteredFiles;
}
// Use different filtering methods based on different modes
if (this.options.mode === "daily") {
// Daily mode: filter daily notes by date
this.filterDailyNotesByRange();
} else {
// Folder and tag modes: filter files by creation or modification time
this.filterFilesByTimeRange();
}
return this.filteredFiles;
}
/**
* Filter files by time range
* Applicable to folder and tag modes
*/
private filterFilesByTimeRange(): void {
const now = moment();
const timeField = this.options.timeField || "mtime"; // 默认使用修改时间
// Filter files by creation or modification time
this.filteredFiles = this.allFiles.filter((file) => {
// Get the time of the file based on the timeField option
const fileDate = moment(file.stat[timeField]);
return this.isDateInRange(fileDate, now);
});
}
/**
* Filter daily notes by date
* Applicable to daily mode
*/
private filterDailyNotesByRange(): void {
const now = moment();
const fileFormat =
getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
this.filteredFiles = this.allFiles.filter((file) => {
const fileDate = moment(file.basename, fileFormat);
return this.isDateInRange(fileDate, now);
});
}
/**
* Check if the file date is in the range
* @param fileDate file date
* @param now current date
* @returns whether in the range
*/
private isDateInRange(
fileDate: moment.Moment,
now: moment.Moment
): boolean {
switch (this.options.timeRange) {
case "week":
return fileDate.isSame(now, "week");
case "month":
return fileDate.isSame(now, "month");
case "year":
return fileDate.isSame(now, "year");
case "last-week":
return fileDate.isBetween(
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
null,
"[]"
);
case "last-month":
return fileDate.isBetween(
moment().subtract(1, "month").startOf("month"),
moment().subtract(1, "month").endOf("month"),
null,
"[]"
);
case "last-year":
return fileDate.isBetween(
moment().subtract(1, "year").startOf("year"),
moment().subtract(1, "year").endOf("year"),
null,
"[]"
);
case "quarter":
return fileDate.isSame(now, "quarter");
case "last-quarter":
return fileDate.isBetween(
moment().subtract(1, "quarter").startOf("quarter"),
moment().subtract(1, "quarter").endOf("quarter"),
null,
"[]"
);
case "custom":
if (this.options.customRange) {
const startDate = moment(this.options.customRange.start);
const endDate = moment(this.options.customRange.end);
return fileDate.isBetween(startDate, endDate, null, "[]");
}
return false;
default:
return true;
}
}
public checkDailyNote(): boolean {
if (this.options.mode !== "daily") {
this.hasCurrentDay = true;
return true;
}
// @ts-ignore
const currentDate = moment();
const currentDailyNote = getDailyNote(
currentDate,
this.cacheDailyNotes
);
if (!currentDailyNote) {
this.hasCurrentDay = false;
return false;
}
this.hasCurrentDay = true;
return true;
}
public async createNewDailyNote(): Promise<TFile | null> {
if (this.options.mode !== "daily" || this.hasCurrentDay) {
return null;
}
const currentDate = moment();
const currentDailyNote: any = await createDailyNote(currentDate);
if (currentDailyNote) {
this.allFiles.push(currentDailyNote);
this.allFiles = this.sortDailyNotes(this.allFiles);
this.hasCurrentDay = true;
this.filterFilesByRange();
return currentDailyNote;
}
return null;
}
public fileCreate(file: TFile): void {
if (this.options.mode === "daily") {
this.handleDailyNoteCreate(file);
} else if (this.options.mode === "folder") {
this.handleFolderFileCreate(file);
} else if (this.options.mode === "tag") {
this.handleTaggedFileCreate(file);
}
}
private handleDailyNoteCreate(file: TFile): void {
const fileDate = getDateFromFile(file as any, "day");
const fileFormat =
getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
if (!fileDate) return;
if (this.filteredFiles.length === 0) {
this.allFiles.push(file);
this.allFiles = this.sortDailyNotes(this.allFiles);
this.filterFilesByRange();
return;
}
const lastFilteredFile =
this.filteredFiles[this.filteredFiles.length - 1];
const firstFilteredFile = this.filteredFiles[0];
const lastFilteredFileDate = moment(
lastFilteredFile.basename,
fileFormat
);
const firstFilteredFileDate = moment(
firstFilteredFile.basename,
fileFormat
);
if (fileDate.isBetween(lastFilteredFileDate, firstFilteredFileDate)) {
this.filteredFiles.push(file);
this.filteredFiles = this.sortDailyNotes(this.filteredFiles);
} else if (fileDate.isBefore(lastFilteredFileDate)) {
this.allFiles.push(file);
this.allFiles = this.sortDailyNotes(this.allFiles);
this.filterFilesByRange();
} else if (fileDate.isAfter(firstFilteredFileDate)) {
this.filteredFiles.push(file);
this.filteredFiles = this.sortDailyNotes(this.filteredFiles);
}
if (fileDate.isSame(moment(), "day")) this.hasCurrentDay = true;
}
private handleFolderFileCreate(file: TFile): void {
if (!this.options.target) return;
// Check if the file belongs to the target folder
const folderPath = file.parent?.path || "";
if (
folderPath === this.options.target ||
folderPath.startsWith(this.options.target + "/")
) {
// Add the file to the collections
this.allFiles.push(file);
// Sort files by modification time (newest first)
this.allFiles = this.allFiles.sort((a, b) => {
return b.stat.mtime - a.stat.mtime;
});
// Update filtered files
this.filterFilesByRange();
}
}
private handleTaggedFileCreate(file: TFile): void {
if (!this.options.target || !this.options.app) return;
// Check if the file has the target tag
const targetTag = this.options.target.startsWith("#")
? this.options.target
: "#" + this.options.target;
const fileCache = this.options.app.metadataCache.getFileCache(file);
if (!fileCache || !fileCache.tags) return;
if (fileCache.tags.some((tag) => tag.tag === targetTag)) {
// Add the file to the collections
this.allFiles.push(file);
// Sort files by modification time (newest first)
this.allFiles = this.allFiles.sort((a, b) => {
return b.stat.mtime - a.stat.mtime;
});
// Update filtered files
this.filterFilesByRange();
}
}
public fileDelete(file: TFile): void {
if (
this.options.mode === "daily" &&
getDateFromFile(file as any, "day")
) {
this.filteredFiles = this.filteredFiles.filter((f) => {
return f.basename !== file.basename;
});
this.allFiles = this.allFiles.filter((f) => {
return f.basename !== file.basename;
});
this.filterFilesByRange();
this.checkDailyNote();
} else {
// Handle deletion for folder and tag modes
this.filteredFiles = this.filteredFiles.filter((f) => {
return f.basename !== file.basename;
});
this.allFiles = this.allFiles.filter((f) => {
return f.basename !== file.basename;
});
}
}
private sortDailyNotes(notes: TFile[]): TFile[] {
const fileFormat =
getDailyNoteSettings().format || DEFAULT_DAILY_NOTE_FORMAT;
return notes.sort((a, b) => {
return (
moment(b.basename, fileFormat).valueOf() -
moment(a.basename, fileFormat).valueOf()
);
});
}
public getAllFiles(): TFile[] {
return this.allFiles;
}
public getFilteredFiles(): TFile[] {
return this.filteredFiles;
}
public hasCurrentDayNote(): boolean {
return this.hasCurrentDay;
}
public updateOptions(options: Partial<FileManagerOptions>): void {
this.options = { ...this.options, ...options };
if (options.timeRange || options.customRange) {
this.filterFilesByRange();
}
if (options.mode || options.target) {
this.allFiles = [];
this.filteredFiles = [];
this.hasFetched = false;
this.fetchFiles();
}
}
}

View file

@ -1,4 +1,4 @@
.daily-note.svelte-18gimyz.svelte-18gimyz{margin-bottom:var(--size-4-5);padding-bottom:var(--size-4-5)}.daily-note.svelte-18gimyz:has(.is-readable-line-width) .daily-note-title.svelte-18gimyz{max-width:var(--file-line-width);margin-left:auto;margin-right:auto}.daily-note-title.svelte-18gimyz.svelte-18gimyz{display:flex;justify-content:space-between;margin-top:var(--size-4-12)}.daily-node-icon.svelte-18gimyz.svelte-18gimyz{width:var(--size-4-8);height:var(--size-4-8);cursor:pointer}.daily-node-icon.svelte-18gimyz.svelte-18gimyz:hover{background-color:var(--background-modifier-hover)}.dn-stock.svelte-i23r02{height:1000px;width:100%}.no-more-text.svelte-i23r02{margin-left:auto;margin-right:auto;text-align:center}.dn-blank-day.svelte-i23r02{display:flex;margin-left:auto;margin-right:auto;max-width:var(--file-line-width);color:var(--color-base-40);padding-top:20px;padding-bottom:20px;transition:all 300ms}.dn-blank-day.svelte-i23r02:hover{padding-top:40px;padding-bottom:40px;transition:padding 300ms}.dn-blank-day-text.svelte-i23r02{margin-left:auto;margin-right:auto;text-align:center}.dn-range-indicator.svelte-i23r02{margin-left:auto;margin-right:auto;max-width:var(--file-line-width);padding:5px 0}.dn-range-text.svelte-i23r02{font-size:0.8em;color:var(--color-base-50);text-align:center;font-style:italic}.dn-editor {
.daily-note.svelte-18gimyz.svelte-18gimyz{margin-bottom:var(--size-4-5);padding-bottom:var(--size-4-5)}.daily-note.svelte-18gimyz:has(.is-readable-line-width) .daily-note-title.svelte-18gimyz{max-width:var(--file-line-width);margin-left:auto;margin-right:auto}.daily-note-title.svelte-18gimyz.svelte-18gimyz{display:flex;justify-content:space-between;margin-top:var(--size-4-12)}.daily-node-icon.svelte-18gimyz.svelte-18gimyz{width:var(--size-4-8);height:var(--size-4-8);cursor:pointer}.daily-node-icon.svelte-18gimyz.svelte-18gimyz:hover{background-color:var(--background-modifier-hover)}.dn-stock.svelte-gpxxkc{height:1000px;width:100%}.no-more-text.svelte-gpxxkc{margin-left:auto;margin-right:auto;text-align:center}.dn-blank-day.svelte-gpxxkc{display:flex;margin-left:auto;margin-right:auto;max-width:var(--file-line-width);color:var(--color-base-40);padding-top:20px;padding-bottom:20px;transition:all 300ms}.dn-blank-day.svelte-gpxxkc:hover{padding-top:40px;padding-bottom:40px;transition:padding 300ms}.dn-blank-day-text.svelte-gpxxkc{margin-left:auto;margin-right:auto;text-align:center}.dn-range-indicator.svelte-gpxxkc{margin-left:auto;margin-right:auto;max-width:var(--file-line-width);padding:5px 0}.dn-range-text.svelte-gpxxkc{font-size:0.8em;color:var(--color-base-50);text-align:center;font-style:italic}.dn-time-field.svelte-gpxxkc{font-size:0.9em;color:var(--color-base-40)}.dn-editor {
/*overflow-y: hidden;*/
}
@ -42,7 +42,6 @@
padding: 0 !important;
overflow-y: clip;
}
.daily-note-view .daily-note-container::before {
content: "";
display: block;
@ -54,7 +53,11 @@
background-color: var(--color-base-25);
}
.daily-note-view .daily-note-container:first-child::before {
.daily-note-view .daily-note-container:first-of-type::before {
height: 0px;
}
.daily-note-view .dn-range-indicator + .daily-note-container::before {
height: 0px;
}