diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx
index cc3585493..9112ccbb5 100644
--- a/apps/client/src/layouts/desktop_layout.tsx
+++ b/apps/client/src/layouts/desktop_layout.tsx
@@ -51,6 +51,7 @@ import NoteActions from "../widgets/ribbon/NoteActions.jsx";
import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.jsx";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import NoteBadges from "../widgets/BreadcrumbBadges.jsx";
+import NoteTitleDetails from "../widgets/NoteTitleDetails.jsx";
export default class DesktopLayout {
@@ -156,6 +157,7 @@ export default class DesktopLayout {
new ScrollingContainer()
.filling()
.optChild(isNewLayout, titleRow)
+ .optChild(isNewLayout, )
.optChild(!isNewLayout, new ContentHeader()
.child()
.child()
diff --git a/apps/client/src/widgets/NoteTitleDetails.tsx b/apps/client/src/widgets/NoteTitleDetails.tsx
new file mode 100644
index 000000000..43bc84a2e
--- /dev/null
+++ b/apps/client/src/widgets/NoteTitleDetails.tsx
@@ -0,0 +1,22 @@
+import { formatDateTime } from "../utils/formatters";
+import { useNoteContext } from "./react/hooks";
+import { joinElements } from "./react/react_utils";
+import { useNoteMetadata } from "./ribbon/NoteInfoTab";
+
+export default function NoteTitleDetails() {
+ const { note } = useNoteContext();
+ const { metadata } = useNoteMetadata(note);
+
+ return (
+
+ {joinElements([
+ metadata?.dateCreated &&
+ Created on {formatDateTime(metadata.dateCreated, "medium", "none")}
+ ,
+ metadata?.dateModified &&
+ Modified on {formatDateTime(metadata.dateModified, "medium", "none")}
+
+ ], " • ")}
+
+ );
+}
diff --git a/apps/client/src/widgets/note_title.css b/apps/client/src/widgets/note_title.css
index 716ac27b0..7d39b6b02 100644
--- a/apps/client/src/widgets/note_title.css
+++ b/apps/client/src/widgets/note_title.css
@@ -29,11 +29,30 @@ body.desktop .note-title-widget input.note-title {
font-size: 180%;
}
-body.experimental-feature-new-layout .title-row {
- margin-top: 1em;
+body.experimental-feature-new-layout .title-row,
+body.experimental-feature-new-layout .title-details {
max-width: var(--max-content-width);
}
-body.experimental-feature-new-layout.prefers-centered-content .title-row {
+body.experimental-feature-new-layout .title-row {
+ margin-top: 2em;
+ margin-left: 12px;
+}
+
+body.experimental-feature-new-layout .title-details {
+ margin-top: 0;
+ contain: none;
+ padding: 0;
+ padding-inline-start: 24px;
+ opacity: 0.85;
+ display: flex;
+ gap: 0.25em;
+ margin: 0;
+ list-style-type: none;
+ margin-bottom: 2em;
+}
+
+body.experimental-feature-new-layout.prefers-centered-content .title-row,
+body.experimental-feature-new-layout.prefers-centered-content .title-details {
margin-inline: auto;
}
diff --git a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx
index 5901db3a2..888412867 100644
--- a/apps/client/src/widgets/ribbon/NoteInfoTab.tsx
+++ b/apps/client/src/widgets/ribbon/NoteInfoTab.tsx
@@ -8,30 +8,13 @@ import { formatDateTime } from "../../utils/formatters";
import { formatSize } from "../../services/utils";
import LoadingSpinner from "../react/LoadingSpinner";
import { useTriliumEvent } from "../react/hooks";
+import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
+import FNote from "../../entities/fnote";
+
+const isNewLayout = isExperimentalFeatureEnabled("new-layout");
export default function NoteInfoTab({ note }: TabContext) {
- const [ metadata, setMetadata ] = useState();
- const [ isLoading, setIsLoading ] = useState(false);
- const [ noteSizeResponse, setNoteSizeResponse ] = useState();
- const [ subtreeSizeResponse, setSubtreeSizeResponse ] = useState();
-
- function refresh() {
- if (note) {
- server.get(`notes/${note?.noteId}/metadata`).then(setMetadata);
- }
-
- setNoteSizeResponse(undefined);
- setSubtreeSizeResponse(undefined);
- setIsLoading(false);
- }
-
- useEffect(refresh, [ note?.noteId ]);
- useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
- const noteId = note?.noteId;
- if (noteId && (loadResults.isNoteReloaded(noteId) || loadResults.isNoteContentReloaded(noteId))) {
- refresh();
- }
- });
+ const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note);
return (
@@ -41,14 +24,14 @@ export default function NoteInfoTab({ note }: TabContext) {
{t("note_info_widget.note_id")}:
{note.noteId}
-
+ {!isNewLayout &&
{t("note_info_widget.created")}:
{formatDateTime(metadata?.dateCreated)}
-
-
+
}
+ {!isNewLayout &&
{t("note_info_widget.modified")}:
{formatDateTime(metadata?.dateModified)}
-
+
}
{t("note_info_widget.type")}:
@@ -64,16 +47,7 @@ export default function NoteInfoTab({ note }: TabContext) {
className="calculate-button"
icon="bx bx-calculator"
text={t("note_info_widget.calculate")}
- onClick={() => {
- setIsLoading(true);
- setTimeout(async () => {
- await Promise.allSettled([
- server.get(`stats/note-size/${note.noteId}`).then(setNoteSizeResponse),
- server.get(`stats/subtree-size/${note.noteId}`).then(setSubtreeSizeResponse)
- ]);
- setIsLoading(false);
- }, 0);
- }}
+ onClick={requestSizeInfo}
/>
)}
@@ -90,5 +64,45 @@ export default function NoteInfoTab({ note }: TabContext) {
>
)}
- )
+ );
+}
+
+export function useNoteMetadata(note: FNote | null | undefined) {
+ const [ isLoading, setIsLoading ] = useState(false);
+ const [ noteSizeResponse, setNoteSizeResponse ] = useState();
+ const [ subtreeSizeResponse, setSubtreeSizeResponse ] = useState();
+ const [ metadata, setMetadata ] = useState();
+
+ function refresh() {
+ if (note) {
+ server.get(`notes/${note?.noteId}/metadata`).then(setMetadata);
+ }
+
+ setNoteSizeResponse(undefined);
+ setSubtreeSizeResponse(undefined);
+ setIsLoading(false);
+ }
+
+ function requestSizeInfo() {
+ if (!note) return;
+
+ setIsLoading(true);
+ setTimeout(async () => {
+ await Promise.allSettled([
+ server.get(`stats/note-size/${note.noteId}`).then(setNoteSizeResponse),
+ server.get(`stats/subtree-size/${note.noteId}`).then(setSubtreeSizeResponse)
+ ]);
+ setIsLoading(false);
+ }, 0);
+ }
+
+ useEffect(refresh, [ note ]);
+ useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
+ const noteId = note?.noteId;
+ if (noteId && (loadResults.isNoteReloaded(noteId) || loadResults.isNoteContentReloaded(noteId))) {
+ refresh();
+ }
+ });
+
+ return { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo };
}