Skip to content

Commit 1f003e4

Browse files
feat: Add advanced search with Lucene syntax to search page
Integrate advanced search into the existing /content/query page as a toggleable panel with tab-style navigation between Simple and Advanced modes. Advanced mode provides a textarea for Lucene query syntax (AND, OR, NOT, wildcards, proximity, grouping), inline syntax hints with link to Solr documentation, collapsible syntax help table, and upfront facet filter grid (species, types, compartments, keywords). - Add getAllFacets() to search service calling GET /search/facet - Add advanced mode state, toggle, and submit logic to search component - Add FormsModule for ngModel on textarea - Update nav-options.json Advanced Data Search link to /content/query?advanced=true - Add redirects for /content/advanced and /tools/advanced - Responsive facet grid (4 cols desktop, 2 tablet, 1 mobile) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a6f7f63 commit 1f003e4

6 files changed

Lines changed: 561 additions & 6 deletions

File tree

projects/website-angular/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export const routes: Routes = [
2727
{ path: 'content/reactome-research-spotlight/:slug', loadComponent: () => import('./article/article/article.component').then(m => m.ArticleComponent), pathMatch: 'full' },
2828

2929
//Search Page
30+
{ path: 'content/advanced', redirectTo: '/content/query?advanced=true' },
31+
{ path: 'tools/advanced', redirectTo: '/content/query?advanced=true' },
3032
{ path: 'content/query', loadComponent: () => import('./search/search.component').then(m => m.SearchComponent) },
3133

3234
//404 Page

projects/website-angular/src/app/search/search.component.html

Lines changed: 184 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,43 @@
33
@if (query && searchSubmitted) {
44
<!-- Search bar + filters above the two-column layout -->
55
<div class="search-top">
6-
<app-search-bar [query]="query" (queryChange)="onQueryInput($event)"/>
6+
<div class="search-mode-tabs">
7+
<button class="mode-tab" [class.active]="!advancedMode" (click)="advancedMode && toggleAdvancedMode()">
8+
<span class="material-symbols-rounded">search</span>
9+
Simple Search
10+
</button>
11+
<button class="mode-tab" [class.active]="advancedMode" (click)="!advancedMode && toggleAdvancedMode()">
12+
<span class="material-symbols-rounded">tune</span>
13+
Advanced Search
14+
</button>
15+
</div>
16+
17+
@if (advancedMode) {
18+
<div class="advanced-input-area">
19+
<div class="advanced-input-column">
20+
<textarea
21+
class="advanced-query-input"
22+
rows="4"
23+
[(ngModel)]="advancedQuery"
24+
placeholder='e.g. "apoptotic process" AND TP53'
25+
></textarea>
26+
<div class="syntax-hints">
27+
<span><code>" "</code> exact phrase</span>
28+
<span><code>AND</code> <code>OR</code> <code>NOT</code> boolean</span>
29+
<span><code>?</code> <code>*</code> wildcards</span>
30+
<span><code>"raf map"~4</code> proximity</span>
31+
<span><code>( )</code> grouping</span>
32+
<a class="syntax-hints-link" href="https://solr.apache.org/guide/solr/latest/query-guide/standard-query-parser.html" target="_blank" rel="noopener">Full syntax reference</a>
33+
</div>
34+
</div>
35+
<button class="advanced-search-btn" (click)="submitAdvancedSearch()" [disabled]="!advancedQuery.trim()">
36+
<span class="material-symbols-rounded">search</span>
37+
Search
38+
</button>
39+
</div>
40+
} @else {
41+
<app-search-bar [query]="query" (queryChange)="onQueryInput($event)"/>
42+
}
743
</div>
844

945
<!-- Active filter chips -->
@@ -374,9 +410,154 @@ <h2>{{ group.typeName }} ({{ group.entriesCount }})</h2>
374410
<div class="empty-state">
375411
<h1>Search Reactome</h1>
376412
<p>Enter a search term to find pathways, reactions, proteins, and more.</p>
377-
<div class="empty-search-bar">
378-
<app-search-bar [query]="query" (queryChange)="onQueryInput($event)"/>
413+
414+
<div class="search-mode-tabs">
415+
<button class="mode-tab" [class.active]="!advancedMode" (click)="advancedMode && toggleAdvancedMode()">
416+
<span class="material-symbols-rounded">search</span>
417+
Simple Search
418+
</button>
419+
<button class="mode-tab" [class.active]="advancedMode" (click)="!advancedMode && toggleAdvancedMode()">
420+
<span class="material-symbols-rounded">tune</span>
421+
Advanced Search
422+
</button>
379423
</div>
424+
425+
@if (advancedMode) {
426+
<div class="advanced-panel">
427+
<textarea
428+
class="advanced-query-input"
429+
rows="6"
430+
[(ngModel)]="advancedQuery"
431+
placeholder='e.g. "apoptotic process" AND TP53'
432+
></textarea>
433+
434+
<button class="syntax-help-toggle" (click)="syntaxHelpOpen = !syntaxHelpOpen">
435+
<span class="material-symbols-rounded">help_outline</span>
436+
Syntax Help
437+
<span class="material-symbols-rounded">{{ syntaxHelpOpen ? 'expand_less' : 'expand_more' }}</span>
438+
</button>
439+
440+
@if (syntaxHelpOpen) {
441+
<div class="syntax-help">
442+
<table class="syntax-table">
443+
<thead>
444+
<tr><th>Syntax</th><th>Description</th><th>Example</th></tr>
445+
</thead>
446+
<tbody>
447+
<tr><td><code>"..."</code></td><td>Exact phrase match</td><td><code>"apoptotic process"</code></td></tr>
448+
<tr><td><code>AND</code></td><td>Both terms must be present</td><td><code>apoptosis AND TP53</code></td></tr>
449+
<tr><td><code>OR</code></td><td>Either term can be present</td><td><code>apoptosis OR autophagy</code></td></tr>
450+
<tr><td><code>NOT</code></td><td>Exclude a term</td><td><code>apoptosis NOT cancer</code></td></tr>
451+
<tr><td><code>?</code></td><td>Single character wildcard</td><td><code>te?t</code></td></tr>
452+
<tr><td><code>*</code></td><td>Multi-character wildcard</td><td><code>apopt*</code></td></tr>
453+
<tr><td><code>"..."~N</code></td><td>Proximity search (within N words)</td><td><code>"cell death"~3</code></td></tr>
454+
<tr><td><code>( )</code></td><td>Grouping for complex queries</td><td><code>(apoptosis OR autophagy) AND TP53</code></td></tr>
455+
</tbody>
456+
</table>
457+
<p class="syntax-help-footer">
458+
For the full query syntax, see the <a href="https://solr.apache.org/guide/solr/latest/query-guide/standard-query-parser.html" target="_blank" rel="noopener">Solr Standard Query Parser documentation</a>.
459+
</p>
460+
</div>
461+
}
462+
463+
@if (allFacets) {
464+
<div class="advanced-facets">
465+
<h3 class="advanced-facets-title">Filter by</h3>
466+
<div class="advanced-facet-grid">
467+
@if (getFacetItems(allFacets.speciesFacet); as items) {
468+
@if (items.length) {
469+
<div class="advanced-facet-column">
470+
<h4>Species</h4>
471+
<div class="advanced-facet-list">
472+
@for (item of items; track item.name) {
473+
<label class="facet-option">
474+
<input
475+
type="checkbox"
476+
[checked]="isAdvancedFacetSelected('species', item.name)"
477+
(change)="toggleAdvancedFacet('species', item.name)"
478+
/>
479+
<span class="facet-name">{{ item.name }}</span>
480+
<span class="facet-count">({{ item.count }})</span>
481+
</label>
482+
}
483+
</div>
484+
</div>
485+
}
486+
}
487+
@if (getFacetItems(allFacets.typeFacet); as items) {
488+
@if (items.length) {
489+
<div class="advanced-facet-column">
490+
<h4>Types</h4>
491+
<div class="advanced-facet-list">
492+
@for (item of items; track item.name) {
493+
<label class="facet-option">
494+
<input
495+
type="checkbox"
496+
[checked]="isAdvancedFacetSelected('types', item.name)"
497+
(change)="toggleAdvancedFacet('types', item.name)"
498+
/>
499+
<span class="facet-name">{{ item.name }}</span>
500+
<span class="facet-count">({{ item.count }})</span>
501+
</label>
502+
}
503+
</div>
504+
</div>
505+
}
506+
}
507+
@if (getFacetItems(allFacets.compartmentFacet); as items) {
508+
@if (items.length) {
509+
<div class="advanced-facet-column">
510+
<h4>Compartments</h4>
511+
<div class="advanced-facet-list">
512+
@for (item of items; track item.name) {
513+
<label class="facet-option">
514+
<input
515+
type="checkbox"
516+
[checked]="isAdvancedFacetSelected('compartments', item.name)"
517+
(change)="toggleAdvancedFacet('compartments', item.name)"
518+
/>
519+
<span class="facet-name">{{ item.name }}</span>
520+
<span class="facet-count">({{ item.count }})</span>
521+
</label>
522+
}
523+
</div>
524+
</div>
525+
}
526+
}
527+
@if (getFacetItems(allFacets.keywordFacet); as items) {
528+
@if (items.length) {
529+
<div class="advanced-facet-column">
530+
<h4>Keywords</h4>
531+
<div class="advanced-facet-list">
532+
@for (item of items; track item.name) {
533+
<label class="facet-option">
534+
<input
535+
type="checkbox"
536+
[checked]="isAdvancedFacetSelected('keywords', item.name)"
537+
(change)="toggleAdvancedFacet('keywords', item.name)"
538+
/>
539+
<span class="facet-name">{{ item.name }}</span>
540+
<span class="facet-count">({{ item.count }})</span>
541+
</label>
542+
}
543+
</div>
544+
</div>
545+
}
546+
}
547+
</div>
548+
</div>
549+
}
550+
551+
<button class="advanced-search-btn" (click)="submitAdvancedSearch()" [disabled]="!advancedQuery.trim()">
552+
<span class="material-symbols-rounded">search</span>
553+
Search
554+
</button>
555+
</div>
556+
} @else {
557+
<div class="empty-search-bar">
558+
<app-search-bar [query]="query" (queryChange)="onQueryInput($event)"/>
559+
</div>
560+
}
380561
</div>
381562
}
382563
</div>

0 commit comments

Comments
 (0)