import "./StatusBar.css";
import { Locale } from "@triliumnext/commons";
import clsx from "clsx";
import { type ComponentChildren } from "preact";
import { createPortal } from "preact/compat";
import { useContext, 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, useStaticTooltip, useTriliumEvent, useTriliumEvents } from "../react/hooks";
import Icon from "../react/Icon";
import { ParentComponent } from "../react/react_utils";
import { ContentLanguagesModal, useLanguageSwitcher } 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";
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 && <>
>}
);
}
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 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