chore(react/ribbon): add menu

This commit is contained in:
Elian Doran 2025-08-23 19:48:01 +03:00
parent 73ca285b7a
commit 3f3c7cfe88
No known key found for this signature in database
3 changed files with 89 additions and 95 deletions

View File

@ -1,25 +1,12 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.js";
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
import froca from "../../services/froca.js";
import noteCreateService from "../../services/note_create.js";
import attributeService from "../../services/attributes.js";
import linkService from "../../services/link.js";
import type AttributeDetailWidget from "./attribute_detail.js";
import type { CommandData, EventData, EventListener, FilteredCommandNames } from "../../components/app_context.js";
import type { default as FAttribute, AttributeType } from "../../entities/fattribute.js";
import { escapeQuotes } from "../../services/utils.js";
const TPL = /*html*/`
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
</div>
`;
type AttributeCommandNames = FilteredCommandNames<CommandData>;
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
private attributeDetailWidget: AttributeDetailWidget;
@ -48,30 +35,9 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
this.$editor.on("blur", () => setTimeout(() => this.save(), 100)); // Timeout to fix https://github.com/zadam/trilium/issues/4160
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
this.$saveAttributesButton.on("click", () => this.save());
}
addNewAttribute(e: JQuery.ClickEvent) {
contextMenuService.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ title: "----" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: ({ command }) => this.handleAddNewAttributeCommand(command)
});
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
}
// triggered from keyboard shortcut
async addNewLabelEvent({ ntxId }: EventData<"addNewLabel">) {
if (this.isNoteContext(ntxId)) {
@ -90,66 +56,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
}
}
async handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
// TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
const attrs = this.parseAttributes() as FAttribute[];
if (!attrs) {
return;
}
let type: AttributeType;
let name;
let value;
if (command === "addNewLabel") {
type = "label";
name = "myLabel";
value = "";
} else if (command === "addNewRelation") {
type = "relation";
name = "myRelation";
value = "";
} else if (command === "addNewLabelDefinition") {
type = "label";
name = "label:myLabel";
value = "promoted,single,text";
} else if (command === "addNewRelationDefinition") {
type = "label";
name = "relation:myRelation";
value = "promoted,single";
} else {
return;
}
// TODO: Incomplete type
//@ts-ignore
attrs.push({
type,
name,
value,
isInheritable: false
});
await this.renderOwnedAttributes(attrs, false);
this.$editor.scrollTop(this.$editor[0].scrollHeight);
const rect = this.$editor[0].getBoundingClientRect();
setTimeout(() => {
// showing a little bit later because there's a conflict with outside click closing the attr detail
this.attributeDetailWidget.showAttributeDetail({
allAttributes: attrs,
attribute: attrs[attrs.length - 1],
isOwned: true,
x: (rect.left + rect.right) / 2,
y: rect.bottom,
focus: "name"
});
}, 100);
}
async save() {
if (this.lastUpdatedNoteId !== this.noteId) {
// https://github.com/zadam/trilium/issues/3090

View File

@ -2,7 +2,7 @@ interface ActionButtonProps {
text: string;
icon: string;
className?: string;
onClick?: () => void;
onClick?: (e: MouseEvent) => void;
}
export default function ActionButton({ text, icon, className, onClick }: ActionButtonProps) {

View File

@ -16,6 +16,11 @@ import { ParentComponent } from "../../react/react_utils";
import Component from "../../../components/component";
import link from "../../../services/link";
import froca from "../../../services/froca";
import contextMenu from "../../../menus/context_menu";
import type { CommandData, FilteredCommandNames } from "../../../components/app_context";
import { AttributeType } from "@triliumnext/commons";
type AttributeCommandNames = FilteredCommandNames<CommandData>;
const HELP_TEXT = `
<p>${t("attribute_editor.help_text_body1")}</p>
@ -143,6 +148,65 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co
}
}
async function handleAddNewAttributeCommand(command: AttributeCommandNames | undefined) {
// TODO: Not sure what the relation between FAttribute[] and Attribute[] is.
const attrs = parseAttributes() as FAttribute[];
if (!attrs) {
return;
}
let type: AttributeType;
let name;
let value;
if (command === "addNewLabel") {
type = "label";
name = "myLabel";
value = "";
} else if (command === "addNewRelation") {
type = "relation";
name = "myRelation";
value = "";
} else if (command === "addNewLabelDefinition") {
type = "label";
name = "label:myLabel";
value = "promoted,single,text";
} else if (command === "addNewRelationDefinition") {
type = "label";
name = "relation:myRelation";
value = "promoted,single";
} else {
return;
}
// TODO: Incomplete type
//@ts-ignore
attrs.push({
type,
name,
value,
isInheritable: false
});
await renderOwnedAttributes(attrs, false);
// this.$editor.scrollTop(this.$editor[0].scrollHeight);
const rect = wrapperRef.current?.getBoundingClientRect();
setTimeout(() => {
// showing a little bit later because there's a conflict with outside click closing the attr detail
attributeDetailWidget.showAttributeDetail({
allAttributes: attrs,
attribute: attrs[attrs.length - 1],
isOwned: true,
x: rect ? (rect.left + rect.right) / 2 : 0,
y: rect?.bottom ?? 0,
focus: "name"
});
}, 100);
}
useEffect(() => {
renderOwnedAttributes(note.getOwnedAttributes(), true);
}, [ note ]);
@ -228,6 +292,30 @@ export default function AttributeEditor({ note, componentId }: { note: FNote, co
onClick={save}
/> }
<ActionButton
icon="bx bx-plus"
className="add-new-attribute-button"
text={escapeQuotes(t("attribute_editor.add_a_new_attribute"))}
onClick={(e) => {
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
contextMenu.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ title: "----" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command)
});
}}
/>
{ error && (
<div className="attribute-errors">
{typeof error === "object" && "message" in error && typeof error.message === "string" && error.message}