diff --git a/apps/client/src/widgets/collections/Pagination.tsx b/apps/client/src/widgets/collections/Pagination.tsx index 36a751cda..6b74964a6 100644 --- a/apps/client/src/widgets/collections/Pagination.tsx +++ b/apps/client/src/widgets/collections/Pagination.tsx @@ -2,7 +2,7 @@ import { ComponentChildren } from "preact"; import { Dispatch, StateUpdater, useEffect, useState } from "preact/hooks"; import FNote from "../../entities/fnote"; import froca from "../../services/froca"; -import { useNoteLabel } from "../react/hooks"; +import { useNoteLabelInt } from "../react/hooks"; import { t } from "../../services/i18n"; interface PaginationContext { @@ -62,9 +62,8 @@ export function usePagination(note: FNote, noteIds: string[]): PaginationContext const [ pageNotes, setPageNotes ] = useState(); // Parse page size. - const [ pageSize ] = useNoteLabel(note, "pageSize"); - const pageSizeNum = parseInt(pageSize ?? "", 10); - const normalizedPageSize = (pageSizeNum && pageSizeNum > 0 ? pageSizeNum : 20); + const [ pageSize ] = useNoteLabelInt(note, "pageSize"); + const normalizedPageSize = (pageSize && pageSize > 0 ? pageSize : 20); // Calculate start/end index. const startIdx = (page - 1) * normalizedPageSize; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index d991bc0e9..4401b013f 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -129,7 +129,7 @@ function NoteIconList({ note }: { note: FNote }) { class="icon-list" onClick={(e) => { const clickedTarget = e.target as HTMLElement; - + if (!clickedTarget.classList.contains("bx")) { return; } @@ -152,9 +152,9 @@ function NoteIconList({ note }: { note: FNote }) { for (const label of getIconLabels(note)) { attributes.removeAttributeById(note.noteId, label.attributeId); } - }} + }} /> - + )} {(iconData?.icons ?? []).map(({className, name}) => ( @@ -181,4 +181,4 @@ function getIconLabels(note: FNote) { return note.getOwnedLabels() .filter((label) => ["workspaceIconClass", "iconClass"] .includes(label.name)); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 1b218bb33..76ea6077e 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -2,7 +2,7 @@ import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, import { CommandListenerData, EventData, EventNames } from "../../components/app_context"; import { ParentComponent } from "./react_utils"; import SpacedUpdate from "../../services/spaced_update"; -import { KeyboardActionNames, OptionNames } from "@triliumnext/commons"; +import { FilterLabelsByType, KeyboardActionNames, OptionNames } from "@triliumnext/commons"; import options, { type OptionValue } from "../../services/options"; import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils"; import NoteContext from "../../components/note_context"; @@ -13,7 +13,7 @@ import FBlob from "../../entities/fblob"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { RefObject, VNode } from "preact"; import { Tooltip } from "bootstrap"; -import { CSSProperties, DragEventHandler } from "preact/compat"; +import { CSSProperties } from "preact/compat"; import keyboard_actions from "../../services/keyboard_actions"; import Mark from "mark.js"; import { DragData } from "../note_tree"; @@ -291,7 +291,7 @@ export function useNoteRelation(note: FNote | undefined | null, relationName: st * @param labelName the name of the label to read/write. * @returns an array where the first element is the getter and the second element is the setter. The setter has a special behaviour for convenience: if the value is undefined, the label is created without a value (e.g. a tag), if the value is null then the label is removed. */ -export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string | null | undefined) => void] { +export function useNoteLabel(note: FNote | undefined | null, labelName: FilterLabelsByType): [string | null | undefined, (newValue: string | null | undefined) => void] { const [ , setLabelValue ] = useState(); useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]); @@ -325,12 +325,12 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string): ] as const; } -export function useNoteLabelWithDefault(note: FNote | undefined | null, labelName: string, defaultValue: string): [string, (newValue: string | null | undefined) => void] { +export function useNoteLabelWithDefault(note: FNote | undefined | null, labelName: FilterLabelsByType, defaultValue: string): [string, (newValue: string | null | undefined) => void] { const [ labelValue, setLabelValue ] = useNoteLabel(note, labelName); return [ labelValue ?? defaultValue, setLabelValue]; } -export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean, (newValue: boolean) => void] { +export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: FilterLabelsByType): [ boolean, (newValue: boolean) => void] { const [ labelValue, setLabelValue ] = useState(!!note?.hasLabel(labelName)); useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]); @@ -358,7 +358,8 @@ export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: s return [ labelValue, setter ] as const; } -export function useNoteLabelInt(note: FNote | undefined | null, labelName: string): [ number | undefined, (newValue: number) => void] { +export function useNoteLabelInt(note: FNote | undefined | null, labelName: FilterLabelsByType): [ number | undefined, (newValue: number) => void] { + //@ts-expect-error `useNoteLabel` only accepts string properties but we need to be able to read number ones. const [ value, setValue ] = useNoteLabel(note, labelName); useDebugValue(labelName); return [ diff --git a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx index d5e3332c1..2232503db 100644 --- a/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/BasicPropertiesTab.tsx @@ -23,7 +23,7 @@ import { ContentLanguagesList } from "../type_widgets/options/i18n"; export default function BasicPropertiesTab({ note }: TabContext) { return ( -
+
@@ -43,7 +43,7 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { 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 currentNoteType = useNoteProperty(note, "type") ?? undefined; const currentNoteMime = useNoteProperty(note, "mime"); const [ modalShown, setModalShown ] = useState(false); @@ -95,7 +95,7 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { checked={checked} badges={badges} onClick={() => changeNoteType(type, mime)} - >{title} + >{title} ); } else { return ( @@ -103,7 +103,7 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) { {title} @@ -131,7 +131,7 @@ function NoteTypeWidget({ note }: { note?: FNote | null }) {
- ) + ) } function ProtectedNoteSwitch({ note }: { note?: FNote | null }) { @@ -151,7 +151,7 @@ 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 [ autoReadOnlyDisabled, setAutoReadOnlyDisabled ] = useNoteLabelBoolean(note, "autoReadOnlyDisabled"); const options = useMemo(() => ([ { @@ -208,7 +208,7 @@ function BookmarkSwitch({ note }: { note?: FNote | null }) { { if (!note) return; const resp = await server.put(`notes/${note.noteId}/toggle-in-parent/_lbBookmarks/${shouldBookmark}`); @@ -260,11 +260,11 @@ function SharedSwitch({ note }: { note?: FNote | null }) { } else { if (note?.getParentBranches().length === 1 && !(await dialog.confirm(t("shared_switch.shared-branch")))) { return; - } + } const shareBranch = note?.getParentBranches().find((b) => b.parentNoteId === "_share"); if (!shareBranch?.branchId) return; - await server.remove(`branches/${shareBranch.branchId}?taskId=no-progress-reporting`); + await server.remove(`branches/${shareBranch.branchId}?taskId=no-progress-reporting`); } sync.syncNow(true); @@ -330,7 +330,7 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { return locales.find(locale => typeof locale === "object" && locale.id === currentNoteLanguage) as Locale | undefined; }, [ currentNoteLanguage ]); - return ( + return (
{t("basic_properties.language")}:   @@ -350,7 +350,7 @@ function NoteLanguageSwitch({ note }: { note?: FNote | null }) { setModalShown(true)} - >{t("note_language.configure-languages")} + >{t("note_language.configure-languages")} @@ -378,4 +378,4 @@ function findTypeTitle(type?: NoteType, mime?: string | null) { return noteType ? noteType.title : type; } -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx index 2594dd6c2..8960fe46d 100644 --- a/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/CollectionPropertiesTab.tsx @@ -118,6 +118,7 @@ function CheckboxPropertyView({ note, property }: { note: FNote, property: Check } function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) { + //@ts-expect-error Interop with text box which takes in string values even for numbers. const [ value, setValue ] = useNoteLabel(note, property.bindToLabel); return ( diff --git a/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx b/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx index 8cc0c2b85..9dc1574c7 100644 --- a/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx +++ b/apps/client/src/widgets/ribbon/NotePropertiesTab.tsx @@ -17,4 +17,4 @@ export default function NotePropertiesTab({ note }: TabContext) { )}
) -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/ribbon/ScriptTab.tsx b/apps/client/src/widgets/ribbon/ScriptTab.tsx index 81ac3a3ef..dbdd49a0b 100644 --- a/apps/client/src/widgets/ribbon/ScriptTab.tsx +++ b/apps/client/src/widgets/ribbon/ScriptTab.tsx @@ -25,4 +25,4 @@ export default function ScriptTab({ note }: TabContext) {
); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx b/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx index 6f98c63e1..69e33cf51 100644 --- a/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx +++ b/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx @@ -57,7 +57,7 @@ export const SEARCH_OPTIONS: SearchOption[] = [ defaultValue: "root", icon: "bx bx-filter-alt", label: t("search_definition.ancestor"), - component: AncestorOption, + component: AncestorOption, additionalAttributesToDelete: [ { type: "label", name: "ancestorDepth" } ] }, { @@ -173,7 +173,7 @@ function SearchStringOption({ note, refreshResults, error, ...restProps }: Searc } }, [ error ]); - return {t("search_string.search_syntax")} - {t("search_string.also_see")} {t("search_string.complete_help")} @@ -243,7 +243,7 @@ function AncestorOption({ note, ...restProps}: SearchOptionProps) { const options: { value: string | undefined; label: string }[] = [ { value: "", label: t("ancestor.depth_doesnt_matter") }, { value: "eq1", label: `${t("ancestor.depth_eq", { count: 1 })} (${t("ancestor.direct_children")})` } - ]; + ]; for (let i=2; i<=9; i++) options.push({ value: "eq" + i, label: t("ancestor.depth_eq", { count: i }) }); for (let i=0; i<=9; i++) options.push({ value: "gt" + i, label: t("ancestor.depth_gt", { count: i }) }); @@ -253,7 +253,7 @@ function AncestorOption({ note, ...restProps}: SearchOptionProps) { }, []); return
@@ -357,4 +357,4 @@ function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) { currentValue={limit ?? defaultValue} onChange={setLimit} /> -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.ts b/apps/client/src/widgets/ribbon/collection-properties-config.ts index d53513a43..93dbc1076 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.ts +++ b/apps/client/src/widgets/ribbon/collection-properties-config.ts @@ -4,6 +4,7 @@ import attributes from "../../services/attributes"; import NoteContextAwareWidget from "../note_context_aware_widget"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer"; import { ViewTypeOptions } from "../collections/interface"; +import { FilterLabelsByType } from "@triliumnext/commons"; interface BookConfig { properties: BookProperty[]; @@ -12,7 +13,7 @@ interface BookConfig { export interface CheckBoxProperty { type: "checkbox", label: string; - bindToLabel: string + bindToLabel: FilterLabelsByType } export interface ButtonProperty { @@ -26,7 +27,7 @@ export interface ButtonProperty { export interface NumberProperty { type: "number", label: string; - bindToLabel: string; + bindToLabel: FilterLabelsByType; width?: number; min?: number; } @@ -44,7 +45,7 @@ interface ComboBoxGroup { export interface ComboBoxProperty { type: "combobox", label: string; - bindToLabel: string; + bindToLabel: FilterLabelsByType; /** * The default value is used when the label is not set. */ diff --git a/packages/commons/src/index.ts b/packages/commons/src/index.ts index 7bbde59ff..ef60f29be 100644 --- a/packages/commons/src/index.ts +++ b/packages/commons/src/index.ts @@ -9,3 +9,4 @@ export * from "./lib/bulk_actions.js"; export * from "./lib/server_api.js"; export * from "./lib/shared_constants.js"; export * from "./lib/ws_api.js"; +export * from "./lib/attribute_names.js"; diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts new file mode 100644 index 000000000..9494d7b92 --- /dev/null +++ b/packages/commons/src/lib/attribute_names.ts @@ -0,0 +1,45 @@ +type Labels = { + color: string; + iconClass: string; + workspaceIconClass: string; + executeDescription: string; + executeTitle: string; + limit: string; // should be probably be number + calendarRoot: boolean; + workspaceCalendarRoot: boolean; + archived: boolean; + sorted: boolean; + template: boolean; + autoReadOnlyDisabled: boolean; + language: string; + originalFileName: string; + pageUrl: string; + + // Search + searchString: string; + ancestorDepth: string; + orderBy: string; + orderDirection: string; + + // Collection-specific + viewType: string; + status: string; + pageSize: number; + geolocation: string; + readOnly: boolean; + expanded: boolean; + "calendar:hideWeekends": boolean; + "calendar:weekNumbers": boolean; + "calendar:view": string; + "map:style": string; + "map:scale": boolean; + "board:groupBy": string; + maxNestingDepth: number; + includeArchived: boolean; +} + +export type LabelNames = keyof Labels; + +export type FilterLabelsByType = { + [K in keyof Labels]: Labels[K] extends U ? K : never; +}[keyof Labels];