chore(react/ribbon): change note type

This commit is contained in:
Elian Doran 2025-08-21 20:56:37 +03:00
parent c0beab8a5d
commit f45da049b9
No known key found for this signature in database
4 changed files with 51 additions and 117 deletions

View File

@ -11,14 +11,6 @@ import type FNote from "../entities/fnote.js";
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type); const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type);
const TPL = /*html*/`
<div class="dropdown note-type-widget">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-type-button">
<span class=""></span>
</button>
</div>
`;
export default class NoteTypeWidget extends NoteContextAwareWidget { export default class NoteTypeWidget extends NoteContextAwareWidget {
private dropdown!: Dropdown; private dropdown!: Dropdown;
@ -47,93 +39,4 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.dropdown.hide(); this.dropdown.hide();
} }
/** the actual body is rendered lazily on note-type button click */
async renderDropdown() {
this.$noteTypeDropdown.empty();
if (!this.note) {
return;
}
for (const noteType of ) {
let $typeLink: JQuery<HTMLElement>;
if (noteType.type !== "code") {
$typeLink = $('<a class="dropdown-item">')
.attr("data-note-type", noteType.type)
.append('<span class="check">&check;</span> ')
.append($title)
.on("click", (e) => {
const type = $typeLink.attr("data-note-type");
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
if (noteType) {
this.save(noteType.type, noteType.mime);
}
});
} else {
this.$noteTypeDropdown.append('<div class="dropdown-divider"></div>');
$typeLink = $('<a class="dropdown-item disabled">').attr("data-note-type", noteType.type).append('<span class="check">&check;</span> ').append($("<strong>").text());
}
if (this.note.type === noteType.type) {
$typeLink.addClass("selected");
}
this.$noteTypeDropdown.append($typeLink);
}
for (const mimeType of ) {
const $mimeLink = $('<a class="dropdown-item">')
.attr("data-mime-type", mimeType.mime)
.append('<span class="check">&check;</span> ')
.on("click", (e) => {
const $link = $(e.target).closest(".dropdown-item");
this.save("code", $link.attr("data-mime-type") ?? "");
});
if (this.note.type === "code" && this.note.mime === mimeType.mime) {
$mimeLink.addClass("selected");
this.$noteTypeDesc.text(mimeType.title);
}
this.$noteTypeDropdown.append($mimeLink);
}
}
async save(type: NoteType, mime?: string) {
if (type === this.note?.type && mime === this.note?.mime) {
return;
}
if (type !== this.note?.type && !(await this.confirmChangeIfContent())) {
return;
}
await server.put(`notes/${this.noteId}/type`, { type, mime });
}
async confirmChangeIfContent() {
if (!this.note) {
return;
}
const blob = await this.note.getBlob();
if (!blob?.content || !blob.content.trim().length) {
return true;
}
return await dialogService.confirm(t("note_types.confirm-change"));
}
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh();
}
}
} }

View File

