feat(layout/search_definition): integrate view options directly in search parameters

This commit is contained in:
Elian Doran 2025-12-14 20:48:50 +02:00
parent 6b9b9a96c3
commit a7ca839afb
No known key found for this signature in database
7 changed files with 424 additions and 412 deletions

View File

@ -889,7 +889,8 @@
"search_parameters": "Search Parameters", "search_parameters": "Search Parameters",
"unknown_search_option": "Unknown search option {{searchOptionName}}", "unknown_search_option": "Unknown search option {{searchOptionName}}",
"search_note_saved": "Search note has been saved into {{- notePathTitle}}", "search_note_saved": "Search note has been saved into {{- notePathTitle}}",
"actions_executed": "Actions have been executed." "actions_executed": "Actions have been executed.",
"view_options": "View options:"
}, },
"similar_notes": { "similar_notes": {
"title": "Similar Notes", "title": "Similar Notes",

View File

@ -4,29 +4,8 @@ body.experimental-feature-new-layout {
} }
.title-actions { .title-actions {
padding: 0;
display: flex;
gap: 0.25em;
align-items: center;
width: 100%;
max-width: unset;
padding-inline-start: 15px; padding-inline-start: 15px;
padding-top: 1em;
padding-bottom: 0.2em; padding-bottom: 0.2em;
font-size: 0.8em;
.collapsible-title {
font-size: 1rem;
}
.dropdown-menu {
input.form-control {
padding: 2px 8px;
margin-left: 1em;
}
}
.spacer {
flex-grow: 1;
}
} }
} }

View File

@ -0,0 +1,20 @@
.collection-properties {
padding: 0;
display: flex;
gap: 0.25em;
align-items: center;
width: 100%;
max-width: unset;
font-size: 0.8em;
.dropdown-menu {
input.form-control {
padding: 2px 8px;
margin-left: 1em;
}
}
.spacer {
flex-grow: 1;
}
}

View File

@ -1,9 +1,14 @@
import "./CollectionProperties.css";
import { t } from "i18next"; import { t } from "i18next";
import { useContext } from "preact/hooks"; import { useContext } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime"; import { Fragment } from "preact/jsx-runtime";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import { getHelpUrlForNote } from "../../services/in_app_help";
import { openInAppHelpFromUrl } from "../../services/utils";
import { ViewTypeOptions } from "../collections/interface"; import { ViewTypeOptions } from "../collections/interface";
import ActionButton from "../react/ActionButton";
import Dropdown from "../react/Dropdown"; import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList"; import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
import FormTextBox from "../react/FormTextBox"; import FormTextBox from "../react/FormTextBox";
@ -12,9 +17,6 @@ import Icon from "../react/Icon";
import { ParentComponent } from "../react/react_utils"; import { ParentComponent } from "../react/react_utils";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config"; import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab"; import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
import ActionButton from "../react/ActionButton";
import { getHelpUrlForNote } from "../../services/in_app_help";
import { openInAppHelpFromUrl } from "../../services/utils";
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = { const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: "bx bxs-grid", grid: "bx bxs-grid",
@ -30,12 +32,12 @@ export default function CollectionProperties({ note }: { note: FNote }) {
const [ viewType, setViewType ] = useViewType(note); const [ viewType, setViewType ] = useViewType(note);
return ( return (
<> <div className="collection-properties">
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} /> <ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} /> <ViewOptions note={note} viewType={viewType} />
<div className="spacer" /> <div className="spacer" />
<HelpButton note={note} /> <HelpButton note={note} />
</> </div>
); );
} }
@ -187,9 +189,9 @@ function ComboBoxPropertyView({ note, property }: { note: FNote, property: Combo
{index < property.options.length - 1 && <FormDropdownDivider />} {index < property.options.length - 1 && <FormDropdownDivider />}
</Fragment> </Fragment>
); );
} else {
return renderItem(option);
} }
return renderItem(option);
})} })}
</FormDropdownSubmenu> </FormDropdownSubmenu>
); );

