feat(note_actions): integrate note type

This commit is contained in:
Elian Doran 2025-12-10 21:54:17 +02:00
parent 77f5770bff
commit 6f85b7cc09
No known key found for this signature in database
2 changed files with 91 additions and 69 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } 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 { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList"; import { FormDropdownDivider, FormListBadge, FormListItem } from "../react/FormList";
@ -28,7 +28,7 @@ const isNewLayout = isExperimentalFeatureEnabled("new-layout");
export default function BasicPropertiesTab({ note }: TabContext) { export default function BasicPropertiesTab({ note }: TabContext) {
return ( return (
<div className="basic-properties-widget"> <div className="basic-properties-widget">
<NoteTypeWidget note={note} /> {!isNewLayout && <NoteTypeWidget note={note} />}
{!isNewLayout && <ProtectedNoteSwitch note={note} />} {!isNewLayout && <ProtectedNoteSwitch note={note} />}
{!isNewLayout && <EditabilitySelect note={note} />} {!isNewLayout && <EditabilitySelect note={note} />}
{!isNewLayout && <BookmarkSwitch note={note} />} {!isNewLayout && <BookmarkSwitch note={note} />}
@ -40,18 +40,42 @@ export default function BasicPropertiesTab({ note }: TabContext) {
} }
function NoteTypeWidget({ note }: { note?: FNote | null }) { function NoteTypeWidget({ note }: { note?: FNote | null }) {
const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []);
const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes");
const mimeTypes = useMemo(() => {
mime_types.loadMimeTypes();
return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled)
}, [ codeNotesMimeTypes ]);
const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []); const notSelectableNoteTypes = useMemo(() => NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type), []);
const currentNoteType = useNoteProperty(note, "type") ?? undefined; const currentNoteType = useNoteProperty(note, "type") ?? undefined;
const currentNoteMime = useNoteProperty(note, "mime"); const currentNoteMime = useNoteProperty(note, "mime");
const [ modalShown, setModalShown ] = useState(false); const [ modalShown, setModalShown ] = useState(false);
return (
<div className="note-type-container">
<span>{t("basic_properties.note_type")}:</span> &nbsp;
<Dropdown
dropdownContainerClassName="note-type-dropdown"
text={<span className="note-type-desc">{findTypeTitle(currentNoteType, currentNoteMime)}</span>}
disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")}
>
<NoteTypeDropdownContent currentNoteType={currentNoteType} currentNoteMime={currentNoteMime} note={note} setModalShown={setModalShown} />
</Dropdown>
<Modal
className="code-mime-types-modal"
title={t("code_mime_types.title")}
show={modalShown} onHidden={() => setModalShown(false)}
size="xl" scrollable
>
<CodeMimeTypesList />
</Modal>
</div>
);
}
export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note, setModalShown }: { currentNoteType?: NoteType, currentNoteMime?: string | null, note?: FNote | null, setModalShown: Dispatch<StateUpdater<boolean>> }) {
const [ codeNotesMimeTypes ] = useTriliumOption("codeNotesMimeTypes");
const noteTypes = useMemo(() => NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static), []);
const mimeTypes = useMemo(() => {
mime_types.loadMimeTypes();
return mime_types.getMimeTypes().filter(mimeType => mimeType.enabled);
}, [ codeNotesMimeTypes ]);
const changeNoteType = useCallback(async (type: NoteType, mime?: string) => { const changeNoteType = useCallback(async (type: NoteType, mime?: string) => {
if (!note || (type === currentNoteType && mime === currentNoteMime)) { if (!note || (type === currentNoteType && mime === currentNoteMime)) {
return; return;
@ -71,70 +95,54 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) {
}, [ note, currentNoteType, currentNoteMime ]); }, [ note, currentNoteType, currentNoteMime ]);
return ( return (
<div className="note-type-container"> <>
<span>{t("basic_properties.note_type")}:</span> &nbsp; {noteTypes.map(({ isNew, isBeta, type, mime, title }) => {
<Dropdown const badges: FormListBadge[] = [];
dropdownContainerClassName="note-type-dropdown" if (isNew) {
text={<span className="note-type-desc">{findTypeTitle(currentNoteType, currentNoteMime)}</span>} badges.push({
disabled={notSelectableNoteTypes.includes(currentNoteType ?? "text")} className: "new-note-type-badge",
> text: t("note_types.new-feature")
{noteTypes.map(({ isNew, isBeta, type, mime, title }) => { });
const badges: FormListBadge[] = []; }
if (isNew) { if (isBeta) {
badges.push({ badges.push({
className: "new-note-type-badge", text: t("note_types.beta-feature")
text: t("note_types.new-feature") });
}); }
}
if (isBeta) {
badges.push({
text: t("note_types.beta-feature")
});
}
const checked = (type === currentNoteType); const checked = (type === currentNoteType);
if (type !== "code") { if (type !== "code") {
return ( return (
<FormListItem
checked={checked}
badges={badges}
onClick={() => changeNoteType(type, mime)}
>{title}</FormListItem>
);
} else {
return (
<>
<FormDropdownDivider />
<FormListItem <FormListItem
checked={checked} checked={checked}
badges={badges} disabled
onClick={() => changeNoteType(type, mime)} >
>{title}</FormListItem> <strong>{title}</strong>
); </FormListItem>
} else { </>
return ( );
<> }
<FormDropdownDivider /> })}
<FormListItem
checked={checked}
disabled
>
<strong>{title}</strong>
</FormListItem>
</>
)
}
})}
{mimeTypes.map(({ title, mime }) => ( {mimeTypes.map(({ title, mime }) => (
<FormListItem onClick={() => changeNoteType("code", mime)}> <FormListItem onClick={() => changeNoteType("code", mime)}>
{title} {title}
</FormListItem> </FormListItem>
))} ))}
<FormDropdownDivider /> <FormDropdownDivider />
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem> <FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
</Dropdown> </>
<Modal
className="code-mime-types-modal"
title={t("code_mime_types.title")}
show={modalShown} onHidden={() => setModalShown(false)}
size="xl" scrollable
>
<CodeMimeTypesList />
</Modal>
</div>
) )
} }

