Skip to content

Commit ab6da26

Browse files
committed
refactor(client): standalone rendering mechanism for ribbon
1 parent f95082c commit ab6da26

File tree

6 files changed

+207
-163
lines changed

6 files changed

+207
-163
lines changed

apps/client/src/layouts/layout_commons.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
2929
import NoteDetailWidget from "../widgets/note_detail.js";
3030
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
3131
import NoteTitleWidget from "../widgets/note_title.jsx";
32-
import { PopupEditorFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.js";
32+
import FormattingToolbar from "../widgets/ribbon/FormattingToolbar.js";
3333
import NoteList from "../widgets/collections/NoteList.jsx";
34+
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
3435

3536
export function applyModals(rootContainer: RootContainer) {
3637
rootContainer
@@ -63,7 +64,7 @@ export function applyModals(rootContainer: RootContainer) {
6364
.cssBlock(".title-row > * { margin: 5px; }")
6465
.child(<NoteIconWidget />)
6566
.child(<NoteTitleWidget />))
66-
.child(<PopupEditorFormattingToolbar />)
67+
.child(<StandaloneRibbonAdapter component={FormattingToolbar} />)
6768
.child(new PromotedAttributesWidget())
6869
.child(new NoteDetailWidget())
6970
.child(<NoteList media="screen" displayOnlyCollections />))
Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
1-
import { useNoteContext, useTriliumOption } from "../react/hooks";
1+
import { useTriliumOption } from "../react/hooks";
2+
import { TabContext } from "./ribbon-interface";
23

34
/**
45
* Handles the editing toolbar when the CKEditor is in decoupled mode.
56
*
67
* This toolbar is only enabled if the user has selected the classic CKEditor.
78
*
89
* The ribbon item is active by default for text notes, as long as they are not in read-only mode.
9-
*
10+
*
1011
* ! The toolbar is not only used in the ribbon, but also in the quick edit feature.
1112
*/
12-
export default function FormattingToolbar({ hidden }: { hidden?: boolean }) {
13+
export default function FormattingToolbar({ hidden }: TabContext) {
1314
const [ textNoteEditorType ] = useTriliumOption("textNoteEditorType");
1415

1516
return (textNoteEditorType === "ckeditor-classic" &&
1617
<div className={`classic-toolbar-widget ${hidden ? "hidden-ext" : ""}`} />
1718
)
1819
};
19-
20-
export function PopupEditorFormattingToolbar() {
21-
// TODO: Integrate this directly once we migrate away from class components.
22-
const { note } = useNoteContext();
23-
return <FormattingToolbar hidden={note?.type !== "text"} />;
24-
}

apps/client/src/widgets/ribbon/Ribbon.tsx

Lines changed: 4 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,15 @@
11
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
2-
import { t } from "../../services/i18n";
32
import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
43
import "./style.css";
5-
import { VNode } from "preact";
6-
import BasicPropertiesTab from "./BasicPropertiesTab";
7-
import FormattingToolbar from "./FormattingToolbar";
4+
85
import { numberObjectsInPlace } from "../../services/utils";
9-
import { TabContext } from "./ribbon-interface";
10-
import options from "../../services/options";
116
import { EventNames } from "../../components/app_context";
12-
import FNote from "../../entities/fnote";
13-
import ScriptTab from "./ScriptTab";
14-
import EditedNotesTab from "./EditedNotesTab";
15-
import NotePropertiesTab from "./NotePropertiesTab";
16-
import NoteInfoTab from "./NoteInfoTab";
17-
import SimilarNotesTab from "./SimilarNotesTab";
18-
import FilePropertiesTab from "./FilePropertiesTab";
19-
import ImagePropertiesTab from "./ImagePropertiesTab";
20-
import NotePathsTab from "./NotePathsTab";
21-
import NoteMapTab from "./NoteMapTab";
22-
import OwnedAttributesTab from "./OwnedAttributesTab";
23-
import InheritedAttributesTab from "./InheritedAttributesTab";
24-
import CollectionPropertiesTab from "./CollectionPropertiesTab";
25-
import SearchDefinitionTab from "./SearchDefinitionTab";
267
import NoteActions from "./NoteActions";
278
import { KeyboardActionNames } from "@triliumnext/commons";
9+
import { RIBBON_TAB_DEFINITIONS } from "./RibbonDefinition";
10+
import { TabConfiguration, TitleContext } from "./ribbon-interface";
2811

29-
interface TitleContext {
30-
note: FNote | null | undefined;
31-
}
32-
33-
interface TabConfiguration {
34-
title: string | ((context: TitleContext) => string);
35-
icon: string;
36-
content: (context: TabContext) => VNode | false;
37-
show: boolean | ((context: TitleContext) => boolean | null | undefined);
38-
toggleCommand?: KeyboardActionNames;
39-
activate?: boolean | ((context: TitleContext) => boolean);
40-
/**
41-
* By default the tab content will not be rendered unless the tab is active (i.e. selected by the user). Setting to `true` will ensure that the tab is rendered even when inactive, for cases where the tab needs to be accessible at all times (e.g. for the detached editor toolbar) or if event handling is needed.
42-
*/
43-
stayInDom?: boolean;
44-
}
45-
46-
const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
47-
{
48-
title: t("classic_editor_toolbar.title"),
49-
icon: "bx bx-text",
50-
show: ({ note }) => note?.type === "text" && options.get("textNoteEditorType") === "ckeditor-classic",
51-
toggleCommand: "toggleRibbonTabClassicEditor",
52-
content: FormattingToolbar,
53-
activate: true,
54-
stayInDom: true
55-
},
56-
{
57-
title: ({ note }) => note?.isTriliumSqlite() ? t("script_executor.query") : t("script_executor.script"),
58-
icon: "bx bx-play",
59-
content: ScriptTab,
60-
activate: true,
61-
show: ({ note }) => note &&
62-
(note.isTriliumScript() || note.isTriliumSqlite()) &&
63-
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
64-
},
65-
{
66-
title: t("search_definition.search_parameters"),
67-
icon: "bx bx-search",
68-
content: SearchDefinitionTab,
69-
activate: true,
70-
show: ({ note }) => note?.type === "search"
71-
},
72-
{
73-
title: t("edited_notes.title"),
74-
icon: "bx bx-calendar-edit",
75-
content: EditedNotesTab,
76-
show: ({ note }) => note?.hasOwnedLabel("dateNote"),
77-
activate: ({ note }) => (note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon")
78-
},
79-
{
80-
title: t("book_properties.book_properties"),
81-
icon: "bx bx-book",
82-
content: CollectionPropertiesTab,
83-
show: ({ note }) => note?.type === "book" || note?.type === "search",
84-
toggleCommand: "toggleRibbonTabBookProperties"
85-
},
86-
{
87-
title: t("note_properties.info"),
88-
icon: "bx bx-info-square",
89-
content: NotePropertiesTab,
90-
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
91-
activate: true
92-
},
93-
{
94-
title: t("file_properties.title"),
95-
icon: "bx bx-file",
96-
content: FilePropertiesTab,
97-
show: ({ note }) => note?.type === "file",
98-
toggleCommand: "toggleRibbonTabFileProperties",
99-
activate: ({ note }) => note?.mime !== "application/pdf"
100-
},
101-
{
102-
title: t("image_properties.title"),
103-
icon: "bx bx-image",
104-
content: ImagePropertiesTab,
105-
show: ({ note }) => note?.type === "image",
106-
toggleCommand: "toggleRibbonTabImageProperties",
107-
activate: true,
108-
},
109-
{
110-
// BasicProperties
111-
title: t("basic_properties.basic_properties"),
112-
icon: "bx bx-slider",
113-
content: BasicPropertiesTab,
114-
show: ({note}) => !note?.isLaunchBarConfig(),
115-
toggleCommand: "toggleRibbonTabBasicProperties"
116-
},
117-
{
118-
title: t("owned_attribute_list.owned_attributes"),
119-
icon: "bx bx-list-check",
120-
content: OwnedAttributesTab,
121-
show: ({note}) => !note?.isLaunchBarConfig(),
122-
toggleCommand: "toggleRibbonTabOwnedAttributes",
123-
stayInDom: true
124-
},
125-
{
126-
title: t("inherited_attribute_list.title"),
127-
icon: "bx bx-list-plus",
128-
content: InheritedAttributesTab,
129-
show: ({note}) => !note?.isLaunchBarConfig(),
130-
toggleCommand: "toggleRibbonTabInheritedAttributes"
131-
},
132-
{
133-
title: t("note_paths.title"),
134-
icon: "bx bx-collection",
135-
content: NotePathsTab,
136-
show: true,
137-
toggleCommand: "toggleRibbonTabNotePaths"
138-
},
139-
{
140-
title: t("note_map.title"),
141-
icon: "bx bxs-network-chart",
142-
content: NoteMapTab,
143-
show: true,
144-
toggleCommand: "toggleRibbonTabNoteMap"
145-
},
146-
{
147-
title: t("similar_notes.title"),
148-
icon: "bx bx-bar-chart",
149-
show: ({ note }) => note?.type !== "search" && !note?.isLabelTruthy("similarNotesWidgetDisabled"),
150-
content: SimilarNotesTab,
151-
toggleCommand: "toggleRibbonTabSimilarNotes"
152-
},
153-
{
154-
title: t("note_info_widget.title"),
155-
icon: "bx bx-info-circle",
156-
show: ({ note }) => !!note,
157-
content: NoteInfoTab,
158-
toggleCommand: "toggleRibbonTabNoteInfo"
159-
}
160-
]);
12+
const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>(RIBBON_TAB_DEFINITIONS);
16113

16214
export default function Ribbon() {
16315
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import ScriptTab from "./ScriptTab";
2+
import EditedNotesTab from "./EditedNotesTab";
3+
import NotePropertiesTab from "./NotePropertiesTab";
4+
import NoteInfoTab from "./NoteInfoTab";
5+
import SimilarNotesTab from "./SimilarNotesTab";
6+
import FilePropertiesTab from "./FilePropertiesTab";
7+
import ImagePropertiesTab from "./ImagePropertiesTab";
8+
import NotePathsTab from "./NotePathsTab";
9+
import NoteMapTab from "./NoteMapTab";
10+
import OwnedAttributesTab from "./OwnedAttributesTab";
11+
import InheritedAttributesTab from "./InheritedAttributesTab";
12+
import CollectionPropertiesTab from "./CollectionPropertiesTab";
13+
import SearchDefinitionTab from "./SearchDefinitionTab";
14+
import BasicPropertiesTab from "./BasicPropertiesTab";
15+
import FormattingToolbar from "./FormattingToolbar";
16+
import options from "../../services/options";
17+
import { t } from "../../services/i18n";
18+
import { TabConfiguration } from "./ribbon-interface";
19+
20+
export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
21+
{
22+
title: t("classic_editor_toolbar.title"),
23+
icon: "bx bx-text",
24+
show: ({ note }) => note?.type === "text" && options.get("textNoteEditorType") === "ckeditor-classic",
25+
toggleCommand: "toggleRibbonTabClassicEditor",
26+
content: FormattingToolbar,
27+
activate: true,
28+
stayInDom: true
29+
},
30+
{
31+
title: ({ note }) => note?.isTriliumSqlite() ? t("script_executor.query") : t("script_executor.script"),
32+
icon: "bx bx-play",
33+
content: ScriptTab,
34+
activate: true,
35+
show: ({ note }) => note &&
36+
(note.isTriliumScript() || note.isTriliumSqlite()) &&
37+
(note.hasLabel("executeDescription") || note.hasLabel("executeButton"))
38+
},
39+
{
40+
title: t("search_definition.search_parameters"),
41+
icon: "bx bx-search",
42+
content: SearchDefinitionTab,
43+
activate: true,
44+
show: ({ note }) => note?.type === "search"
45+
},
46+
{
47+
title: t("edited_notes.title"),
48+
icon: "bx bx-calendar-edit",
49+
content: EditedNotesTab,
50+
show: ({ note }) => note?.hasOwnedLabel("dateNote"),
51+
activate: ({ note }) => (note?.getPromotedDefinitionAttributes().length === 0 || !options.is("promotedAttributesOpenInRibbon")) && options.is("editedNotesOpenInRibbon")
52+
},
53+
{
54+
title: t("book_properties.book_properties"),
55+
icon: "bx bx-book",
56+
content: CollectionPropertiesTab,
57+
show: ({ note }) => note?.type === "book" || note?.type === "search",
58+
toggleCommand: "toggleRibbonTabBookProperties"
59+
},
60+
{
61+
title: t("note_properties.info"),
62+
icon: "bx bx-info-square",
63+
content: NotePropertiesTab,
64+
show: ({ note }) => !!note?.getLabelValue("pageUrl"),
65+
activate: true
66+
},
67+
{
68+
title: t("file_properties.title"),
69+
icon: "bx bx-file",
70+
content: FilePropertiesTab,
71+
show: ({ note }) => note?.type === "file",
72+
toggleCommand: "toggleRibbonTabFileProperties",
73+
activate: ({ note }) => note?.mime !== "application/pdf"
74+
},
75+
{
76+
title: t("image_properties.title"),
77+
icon: "bx bx-image",
78+
content: ImagePropertiesTab,
79+
show: ({ note }) => note?.type === "image",
80+
toggleCommand: "toggleRibbonTabImageProperties",
81+
activate: true,
82+
},
83+
{
84+
// BasicProperties
85+
title: t("basic_properties.basic_properties"),
86+
icon: "bx bx-slider",
87+
content: BasicPropertiesTab,
88+
show: ({note}) => !note?.isLaunchBarConfig(),
89+
toggleCommand: "toggleRibbonTabBasicProperties"
90+
},
91+
{
92+
title: t("owned_attribute_list.owned_attributes"),
93+
icon: "bx bx-list-check",
94+
content: OwnedAttributesTab,
95+
show: ({note}) => !note?.isLaunchBarConfig(),
96+
toggleCommand: "toggleRibbonTabOwnedAttributes",
97+
stayInDom: true
98+
},
99+
{
100+
title: t("inherited_attribute_list.title"),
101+
icon: "bx bx-list-plus",
102+
content: InheritedAttributesTab,
103+
show: ({note}) => !note?.isLaunchBarConfig(),
104+
toggleCommand: "toggleRibbonTabInheritedAttributes"
105+
},
106+
{
107+
title: t("note_paths.title"),
108+
icon: "bx bx-collection",
109+
content: NotePathsTab,
110+
show: true,
111+
toggleCommand: "toggleRibbonTabNotePaths"
112+
},
113+
{
114+
title: t("note_map.title"),
115+
icon: "bx bxs-network-chart",
116+
content: NoteMapTab,
117+
show: true,
118+
toggleCommand: "toggleRibbonTabNoteMap"
119+
},
120+
{
121+
title: t("similar_notes.title"),
122+
icon: "bx bx-bar-chart",
123+
show: ({ note }) => note?.type !== "search" && !note?.isLabelTruthy("similarNotesWidgetDisabled"),
124+
content: SimilarNotesTab,
125+
toggleCommand: "toggleRibbonTabSimilarNotes"
126+
},
127+
{
128+
title: t("note_info_widget.title"),
129+
icon: "bx bx-info-circle",
130+
show: ({ note }) => !!note,
131+
content: NoteInfoTab,
132+
toggleCommand: "toggleRibbonTabNoteInfo"
133+
}
134+
];
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { ComponentChildren } from "preact";
2+
import { useNoteContext } from "../../react/hooks";
3+
import { TabContext, TitleContext } from "../ribbon-interface";
4+
import { useEffect, useMemo, useState } from "preact/hooks";
5+
import { RIBBON_TAB_DEFINITIONS } from "../RibbonDefinition";
6+
7+
interface StandaloneRibbonAdapterProps {
8+
component: (props: TabContext) => ComponentChildren;
9+
}
10+
11+
/**
12+
* Takes in any ribbon tab component and renders it in standalone mod using the note context, thus requiring no inputs.
13+
* Especially useful on mobile to detach components that would normally fit in the ribbon.
14+
*/
15+
export default function StandaloneRibbonAdapter({ component }: StandaloneRibbonAdapterProps) {
16+
const Component = component;
17+
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
18+
const definition = useMemo(() => RIBBON_TAB_DEFINITIONS.find(def => def.content === component), [ component ]);
19+
const [ shown, setShown ] = useState(unwrapShown(definition?.show, { note }));
20+
21+
useEffect(() => {
22+
setShown(unwrapShown(definition?.show, { note }));
23+
}, [ note ]);
24+
25+
return (
26+
<Component
27+
note={note}
28+
hidden={!shown}
29+
ntxId={ntxId}
30+
hoistedNoteId={hoistedNoteId}
31+
notePath={notePath}
32+
noteContext={noteContext}
33+
componentId={componentId}
34+
activate={() => {}}
35+
/>
36+
);
37+
}
38+
39+
function unwrapShown(value: boolean | ((context: TitleContext) => boolean | null | undefined) | undefined, context: TitleContext) {
40+
if (!value) return true;
41+
if (typeof value === "boolean") return value;
42+
return !!value(context);
43+
}

0 commit comments

Comments
 (0)