Files
Trilium/apps/client/src/widgets/dialogs/PopupEditor.tsx

128 lines
5.2 KiB
TypeScript
Raw Normal View History

2025-11-22 19:07:50 +02:00
import "./PopupEditor.css";
2025-11-22 20:08:37 +02:00
import { ComponentChildren } from "preact";
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import NoteContext from "../../components/note_context";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import tree from "../../services/tree";
import utils from "../../services/utils";
import NoteList from "../collections/NoteList";
import FloatingButtons from "../FloatingButtons";
import { DESKTOP_FLOATING_BUTTONS, MOBILE_FLOATING_BUTTONS, POPUP_HIDDEN_FLOATING_BUTTONS } from "../FloatingButtonsDefinitions";
import NoteIcon from "../note_icon";
import NoteTitleWidget from "../note_title";
import NoteDetail from "../NoteDetail";
import PromotedAttributes from "../PromotedAttributes";
import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
import Modal from "../react/Modal";
import { NoteContextContext, ParentComponent } from "../react/react_utils";
2025-11-25 08:34:35 +02:00
import ReadOnlyNoteInfoBar from "../ReadOnlyNoteInfoBar";
import StandaloneRibbonAdapter from "../ribbon/components/StandaloneRibbonAdapter";
import FormattingToolbar from "../ribbon/FormattingToolbar";
import MobileEditorToolbar from "../type_widgets/text/mobile_editor_toolbar";
import BreadcrumbBadges from "../layout/BreadcrumbBadges";
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
2025-11-22 19:07:50 +02:00
export default function PopupEditor() {
const [ shown, setShown ] = useState(false);
2025-11-22 19:34:14 +02:00
const parentComponent = useContext(ParentComponent);
2025-11-22 20:08:37 +02:00
const [ noteContext, setNoteContext ] = useState(new NoteContext("_popup-editor"));
const isMobile = utils.isMobile();
const items = useMemo(() => {
const baseItems = isMobile ? MOBILE_FLOATING_BUTTONS : DESKTOP_FLOATING_BUTTONS;
return baseItems.filter(item => !POPUP_HIDDEN_FLOATING_BUTTONS.includes(item));
}, [ isMobile ]);
2025-11-22 19:11:20 +02:00
useTriliumEvent("openInPopup", async ({ noteIdOrPath }) => {
2025-11-22 20:08:37 +02:00
const noteContext = new NoteContext("_popup-editor");
const noteId = tree.getNoteIdAndParentIdFromUrl(noteIdOrPath);
if (!noteId.noteId) return;
const note = await froca.getNote(noteId.noteId);
if (!note) return;
const hasUserSetNoteReadOnly = note.hasLabel("readOnly");
2025-11-22 19:11:20 +02:00
await noteContext.setNote(noteIdOrPath, {
viewScope: {
// Override auto-readonly notes to be editable, but respect user's choice to have a read-only note.
readOnlyTemporarilyDisabled: !hasUserSetNoteReadOnly
2025-11-22 19:11:20 +02:00
}
});
2025-11-22 19:07:50 +02:00
2025-11-22 20:08:37 +02:00
setNoteContext(noteContext);
2025-11-22 19:07:50 +02:00
setShown(true);
});
// Add a global class to be able to handle issues with z-index due to rendering in a popup.
useEffect(() => {
document.body.classList.toggle("popup-editor-open", shown);
}, [shown]);
2025-11-22 19:07:50 +02:00
return (
<NoteContextContext.Provider value={noteContext}>
2025-11-22 20:08:37 +02:00
<DialogWrapper>
<Modal
title={<>
<TitleRow />
{isNewLayout && <BreadcrumbBadges />}
</>}
customTitleBarButtons={[{
iconClassName: "bx-expand-alt",
title: t("popup-editor.maximize"),
onClick: async () => {
if (!noteContext.noteId) return;
const { noteId, hoistedNoteId } = noteContext;
await appContext.tabManager.openInNewTab(noteId, hoistedNoteId, true);
setShown(false);
}
}]}
2025-11-22 20:08:37 +02:00
className="popup-editor-dialog"
size="lg"
show={shown}
onShown={() => parentComponent?.handleEvent("focusOnDetail", { ntxId: noteContext.ntxId })}
2025-11-22 20:08:37 +02:00
onHidden={() => setShown(false)}
2025-11-24 19:38:18 +02:00
keepInDom // needed for faster loading
noFocus // automatic focus breaks block popup
2025-11-22 20:08:37 +02:00
>
{!isNewLayout && <ReadOnlyNoteInfoBar />}
<PromotedAttributes />
{isMobile
? <MobileEditorToolbar inPopupEditor />
: <StandaloneRibbonAdapter component={FormattingToolbar} />}
<FloatingButtons items={items} />
2025-11-22 20:08:37 +02:00
<NoteDetail />
<NoteList media="screen" displayOnlyCollections />
2025-11-22 20:08:37 +02:00
</Modal>
</DialogWrapper>
</NoteContextContext.Provider>
);
2025-11-22 19:07:50 +02:00
}
2025-11-22 20:08:37 +02:00
export function DialogWrapper({ children }: { children: ComponentChildren }) {
const { note } = useNoteContext();
2025-11-22 20:08:37 +02:00
const wrapperRef = useRef<HTMLDivElement>(null);
useNoteLabel(note, "color"); // to update color class
2025-11-22 20:08:37 +02:00
return (
<div ref={wrapperRef} class={`quick-edit-dialog-wrapper ${note?.getColorClass() ?? ""}`}>
2025-11-22 20:08:37 +02:00
{children}
</div>
);
2025-11-22 20:08:37 +02:00
}
export function TitleRow() {
return (
<div className="title-row">
<NoteIcon />
<NoteTitleWidget />
</div>
);
}