feat(react/widgets): port search result

This commit is contained in:
Elian Doran
2025-08-28 23:13:24 +03:00
parent fa66e50193
commit 4ef103063d
5 changed files with 84 additions and 93 deletions

View File

@@ -11,7 +11,6 @@ import NoteListWidget from "../widgets/note_list.js";
import SqlResultWidget from "../widgets/sql_result.js"; import SqlResultWidget from "../widgets/sql_result.js";
import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js"; import SqlTableSchemasWidget from "../widgets/sql_table_schemas.js";
import NoteIconWidget from "../widgets/note_icon.jsx"; import NoteIconWidget from "../widgets/note_icon.jsx";
import SearchResultWidget from "../widgets/search_result.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js"; import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js"; import RootContainer from "../widgets/containers/root_container.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js"; import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
@@ -42,6 +41,7 @@ import { applyModals } from "./layout_commons.js";
import Ribbon from "../widgets/ribbon/Ribbon.jsx"; import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx"; import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import SearchResult from "../widgets/search_result.jsx";
export default class DesktopLayout { export default class DesktopLayout {
@@ -139,7 +139,7 @@ export default class DesktopLayout {
.child(new SqlTableSchemasWidget()) .child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget()) .child(new NoteDetailWidget())
.child(new NoteListWidget(false)) .child(new NoteListWidget(false))
.child(new SearchResultWidget()) .child(<SearchResult />)
.child(new SqlResultWidget()) .child(new SqlResultWidget())
.child(<ScrollPadding />) .child(<ScrollPadding />)
) )

View File

@@ -4,11 +4,12 @@ interface AlertProps {
type: "info" | "danger" | "warning"; type: "info" | "danger" | "warning";
title?: string; title?: string;
children: ComponentChildren; children: ComponentChildren;
className?: string;
} }
export default function Alert({ title, type, children }: AlertProps) { export default function Alert({ title, type, children, className }: AlertProps) {
return ( return (
<div className={`alert alert-${type}`}> <div className={`alert alert-${type} ${className ?? ""}`}>
{title && <h4>{title}</h4>} {title && <h4>{title}</h4>}
{children} {children}

View File

@@ -0,0 +1,16 @@
.search-result-widget {
flex-grow: 100000;
flex-shrink: 100000;
min-height: 0;
overflow: auto;
contain: none !important;
}
.search-result-widget .note-list {
padding: 10px;
}
.search-no-results, .search-not-executed-yet {
margin: 20px;
padding: 20px !important;
}

View File

@@ -1,89 +0,0 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import NoteListRenderer from "../services/note_list_renderer.js";
import type FNote from "../entities/fnote.js";
import type { EventData } from "../components/app_context.js";
const TPL = /*html*/`
<div class="search-result-widget">
<style>
.search-result-widget {
flex-grow: 100000;
flex-shrink: 100000;
min-height: 0;
overflow: auto;
}
.search-result-widget .note-list {
padding: 10px;
}
.search-no-results, .search-not-executed-yet {
margin: 20px;
padding: 20px;
}
</style>
<div class="search-no-results alert alert-info">
${t("search_result.no_notes_found")}
</div>
<div class="search-not-executed-yet alert alert-info">
${t("search_result.search_not_executed")}
</div>
<div class="search-result-widget-content">
</div>
</div>`;
export default class SearchResultWidget extends NoteContextAwareWidget {
private $content!: JQuery<HTMLElement>;
private $noResults!: JQuery<HTMLElement>;
private $notExecutedYet!: JQuery<HTMLElement>;
isEnabled() {
return super.isEnabled() && this.note?.type === "search";
}
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$content = this.$widget.find(".search-result-widget-content");
this.$noResults = this.$widget.find(".search-no-results");
this.$notExecutedYet = this.$widget.find(".search-not-executed-yet");
}
async refreshWithNote(note: FNote) {
const noResults = note.getChildNoteIds().length === 0 && !!note.searchResultsLoaded;
this.$content.empty();
this.$noResults.toggle(noResults);
this.$notExecutedYet.toggle(!note.searchResultsLoaded);
if (noResults || !note.searchResultsLoaded) {
return;
}
const noteListRenderer = new NoteListRenderer({
$parent: this.$content,
parentNote: note,
showNotePath: true
});
await noteListRenderer.renderList();
}
searchRefreshedEvent({ ntxId }: EventData<"searchRefreshed">) {
if (!this.isNoteContext(ntxId)) {
return;
}
this.refresh();
}
notesReloadedEvent({ noteIds }: EventData<"notesReloaded">) {
if (this.noteId && noteIds.includes(this.noteId)) {
this.refresh();
}
}
}

View File

@@ -0,0 +1,63 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { t } from "../services/i18n";
import Alert from "./react/Alert";
import { useNoteContext, useNoteProperty, useTriliumEvent } from "./react/hooks";
import "./search_result.css";
import NoteListRenderer from "../services/note_list_renderer";
enum SearchResultState {
NO_RESULTS,
NOT_EXECUTED,
GOT_RESULTS
}
export default function SearchResult() {
const { note, ntxId } = useNoteContext();
const [ state, setState ] = useState<SearchResultState>();
const searchContainerRef = useRef<HTMLDivElement>(null);
function refresh() {
searchContainerRef.current?.replaceChildren();
if (!note?.searchResultsLoaded) {
setState(SearchResultState.NOT_EXECUTED);
} else if (note.getChildNoteIds().length === 0) {
setState(SearchResultState.NO_RESULTS);
} else if (searchContainerRef.current) {
setState(SearchResultState.GOT_RESULTS);
const noteListRenderer = new NoteListRenderer({
$parent: $(searchContainerRef.current),
parentNote: note,
showNotePath: true
});
noteListRenderer.renderList();
}
}
useEffect(() => refresh(), [ note ]);
useTriliumEvent("searchRefreshed", ({ ntxId: eventNtxId }) => {
if (eventNtxId === ntxId) {
refresh();
}
});
useTriliumEvent("notesReloaded", ({ noteIds }) => {
if (note?.noteId && noteIds.includes(note.noteId)) {
refresh();
}
});
return (
<div className="search-result-widget">
{state === SearchResultState.NOT_EXECUTED && (
<Alert type="info" className="search-not-executed-yet">{t("search_result.search_not_executed")}</Alert>
)}
{state === SearchResultState.NO_RESULTS && (
<Alert type="info" className="search-no-results">{t("search_result.no_notes_found")}</Alert>
)}
<div ref={searchContainerRef} className="search-results" />
</div>
);
}