New layout: Note info (#8015)

This commit is contained in:
Elian Doran 2025-12-11 17:18:19 +02:00 committed by GitHub
commit c3829f82ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 146 additions and 89 deletions

View File

@ -14,6 +14,7 @@ import NoteLink from "./react/NoteLink";
import link_context_menu from "../menus/link_context_menu";
import { TitleEditor } from "./collections/board";
import server from "../services/server";
import { NoteInfoBadge } from "./BreadcrumbBadges";
const COLLAPSE_THRESHOLD = 5;
const INITIAL_ITEMS = 2;
@ -119,7 +120,10 @@ function BreadcrumbItem({ index, notePath, noteContext, notePathLength }: { inde
}
if (index === notePathLength - 1) {
return <BreadcrumbLastItem notePath={notePath} />;
return <>
<BreadcrumbLastItem notePath={notePath} />
<NoteInfoBadge note={noteContext?.note} />
</>;
}
return <BreadcrumbLink notePath={notePath} />;

View File

@ -9,6 +9,7 @@
flex-shrink: 1;
overflow: hidden;
--badge-radius: 12px;
}
.breadcrumb-badge {
display: flex;
@ -35,7 +36,7 @@
&.backlinks-badge { color: var(--badge-text-color); }
a {
color: inherit;
color: inherit !important;
text-decoration: none;
}
@ -47,7 +48,7 @@
}
}
.dropdown {
.breadcrumb-dropdown-badge {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
@ -58,6 +59,30 @@
min-width: 500px;
}
&.dropdown-note-info-badge {
.dropdown-menu.show ul {
list-style-type: none;
padding: 0.5em;
margin: 0;
display: table;
li {
display: table-row;
> strong {
display: table-cell;
padding: 0.2em 0;
}
> span {
display: table-cell;
user-select: text;
padding-left: 2em;
}
}
}
}
.breadcrumb-badge {
border-radius: 0;
}
@ -68,4 +93,3 @@
padding: 0;
}
}
}

View File

