diff --git a/apps/client/src/widgets/editability_select.ts b/apps/client/src/widgets/editability_select.ts
deleted file mode 100644
index e7127ca8a..000000000
--- a/apps/client/src/widgets/editability_select.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-import attributeService from "../services/attributes.js";
-import NoteContextAwareWidget from "./note_context_aware_widget.js";
-import { t } from "../services/i18n.js";
-import type FNote from "../entities/fnote.js";
-import type { EventData } from "../components/app_context.js";
-import { Dropdown } from "bootstrap";
-
-type Editability = "auto" | "readOnly" | "autoReadOnlyDisabled";
-
-const TPL = /*html*/`
-
-
-
-
-
-`;
-
-export default class EditabilitySelectWidget extends NoteContextAwareWidget {
-
- private dropdown!: Dropdown;
- private $editabilityActiveDesc!: JQuery;
-
- doRender() {
- this.$widget = $(TPL);
-
- this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]);
-
- this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
-
- this.$widget.on("click", ".dropdown-item", async (e) => {
- this.dropdown.toggle();
-
- const editability = $(e.target).closest("[data-editability]").attr("data-editability");
-
- if (!this.note || !this.noteId) {
- return;
- }
-
- for (const ownedAttr of this.note.getOwnedLabels()) {
- if (["readOnly", "autoReadOnlyDisabled"].includes(ownedAttr.name)) {
- await attributeService.removeAttributeById(this.noteId, ownedAttr.attributeId);
- }
- }
-
- if (editability && editability !== "auto") {
- await attributeService.addLabel(this.noteId, editability);
- }
- });
- }
-
- async refreshWithNote(note: FNote) {
- let editability: Editability = "auto";
-
- if (this.note?.isLabelTruthy("readOnly")) {
- editability = "readOnly";
- } else if (this.note?.isLabelTruthy("autoReadOnlyDisabled")) {
- editability = "autoReadOnlyDisabled";
- }
-
- const labels = {
- auto: t("editability_select.auto"),
- readOnly: t("editability_select.read_only"),
- autoReadOnlyDisabled: t("editability_select.always_editable")
- };
-
- this.$widget.find(".dropdown-item").removeClass("selected");
- this.$widget.find(`.dropdown-item[data-editability='${editability}']`).addClass("selected");
-
- this.$editabilityActiveDesc.text(labels[editability]);
- }
-
- entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
- if (loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId)) {
- this.refresh();
- }
- }
-}
diff --git a/apps/client/src/widgets/react/FormDropdownList.tsx b/apps/client/src/widgets/react/FormDropdownList.tsx
new file mode 100644
index 000000000..4bab06948
--- /dev/null
+++ b/apps/client/src/widgets/react/FormDropdownList.tsx
@@ -0,0 +1,30 @@
+import Dropdown from "./Dropdown";
+import { FormListItem } from "./FormList";
+
+interface FormDropdownList {
+ values: T[];
+ keyProperty: keyof T;
+ titleProperty: keyof T;
+ descriptionProperty?: keyof T;
+ currentValue: string;
+ onChange(newValue: string): void;
+}
+
+export default function FormDropdownList({ values, keyProperty, titleProperty, descriptionProperty, currentValue, onChange }: FormDropdownList) {
+ const currentValueData = values.find(value => value[keyProperty] === currentValue);
+
+ return (
+
+ {values.map(item => (
+ onChange(item[keyProperty] as string)}
+ checked={currentValue === item[keyProperty]}
+ description={descriptionProperty && item[descriptionProperty] as string}
+ selected={currentValue === item[keyProperty]}
+ >
+ {item[titleProperty] as string}
+
+ ))}
+
+ )
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/react/FormList.css b/apps/client/src/widgets/react/FormList.css
new file mode 100644
index 000000000..62631b131
--- /dev/null
+++ b/apps/client/src/widgets/react/FormList.css
@@ -0,0 +1,5 @@
+.dropdown-item .description {
+ font-size: small;
+ color: var(--muted-text-color);
+ white-space: normal;
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/react/FormList.tsx b/apps/client/src/widgets/react/FormList.tsx
index ff0da091a..42aeb93d5 100644
--- a/apps/client/src/widgets/react/FormList.tsx
+++ b/apps/client/src/widgets/react/FormList.tsx
@@ -2,6 +2,7 @@ import { Dropdown as BootstrapDropdown } from "bootstrap";
import { ComponentChildren } from "preact";
import Icon from "./Icon";
import { useEffect, useMemo, useRef, type CSSProperties } from "preact/compat";
+import "./FormList.css";
interface FormListOpts {
children: ComponentChildren;
@@ -76,27 +77,33 @@ interface FormListItemOpts {
active?: boolean;
badges?: FormListBadge[];
disabled?: boolean;
- checked?: boolean;
+ checked?: boolean | null;
+ selected?: boolean;
onClick?: () => void;
+ description?: string;
+ className?: string;
}
-export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick }: FormListItemOpts) {
+export function FormListItem({ children, icon, value, title, active, badges, disabled, checked, onClick, description, selected }: FormListItemOpts) {
if (checked) {
icon = "bx bx-check";
}
return (
- {children}
- {badges && badges.map(({ className, text }) => (
- {text}
- ))}
+
+ {children}
+ {badges && badges.map(({ className, text }) => (
+
{text}
+ ))}
+ {description &&
{description}
}
+
);
}
diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx
index bc520dc0e..7c8257ead 100644
--- a/apps/client/src/widgets/react/hooks.tsx
+++ b/apps/client/src/widgets/react/hooks.tsx
@@ -314,12 +314,12 @@ export function useNoteProperty(note: FNote | null | unde
}
export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string) => void] {
- const [ labelValue, setNoteValue ] = useState(note?.getLabelValue(labelName));
+ const [ labelValue, setLabelValue ] = useState(note?.getLabelValue(labelName));
useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
for (const attr of loadResults.getAttributeRows()) {
if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
- setNoteValue(attr.value ?? null);
+ setLabelValue(attr.value ?? null);
}
}
});
@@ -334,4 +334,28 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string):
labelValue,
setter
] as const;
+}
+
+export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean | null | undefined, (newValue: boolean) => void] {
+ const [ labelValue, setLabelValue ] = useState(note?.hasLabel(labelName));
+
+ useTriliumEventBeta("entitiesReloaded", ({ loadResults }) => {
+ for (const attr of loadResults.getAttributeRows()) {
+ if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) {
+ setLabelValue(!attr.isDeleted);
+ }
+ }
+ });
+
+ const setter = useCallback((value: boolean) => {
+ if (note) {
+ if (value) {
+ attributes.setLabel(note.noteId, labelName, "");
+ } else {
+ attributes.removeOwnedLabelByName(note, labelName);
+ }
+ }
+ }, [note]);
+
+ return [ labelValue, setter ] as const;
}
\ No newline at end of file
diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx
index 5c7ab1937..84de1b425 100644
--- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx
+++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx
@@ -3,7 +3,7 @@ import Dropdown from "../react/Dropdown";
import { NOTE_TYPES } from "../../services/note_types";
import { FormDivider, FormListBadge, FormListItem } from "../react/FormList";
import { t } from "../../services/i18n";
-import { useNoteContext, useNoteProperty, useTriliumOption } from "../react/hooks";
+import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumOption } from "../react/hooks";
import mime_types from "../../services/mime_types";
import { NoteType } from "@triliumnext/commons";
import server from "../../services/server";
@@ -11,6 +11,7 @@ import dialog from "../../services/dialog";
import FormToggle from "../react/FormToggle";
import FNote from "../../entities/fnote";
import protected_session from "../../services/protected_session";
+import FormDropdownList from "../react/FormDropdownList";
export default function BasicPropertiesTab() {
const { note } = useNoteContext();
@@ -19,6 +20,7 @@ export default function BasicPropertiesTab() {
);
}
@@ -121,6 +123,45 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) {
)
}
+function EditabilitySelect({ note }: { note?: FNote | null }) {
+ const [ readOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
+ const [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled");
+
+ const options = useMemo(() => ([
+ {
+ value: "auto",
+ label: t("editability_select.auto"),
+ description: t("editability_select.note_is_editable"),
+ },
+ {
+ value: "readOnly",
+ label: t("editability_select.read_only"),
+ description: t("editability_select.note_is_read_only")
+ },
+ {
+ value: "autoReadOnlyDisabled",
+ label: t("editability_select.always_editable"),
+ description: t("editability_select.note_is_always_editable")
+ }
+ ]), []);
+
+ return (
+
+ {t("basic_properties.editable")}:
+
+ {
+ setReadOnly(editability === "readOnly");
+ setAutoReadOnlyDisabled(editability === "autoReadOnlyDisabled");
+ }}
+ />
+
+ )
+}
+
function findTypeTitle(type?: NoteType, mime?: string | null) {
if (type === "code") {
const mimeTypes = mime_types.getMimeTypes();
diff --git a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts
index 14fb7468f..f80e163a8 100644
--- a/apps/client/src/widgets/ribbon_widgets/basic_properties.ts
+++ b/apps/client/src/widgets/ribbon_widgets/basic_properties.ts
@@ -10,10 +10,6 @@ import type FNote from "../../entities/fnote.js";
import NoteLanguageWidget from "../note_language.js";
const TPL = /*html*/`
-
- ${t("basic_properties.editable")}:
-
-
@@ -27,8 +23,6 @@ const TPL = /*html*/`
export default class BasicPropertiesWidget extends NoteContextAwareWidget {
- private noteTypeWidget: NoteTypeWidget;
- private protectedNoteSwitchWidget: ProtectedNoteSwitchWidget;
private editabilitySelectWidget: EditabilitySelectWidget;
private bookmarkSwitchWidget: BookmarkSwitchWidget;
private sharedSwitchWidget: SharedSwitchWidget;
@@ -45,8 +39,6 @@ export default class BasicPropertiesWidget extends NoteContextAwareWidget {
this.noteLanguageWidget = new NoteLanguageWidget().contentSized();
this.child(
- this.noteTypeWidget,
- this.protectedNoteSwitchWidget,
this.editabilitySelectWidget,
this.bookmarkSwitchWidget,
this.sharedSwitchWidget,