chore(react/ribbon): port convert to attachment

This commit is contained in:
Elian Doran 2025-08-24 23:20:26 +03:00
parent f91c1f4180
commit a3e8fd374f
No known key found for this signature in database
4 changed files with 60 additions and 37 deletions

View File

@ -10,11 +10,6 @@ import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
import type { FAttachmentRow } from "../../entities/fattachment.js";
// TODO: Deduplicate with server
interface ConvertToAttachmentResponse {
attachment: FAttachmentRow;
}
const TPL = /*html*/`
<div class="dropdown note-actions">
<style>
@ -42,14 +37,7 @@ const TPL = /*html*/`
</style>
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
<li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">
<span class="bx bx-paperclip"></span> ${t("note_actions.convert_into_attachment")}
</li>
<li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button">
<span class="bx bx-extension"></span> ${t("note_actions.re_render_note")}<kbd data-command="renderActiveNote"></kbd>
</li>
@ -214,28 +202,6 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.toggleDisabled(this.$saveRevisionButton, !isInOptions);
}
async convertNoteIntoAttachmentCommand() {
if (!this.note || !(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
return;
}
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${this.noteId}/convert-to-attachment`);
if (!newAttachment) {
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
return;
}
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, {
viewScope: {
viewMode: "attachments",
attachmentId: newAttachment.attachmentId
}
});
}
toggleDisabled($el: JQuery<HTMLElement>, enable: boolean) {
if (enable) {
$el.removeAttr("disabled");

View File

@ -3,6 +3,7 @@ import { ComponentChildren } from "preact";
import Icon from "./Icon";
import { useEffect, useMemo, useRef, type CSSProperties } from "preact/compat";
import "./FormList.css";
import { CommandNames } from "../../components/app_context";
interface FormListOpts {
children: ComponentChildren;
@ -80,12 +81,13 @@ interface FormListItemOpts {
checked?: boolean | null;
selected?: boolean;
onClick?: () => void;
triggerCommand?: CommandNames;
description?: string;
className?: string;
rtl?: boolean;
}
export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected, rtl }: FormListItemOpts) {
export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected, rtl, triggerCommand }: FormListItemOpts) {
if (checked) {
icon = "bx bx-check";
}
@ -96,6 +98,7 @@ export function FormListItem({ children, icon, value, title, active, badges, dis
data-value={value} title={title}
tabIndex={0}
onClick={onClick}
data-trigger-command={triggerCommand}
dir={rtl ? "rtl" : undefined}
>
<Icon icon={icon} />&nbsp;

View File

@ -1,6 +1,14 @@
import { ConvertToAttachmentResponse } from "@triliumnext/commons";
import appContext from "../../components/app_context";
import FNote from "../../entities/fnote"
import dialog from "../../services/dialog";
import { t } from "../../services/i18n"
import server from "../../services/server";
import toast from "../../services/toast";
import ws from "../../services/ws";
import ActionButton from "../react/ActionButton"
import Dropdown from "../react/Dropdown";
import { FormListItem } from "../react/FormList";
interface NoteActionsProps {
note?: FNote;
@ -10,8 +18,9 @@ export default function NoteActions(props: NoteActionsProps) {
return (
<>
<RevisionsButton {...props} />
<NoteContextMenu {...props} />
</>
)
);
}
function RevisionsButton({ note }: NoteActionsProps) {
@ -24,5 +33,46 @@ function RevisionsButton({ note }: NoteActionsProps) {
triggerCommand="showRevisions"
titlePosition="bottom"
/>
);
}
function NoteContextMenu(props: NoteActionsProps) {
return (
<Dropdown
buttonClassName="bx bx-dots-vertical-rounded"
hideToggleArrow
noSelectButtonStyle
>
<ConvertToAttachment {...props} />
</Dropdown>
);
}
function ConvertToAttachment({ note }: NoteActionsProps) {
return (note?.isEligibleForConversionToAttachment() &&
<FormListItem
icon="bx bx-paperclip"
onClick={async () => {
if (!note || !(await dialog.confirm(t("note_actions.convert_into_attachment_prompt", { title: note.title })))) {
return;
}
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${note.noteId}/convert-to-attachment`);
if (!newAttachment) {
toast.showMessage(t("note_actions.convert_into_attachment_failed", { title: note.title }));
return;
}
toast.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(newAttachment.ownerId, {
viewScope: {
viewMode: "attachments",
attachmentId: newAttachment.attachmentId
}
});
}}
>{t("note_actions.convert_into_attachment")}</FormListItem>
)
}

View File

@ -1,4 +1,4 @@
import { AttributeRow, NoteType } from "./rows.js";
import { AttachmentRow, AttributeRow, NoteType } from "./rows.js";
type Response = {
success: true,
@ -202,3 +202,7 @@ export interface CloneResponse {
branchId?: string;
notePath?: string;
}
export interface ConvertToAttachmentResponse {
attachment: AttachmentRow;
}