View File

@ -1,5 +1,5 @@
import { ConvertToAttachmentResponse } from "@triliumnext/commons"; import { ConvertToAttachmentResponse } from "@triliumnext/commons";
import { useContext } from "preact/hooks"; import { useContext, useState } from "preact/hooks";
import appContext, { CommandNames } from "../../components/app_context"; import appContext, { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context"; import NoteContext from "../../components/note_context";
@ -17,7 +17,7 @@ import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListTogglea
import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks"; import { useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks";
import { ParentComponent } from "../react/react_utils"; import { ParentComponent } from "../react/react_utils";
import { isExperimentalFeatureEnabled } from "../../services/experimental_features"; import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
import { useNoteBookmarkState, useShareState } from "./BasicPropertiesTab"; import { NoteTypeDropdownContent, useNoteBookmarkState, useShareState } from "./BasicPropertiesTab";
import protected_session from "../../services/protected_session"; import protected_session from "../../services/protected_session";
const isNewLayout = isExperimentalFeatureEnabled("new-layout"); const isNewLayout = isExperimentalFeatureEnabled("new-layout");
@ -148,6 +148,8 @@ function NoteBasicProperties({ note }: { note: FNote }) {
title={t("protect_note.toggle-on")} title={t("protect_note.toggle-on")}
currentValue={!!isProtected} onChange={shouldProtect => protected_session.protectNote(note.noteId, shouldProtect, false)} currentValue={!!isProtected} onChange={shouldProtect => protected_session.protectNote(note.noteId, shouldProtect, false)}
/> />
<FormDropdownDivider />
<NoteTypeDropdown note={note} />
</>; </>;
} }
@ -169,6 +171,18 @@ function EditabilityDropdown({ note }: { note: FNote }) {
); );
} }
function NoteTypeDropdown({ note }: { note: FNote }) {
const currentNoteType = useNoteProperty(note, "type") ?? undefined;
const currentNoteMime = useNoteProperty(note, "mime");
const [ modalShown, setModalShown ] = useState(false);
return (
<FormDropdownSubmenu title={t("basic_properties.note_type")} icon="bx bx-file" dropStart>
<NoteTypeDropdownContent currentNoteType={currentNoteType} currentNoteMime={currentNoteMime} note={note} setModalShown={setModalShown} />
</FormDropdownSubmenu>
);
}
function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { function DevelopmentActions({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) {
return ( return (
<FormDropdownSubmenu title="Development Actions" icon="bx bx-wrench" dropStart> <FormDropdownSubmenu title="Development Actions" icon="bx bx-wrench" dropStart>