Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit 8ab0084

Browse files
committed
chore(client/ts): port search_definition
1 parent b91b243 commit 8ab0084

File tree

3 files changed

+94
-51
lines changed

3 files changed

+94
-51
lines changed

src/public/app/components/app_context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ type EventMappings = {
288288
showHighlightsListWidget: {
289289
noteId: string;
290290
};
291+
showSearchError: {
292+
error: string;
293+
};
294+
searchRefreshed: { ntxId?: string | null };
291295
hoistedNoteChanged: {
292296
noteId: string;
293297
ntxId: string | null;

src/public/app/widgets/ribbon_widgets/search_definition.js renamed to src/public/app/widgets/ribbon_widgets/search_definition.ts

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,44 @@ import OrderBy from "../search_options/order_by.js";
1414
import SearchScript from "../search_options/search_script.js";
1515
import Limit from "../search_options/limit.js";
1616
import Debug from "../search_options/debug.js";
17-
import appContext from "../../components/app_context.js";
17+
import appContext, { type EventData } from "../../components/app_context.js";
1818
import bulkActionService from "../../services/bulk_action.js";
1919
import { Dropdown } from "bootstrap";
20+
import type FNote from "../../entities/fnote.js";
21+
import type { AttributeType } from "../../entities/fattribute.js";
2022

2123
const TPL = `
2224
<div class="search-definition-widget">
23-
<style>
25+
<style>
2426
.search-setting-table {
2527
margin-top: 0;
2628
margin-bottom: 7px;
2729
width: 100%;
2830
border-collapse: separate;
2931
border-spacing: 10px;
3032
}
31-
33+
3234
.search-setting-table div {
3335
white-space: nowrap;
3436
}
35-
37+
3638
.search-setting-table .button-column {
3739
/* minimal width so that table remains static sized and most space remains for middle column with settings */
3840
width: 50px;
3941
white-space: nowrap;
4042
text-align: right;
4143
}
42-
44+
4345
.search-setting-table .title-column {
4446
/* minimal width so that table remains static sized and most space remains for middle column with settings */
4547
width: 50px;
46-
white-space: nowrap;
48+
white-space: nowrap;
4749
}
48-
50+
4951
.search-setting-table .button-column .dropdown-menu {
5052
white-space: normal;
5153
}
52-
54+
5355
.attribute-list hr {
5456
height: 1px;
5557
border-color: var(--main-border-color);
@@ -58,15 +60,15 @@ const TPL = `
5860
margin-top: 5px;
5961
margin-bottom: 0;
6062
}
61-
63+
6264
.search-definition-widget input:invalid {
6365
border: 3px solid red;
6466
}
6567
6668
.add-search-option button {
6769
margin-top: 5px; /* to give some spacing when buttons overflow on the next line */
6870
}
69-
71+
7072
.dropdown-header {
7173
background-color: var(--accented-background-color);
7274
}
@@ -78,47 +80,47 @@ const TPL = `
7880
<td class="title-column">${t("search_definition.add_search_option")}</td>
7981
<td colspan="2" class="add-search-option">
8082
<button type="button" class="btn btn-sm" data-search-option-add="searchString">
81-
<span class="bx bx-text"></span>
83+
<span class="bx bx-text"></span>
8284
${t("search_definition.search_string")}
8385
</button>
84-
86+
8587
<button type="button" class="btn btn-sm" data-search-option-add="searchScript">
86-
<span class="bx bx-code"></span>
88+
<span class="bx bx-code"></span>
8789
${t("search_definition.search_script")}
8890
</button>
89-
91+
9092
<button type="button" class="btn btn-sm" data-search-option-add="ancestor">
91-
<span class="bx bx-filter-alt"></span>
93+
<span class="bx bx-filter-alt"></span>
9294
${t("search_definition.ancestor")}
9395
</button>
94-
96+
9597
<button type="button" class="btn btn-sm" data-search-option-add="fastSearch"
9698
title="${t("search_definition.fast_search_description")}">
9799
<span class="bx bx-run"></span>
98100
${t("search_definition.fast_search")}
99101
</button>
100-
102+
101103
<button type="button" class="btn btn-sm" data-search-option-add="includeArchivedNotes"
102104
title="${t("search_definition.include_archived_notes_description")}">
103105
<span class="bx bx-archive"></span>
104106
${t("search_definition.include_archived")}
105107
</button>
106-
108+
107109
<button type="button" class="btn btn-sm" data-search-option-add="orderBy">
108110
<span class="bx bx-arrow-from-top"></span>
109111
${t("search_definition.order_by")}
110112
</button>
111-
113+
112114
<button type="button" class="btn btn-sm" data-search-option-add="limit" title="${t("search_definition.limit_description")}">
113115
<span class="bx bx-stop"></span>
114116
${t("search_definition.limit")}
115117
</button>
116-
118+
117119
<button type="button" class="btn btn-sm" data-search-option-add="debug" title="${t("search_definition.debug_description")}">
118120
<span class="bx bx-bug"></span>
119121
${t("search_definition.debug")}
120122
</button>
121-
123+
122124
<div class="dropdown" style="display: inline-block;">
123125
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
124126
<span class="bx bxs-zap"></span>
@@ -138,12 +140,12 @@ const TPL = `
138140
<span class="bx bx-search"></span>
139141
${t("search_definition.search_button")}
140142
</button>
141-
143+
142144
<button type="button" class="btn btn-sm search-and-execute-button">
143145
<span class="bx bxs-zap"></span>
144146
${t("search_definition.search_execute")}
145147
</button>
146-
148+
147149
<button type="button" class="btn btn-sm save-to-note-button">
148150
<span class="bx bx-save"></span>
149151
${t("search_definition.save_to_note")}
@@ -158,7 +160,21 @@ const TPL = `
158160

159161
const OPTION_CLASSES = [SearchString, SearchScript, Ancestor, FastSearch, IncludeArchivedNotes, OrderBy, Limit, Debug];
160162

163+
// TODO: Deduplicate with server
164+
interface SaveSearchNoteResponse {
165+
notePath: string;
166+
}
167+
161168
export default class SearchDefinitionWidget extends NoteContextAwareWidget {
169+
170+
private $component!: JQuery<HTMLElement>;
171+
private $actionList!: JQuery<HTMLElement>;
172+
private $searchOptions!: JQuery<HTMLElement>;
173+
private $searchButton!: JQuery<HTMLElement>;
174+
private $searchAndExecuteButton!: JQuery<HTMLElement>;
175+
private $saveToNoteButton!: JQuery<HTMLElement>;
176+
private $actionOptions!: JQuery<HTMLElement>;
177+
162178
get name() {
163179
return "searchDefinition";
164180
}
@@ -194,7 +210,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
194210
const searchOptionName = $(event.target).attr("data-search-option-add");
195211
const clazz = OPTION_CLASSES.find((SearchOptionClass) => SearchOptionClass.optionName === searchOptionName);
196212

197-
if (clazz) {
213+
if (clazz && this.noteId) {
198214
await clazz.create(this.noteId);
199215
} else {
200216
logError(t("search_definition.unknown_search_option", { searchOptionName }));
@@ -204,11 +220,13 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
204220
});
205221

206222
this.$widget.on("click", "[data-action-add]", async (event) => {
207-
Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle"));
223+
Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle")[0]);
208224

209225
const actionName = $(event.target).attr("data-action-add");
210226

211-
await bulkActionService.addAction(this.noteId, actionName);
227+
if (this.noteId && actionName) {
228+
await bulkActionService.addAction(this.noteId, actionName);
229+
}
212230

213231
this.refresh();
214232
});
@@ -224,7 +242,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
224242

225243
this.$saveToNoteButton = this.$widget.find(".save-to-note-button");
226244
this.$saveToNoteButton.on("click", async () => {
227-
const { notePath } = await server.post("special-notes/save-search-note", { searchNoteId: this.noteId });
245+
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: this.noteId });
228246

229247
await ws.waitForMaxKnownEntityChangeId();
230248

@@ -236,24 +254,32 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
236254
}
237255

238256
async refreshResultsCommand() {
257+
if (!this.noteId) {
258+
return;
259+
}
260+
239261
try {
240-
const { error } = await froca.loadSearchNote(this.noteId);
262+
const result = await froca.loadSearchNote(this.noteId);
241263

242-
if (error) {
243-
this.handleEvent("showSearchError", { error });
264+
if (result && result.error) {
265+
this.handleEvent("showSearchError", { error: result.error });
244266
}
245-
} catch (e) {
267+
} catch (e: any) {
246268
toastService.showError(e.message);
247269
}
248270

249-
this.triggerEvent("searchRefreshed", { ntxId: this.noteContext.ntxId });
271+
this.triggerEvent("searchRefreshed", { ntxId: this.noteContext?.ntxId });
250272
}
251273

252274
async refreshSearchDefinitionCommand() {
253275
await this.refresh();
254276
}
255277

256-
async refreshWithNote(note) {
278+
async refreshWithNote(note: FNote) {
279+
if (!this.note) {
280+
return;
281+
}
282+
257283
this.$component.show();
258284

259285
this.$saveToNoteButton.toggle(note.isHiddenCompletely());
@@ -263,22 +289,27 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
263289
for (const OptionClass of OPTION_CLASSES) {
264290
const { attributeType, optionName } = OptionClass;
265291

266-
const attr = this.note.getAttribute(attributeType, optionName);
292+
const attr = this.note.getAttribute(attributeType as AttributeType, optionName);
267293

268294
this.$widget.find(`[data-search-option-add='${optionName}'`).toggle(!attr);
269295

270296
if (attr) {
271297
const searchOption = new OptionClass(attr, this.note).setParent(this);
272298
this.child(searchOption);
273299

274-
this.$searchOptions.append(searchOption.render());
300+
const renderedEl = searchOption.render();
301+
if (renderedEl) {
302+
this.$searchOptions.append(renderedEl);
303+
}
275304
}
276305
}
277306

278307
const actions = bulkActionService.parseActions(this.note);
308+
const renderedEls = actions
309+
.map((action) => action.render())
310+
.filter((e) => e) as JQuery<HTMLElement>[];
279311

280-
this.$actionOptions.empty().append(...actions.map((action) => action.render()));
281-
312+
this.$actionOptions.empty().append(...renderedEls);
282313
this.$searchAndExecuteButton.css("visibility", actions.length > 0 ? "visible" : "_hidden");
283314
}
284315

@@ -294,7 +325,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
294325
toastService.showMessage(t("search_definition.actions_executed"), 3000);
295326
}
296327

297-
entitiesReloadedEvent({ loadResults }) {
328+
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
298329
// only refreshing deleted attrs, otherwise components update themselves
299330
if (loadResults.getAttributeRows().find((attrRow) => attrRow.type === "label" && attrRow.name === "action" && attrRow.isDeleted)) {
300331
this.refresh();

src/public/app/widgets/search_options/abstract_search_option.js renamed to src/public/app/widgets/search_options/abstract_search_option.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,32 @@ import server from "../../services/server.js";
22
import ws from "../../services/ws.js";
33
import Component from "../../components/component.js";
44
import utils from "../../services/utils.js";
5-
import { t } from "../../services/i18n.js"; // 新增的导入
5+
import { t } from "../../services/i18n.js";
6+
import type FAttribute from "../../entities/fattribute.js";
7+
import type FNote from "../../entities/fnote.js";
8+
import type { AttributeType } from "../../entities/fattribute.js";
69

7-
export default class AbstractSearchOption extends Component {
8-
constructor(attribute, note) {
10+
export default abstract class AbstractSearchOption extends Component {
11+
12+
private attribute: FAttribute;
13+
private note: FNote;
14+
15+
constructor(attribute: FAttribute, note: FNote) {
916
super();
1017

1118
this.attribute = attribute;
1219
this.note = note;
1320
}
1421

15-
static async setAttribute(noteId, type, name, value = "") {
22+
static async setAttribute(noteId: string, type: AttributeType, name: string, value: string = "") {
1623
await server.put(`notes/${noteId}/set-attribute`, { type, name, value });
1724

1825
await ws.waitForMaxKnownEntityChangeId();
1926
}
2027

21-
async setAttribute(type, name, value = "") {
22-
await this.constructor.setAttribute(this.note.noteId, type, name, value);
28+
async setAttribute(type: AttributeType, name: string, value: string = "") {
29+
// TODO: Find a better pattern.
30+
await (this.constructor as any).setAttribute(this.note.noteId, type, name, value);
2331
}
2432

2533
render() {
@@ -29,29 +37,29 @@ export default class AbstractSearchOption extends Component {
2937
$rendered
3038
.find(".search-option-del")
3139
.on("click", () => this.deleteOption())
32-
.attr("title", t("abstract_search_option.remove_this_search_option")); // 使用 t 函数处理 i18n 字符串
40+
.attr("title", t("abstract_search_option.remove_this_search_option"));
3341

3442
utils.initHelpDropdown($rendered);
3543

3644
return $rendered;
37-
} catch (e) {
38-
logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack })); // 使用 t 函数处理 i18n 字符串
45+
} catch (e: any) {
46+
logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack }));
3947
return null;
4048
}
4149
}
4250

43-
// to be overridden
44-
doRender() {}
51+
abstract doRender(): JQuery<HTMLElement>;
4552

4653
async deleteOption() {
47-
await this.deleteAttribute(this.constructor.attributeType, this.constructor.optionName);
54+
// TODO: Find a better pattern.
55+
await this.deleteAttribute((this.constructor as any).attributeType, (this.constructor as any).optionName);
4856

4957
await ws.waitForMaxKnownEntityChangeId();
5058

5159
await this.triggerCommand("refreshSearchDefinition");
5260
}
5361

54-
async deleteAttribute(type, name) {
62+
async deleteAttribute(type: AttributeType, name: string) {
5563
for (const attr of this.note.getOwnedAttributes()) {
5664
if (attr.type === type && attr.name === name) {
5765
await server.remove(`notes/${this.note.noteId}/attributes/${attr.attributeId}`);

0 commit comments

Comments
 (0)