View File

@ -60,7 +60,7 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
title: t("book_properties.book_properties"), title: t("book_properties.book_properties"),
icon: "bx bx-book", icon: "bx bx-book",
content: CollectionPropertiesTab, content: CollectionPropertiesTab,
show: ({ note }) => !isNewLayout && note?.type === "book" || note?.type === "search", show: ({ note }) => !isNewLayout && (note?.type === "book" || note?.type === "search"),
toggleCommand: "toggleRibbonTabBookProperties" toggleCommand: "toggleRibbonTabBookProperties"
}, },
{ {

View File

@ -1,18 +1,19 @@
import FormTextArea from "../react/FormTextArea"; import { AttributeType } from "@triliumnext/commons";
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 { ComponentChildren, VNode } from "preact";
import { useEffect, useMemo, useRef } from "preact/hooks";
import appContext from "../../components/app_context";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import { removeOwnedAttributesByNameOrType } from "../../services/attributes"; import { removeOwnedAttributesByNameOrType } from "../../services/attributes";
import { AttributeType } from "@triliumnext/commons";
import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip } from "../react/hooks";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import { useEffect, useMemo, useRef } from "preact/hooks";
import appContext from "../../components/app_context";
import server from "../../services/server"; import server from "../../services/server";
import FormSelect from "../react/FormSelect";
import FormTextArea from "../react/FormTextArea";
import FormTextBox from "../react/FormTextBox";
import HelpRemoveButtons from "../react/HelpRemoveButtons"; import HelpRemoveButtons from "../react/HelpRemoveButtons";
import { useNoteLabel, useNoteRelation, useSpacedUpdate, useTooltip } from "../react/hooks";
import Icon from "../react/Icon";
import NoteAutocomplete from "../react/NoteAutocomplete";
export interface SearchOption { export interface SearchOption {
attributeName: string; attributeName: string;
@ -134,7 +135,7 @@ function SearchOption({ note, title, titleIcon, children, help, attributeName, a
}} }}
/> />
</tr> </tr>
) );
} }
function SearchStringOption({ note, refreshResults, error, ...restProps }: SearchOptionProps) { function SearchStringOption({ note, refreshResults, error, ...restProps }: SearchOptionProps) {
@ -210,7 +211,7 @@ function SearchStringOption({ note, refreshResults, error, ...restProps }: Searc
} }
}} }}
/> />
</SearchOption> </SearchOption>;
} }
function SearchScriptOption({ note, ...restProps }: SearchOptionProps) { function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
@ -232,7 +233,7 @@ function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {
noteIdChanged={noteId => setSearchScript(noteId ?? "root")} noteIdChanged={noteId => setSearchScript(noteId ?? "root")}
placeholder={t("search_script.placeholder")} placeholder={t("search_script.placeholder")}
/> />
</SearchOption> </SearchOption>;
} }
function AncestorOption({ note, ...restProps}: SearchOptionProps) { function AncestorOption({ note, ...restProps}: SearchOptionProps) {
@ -245,9 +246,9 @@ function AncestorOption({ note, ...restProps}: SearchOptionProps) {
{ value: "eq1", label: `${t("ancestor.depth_eq", { count: 1 })} (${t("ancestor.direct_children")})` } { 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=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=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 }) }); for (let i=2; i<=9; i++) options.push({ value: `lt${ i}`, label: t("ancestor.depth_lt", { count: i }) });
return options; return options;
}, []); }, []);
@ -279,7 +280,7 @@ function FastSearchOption({ ...restProps }: SearchOptionProps) {
titleIcon="bx bx-run" title={t("fast_search.fast_search")} titleIcon="bx bx-run" title={t("fast_search.fast_search")}
help={t("fast_search.description")} help={t("fast_search.description")}
{...restProps} {...restProps}
/> />;
} }
function DebugOption({ ...restProps }: SearchOptionProps) { function DebugOption({ ...restProps }: SearchOptionProps) {
@ -290,14 +291,14 @@ function DebugOption({ ...restProps }: SearchOptionProps) {
{t("debug.access_info")} {t("debug.access_info")}
</>} </>}
{...restProps} {...restProps}
/> />;
} }
function IncludeArchivedNotesOption({ ...restProps }: SearchOptionProps) { function IncludeArchivedNotesOption({ ...restProps }: SearchOptionProps) {
return <SearchOption return <SearchOption
titleIcon="bx bx-archive" title={t("include_archived_notes.include_archived_notes")} titleIcon="bx bx-archive" title={t("include_archived_notes.include_archived_notes")}
{...restProps} {...restProps}
/> />;
} }
function OrderByOption({ note, ...restProps }: SearchOptionProps) { function OrderByOption({ note, ...restProps }: SearchOptionProps) {
@ -340,7 +341,7 @@ function OrderByOption({ note, ...restProps }: SearchOptionProps) {
{ value: "desc", title: t("order_by.desc") } { value: "desc", title: t("order_by.desc") }
]} ]}
/> />
</SearchOption> </SearchOption>;
} }
function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) { function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) {
@ -356,5 +357,5 @@ function LimitOption({ note, defaultValue, ...restProps }: SearchOptionProps) {
type="number" min="1" step="1" type="number" min="1" step="1"
currentValue={limit ?? defaultValue} onChange={setLimit} currentValue={limit ?? defaultValue} onChange={setLimit}
/> />
</SearchOption> </SearchOption>;
} }

