chore(react/ribbon): handle search error

This commit is contained in:
Elian Doran 2025-08-24 16:41:44 +03:00
parent e1fa188244
commit b9193a5562
No known key found for this signature in database
5 changed files with 46 additions and 48 deletions

View File

@ -61,7 +61,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
* @param value the value of the attribute to set. * @param value the value of the attribute to set.
*/ */
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) { export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
if (value) { if (value !== null && value !== undefined) {
// Create or update the attribute. // Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }); await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
} else { } else {

View File

@ -1,4 +1,4 @@
import { TextareaHTMLAttributes } from "preact/compat"; import { RefObject, TextareaHTMLAttributes } from "preact/compat";
interface FormTextAreaProps extends Omit<TextareaHTMLAttributes, "onBlur" | "onChange"> { interface FormTextAreaProps extends Omit<TextareaHTMLAttributes, "onBlur" | "onChange"> {
id?: string; id?: string;
@ -6,10 +6,12 @@ interface FormTextAreaProps extends Omit<TextareaHTMLAttributes, "onBlur" | "onC
onChange?(newValue: string): void; onChange?(newValue: string): void;
onBlur?(newValue: string): void; onBlur?(newValue: string): void;
rows: number; rows: number;
inputRef?: RefObject<HTMLTextAreaElement>
} }
export default function FormTextArea({ id, onBlur, onChange, rows, currentValue, className, ...restProps }: FormTextAreaProps) { export default function FormTextArea({ inputRef, id, onBlur, onChange, rows, currentValue, className, ...restProps }: FormTextAreaProps) {
return ( return (
<textarea <textarea
ref={inputRef}
id={id} id={id}
rows={rows} rows={rows}
className={`form-control ${className ?? ""}`} className={`form-control ${className ?? ""}`}

View File

@ -519,6 +519,7 @@ export function useTooltip(elRef: RefObject<HTMLElement>, config: Partial<Toolti
if (!elRef?.current) return; if (!elRef?.current) return;
const $el = $(elRef.current); const $el = $(elRef.current);
$el.tooltip("dispose");
$el.tooltip(config); $el.tooltip(config);
}, [ elRef, config ]); }, [ elRef, config ]);
@ -527,7 +528,8 @@ export function useTooltip(elRef: RefObject<HTMLElement>, config: Partial<Toolti
const $el = $(elRef.current); const $el = $(elRef.current);
$el.tooltip("show"); $el.tooltip("show");
}, [ elRef ]); console.log("Show tooltip ", elRef.current);
}, [ elRef, config ]);
const hideTooltip = useCallback(() => { const hideTooltip = useCallback(() => {
if (!elRef?.current) return; if (!elRef?.current) return;

View File

@ -13,9 +13,10 @@ 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, useRef, useState } from "preact/hooks";
import { ParentComponent } from "../react/react_utils"; import { ParentComponent } from "../react/react_utils";
import { useSpacedUpdate, useTriliumEventBeta } from "../react/hooks"; import { useNoteLabel, useSpacedUpdate, useTooltip, useTriliumEventBeta } from "../react/hooks";
import appContext from "../../components/app_context"; import appContext from "../../components/app_context";
import server from "../../services/server"; import server from "../../services/server";
import { tooltip } from "leaflet";
interface SearchOption { interface SearchOption {
attributeName: string; attributeName: string;
@ -32,6 +33,7 @@ interface SearchOptionProps {
refreshResults: () => void; refreshResults: () => void;
attributeName: string; attributeName: string;
attributeType: "label" | "relation"; attributeType: "label" | "relation";
error?: { message: string };
} }
const SEARCH_OPTIONS: SearchOption[] = [ const SEARCH_OPTIONS: SearchOption[] = [
@ -93,6 +95,7 @@ const SEARCH_OPTIONS: SearchOption[] = [
export default function SearchDefinitionTab({ note, ntxId }: TabContext) { export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>(); const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
const [ error, setError ] = useState<{ message: string }>();
function refreshOptions() { function refreshOptions() {
if (!note) return; if (!note) return;
@ -120,9 +123,10 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
try { try {
const result = await froca.loadSearchNote(noteId); const result = await froca.loadSearchNote(noteId);
if (result?.error) {
if (result && result.error) { setError({ message: result?.error})
//this.handleEvent("showSearchError", { error: result.error }); } else {
setError(undefined);
} }
} catch (e: any) { } catch (e: any) {
toast.showError(e.message); toast.showError(e.message);
@ -147,11 +151,12 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
<tr> <tr>
<td className="title-column">{t("search_definition.add_search_option")}</td> <td className="title-column">{t("search_definition.add_search_option")}</td>
<td colSpan={2} className="add-search-option"> <td colSpan={2} className="add-search-option">
{searchOptions?.availableOptions.map(({ icon, label, tooltip }) => ( {searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType }) => (
<Button <Button
icon={icon} icon={icon}
text={label} text={label}
title={tooltip} title={tooltip}
onClick={() => attributes.setAttribute(note, attributeType, attributeName, "")}
/> />
))} ))}
</td> </td>
@ -162,7 +167,8 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
attributeName, attributeName,
attributeType, attributeType,
note, note,
refreshResults refreshResults,
error
}); });
})} })}
</tbody> </tbody>
@ -224,13 +230,14 @@ function SearchOption({ note, title, children, help, attributeName, attributeTyp
) )
} }
function SearchStringOption({ note, refreshResults, ...restProps }: SearchOptionProps) { function SearchStringOption({ note, refreshResults, error, ...restProps }: SearchOptionProps) {
const currentValue = useRef(""); const [ searchString, setSearchString ] = useNoteLabel(note, "searchString");
const inputRef = useRef<HTMLTextAreaElement>(null);
const currentValue = useRef(searchString ?? "");
const spacedUpdate = useSpacedUpdate(async () => { const spacedUpdate = useSpacedUpdate(async () => {
const searchString = currentValue.current; const searchString = currentValue.current;
appContext.lastSearchString = searchString; appContext.lastSearchString = searchString;
setSearchString(searchString);
await attributes.setAttribute(note, "label", "searchString", searchString);
if (note.title.startsWith(t("search_string.search_prefix"))) { if (note.title.startsWith(t("search_string.search_prefix"))) {
await server.put(`notes/${note.noteId}/title`, { await server.put(`notes/${note.noteId}/title`, {
@ -239,6 +246,23 @@ function SearchStringOption({ note, refreshResults, ...restProps }: SearchOption
} }
}, 1000); }, 1000);
// React to errors
const { showTooltip, hideTooltip } = useTooltip(inputRef, {
trigger: "manual",
title: `${t("search_string.error", { error: error?.message })}`,
html: true,
placement: "bottom"
});
useEffect(() => {
if (error) {
showTooltip();
setTimeout(() => hideTooltip(), 4000);
} else {
hideTooltip();
}
}, [ error ]);
return <SearchOption return <SearchOption
title={t("search_string.title_column")} title={t("search_string.title_column")}
help={<> help={<>
@ -256,8 +280,10 @@ function SearchStringOption({ note, refreshResults, ...restProps }: SearchOption
note={note} {...restProps} note={note} {...restProps}
> >
<FormTextArea <FormTextArea
inputRef={inputRef}
className="search-string" className="search-string"
placeholder={t("search_string.placeholder")} placeholder={t("search_string.placeholder")}
currentValue={searchString ?? ""}
onChange={text => { onChange={text => {
currentValue.current = text; currentValue.current = text;
spacedUpdate.scheduleUpdate(); spacedUpdate.scheduleUpdate();

View File

@ -8,38 +8,6 @@ import { Tooltip } from "bootstrap";
export default class SearchString extends AbstractSearchOption { export default class SearchString extends AbstractSearchOption {
private $searchString!: JQuery<HTMLElement>;
private spacedUpdate!: SpacedUpdate;
static async create(noteId: string) {
await AbstractSearchOption.setAttribute(noteId, "label", "searchString");
}
doRender() {
const $option = $(TPL);
this.$searchString = $option.find(".search-string");
this.spacedUpdate = new SpacedUpdate(async () => {
}, 1000);
this.$searchString.val(this.note.getLabelValue("searchString") ?? "");
return $option;
}
showSearchErrorEvent({ error }: EventData<"showSearchError">) {
let tooltip = new Tooltip(this.$searchString[0], {
trigger: "manual",
title: `${t("search_string.error", { error })}`,
placement: "bottom"
});
tooltip.show();
setTimeout(() => tooltip.dispose(), 4000);
}
focusOnSearchDefinitionEvent() { focusOnSearchDefinitionEvent() {
this.$searchString this.$searchString
.val(String(this.$searchString.val()).trim() ?? appContext.lastSearchString) .val(String(this.$searchString.val()).trim() ?? appContext.lastSearchString)