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 }; }