Conversation
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
| private _highlightMatch( | ||
| text?: string, | ||
| query: string = "", | ||
| maxLen = 180, | ||
| ): string { | ||
| if (!text) return ""; | ||
|
|
||
| const safeQuery = query.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| const regex = new RegExp(safeQuery, "ig"); | ||
|
|
||
| const matchIndex = text.search(regex); | ||
| if (matchIndex === -1) return text.slice(0, maxLen) + "..."; | ||
|
|
||
| const previewStart = Math.max(0, matchIndex - 30); | ||
| const preview = text.slice(previewStart, previewStart + maxLen); | ||
|
|
||
| return preview.replace(regex, (m) => `<b>${m}</b>`) + "..."; | ||
| } |
There was a problem hiding this comment.
Suggestion: The current implementation is vulnerable to XSS attacks because it directly sets innerHTML with user-controlled content. Even though you're escaping regex special characters, HTML special characters aren't being escaped, which could allow injection of malicious HTML. [security, importance: 9]
| private _highlightMatch( | |
| text?: string, | |
| query: string = "", | |
| maxLen = 180, | |
| ): string { | |
| if (!text) return ""; | |
| const safeQuery = query.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| const regex = new RegExp(safeQuery, "ig"); | |
| const matchIndex = text.search(regex); | |
| if (matchIndex === -1) return text.slice(0, maxLen) + "..."; | |
| const previewStart = Math.max(0, matchIndex - 30); | |
| const preview = text.slice(previewStart, previewStart + maxLen); | |
| return preview.replace(regex, (m) => `<b>${m}</b>`) + "..."; | |
| } | |
| private _highlightMatch( | |
| text?: string, | |
| query: string = "", | |
| maxLen = 180, | |
| ): string { | |
| if (!text) return ""; | |
| const safeQuery = query.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | |
| const regex = new RegExp(safeQuery, "ig"); | |
| const matchIndex = text.search(regex); | |
| if (matchIndex === -1) return text.slice(0, maxLen) + "..."; | |
| const previewStart = Math.max(0, matchIndex - 30); | |
| const preview = text.slice(previewStart, previewStart + maxLen); | |
| // Escape HTML special characters first | |
| const escapedPreview = preview.replace(/[&<>"']/g, (m) => { | |
| return {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[m] || m; | |
| }); | |
| return escapedPreview.replace(regex, (m) => `<b>${m}</b>`) + "..."; | |
| } |
| this.flex = new FlexIndex(); | ||
| this.pages.forEach((p) => { | ||
| const text = p.url + (p.title ? ` ${p.title}` : ""); | ||
| this.flex.add(p.ts, text); // use ts (timestamp) as a unique id | ||
| }); | ||
| } | ||
|
|
There was a problem hiding this comment.
Suggestion: The buildIndex() method is defined but never called in the code. Additionally, it creates a new index without the same configuration options used in the updated() method, and it doesn't include the page text in the indexed content, unlike the implementation in updated(). [possible issue, importance: 7]
| private buildIndex() { | |
| this.flex = new FlexIndex(); | |
| this.pages.forEach((p) => { | |
| const text = p.url + (p.title ? ` ${p.title}` : ""); | |
| this.flex.add(p.ts, text); // use ts (timestamp) as a unique id | |
| }); | |
| } | |
| private buildIndex() { | |
| this.flex = new FlexIndex<string>({ | |
| tokenize: "forward", | |
| resolution: 3, | |
| }); | |
| this.pages.forEach((p) => { | |
| const toIndex = [p.title ?? "", p.text ?? "", p.url].join(" "); | |
| this.flex.add(p.ts, toIndex); | |
| }); | |
| } |
| await startRecorder( | ||
| tabId, | ||
| { collId: defaultCollId, port: null, autorun }, | ||
| { | ||
| // @ts-expect-error - collId implicitly has an 'any' type. | ||
| collId: defaultCollId, | ||
| port: null, | ||
| autorun, | ||
| }, | ||
|
|
||
| // @ts-expect-error - 2 parameters but 3 | ||
| tab.url, | ||
| ); |
There was a problem hiding this comment.
Suggestion: The code is passing three parameters to startRecorder() but adding a TypeScript error comment indicating it expects only two. This suggests a mismatch between the function signature and its usage. Either the function should be updated to accept three parameters, or the third parameter should be removed from all calls. [possible issue, importance: 8]
| await startRecorder( | |
| tabId, | |
| { collId: defaultCollId, port: null, autorun }, | |
| { | |
| // @ts-expect-error - collId implicitly has an 'any' type. | |
| collId: defaultCollId, | |
| port: null, | |
| autorun, | |
| }, | |
| // @ts-expect-error - 2 parameters but 3 | |
| tab.url, | |
| ); | |
| await startRecorder( | |
| tabId, | |
| { | |
| collId: defaultCollId, | |
| port: null, | |
| autorun, | |
| url: tab.url, // Include URL in the options object instead of as a separate parameter | |
| } | |
| ); |
| style="flex: 1; overflow-y: auto; position: relative; flex-grow: 1;" | ||
| > | ||
| <div id="my-archives" class="tab-panel" active> | ||
| <argo-archive-list id="archive-list"></argo-archive-list> | ||
| <argo-archive-list | ||
| id="archive-list" | ||
| .filterQuery=${ | ||
| //@ts-expect-error - TS2339 - Property 'searchQuery' does not exist on type 'ArgoViewer'. | ||
| this.searchQuery | ||
| } | ||
| ></argo-archive-list> | ||
| </div> |
There was a problem hiding this comment.
Suggestion: Instead of using TypeScript error suppressions for the missing searchQuery property, properly define it in the class properties. This will make the code more maintainable and type-safe. [general, importance: 3]
New proposed code:
<div
class="tab-panels"
style="flex: 1; overflow-y: auto; position: relative; flex-grow: 1;"
>
<div id="my-archives" class="tab-panel" active>
<argo-archive-list
id="archive-list"
- .filterQuery=${
- //@ts-expect-error - TS2339 - Property 'searchQuery' does not exist on type 'ArgoViewer'.
- this.searchQuery
- }
+ .filterQuery=${this.searchQuery}
></argo-archive-list>
</div>| import "@material/web/divider/divider.js"; | ||
| import { mapIntegerToRange, truncateString } from "./utils"; | ||
| import { CollectionLoader } from "@webrecorder/wabac/swlib"; | ||
| import WebTorrent from "webtorrent"; |
There was a problem hiding this comment.
@yamijuan No since we have a global import.
User description
This PR tackes #22 and #10.
The search bar is fully functional.
a) whenever the user types something, webpage title, link, and text are being indexed.
b) On the UI side, the text found is being highlighted.
Whenever the user opens an archived page and then clicks another archived page in the app, that page is opened in the same tab.
PR Type
Enhancement
Description
Search functionality for archived pages
Open archived pages in same tab
Highlight search matches in results
Improved navigation between archived pages
Changes walkthrough 📝
argo-archive-list.ts
Implement search and improve navigationsrc/argo-archive-list.ts
sidepanel.ts
Add search UI and connect to archive listsrc/sidepanel.ts
bg.ts
Fix TypeScript errors in background scriptsrc/ext/bg.ts
package.json
Add FlexSearch dependencypackage.json