diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index ab97764f30..e417e39386 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -323,6 +323,10 @@ "source_llm": "LLM", "source_restore": "Restore", "source_unknown": "Snapshot", + "date_today": "Today", + "date_yesterday": "Yesterday", + "date_this_week": "This week", + "date_this_month": "This month", "source_description_auto": "Automatically saved by the system at regular intervals", "source_description_manual": "Manually saved by the user", "source_description_etapi": "Created via the External Trilium API", diff --git a/apps/client/src/widgets/dialogs/revisions.css b/apps/client/src/widgets/dialogs/revisions.css index eebf6b532f..189858ccfb 100644 --- a/apps/client/src/widgets/dialogs/revisions.css +++ b/apps/client/src/widgets/dialogs/revisions.css @@ -125,10 +125,16 @@ body.mobile .revisions-dialog { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-size: 0.85em; + opacity: 0.7; + } - &.fallback { - opacity: 0.75; - } + .revision-group-header { + font-size: 0.75em; + font-weight: bold; + text-transform: uppercase; + opacity: 0.5; + padding: 6px 12px 2px; } .revision-item-meta { diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index 1ddf3938d3..8438d37a07 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -4,6 +4,7 @@ import { dayjs, type RevisionItem, type RevisionPojo } from "@triliumnext/common import clsx from "clsx"; import { diffWords } from "diff"; import HtmlDiff from "htmldiff-js"; +import { Fragment } from "preact"; import type { CSSProperties } from "preact/compat"; import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; @@ -207,33 +208,61 @@ function getRevisionSourceTitle(source?: string): string { return t(`revisions.source_description_${source ?? "unknown"}`); } -function formatRevisionFallback(source?: string): string { - return t(`revisions.source_${source ?? "unknown"}`); +function getRelativeDateGroup(dateStr: string): string { + const date = dayjs(dateStr); + const now = dayjs(); + + if (date.isSame(now, "day")) return t("revisions.date_today"); + if (date.isSame(now.subtract(1, "day"), "day")) return t("revisions.date_yesterday"); + if (date.isSame(now, "week")) return t("revisions.date_this_week"); + if (date.isSame(now, "month")) return t("revisions.date_this_month"); + return date.format("MMMM YYYY"); +} + +function buildRevisionTooltip(item: RevisionItem): string { + return [ + getRevisionSourceTitle(item.source), + item.dateCreated && dayjs(item.dateCreated).fromNow(), + item.contentLength && utils.formatSize(item.contentLength) + ].filter(Boolean).join("\n"); } function RevisionsList({ revisions, onSelect, currentRevision }: { revisions: RevisionItem[], onSelect: (val: string) => void, currentRevision?: RevisionItem }) { + let lastGroup = ""; + return ( - {revisions.map((item) => - -
-
- {item.description || formatRevisionFallback(item.source)} -
-
- {item.dateCreated && dayjs(item.dateCreated).fromNow()} - {item.dateCreated && item.contentLength && " · "} - {item.contentLength && utils.formatSize(item.contentLength)} -
-
-
- )} + {revisions.map((item) => { + const group = item.dateCreated ? getRelativeDateGroup(item.dateCreated) : ""; + const showHeader = group !== lastGroup; + lastGroup = group; + + return ( + + {showHeader && ( +
{group}
+ )} + +
+
+ {item.dateCreated && dayjs(item.dateCreated).format("MMM D · HH:mm")} +
+ {item.description && ( +
+ {item.description} +
+ )} +
+
+
+ ); + })}
); }