feat(status_bar): add new attachment count

This commit is contained in:
Elian Doran 2025-12-12 20:55:54 +02:00
parent 95d2160c76
commit 6eff62f73f
No known key found for this signature in database
4 changed files with 89 additions and 27 deletions

View File

@ -2156,8 +2156,9 @@
"status_bar": {
"language_title": "Change the language of the entire content",
"note_info_title": "View information about this note such as the creation/modification date or the note size.",
"backlinks": "{{count}}",
"backlinks_title_one": "This note is linked from {{count}} other note.\n\nClick to view the list of backlinks.",
"backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks."
"backlinks_title_other": "This note is linked from {{count}} other notes.\n\nClick to view the list of backlinks.",
"attachments_title_one": "This note has {{count}} attachment. Click to open the list of attachments in a new tab.",
"attachments_title_other": "This note has {{count}} attachments. Click to open the list of attachments in a new tab."
}
}

View File

@ -15,27 +15,29 @@
padding: 0.1em;
display: flex;
gap: 0.1em;
font-size: 0.85em;
.status-bar-dropdown-button {
background: transparent;
.btn {
padding: 0 0.5em !important;
background: transparent;
display: flex;
align-items: center;
&:after {
content: unset;
}
border: 0;
&:focus,
&:hover {
background: var(--input-background-color);
}
}
.status-bar-dropdown-button {
&:after {
content: unset;
}
}
}
.dropdown {
font-size: 0.85em;
.dropdown-toggle {
padding: 0.1em 0.25em;
}

View File

@ -4,7 +4,7 @@ import { Locale } from "@triliumnext/commons";
import clsx from "clsx";
import { type ComponentChildren } from "preact";
import { createPortal } from "preact/compat";
import { useState } from "preact/hooks";
import { useContext, useRef, useState } from "preact/hooks";
import NoteContext from "../../components/note_context";
import FNote from "../../entities/fnote";
@ -15,12 +15,17 @@ import { formatDateTime } from "../../utils/formatters";
import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions";
import Dropdown, { DropdownProps } from "../react/Dropdown";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useActiveNoteContext } from "../react/hooks";
import { useActiveNoteContext, useStaticTooltip, useTooltip } from "../react/hooks";
import Icon from "../react/Icon";
import { ContentLanguagesModal, useLanguageSwitcher } from "../ribbon/BasicPropertiesTab";
import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab";
import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector";
import Breadcrumb from "./Breadcrumb";
import { useAttachments } from "../type_widgets/Attachment";
import ActionButton from "../react/ActionButton";
import Button from "../react/Button";
import { CommandNames } from "../../components/app_context";
import { ParentComponent } from "../react/react_utils";
interface StatusBarContext {
note: FNote;
@ -40,6 +45,7 @@ export default function StatusBar() {
</div>
<div className="actions-row">
<AttachmentCount {...context} />
<BacklinksBadge {...context} />
<LanguageSwitcher {...context} />
<NoteInfoBadge {...context} />
@ -75,6 +81,35 @@ function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions
);
}
function StatusBarButton({ className, icon, text, title, triggerCommand }: {
className?: string;
icon: string;
title: string;
text?: string | number;
disabled?: boolean;
triggerCommand: CommandNames;
}) {
const parentComponent = useContext(ParentComponent);
const buttonRef = useRef<HTMLButtonElement>(null);
useStaticTooltip(buttonRef, {
placement: "top",
fallbackPlacements: [ "top" ],
popperConfig: { strategy: "fixed" },
title
});
return (
<button
ref={buttonRef}
className={clsx("btn select-button", className)}
type="button"
onClick={() => parentComponent?.triggerCommand(triggerCommand)}
>
<Icon icon={icon} />&nbsp;{text}
</button>
);
}
//#region Language Switcher
function LanguageSwitcher({ note }: StatusBarContext) {
const [ modalShown, setModalShown ] = useState(false);
@ -166,7 +201,7 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) {
<StatusBarDropdown
className="backlinks-badge backlinks-widget"
icon="bx bx-revision"
text={t("status_bar.backlinks", { count })}
text={count}
title={t("status_bar.backlinks_title", { count })}
dropdownContainerClassName="backlinks-items"
>
@ -175,3 +210,20 @@ function BacklinksBadge({ note, viewScope }: StatusBarContext) {
);
}
//#endregion
//#region Attachment count
function AttachmentCount({ note }: StatusBarContext) {
const attachments = useAttachments(note);
const count = attachments.length;
return (note && count > 0 &&
<StatusBarButton
className="attachment-count"
icon="bx bx-paperclip"
text={count}
title={t("status_bar.attachments_title", { count })}
triggerCommand="showAttachments"
/>
);
}
//#endregion

View File

@ -26,24 +26,13 @@ import ws from "../../services/ws";
import appContext from "../../components/app_context";
import { ConvertAttachmentToNoteResponse } from "@triliumnext/commons";
import options from "../../services/options";
import FNote from "../../entities/fnote";
/**
* Displays the full list of attachments of a note and allows the user to interact with them.
*/
export function AttachmentList({ note }: TypeWidgetProps) {
const [ attachments, setAttachments ] = useState<FAttachment[]>([]);
function refresh() {
note.getAttachments().then(attachments => setAttachments(Array.from(attachments)));
}
useEffect(refresh, [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttachmentRows().some((att) => att.attachmentId && att.ownerId === note.noteId)) {
refresh();
}
});
const attachments = useAttachments(note);
return (
<>
@ -59,7 +48,25 @@ export function AttachmentList({ note }: TypeWidgetProps) {
)}
</div>
</>
)
);
}
export function useAttachments(note: FNote) {
const [ attachments, setAttachments ] = useState<FAttachment[]>([]);
function refresh() {
note.getAttachments().then(attachments => setAttachments(Array.from(attachments)));
}
useEffect(refresh, [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttachmentRows().some((att) => att.attachmentId && att.ownerId === note.noteId)) {
refresh();
}
});
return attachments;
}
function AttachmentListHeader({ noteId }: { noteId: string }) {