mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 19:49:01 +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) { | ||||
|             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]); | ||||
| 
 | ||||
|  | ||||
| @ -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} | ||||
|   > | ||||
|     <NoteAutocomplete | ||||
|       noteId={ancestor !== "root" ? ancestor ?? undefined : undefined} | ||||
|       noteIdChanged={noteId => setAncestor(noteId ?? "root")} | ||||
|       placeholder={t("ancestor.placeholder")} | ||||
|     /> | ||||
|     <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
	 Elian Doran
						Elian Doran