@ -76,14 +76,21 @@ interface FormListItemOpts {
active?: boolean; active?: boolean;
badges?: FormListBadge[]; badges?: FormListBadge[];
disabled?: boolean; disabled?: boolean;
checked?: boolean;
onClick?: () => void;
} }
export function FormListItem({ children, icon, value, title, active, badges, disabled }: FormListItemOpts) { export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick }: FormListItemOpts) {
if (checked) {
icon = "bx bx-check";
}
return ( return (
<a <a
class={`dropdown-item ${active ? "active" : ""} ${disabled ? "disabled" : ""}`} class={`dropdown-item ${active ? "active" : ""} ${disabled ? "disabled" : ""}`}
data-value={value} title={title} data-value={value} title={title}
tabIndex={0} tabIndex={0}
onClick={onClick}
> >
<Icon icon={icon} />&nbsp; <Icon icon={icon} />&nbsp;
{children} {children}

View File

@ -1,4 +1,4 @@
import { useMemo } from "preact/hooks"; import { useCallback, useMemo } from "preact/hooks";
import Dropdown from "../react/Dropdown"; import Dropdown from "../react/Dropdown";
import { NOTE_TYPES } from "../../services/note_types"; import { NOTE_TYPES } from "../../services/note_types";
import { FormDivider, FormListBadge, FormListItem } from "../react/FormList"; import { FormDivider, FormListBadge, FormListItem } from "../react/FormList";
@ -6,6 +6,8 @@ import { t } from "../../services/i18n";
import { useNoteContext, useNoteProperty, useTriliumOption } from "../react/hooks"; import { useNoteContext, useNoteProperty, useTriliumOption } from "../react/hooks";
import mime_types from "../../services/mime_types"; import mime_types from "../../services/mime_types";
import { NoteType } from "@triliumnext/commons"; import { NoteType } from "@triliumnext/commons";
import server from "../../services/server";
import dialog from "../../services/dialog";
export default function BasicPropertiesTab() { export default function BasicPropertiesTab() {
return ( return (
@ -21,53 +23,79 @@ function NoteTypeWidget() {
const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]); const mimeTypes = useMemo(() => mime_types.getMimeTypes().filter(mimeType => mimeType.enabled), [ codeNotesMimeTypes ]);
const { note } = useNoteContext(); const { note } = useNoteContext();
const type = useNoteProperty(note, "type") ?? undefined; const currentNoteType = useNoteProperty(note, "type") ?? undefined;
const mime = useNoteProperty(note, "mime"); const currentNoteMime = useNoteProperty(note, "mime");
const changeNoteType = useCallback(async (type: NoteType, mime?: string) => {
if (!note || (type === currentNoteType && mime === currentNoteMime)) {
return;
}
// Confirm change if the note already has a content.
if (type !== currentNoteType) {
const blob = await note.getBlob();
if (blob?.content && blob.content.trim().length &&
!await (dialog.confirm(t("note_types.confirm-change")))) {
return;
}
}
await server.put(`notes/${note.noteId}/type`, { type, mime });
}, [ note, currentNoteType, currentNoteMime ]);
return ( return (
<> <div className="note-type-container">
<span>{t("basic_properties.note_type")}:</span> &nbsp; <span>{t("basic_properties.note_type")}:</span> &nbsp;
<Dropdown <Dropdown
dropdownContainerClassName="note-type-dropdown" dropdownContainerClassName="note-type-dropdown"
text={<span className="note-type-desc">{findTypeTitle(type, mime)}</span>} text={<span className="note-type-desc">{findTypeTitle(currentNoteType, currentNoteMime)}</span>}
> >
{noteTypes.map(noteType => { {noteTypes.map(({ isNew, isBeta, type, mime, title }) => {
const badges: FormListBadge[] = []; const badges: FormListBadge[] = [];
if (noteType.isNew) { if (isNew) {
badges.push({ badges.push({
className: "new-note-type-badge", className: "new-note-type-badge",
text: t("note_types.new-feature") text: t("note_types.new-feature")
}); });
} }
if (noteType.isBeta) { if (isBeta) {
badges.push({ badges.push({
text: t("note_types.beta-feature") text: t("note_types.beta-feature")
}); });
} }
if (noteType.type !== "code") { const checked = (type === currentNoteType);
if (type !== "code") {
return ( return (
<FormListItem <FormListItem
checked={checked}
badges={badges} badges={badges}
>{noteType.title}</FormListItem> onClick={() => changeNoteType(type, mime)}
>{title}</FormListItem>
); );
} else { } else {
return ( return (
<> <>
<FormDivider /> <FormDivider />
<FormListItem disabled> <FormListItem
<strong>{noteType.title}</strong> checked={checked}
disabled
>
<strong>{title}</strong>
</FormListItem> </FormListItem>
</> </>
) )
} }
})} })}
{mimeTypes.map(mimeType => ( {mimeTypes.map(({ title, mime }) => (
<FormListItem>{mimeType.title}</FormListItem> <FormListItem onClick={() => changeNoteType("code", mime)}>
{title}
</FormListItem>
))} ))}
</Dropdown> </Dropdown>
</> </div>
) )
} }

View File

@ -10,10 +10,6 @@ import type FNote from "../../entities/fnote.js";
import NoteLanguageWidget from "../note_language.js"; import NoteLanguageWidget from "../note_language.js";
const TPL = /*html*/` const TPL = /*html*/`
<div class="note-type-container">
</div>
<div class="protected-note-switch-container"></div> <div class="protected-note-switch-container"></div>
<div class="editability-select-container"> <div class="editability-select-container">