diff --git a/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx b/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx
new file mode 100644
index 000000000..b7ded28a1
--- /dev/null
+++ b/apps/client/src/widgets/ribbon/SearchDefinitionOptions.tsx
@@ -0,0 +1,364 @@
+import Dropdown from "../react/Dropdown";
+import ActionButton from "../react/ActionButton";
+import FormTextArea from "../react/FormTextArea";
+import NoteAutocomplete from "../react/NoteAutocomplete";
+import FormSelect from "../react/FormSelect";
+import Icon from "../react/Icon";
+import FormTextBox from "../react/FormTextBox";
+import { ComponentChildren, VNode } from "preact";
+import FNote from "../../entities/fnote";
+import { removeOwnedAttributesByNameOrType } from "../../services/attributes";
+import { AttributeType } from "@triliumnext/commons";
+import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip } from "../react/hooks";
+import { t } from "../../services/i18n";
+import { useEffect, useMemo, useRef } from "preact/hooks";
+import appContext from "../../components/app_context";
+import server from "../../services/server";
+
+export interface SearchOption {
+ attributeName: string;
+ attributeType: "label" | "relation";
+ icon: string;
+ label: string;
+ tooltip?: string;
+ component?: (props: SearchOptionProps) => VNode;
+ defaultValue?: string;
+ additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
+}
+
+interface SearchOptionProps {
+ note: FNote;
+ refreshResults: () => void;
+ attributeName: string;
+ attributeType: "label" | "relation";
+ additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
+ defaultValue?: string;
+ error?: { message: string };
+}
+
+export const SEARCH_OPTIONS: SearchOption[] = [
+ {
+ attributeName: "searchString",
+ attributeType: "label",
+ icon: "bx bx-text",
+ label: t("search_definition.search_string"),
+ component: SearchStringOption
+ },
+ {
+ attributeName: "searchScript",
+ attributeType: "relation",
+ defaultValue: "root",
+ icon: "bx bx-code",
+ label: t("search_definition.search_script"),
+ component: SearchScriptOption
+ },
+ {
+ attributeName: "ancestor",
+ attributeType: "relation",
+ defaultValue: "root",
+ icon: "bx bx-filter-alt",
+ label: t("search_definition.ancestor"),
+ component: AncestorOption,
+ additionalAttributesToDelete: [ { type: "label", name: "ancestorDepth" } ]
+ },
+ {
+ attributeName: "fastSearch",
+ attributeType: "label",
+ icon: "bx bx-run",
+ label: t("search_definition.fast_search"),
+ tooltip: t("search_definition.fast_search_description"),
+ component: FastSearchOption
+ },
+ {
+ attributeName: "includeArchivedNotes",
+ attributeType: "label",
+ icon: "bx bx-archive",
+ label: t("search_definition.include_archived"),
+ tooltip: t("search_definition.include_archived_notes_description"),
+ component: IncludeArchivedNotesOption
+ },
+ {
+ attributeName: "orderBy",
+ attributeType: "label",
+ defaultValue: "relevancy",
+ icon: "bx bx-arrow-from-top",
+ label: t("search_definition.order_by"),
+ component: OrderByOption,
+ additionalAttributesToDelete: [ { type: "label", name: "orderDirection" } ]
+ },
+ {
+ attributeName: "limit",
+ attributeType: "label",
+ defaultValue: "10",
+ icon: "bx bx-stop",
+ label: t("search_definition.limit"),
+ tooltip: t("search_definition.limit_description"),
+ component: LimitOption
+ },
+ {
+ attributeName: "debug",
+ attributeType: "label",
+ icon: "bx bx-bug",
+ label: t("search_definition.debug"),
+ tooltip: t("search_definition.debug_description"),
+ component: DebugOption
+ }
+];
+
+function SearchOption({ note, title, titleIcon, children, help, attributeName, attributeType, additionalAttributesToDelete }: {
+ note: FNote;
+ title: string,
+ titleIcon: string,
+ children?: ComponentChildren,
+ help: ComponentChildren,
+ attributeName: string,
+ attributeType: AttributeType,
+ additionalAttributesToDelete: { type: "label" | "relation", name: string }[]
+}) {
+ return (
+
+ |
+ {titleIcon && <>{" "}>}
+ {title}
+ |
+ {children} |
+
+ {help && {help}}
+ {
+ removeOwnedAttributesByNameOrType(note, attributeType, attributeName);
+ if (additionalAttributesToDelete) {
+ for (const { type, name } of additionalAttributesToDelete) {
+ removeOwnedAttributesByNameOrType(note, type, name);
+ }
+ }
+ }}
+ />
+ |
+
+ )
+}
+
+function SearchStringOption({ note, refreshResults, error, ...restProps }: SearchOptionProps) {
+ const [ searchString, setSearchString ] = useNoteLabel(note, "searchString");
+ const inputRef = useRef(null);
+ const currentValue = useRef(searchString ?? "");
+ const spacedUpdate = useSpacedUpdate(async () => {
+ const searchString = currentValue.current;
+ appContext.lastSearchString = searchString;
+ setSearchString(searchString);
+
+ if (note.title.startsWith(t("search_string.search_prefix"))) {
+ await server.put(`notes/${note.noteId}/title`, {
+ title: `${t("search_string.search_prefix")} ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}…`}`
+ });
+ }
+ }, 1000);
+
+ // React to errors
+ const { showTooltip, hideTooltip } = useTooltip(inputRef, {
+ trigger: "manual",
+ title: `${t("search_string.error", { error: error?.message })}`,
+ html: true,
+ placement: "bottom"
+ });
+
+ // Auto-focus.
+ useEffect(() => inputRef.current?.focus(), []);
+
+ useEffect(() => {
+ if (error) {
+ showTooltip();
+ setTimeout(() => hideTooltip(), 4000);
+ } else {
+ hideTooltip();
+ }
+ }, [ error ]);
+
+ return
+ {t("search_string.search_syntax")} - {t("search_string.also_see")} {t("search_string.complete_help")}
+
+ - {t("search_string.full_text_search")}
+ #abc - {t("search_string.label_abc")}
+ #year = 2019 - {t("search_string.label_year")}
+ #rock #pop - {t("search_string.label_rock_pop")}
+ #rock or #pop - {t("search_string.label_rock_or_pop")}
+ #year <= 2000 - {t("search_string.label_year_comparison")}
+ note.dateCreated >= MONTH-1 - {t("search_string.label_date_created")}
+
+ >}
+ note={note} {...restProps}
+ >
+ {
+ currentValue.current = text;
+ spacedUpdate.scheduleUpdate();
+ }}
+ onKeyDown={async (e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+
+ // this also in effect disallows new lines in query string.
+ // on one hand, this makes sense since search string is a label
+ // on the other hand, it could be nice for structuring long search string. It's probably a niche case though.
+ await spacedUpdate.updateNowIfNecessary();
+ refreshResults();
+ }
+ }}
+ />
+
+}
+
+function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
+ const [ searchScript, setSearchScript ] = useNoteRelation(note, "searchScript");
+
+ return
+ {t("search_script.description1")}
+ {t("search_script.description2")}
+ {t("search_script.example_title")}
+ {t("search_script.example_code")}
+ {t("search_script.note")}
+ >}
+ note={note} {...restProps}
+ >
+ setSearchScript(noteId ?? "root")}
+ placeholder={t("search_script.placeholder")}
+ />
+
+}
+
+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
+
+
setAncestor(noteId ?? "root")}
+ placeholder={t("ancestor.placeholder")}
+ />
+
+ {t("ancestor.depth_label")}:
+ setDepth(value ? value : null)}
+ style={{ flexShrink: 3 }}
+ />
+
+ ;
+}
+
+function FastSearchOption({ ...restProps }: SearchOptionProps) {
+ return
+}
+
+function DebugOption({ ...restProps }: SearchOptionProps) {
+ return
+ {t("debug.debug_info")}
+ {t("debug.access_info")}
+ >}
+ {...restProps}
+ />
+}
+
+function IncludeArchivedNotesOption({ ...restProps }: SearchOptionProps) {
+ return
+}
+
+function OrderByOption({ note, ...restProps }: SearchOptionProps) {
+ const [ orderBy, setOrderBy ] = useNoteLabel(note, "orderBy");
+ const [ orderDirection, setOrderDirection ] = useNoteLabel(note, "orderDirection");
+
+ return
+
+ {" "}
+
+
+}
+
+function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) {
+ const [ limit, setLimit ] = useNoteLabel(note, "limit");
+
+ return
+
+
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx
index 3490e296d..eead1e911 100644
--- a/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx
+++ b/apps/client/src/widgets/ribbon/SearchDefinitionTab.tsx
@@ -1,117 +1,20 @@
-import { ComponentChildren, VNode } from "preact";
+import { VNode } from "preact";
import { t } from "../../services/i18n";
import Button from "../react/Button";
import { TabContext } from "./ribbon-interface";
-import Dropdown from "../react/Dropdown";
-import ActionButton from "../react/ActionButton";
-import FormTextArea from "../react/FormTextArea";
-import { AttributeType, SaveSearchNoteResponse } from "@triliumnext/commons";
-import attributes, { removeOwnedAttributesByNameOrType } from "../../services/attributes";
+import { SaveSearchNoteResponse } from "@triliumnext/commons";
+import attributes from "../../services/attributes";
import FNote from "../../entities/fnote";
import toast from "../../services/toast";
import froca from "../../services/froca";
-import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
+import { useContext, useEffect, useState } from "preact/hooks";
import { ParentComponent } from "../react/react_utils";
-import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip, useTriliumEventBeta } from "../react/hooks";
+import { useTriliumEventBeta } from "../react/hooks";
import appContext from "../../components/app_context";
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";
-import Icon from "../react/Icon";
-import FormTextBox from "../react/FormTextBox";
-
-interface SearchOption {
- attributeName: string;
- attributeType: "label" | "relation";
- icon: string;
- label: string;
- tooltip?: string;
- // TODO: Make mandatory once all components are ported.
- component?: (props: SearchOptionProps) => VNode;
- defaultValue?: string;
- additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
-}
-
-interface SearchOptionProps {
- note: FNote;
- refreshResults: () => void;
- attributeName: string;
- attributeType: "label" | "relation";
- additionalAttributesToDelete?: { type: "label" | "relation", name: string }[];
- defaultValue?: string;
- error?: { message: string };
-}
-
-const SEARCH_OPTIONS: SearchOption[] = [
- {
- attributeName: "searchString",
- attributeType: "label",
- icon: "bx bx-text",
- label: t("search_definition.search_string"),
- component: SearchStringOption
- },
- {
- attributeName: "searchScript",
- attributeType: "relation",
- defaultValue: "root",
- icon: "bx bx-code",
- label: t("search_definition.search_script"),
- component: SearchScriptOption
- },
- {
- attributeName: "ancestor",
- attributeType: "relation",
- defaultValue: "root",
- icon: "bx bx-filter-alt",
- label: t("search_definition.ancestor"),
- component: AncestorOption,
- additionalAttributesToDelete: [ { type: "label", name: "ancestorDepth" } ]
- },
- {
- attributeName: "fastSearch",
- attributeType: "label",
- icon: "bx bx-run",
- label: t("search_definition.fast_search"),
- tooltip: t("search_definition.fast_search_description"),
- component: FastSearchOption
- },
- {
- attributeName: "includeArchivedNotes",
- attributeType: "label",
- icon: "bx bx-archive",
- label: t("search_definition.include_archived"),
- tooltip: t("search_definition.include_archived_notes_description"),
- component: IncludeArchivedNotesOption
- },
- {
- attributeName: "orderBy",
- attributeType: "label",
- defaultValue: "relevancy",
- icon: "bx bx-arrow-from-top",
- label: t("search_definition.order_by"),
- component: OrderByOption,
- additionalAttributesToDelete: [ { type: "label", name: "orderDirection" } ]
- },
- {
- attributeName: "limit",
- attributeType: "label",
- defaultValue: "10",
- icon: "bx bx-stop",
- label: t("search_definition.limit"),
- tooltip: t("search_definition.limit_description"),
- component: LimitOption
- },
- {
- attributeName: "debug",
- attributeType: "label",
- icon: "bx bx-bug",
- label: t("search_definition.debug"),
- tooltip: t("search_definition.debug_description"),
- component: DebugOption
- }
-];
+import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
const parentComponent = useContext(ParentComponent);
@@ -247,260 +150,3 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
)
}
-function SearchOption({ note, title, titleIcon, children, help, attributeName, attributeType, additionalAttributesToDelete }: {
- note: FNote;
- title: string,
- titleIcon: string,
- children?: ComponentChildren,
- help: ComponentChildren,
- attributeName: string,
- attributeType: AttributeType,
- additionalAttributesToDelete: { type: "label" | "relation", name: string }[]
-}) {
- return (
-
- |
- {titleIcon && <>{" "}>}
- {title}
- |
- {children} |
-
- {help && {help}}
- {
- removeOwnedAttributesByNameOrType(note, attributeType, attributeName);
- if (additionalAttributesToDelete) {
- for (const { type, name } of additionalAttributesToDelete) {
- removeOwnedAttributesByNameOrType(note, type, name);
- }
- }
- }}
- />
- |
-
- )
-}
-
-function SearchStringOption({ note, refreshResults, error, ...restProps }: SearchOptionProps) {
- const [ searchString, setSearchString ] = useNoteLabel(note, "searchString");
- const inputRef = useRef(null);
- const currentValue = useRef(searchString ?? "");
- const spacedUpdate = useSpacedUpdate(async () => {
- const searchString = currentValue.current;
- appContext.lastSearchString = searchString;
- setSearchString(searchString);
-
- if (note.title.startsWith(t("search_string.search_prefix"))) {
- await server.put(`notes/${note.noteId}/title`, {
- title: `${t("search_string.search_prefix")} ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}…`}`
- });
- }
- }, 1000);
-
- // React to errors
- const { showTooltip, hideTooltip } = useTooltip(inputRef, {
- trigger: "manual",
- title: `${t("search_string.error", { error: error?.message })}`,
- html: true,
- placement: "bottom"
- });
-
- // Auto-focus.
- useEffect(() => inputRef.current?.focus(), []);
-
- useEffect(() => {
- if (error) {
- showTooltip();
- setTimeout(() => hideTooltip(), 4000);
- } else {
- hideTooltip();
- }
- }, [ error ]);
-
- return
- {t("search_string.search_syntax")} - {t("search_string.also_see")} {t("search_string.complete_help")}
-
- - {t("search_string.full_text_search")}
- #abc - {t("search_string.label_abc")}
- #year = 2019 - {t("search_string.label_year")}
- #rock #pop - {t("search_string.label_rock_pop")}
- #rock or #pop - {t("search_string.label_rock_or_pop")}
- #year <= 2000 - {t("search_string.label_year_comparison")}
- note.dateCreated >= MONTH-1 - {t("search_string.label_date_created")}
-
- >}
- note={note} {...restProps}
- >
- {
- currentValue.current = text;
- spacedUpdate.scheduleUpdate();
- }}
- onKeyDown={async (e) => {
- if (e.key === "Enter") {
- e.preventDefault();
-
- // this also in effect disallows new lines in query string.
- // on one hand, this makes sense since search string is a label
- // on the other hand, it could be nice for structuring long search string. It's probably a niche case though.
- await spacedUpdate.updateNowIfNecessary();
- refreshResults();
- }
- }}
- />
-
-}
-
-function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
- const [ searchScript, setSearchScript ] = useNoteRelation(note, "searchScript");
-
- return
- {t("search_script.description1")}
- {t("search_script.description2")}
- {t("search_script.example_title")}
- {t("search_script.example_code")}
- {t("search_script.note")}
- >}
- note={note} {...restProps}
- >
- setSearchScript(noteId ?? "root")}
- placeholder={t("search_script.placeholder")}
- />
-
-}
-
-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
-
-
setAncestor(noteId ?? "root")}
- placeholder={t("ancestor.placeholder")}
- />
-
- {t("ancestor.depth_label")}:
- setDepth(value ? value : null)}
- style={{ flexShrink: 3 }}
- />
-
- ;
-}
-
-function FastSearchOption({ ...restProps }: SearchOptionProps) {
- return
-}
-
-function DebugOption({ ...restProps }: SearchOptionProps) {
- return
- {t("debug.debug_info")}
- {t("debug.access_info")}
- >}
- {...restProps}
- />
-}
-
-function IncludeArchivedNotesOption({ ...restProps }: SearchOptionProps) {
- return
-}
-
-function OrderByOption({ note, ...restProps }: SearchOptionProps) {
- const [ orderBy, setOrderBy ] = useNoteLabel(note, "orderBy");
- const [ orderDirection, setOrderDirection ] = useNoteLabel(note, "orderDirection");
-
- return
-
- {" "}
-
-
-}
-
-function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) {
- const [ limit, setLimit ] = useNoteLabel(note, "limit");
-
- return
-
-
-}
\ No newline at end of file