@ -5,11 +5,14 @@ import { ComponentChildren, MouseEventHandler } from "preact";
import { useRef } from "preact/hooks";
import { t } from "../services/i18n";
import { formatDateTime } from "../utils/formatters";
import { BacklinksList, useBacklinkCount } from "./FloatingButtonsDefinitions";
import Dropdown, { DropdownProps } from "./react/Dropdown";
import { useIsNoteReadOnly, useNoteContext, useStaticTooltip } from "./react/hooks";
import Icon from "./react/Icon";
import { NoteSizeWidget, useNoteMetadata } from "./ribbon/NoteInfoTab";
import { useShareInfo } from "./shared_info";
import FNote from "../entities/fnote";
export default function BreadcrumbBadges() {
return (
@ -21,6 +24,35 @@ export default function BreadcrumbBadges() {
);
}
export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) {
const { metadata, ...sizeProps } = useNoteMetadata(note);
return (note &&
<BadgeWithDropdown
icon="bx bx-info-circle"
className="note-info-badge"
dropdownOptions={{ dropdownOptions: { autoClose: "outside" } }}
>
<ul>
<NoteInfoValue text={t("note_info_widget.created")} value={formatDateTime(metadata?.dateCreated)} />
<NoteInfoValue text={t("note_info_widget.modified")} value={formatDateTime(metadata?.dateModified)} />
<NoteInfoValue text={t("note_info_widget.type")} value={<span>{note.type} {note.mime && <span>({note.mime})</span>}</span>} />
<NoteInfoValue text={t("note_info_widget.note_id")} value={<code>{note.noteId}</code>} />
<NoteInfoValue text={t("note_info_widget.note_size")} title={t("note_info_widget.note_size_info")} value={<NoteSizeWidget {...sizeProps} />} />
</ul>
</BadgeWithDropdown>
);
}
function NoteInfoValue({ text, title, value }: { text: string; title?: string, value: ComponentChildren }) {
return (
<li>
<strong title={title}>{text}{": "}</strong>
<span>{value}</span>
</li>
);
}
function ReadOnlyBadge() {
const { note, noteContext } = useNoteContext();
const { isReadOnly, enableEditing } = useIsNoteReadOnly(note, noteContext);
@ -83,7 +115,7 @@ function BacklinksBadge() {
}
interface BadgeProps {
text: string;
text?: string;
icon?: string;
className: string;
tooltip?: string;
@ -123,15 +155,21 @@ function BadgeWithDropdown({ children, tooltip, className, dropdownOptions, ...p
}) {
return (
<Dropdown
className={`dropdown-${className}`}
className={`breadcrumb-dropdown-badge dropdown-${className}`}
text={<Badge className={className} {...props} />}
noDropdownListStyle
noSelectButtonStyle
hideToggleArrow
title={tooltip}
titlePosition="bottom"
dropdownOptions={{ popperConfig: { placement: "bottom", strategy: "fixed" } }}
{...dropdownOptions}
dropdownOptions={{
...dropdownOptions?.dropdownOptions,
popperConfig: {
...dropdownOptions?.dropdownOptions?.popperConfig,
placement: "bottom", strategy: "fixed"
}
}}
>{children}</Dropdown>
);
}

View File

@ -9,26 +9,12 @@ import { useRef } from "preact/hooks";
export default function NoteTitleDetails() {
const { note, noteContext } = useNoteContext();
const { metadata } = useNoteMetadata(note);
const isHiddenNote = note?.noteId.startsWith("_");
const isDefaultView = noteContext?.viewScope?.viewMode === "default";
const items: ComponentChild[] = [
(isDefaultView && !isHiddenNote && metadata?.dateCreated &&
<TextWithValue
i18nKey="note_title.created_on"
value={formatDateTime(metadata.dateCreated, "medium", "none")}
valueTooltip={formatDateTime(metadata.dateCreated, "full", "long")}
/>),
(isDefaultView && !isHiddenNote && metadata?.dateModified &&
<TextWithValue
i18nKey="note_title.last_modified"
value={formatDateTime(metadata.dateModified, "medium", "none")}
valueTooltip={formatDateTime(metadata.dateModified, "full", "long")}
/>)
].filter(item => !!item);
const items: ComponentChild[] = [].filter(item => !!item);
return (
return items.length && (
<div className="title-details">
{joinElements(items, " • ")}
</div>

View File

@ -1,6 +1,5 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n";
import { TabContext } from "./ribbon-interface";
import { MetadataResponse, NoteSizeResponse, SubtreeSizeResponse } from "@triliumnext/commons";
import server from "../../services/server";
import Button from "../react/Button";
@ -13,8 +12,8 @@ import FNote from "../../entities/fnote";
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
export default function NoteInfoTab({ note }: TabContext) {
const { isLoading, metadata, noteSizeResponse, subtreeSizeResponse, requestSizeInfo } = useNoteMetadata(note);
export default function NoteInfoTab({ note }: { note: FNote | null | undefined }) {
const { metadata, ...sizeProps } = useNoteMetadata(note);
return (
<div className="note-info-widget">
@ -42,6 +41,17 @@ export default function NoteInfoTab({ note }: TabContext) {
<div className="note-info-item">
<span title={t("note_info_widget.note_size_info")}>{t("note_info_widget.note_size")}:</span>
<span className="note-info-size-col-span">
<NoteSizeWidget {...sizeProps} />
</span>
</div>
</>
)}
</div>
);
}
export function NoteSizeWidget({ isLoading, noteSizeResponse, subtreeSizeResponse, requestSizeInfo }: Omit<ReturnType<typeof useNoteMetadata>, "metadata">) {
return <>
{!isLoading && !noteSizeResponse && !subtreeSizeResponse && (
<Button
className="calculate-button"
@ -59,12 +69,7 @@ export default function NoteInfoTab({ note }: TabContext) {
}
{isLoading && <LoadingSpinner />}
</span>
</span>
</div>
</>
)}
</div>
);
</>;
}
export function useNoteMetadata(note: FNote | null | undefined) {

View File

@ -132,7 +132,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
{
title: t("note_info_widget.title"),
icon: "bx bx-info-circle",
show: ({ note }) => !!note,
show: ({ note }) => !isNewLayout && !!note,
content: NoteInfoTab,
toggleCommand: "toggleRibbonTabNoteInfo"
}