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;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
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 (value || value === undefined) {
|
||||
attributes.setLabel(note.noteId, labelName, value)
|
||||
} else if (value === null) {
|
||||
attributes.removeOwnedLabelByName(note, labelName);
|
||||
}
|
||||
}
|
||||
}, [note]);
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import attributes, { removeOwnedAttributesByNameOrType } from "../../services/at
|
||||
import FNote from "../../entities/fnote";
|
||||
import toast from "../../services/toast";
|
||||
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 { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip, useTriliumEventBeta } from "../react/hooks";
|
||||
import appContext from "../../components/app_context";
|
||||
@ -18,6 +18,7 @@ import server from "../../services/server";
|
||||
import ws from "../../services/ws";
|
||||
import tree from "../../services/tree";
|
||||
import NoteAutocomplete from "../react/NoteAutocomplete";
|
||||
import FormSelect from "../react/FormSelect";
|
||||
|
||||
interface SearchOption {
|
||||
attributeName: string;
|
||||
@ -27,6 +28,7 @@ interface SearchOption {
|
||||
tooltip?: string;
|
||||
// TODO: Make mandatory once all components are ported.
|
||||
component?: (props: SearchOptionProps) => VNode;
|
||||
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
|
||||
}
|
||||
|
||||
interface SearchOptionProps {
|
||||
@ -34,6 +36,7 @@ interface SearchOptionProps {
|
||||
refreshResults: () => void;
|
||||
attributeName: string;
|
||||
attributeType: "label" | "relation";
|
||||
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
|
||||
error?: { message: string };
|
||||
}
|
||||
|
||||
@ -57,7 +60,8 @@ const SEARCH_OPTIONS: SearchOption[] = [
|
||||
attributeType: "relation",
|
||||
icon: "bx bx-filter-alt",
|
||||
label: t("search_definition.ancestor"),
|
||||
component: AncestorOption
|
||||
component: AncestorOption,
|
||||
additionalAttributesToDelete: [ { type: "label", name: "ancestorDepth" } ]
|
||||
},
|
||||
{
|
||||
attributeName: "fastSearch",
|
||||
@ -168,13 +172,14 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
||||
</td>
|
||||
</tr>
|
||||
<tbody className="search-options">
|
||||
{searchOptions?.activeOptions.map(({ attributeType, attributeName, component }) => {
|
||||
{searchOptions?.activeOptions.map(({ attributeType, attributeName, component, additionalAttributesToDelete }) => {
|
||||
return component?.({
|
||||
attributeName,
|
||||
attributeType,
|
||||
note,
|
||||
refreshResults,
|
||||
error
|
||||
error,
|
||||
additionalAttributesToDelete
|
||||
});
|
||||
})}
|
||||
</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;
|
||||
title: string,
|
||||
children: ComponentChildren,
|
||||
help: ComponentChildren,
|
||||
attributeName: string,
|
||||
attributeType: AttributeType
|
||||
attributeType: AttributeType,
|
||||
additionalAttributesToDelete: { type: "label" | "relation", name: string }[]
|
||||
}) {
|
||||
return (
|
||||
<tr>
|
||||
@ -247,7 +253,14 @@ function SearchOption({ note, title, children, help, attributeName, attributeTyp
|
||||
<ActionButton
|
||||
icon="bx bx-x"
|
||||
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>
|
||||
</tr>
|
||||
@ -354,15 +367,39 @@ function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
|
||||
|
||||
function AncestorOption({ note, ...restProps}: SearchOptionProps) {
|
||||
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
|
||||
title={t("ancestor.label")}
|
||||
note={note} {...restProps}
|
||||
>
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
<NoteAutocomplete
|
||||
noteId={ancestor !== "root" ? ancestor ?? undefined : undefined}
|
||||
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>;
|
||||
}
|
||||
@ -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