mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 03:59:05 +01:00
chore(react/ribbon): port ancestor depth
This commit is contained in:
parent
4b212232c8
commit
3bccbabe53
@ -369,7 +369,14 @@ export function useNoteRelation(note: FNote | undefined | null, relationName: st
|
|||||||
] as const;
|
] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string) => void] {
|
/**
|
||||||
|
* Allows a React component to read or write a note's label while also reacting to changes in value.
|
||||||
|
*
|
||||||
|
* @param note the note whose label to read/write.
|
||||||
|
* @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] {
|
||||||
const [ labelValue, setLabelValue ] = useState<string | null | undefined>(note?.getLabelValue(labelName));
|
const [ labelValue, setLabelValue ] = useState<string | null | undefined>(note?.getLabelValue(labelName));
|
||||||
|
|
||||||
useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]);
|
useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]);
|
||||||
@ -381,9 +388,13 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: string):
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const setter = useCallback((value: string | undefined) => {
|
const setter = useCallback((value: string | null | undefined) => {
|
||||||
if (note) {
|
if (note) {
|
||||||
attributes.setLabel(note.noteId, labelName, value)
|
if (value || value === undefined) {
|
||||||
|
attributes.setLabel(note.noteId, labelName, value)
|
||||||
|
} else if (value === null) {
|
||||||
|
attributes.removeOwnedLabelByName(note, labelName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [note]);
|
}, [note]);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import attributes, { removeOwnedAttributesByNameOrType } from "../../services/at
|
|||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
import toast from "../../services/toast";
|
import toast from "../../services/toast";
|
||||||
import froca from "../../services/froca";
|
import froca from "../../services/froca";
|
||||||
import { useContext, useEffect, useRef, useState } from "preact/hooks";
|
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
import { ParentComponent } from "../react/react_utils";
|
import { ParentComponent } from "../react/react_utils";
|
||||||
import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip, useTriliumEventBeta } from "../react/hooks";
|
import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip, useTriliumEventBeta } from "../react/hooks";
|
||||||
import appContext from "../../components/app_context";
|
import appContext from "../../components/app_context";
|
||||||
@ -18,6 +18,7 @@ import server from "../../services/server";
|
|||||||
import ws from "../../services/ws";
|
import ws from "../../services/ws";
|
||||||
import tree from "../../services/tree";
|
import tree from "../../services/tree";
|
||||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||||
|
import FormSelect from "../react/FormSelect";
|
||||||
|
|
||||||
interface SearchOption {
|
interface SearchOption {
|
||||||
attributeName: string;
|
attributeName: string;
|
||||||
@ -27,6 +28,7 @@ interface SearchOption {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
// TODO: Make mandatory once all components are ported.
|
// TODO: Make mandatory once all components are ported.
|
||||||
component?: (props: SearchOptionProps) => VNode;
|
component?: (props: SearchOptionProps) => VNode;
|
||||||
|
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SearchOptionProps {
|
interface SearchOptionProps {
|
||||||
@ -34,6 +36,7 @@ interface SearchOptionProps {
|
|||||||
refreshResults: () => void;
|
refreshResults: () => void;
|
||||||
attributeName: string;
|
attributeName: string;
|
||||||
attributeType: "label" | "relation";
|
attributeType: "label" | "relation";
|
||||||
|
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
|
||||||
error?: { message: string };
|
error?: { message: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +60,8 @@ const SEARCH_OPTIONS: SearchOption[] = [
|
|||||||
attributeType: "relation",
|
attributeType: "relation",
|
||||||
icon: "bx bx-filter-alt",
|
icon: "bx bx-filter-alt",
|
||||||
label: t("search_definition.ancestor"),
|
label: t("search_definition.ancestor"),
|
||||||
component: AncestorOption
|
component: AncestorOption,
|
||||||
|
additionalAttributesToDelete: [ { type: "label", name: "ancestorDepth" } ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeName: "fastSearch",
|
attributeName: "fastSearch",
|
||||||
@ -168,13 +172,14 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tbody className="search-options">
|
<tbody className="search-options">
|
||||||
{searchOptions?.activeOptions.map(({ attributeType, attributeName, component }) => {
|
{searchOptions?.activeOptions.map(({ attributeType, attributeName, component, additionalAttributesToDelete }) => {
|
||||||
return component?.({
|
return component?.({
|
||||||
attributeName,
|
attributeName,
|
||||||
attributeType,
|
attributeType,
|
||||||
note,
|
note,
|
||||||
refreshResults,
|
refreshResults,
|
||||||
error
|
error,
|
||||||
|
additionalAttributesToDelete
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -230,13 +235,14 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchOption({ note, title, children, help, attributeName, attributeType }: {
|
function SearchOption({ note, title, children, help, attributeName, attributeType, additionalAttributesToDelete }: {
|
||||||
note: FNote;
|
note: FNote;
|
||||||
title: string,
|
title: string,
|
||||||
children: ComponentChildren,
|
children: ComponentChildren,
|
||||||
help: ComponentChildren,
|
help: ComponentChildren,
|
||||||
attributeName: string,
|
attributeName: string,
|
||||||
attributeType: AttributeType
|
attributeType: AttributeType,
|
||||||
|
additionalAttributesToDelete: { type: "label" | "relation", name: string }[]
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
@ -247,7 +253,14 @@ function SearchOption({ note, title, children, help, attributeName, attributeTyp
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
icon="bx bx-x"
|
icon="bx bx-x"
|
||||||
className="search-option-del"
|
className="search-option-del"
|
||||||
onClick={() => removeOwnedAttributesByNameOrType(note, attributeType, attributeName)}
|
onClick={() => {
|
||||||
|
removeOwnedAttributesByNameOrType(note, attributeType, attributeName);
|
||||||
|
if (additionalAttributesToDelete) {
|
||||||
|
for (const { type, name } of additionalAttributesToDelete) {
|
||||||
|
removeOwnedAttributesByNameOrType(note, type, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -354,15 +367,39 @@ function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
|
|||||||
|
|
||||||
function AncestorOption({ note, ...restProps}: SearchOptionProps) {
|
function AncestorOption({ note, ...restProps}: SearchOptionProps) {
|
||||||
const [ ancestor, setAncestor ] = useNoteRelation(note, "ancestor");
|
const [ ancestor, setAncestor ] = useNoteRelation(note, "ancestor");
|
||||||
|
const [ depth, setDepth ] = useNoteLabel(note, "ancestorDepth");
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
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 }) });
|
||||||
|
for (let i=2; i<=9; i++) options.push({ value: "lt" + i, label: t("ancestor.depth_lt", { count: i }) });
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <SearchOption
|
return <SearchOption
|
||||||
title={t("ancestor.label")}
|
title={t("ancestor.label")}
|
||||||
note={note} {...restProps}
|
note={note} {...restProps}
|
||||||
>
|
>
|
||||||
<NoteAutocomplete
|
<div style={{display: "flex", alignItems: "center"}}>
|
||||||
noteId={ancestor !== "root" ? ancestor ?? undefined : undefined}
|
<NoteAutocomplete
|
||||||
noteIdChanged={noteId => setAncestor(noteId ?? "root")}
|
noteId={ancestor !== "root" ? ancestor ?? undefined : undefined}
|
||||||
placeholder={t("ancestor.placeholder")}
|
noteIdChanged={noteId => setAncestor(noteId ?? "root")}
|
||||||
/>
|
placeholder={t("ancestor.placeholder")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div style="margin-left: 10px; margin-right: 10px">{t("ancestor.depth_label")}:</div>
|
||||||
|
<FormSelect
|
||||||
|
values={options}
|
||||||
|
keyProperty="value" titleProperty="label"
|
||||||
|
currentValue={depth ?? ""} onChange={(value) => setDepth(value ? value : null)}
|
||||||
|
style={{ flexShrink: 3 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</SearchOption>;
|
</SearchOption>;
|
||||||
}
|
}
|
||||||
@ -1,88 +0,0 @@
|
|||||||
import AbstractSearchOption from "./abstract_search_option.js";
|
|
||||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
|
||||||
import { t } from "../../services/i18n.js";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div style="margin-left: 10px; margin-right: 10px">${t("ancestor.depth_label")}:</div>
|
|
||||||
<select name="depth" class="form-select d-inline ancestor-depth" style="flex-shrink: 3">
|
|
||||||
<option value="">${t("ancestor.depth_doesnt_matter")}</option>
|
|
||||||
<option value="eq1">${t("ancestor.depth_eq", { count: 1 })} (${t("ancestor.direct_children")})</option>
|
|
||||||
<option value="eq2">${t("ancestor.depth_eq", { count: 2 })}</option>
|
|
||||||
<option value="eq3">${t("ancestor.depth_eq", { count: 3 })}</option>
|
|
||||||
<option value="eq4">${t("ancestor.depth_eq", { count: 4 })}</option>
|
|
||||||
<option value="eq5">${t("ancestor.depth_eq", { count: 5 })}</option>
|
|
||||||
<option value="eq6">${t("ancestor.depth_eq", { count: 6 })}</option>
|
|
||||||
<option value="eq7">${t("ancestor.depth_eq", { count: 7 })}</option>
|
|
||||||
<option value="eq8">${t("ancestor.depth_eq", { count: 8 })}</option>
|
|
||||||
<option value="eq9">${t("ancestor.depth_eq", { count: 9 })}</option>
|
|
||||||
<option value="gt0">${t("ancestor.depth_gt", { count: 0 })}</option>
|
|
||||||
<option value="gt1">${t("ancestor.depth_gt", { count: 1 })}</option>
|
|
||||||
<option value="gt2">${t("ancestor.depth_gt", { count: 2 })}</option>
|
|
||||||
<option value="gt3">${t("ancestor.depth_gt", { count: 3 })}</option>
|
|
||||||
<option value="gt4">${t("ancestor.depth_gt", { count: 4 })}</option>
|
|
||||||
<option value="gt5">${t("ancestor.depth_gt", { count: 5 })}</option>
|
|
||||||
<option value="gt6">${t("ancestor.depth_gt", { count: 6 })}</option>
|
|
||||||
<option value="gt7">${t("ancestor.depth_gt", { count: 7 })}</option>
|
|
||||||
<option value="gt8">${t("ancestor.depth_gt", { count: 8 })}</option>
|
|
||||||
<option value="gt9">${t("ancestor.depth_gt", { count: 9 })}</option>
|
|
||||||
<option value="lt2">${t("ancestor.depth_lt", { count: 2 })}</option>
|
|
||||||
<option value="lt3">${t("ancestor.depth_lt", { count: 3 })}</option>
|
|
||||||
<option value="lt4">${t("ancestor.depth_lt", { count: 4 })}</option>
|
|
||||||
<option value="lt5">${t("ancestor.depth_lt", { count: 5 })}</option>
|
|
||||||
<option value="lt6">${t("ancestor.depth_lt", { count: 6 })}</option>
|
|
||||||
<option value="lt7">${t("ancestor.depth_lt", { count: 7 })}</option>
|
|
||||||
<option value="lt8">${t("ancestor.depth_lt", { count: 8 })}</option>
|
|
||||||
<option value="lt9">${t("ancestor.depth_lt", { count: 9 })}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>`;
|
|
||||||
|
|
||||||
export default class Ancestor extends AbstractSearchOption {
|
|
||||||
doRender() {
|
|
||||||
const $option = $(TPL);
|
|
||||||
const $ancestor = $option.find(".ancestor");
|
|
||||||
const $ancestorDepth = $option.find(".ancestor-depth");
|
|
||||||
noteAutocompleteService.initNoteAutocomplete($ancestor);
|
|
||||||
|
|
||||||
$ancestor.on("autocomplete:closed", async () => {
|
|
||||||
const ancestorNoteId = $ancestor.getSelectedNoteId();
|
|
||||||
|
|
||||||
if (ancestorNoteId) {
|
|
||||||
await this.setAttribute("relation", "ancestor", ancestorNoteId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$ancestorDepth.on("change", async () => {
|
|
||||||
const ancestorDepth = String($ancestorDepth.val());
|
|
||||||
|
|
||||||
if (ancestorDepth) {
|
|
||||||
await this.setAttribute("label", "ancestorDepth", ancestorDepth);
|
|
||||||
} else {
|
|
||||||
await this.deleteAttribute("label", "ancestorDepth");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ancestorNoteId = this.note.getRelationValue("ancestor");
|
|
||||||
|
|
||||||
if (ancestorNoteId && ancestorNoteId !== "root") {
|
|
||||||
$ancestor.setNote(ancestorNoteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ancestorDepth = this.note.getLabelValue("ancestorDepth");
|
|
||||||
|
|
||||||
if (ancestorDepth) {
|
|
||||||
$ancestorDepth.val(ancestorDepth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $option;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteOption() {
|
|
||||||
await this.deleteAttribute("label", "ancestorDepth");
|
|
||||||
|
|
||||||
await super.deleteOption();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user