import "./StatusBar.css"; import { Locale } from "@triliumnext/commons"; import clsx from "clsx"; import { type ComponentChildren } from "preact"; import { createPortal } from "preact/compat"; import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; import NoteContext from "../../components/note_context"; import FNote from "../../entities/fnote"; import attributes from "../../services/attributes"; import { t } from "../../services/i18n"; import { ViewScope } from "../../services/link"; import { openInAppHelpFromUrl } from "../../services/utils"; import { formatDateTime } from "../../utils/formatters"; import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useActiveNoteContext, useLegacyImperativeHandlers, useNoteProperty, useStaticTooltip, useTriliumEvent, useTriliumEvents } from "../react/hooks"; import Icon from "../react/Icon"; import { ParentComponent } from "../react/react_utils"; import { ContentLanguagesModal, NoteTypeCodeNoteList, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab"; import AttributeEditor, { AttributeEditorImperativeHandlers } from "../ribbon/components/AttributeEditor"; import InheritedAttributesTab from "../ribbon/InheritedAttributesTab"; import { NoteSizeWidget, useNoteMetadata } from "../ribbon/NoteInfoTab"; import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab"; import { useAttachments } from "../type_widgets/Attachment"; import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector"; import Breadcrumb from "./Breadcrumb"; import server from "../../services/server"; interface StatusBarContext { note: FNote; notePath: string | null | undefined; noteContext: NoteContext; viewScope?: ViewScope; hoistedNoteId?: string; } export default function StatusBar() { const { note, notePath, noteContext, viewScope, hoistedNoteId } = useActiveNoteContext(); const [ attributesShown, setAttributesShown ] = useState(false); const context: StatusBarContext | undefined | null = note && noteContext && { note, notePath, noteContext, viewScope, hoistedNoteId }; const attributesContext: AttributesProps | undefined | null = context && { ...context, attributesShown, setAttributesShown }; const isHiddenNote = note?.isInHiddenSubtree(); return (
{attributesContext && }
{context && attributesContext && <>
{!isHiddenNote && }
}
); } function StatusBarDropdown({ children, icon, text, buttonClassName, titleOptions, dropdownOptions, ...dropdownProps }: Omit & { title: string; icon?: string; }) { return ( {icon && (<> )} {text} } {...dropdownProps} > {children} ); } interface StatusBarButtonBaseProps { className?: string; icon: string; title: string; text?: string | number; disabled?: boolean; active?: boolean; } type StatusBarButtonWithCommand = StatusBarButtonBaseProps & { triggerCommand: CommandNames; }; type StatusBarButtonWithClick = StatusBarButtonBaseProps & { onClick: () => void; }; function StatusBarButton({ className, icon, text, title, active, ...restProps }: StatusBarButtonWithCommand | StatusBarButtonWithClick) { const parentComponent = useContext(ParentComponent); const buttonRef = useRef(null); useStaticTooltip(buttonRef, { placement: "top", fallbackPlacements: [ "top" ], popperConfig: { strategy: "fixed" }, title }); return ( ); } //#region Language Switcher function LanguageSwitcher({ note }: StatusBarContext) { const [ modalShown, setModalShown ] = useState(false); const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note); const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id); return ( <> {note.type === "text" && {getLocaleName(activeLocale)}} > {processedLocales.map((locale, index) => (typeof locale === "object") ? ( setCurrentNoteLanguage(locale.id)} >{locale.name} ) : ( ) )} openInAppHelpFromUrl("veGu4faJErEM")} icon="bx bx-help-circle" >{t("note_language.help-on-languages")} setModalShown(true)} icon="bx bx-cog" >{t("note_language.configure-languages")} } {createPortal( , document.body )} ); } export function getLocaleName(locale: Locale | null | undefined) { if (!locale) return ""; if (!locale.id) return "-"; if (locale.name.length <= 4 || locale.rtl) return locale.name; // Some locales like Japanese and Chinese look better than their ID. return locale.id .replace("_", "-") .toLocaleUpperCase(); } //#endregion //#region Note info export function NoteInfoBadge({ note }: { note: FNote | null | undefined }) { const { metadata, ...sizeProps } = useNoteMetadata(note); return (note &&
    {note.type} {note.mime && ({note.mime})}} /> {note.noteId}} /> } />
); } function NoteInfoValue({ text, title, value }: { text: string; title?: string, value: ComponentChildren }) { return (
  • {text}{": "} {value}
  • ); } //#endregion //#region Backlinks function BacklinksBadge({ note, viewScope }: StatusBarContext) { const count = useBacklinkCount(note, viewScope?.viewMode === "default"); return (note && count > 0 && ); } //#endregion //#region Attachment count function AttachmentCount({ note }: StatusBarContext) { const attachments = useAttachments(note); const count = attachments.length; return (note && count > 0 && ); } //#endregion //#region Attributes interface AttributesProps extends StatusBarContext { attributesShown: boolean; setAttributesShown: (shown: boolean) => void; } function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) { const [ count, setCount ] = useState(note.attributes.length); // React to note changes. useEffect(() => { setCount(note.attributes.length); }, [ note ]); // React to changes in count. useTriliumEvent("entitiesReloaded", (({loadResults}) => { if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) { setCount(note.attributes.length); } })); return ( setAttributesShown(!attributesShown)} /> ); } function AttributesPane({ note, noteContext, attributesShown, setAttributesShown }: AttributesProps) { const parentComponent = useContext(ParentComponent); const api = useRef(null); const context = parentComponent && { componentId: parentComponent.componentId, note, hidden: !note }; // Show on keyboard shortcuts. useTriliumEvents([ "addNewLabel", "addNewRelation" ], () => setAttributesShown(true)); // Interaction with the attribute editor. useLegacyImperativeHandlers(useMemo(() => ({ saveAttributesCommand: () => api.current?.save(), reloadAttributesCommand: () => api.current?.refresh(), updateAttributeListCommand: ({ attributes }) => api.current?.renderOwnedAttributes(attributes) }), [ api ])); return (context &&
    ); } //#endregion //#region Note paths function NotePaths({ note, hoistedNoteId, notePath }: StatusBarContext) { const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId); return ( ); } //#endregion //#region Code note switcher function CodeNoteSwitcher({ note }: StatusBarContext) { const currentNoteMime = useNoteProperty(note, "mime"); const mimeTypes = useMimeTypes(); const correspondingMimeType = useMemo(() => ( mimeTypes.find(m => m.mime === currentNoteMime) ), [ mimeTypes, currentNoteMime ]); return ( server.put(`notes/${note.noteId}/type`, { type, mime })} setModalShown={() => {}} /> ); } //#endregion