View File

@ -7,6 +7,7 @@ import appContext from "../../components/app_context";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import attributes from "../../services/attributes"; import attributes from "../../services/attributes";
import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action"; import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action";
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
import froca from "../../services/froca"; import froca from "../../services/froca";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import server from "../../services/server"; import server from "../../services/server";
@ -15,6 +16,7 @@ import tree from "../../services/tree";
import { getErrorMessage } from "../../services/utils"; import { getErrorMessage } from "../../services/utils";
import ws from "../../services/ws"; import ws from "../../services/ws";
import RenameNoteBulkAction from "../bulk_actions/note/rename_note"; import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import CollectionProperties from "../note_bars/CollectionProperties";
import Button from "../react/Button"; import Button from "../react/Button";
import Dropdown from "../react/Dropdown"; import Dropdown from "../react/Dropdown";
import { FormListHeader, FormListItem } from "../react/FormList"; import { FormListHeader, FormListItem } from "../react/FormList";
@ -24,6 +26,8 @@ import { ParentComponent } from "../react/react_utils";
import { TabContext } from "./ribbon-interface"; import { TabContext } from "./ribbon-interface";
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions"; import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabContext, "note" | "ntxId" | "hidden">) { export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabContext, "note" | "ntxId" | "hidden">) {
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>(); const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
@ -78,7 +82,7 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
return ( return (
<div className="search-definition-widget"> <div className="search-definition-widget">
<div className="search-settings"> <div className="search-settings">
{note && !hidden && {note && !hidden && (
<table className="search-setting-table"> <table className="search-setting-table">
<tbody> <tbody>
<tr> <tr>
@ -111,6 +115,11 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
defaultValue={defaultValue} defaultValue={defaultValue}
/>; />;
})} })}
{isNewLayout && <tr className="view-options">
<td className="title-column">{t("search_definition.view_options")}</td>
<td><CollectionProperties note={note} /></td>
</tr>}
</tbody> </tbody>
<BulkActionsList note={note} /> <BulkActionsList note={note} />
<tbody className="search-actions"> <tbody className="search-actions">
@ -156,7 +165,7 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
</tr> </tr>
</tbody> </tbody>
</table> </table>
} )}
</div> </div>
</div> </div>
); );