diff --git a/projects/pathway-browser/src/app/details/tabs/result-tab/result-tab.component.ts b/projects/pathway-browser/src/app/details/tabs/result-tab/result-tab.component.ts index 8cfd617..cd9f4e4 100644 --- a/projects/pathway-browser/src/app/details/tabs/result-tab/result-tab.component.ts +++ b/projects/pathway-browser/src/app/details/tabs/result-tab/result-tab.component.ts @@ -148,29 +148,29 @@ export class ResultTabComponent { let size = data.length if (this.includeGrouping() === false) { data = data.filter(p => p.llp) - console.log('Filter llp', size, '==>', data.length) + //console.log('Filter llp', size, '==>', data.length) size = data.length } if (this.includeDisease() === false) { data = data.filter(p => !p.inDisease) - console.log('Filter disease', size, '==>', data.length) + //console.log('Filter disease', size, '==>', data.length) size = data.length } if (this.state.fdrFilter() !== undefined) { data = data.filter(p => p.entities.fdr <= this.state.fdrFilter()!) - console.log('Filter fdr', size, '==>', data.length) + //console.log('Filter fdr', size, '==>', data.length) } if (this.state.minExpressionFilter() !== undefined) { data = data.filter(p => p.entities.exp[this.analysis.sampleIndex()] >= this.state.minExpressionFilter()!) - console.log('Filter minExpression', size, '==>', data.length) + //console.log('Filter minExpression', size, '==>', data.length) } if (this.state.maxExpressionFilter() !== undefined) { data = data.filter(p => p.entities.exp[this.analysis.sampleIndex()] <= this.state.maxExpressionFilter()!) - console.log('Filter minExpression', size, '==>', data.length) + //console.log('Filter minExpression', size, '==>', data.length) } if (this.state.gsaFilter().length !== 0) { data = data.filter(p => this.gsaFilterSet().has(p.entities.exp[this.analysis.sampleIndex()])) - console.log('Filter gsa', size, '==>', data.length) + //console.log('Filter gsa', size, '==>', data.length) } return data }) @@ -182,15 +182,15 @@ export class ResultTabComponent { let size = data.length if (this.state.pathwayMinSizeFilter()) { data = data.filter(p => p.entities.total >= this.state.pathwayMinSizeFilter()!) - console.log('Filter min size', size, '==>', data.length) + //console.log('Filter min size', size, '==>', data.length) size = data.length } if (this.state.pathwayMaxSizeFilter()) { data = data.filter(p => p.entities.total <= this.state.pathwayMaxSizeFilter()!) - console.log('Filter max size', size, '==>', data.length) + //console.log('Filter max size', size, '==>', data.length) size = data.length } - console.log("Total filter", this.analysis.result()?.pathways.length, '==>', size) + //console.log("Total filter", this.analysis.result()?.pathways.length, '==>', size) return data; }) @@ -276,7 +276,7 @@ export class ResultTabComponent { visitPathway(pathway: Analysis.Pathway) { this.data.selectedPathwayStId.set(pathway.stId) - console.log("Navigating to " + pathway.stId) + //console.log("Navigating to " + pathway.stId) this.state.navigateTo(pathway.stId, {queryParamsHandling: 'preserve', preserveFragment: true}) } diff --git a/projects/pathway-browser/src/app/diagram/diagram.component.ts b/projects/pathway-browser/src/app/diagram/diagram.component.ts index a838fa1..1322e64 100644 --- a/projects/pathway-browser/src/app/diagram/diagram.component.ts +++ b/projects/pathway-browser/src/app/diagram/diagram.component.ts @@ -964,7 +964,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { const resource = this.state.overlay(); if (resource) { - console.log('Resource not null', resource) + //console.log('Resource not null', resource) this.interactorsComponent()?.getInteractors(resource) } @@ -1064,7 +1064,7 @@ export class DiagramComponent implements AfterViewInit, OnDestroy { filter((e) => e.detail.cy !== this.legend && e.type === ReactomeEventTypes.unselect) ).subscribe(e => { if (this.state.select() === e.detail.element.data('graph.stId')) { - console.log('Unselect', e.detail.reactomeId) + //console.log('Unselect', e.detail.reactomeId) this.state.select.set(null) } }) diff --git a/projects/pathway-browser/src/app/event-hierarchy/event-hierarchy.component.ts b/projects/pathway-browser/src/app/event-hierarchy/event-hierarchy.component.ts index 762ac02..9856baf 100644 --- a/projects/pathway-browser/src/app/event-hierarchy/event-hierarchy.component.ts +++ b/projects/pathway-browser/src/app/event-hierarchy/event-hierarchy.component.ts @@ -250,7 +250,7 @@ export class EventHierarchyComponent implements AfterViewInit, OnDestroy { // Combine all data and merged into one object initialData$.pipe( - tap(d => console.log('Initial data', d)), + //tap(d => console.log('Initial data', d)), switchMap(initialData => enhancedEventData$.pipe( combineLatestWith( @@ -265,14 +265,14 @@ export class EventHierarchyComponent implements AfterViewInit, OnDestroy { })) ) ), - tap(d => console.log('Combined data', d)), + //tap(d => console.log('Combined data', d)), // Build the tree with all data switchMap(({ enhancedEvent, hitReactions }) => this.eventService.buildTree(enhancedEvent, this.pathwayId(), this.tree, hitReactions)), - tap(d => console.log('Final data', d)), + //tap(d => console.log('Final data', d)), ).subscribe({ next: () => { // Give pathway id when idToUse is PEs diff --git a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts index e28aa6f..f397229 100644 --- a/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts +++ b/projects/pathway-browser/src/app/reacfoam/reacfoam.component.ts @@ -207,7 +207,7 @@ export class ReacfoamComponent implements OnDestroy { if (selectedElement && isRLE(selectedElement)) { const flaggingResult = await firstValueFrom(this.data.getReacfoamFlagging(selectedElement.stId, this.species.currentSpecies().displayName)); if (flaggingResult.matches && flaggingResult.matches.length === 1) { - console.log('Selecting in reacfoam the parent pathway of a reaction as it is only contained in one pathway') + //console.log('Selecting in reacfoam the parent pathway of a reaction as it is only contained in one pathway') this.select.set(flaggingResult.matches[0]); } } diff --git a/projects/pathway-browser/src/app/reacfoam/reacfoam.service.ts b/projects/pathway-browser/src/app/reacfoam/reacfoam.service.ts index 9853f06..1175218 100644 --- a/projects/pathway-browser/src/app/reacfoam/reacfoam.service.ts +++ b/projects/pathway-browser/src/app/reacfoam/reacfoam.service.ts @@ -115,13 +115,13 @@ export class ReacfoamService { fetchEventsHierarchy(species: Species, params: Partial): Observable { cleanObject(params) - console.log('fetch events hierarchy') + //console.log('fetch events hierarchy') return this.http.get(`${CONTENT_SERVICE}/data/eventsHierarchy/${species.taxId}`, {params}) } fetchTLPLayoutMap(): Observable> { - console.log('fetch tlp layout') + //console.log('fetch tlp layout') return this.http.get(LAYOUT_URL, {responseType: "text"}).pipe( map((text) => new Map( text.split("\n") // Split lines diff --git a/projects/pathway-browser/src/app/services/analysis.service.ts b/projects/pathway-browser/src/app/services/analysis.service.ts index b8c8eea..892c08c 100644 --- a/projects/pathway-browser/src/app/services/analysis.service.ts +++ b/projects/pathway-browser/src/app/services/analysis.service.ts @@ -245,7 +245,7 @@ export class AnalysisService { species: this.state.speciesFilter() }), loader: ({request, previous}) => { - console.log("Loading ", request, previous) + //console.log("Loading ", request, previous) return request.token ? this.loadAnalysis(request.token, { resource: request.resource || undefined, @@ -323,7 +323,7 @@ export class AnalysisService { effect(() => { const result = this.result(); - console.log('Result updated', result) + //console.log('Result updated', result) if (!result) return const validGroups: Set = new Set(); if (result.summary.type === 'GSA_REGULATION') { diff --git a/projects/pathway-browser/src/app/services/data-state.service.ts b/projects/pathway-browser/src/app/services/data-state.service.ts index 427d6da..ca7c50b 100644 --- a/projects/pathway-browser/src/app/services/data-state.service.ts +++ b/projects/pathway-browser/src/app/services/data-state.service.ts @@ -96,7 +96,7 @@ export class DataStateService { diagram: this.state.pathwayId(), species: this.species.currentSpecies().displayName, }) - console.log('updating request', params); + // console.log('updating request', params); return params; }) @@ -155,8 +155,8 @@ export class DataStateService { constructor(private state: UrlStateService, private http: HttpClient, private species: SpeciesService) { - effect(() => console.log('Flagging', this.flagIdentifiers())); - effect(() => console.log('Flagging error', this.flagResource.error())); + // effect(() => console.log('Flagging', this.flagIdentifiers())); + // effect(() => console.log('Flagging error', this.flagResource.error())); effect(() => { if (this._selectedElement.error()) this.state.select.set(null); // If selection doesn't exist (wrong id), we remove selection }); diff --git a/projects/pathway-browser/src/app/services/diagram.service.ts b/projects/pathway-browser/src/app/services/diagram.service.ts index a3e6ecb..c9a15e1 100644 --- a/projects/pathway-browser/src/app/services/diagram.service.ts +++ b/projects/pathway-browser/src/app/services/diagram.service.ts @@ -202,7 +202,7 @@ export class DiagramService { diagram: this.http.get(`${this.general.download()}/diagram/${id}.json`), graph: this.http.get(`${this.general.download()}/diagram/${id}.graph.json`) }).pipe( - tap(({diagram, graph}) => console.log('Original diagram:', diagram, 'Original graph', graph)), + // tap(({diagram, graph}) => console.log('Original diagram:', diagram, 'Original graph', graph)), switchMap(({diagram, graph}) => { if (diagram.forNormalDraw !== undefined && !diagram.forNormalDraw) { return this.getNormalPathway(diagram.stableId).pipe( @@ -210,10 +210,10 @@ export class DiagramService { normalDiagram: this.http.get(`${this.general.download()}/diagram/${normalPathwayId}.json`), normalGraph: this.http.get(`${this.general.download()}/diagram/${normalPathwayId}.graph.json`) })), - tap(({ - normalGraph, - normalDiagram - }) => console.log('Normal diagram:', normalGraph, 'Normal graph', normalDiagram)), + // tap(({ + // normalGraph, + // normalDiagram + // }) => console.log('Normal diagram:', normalGraph, 'Normal graph', normalDiagram)), map(({normalGraph, normalDiagram}) => { graph.nodes.push(...normalGraph.nodes); graph.edges.push(...normalGraph.edges); @@ -233,7 +233,7 @@ export class DiagramService { return of({diagram, graph}); } }), - tap((mergedResponse) => console.log('All responses:', mergedResponse)), + // tap((mergedResponse) => console.log('All responses:', mergedResponse)), map(mergedResponse => ({ ...mergedResponse, chebiMapping: this.getCHEBIStructure( @@ -244,7 +244,7 @@ export class DiagramService { ) })), map(({diagram, graph, chebiMapping}) => this.diagramFromData(diagram, graph, id, chebiMapping)), - tap((output) => console.log('Output:', output)), + // tap((output) => console.log('Output:', output)), ) } @@ -255,11 +255,11 @@ export class DiagramService { id: number | string = '', chebiMapping: Map> = new Map() ): cytoscape.ElementsDefinition { - console.log("edge.reactionType", new Set(diagram.edges.flatMap(edge => edge.reactionType))) - console.log("node.connectors.types", new Set(diagram.nodes.flatMap(node => node.connectors.flatMap(con => con.type)))) - console.log("node.renderableClass", new Set(diagram.nodes.flatMap(node => node.renderableClass))) - console.log("links.renderableClass", new Set(diagram.links.flatMap(link => link.renderableClass))) - console.log("shadow.renderableClass", new Set(diagram.shadows.flatMap(shadow => shadow.renderableClass))) + // console.log("edge.reactionType", new Set(diagram.edges.flatMap(edge => edge.reactionType))) + // console.log("node.connectors.types", new Set(diagram.nodes.flatMap(node => node.connectors.flatMap(con => con.type)))) + // console.log("node.renderableClass", new Set(diagram.nodes.flatMap(node => node.renderableClass))) + // console.log("links.renderableClass", new Set(diagram.links.flatMap(link => link.renderableClass))) + // console.log("shadow.renderableClass", new Set(diagram.shadows.flatMap(shadow => shadow.renderableClass))) const idToEdges = new Map(diagram.edges.map(edge => [edge.id, edge])); const idToNodes = new Map(diagram.nodes.map(node => [node.id, node])); @@ -673,7 +673,7 @@ export class DiagramService { } ) - console.log('All data created') + // console.log('All data created') return { nodes: [...compartmentNodes, ...reactionNodes, ...entityNodes, ...shadowNodes], edges: [...edges, ...linkEdges] diff --git a/projects/pathway-browser/src/app/services/event.service.ts b/projects/pathway-browser/src/app/services/event.service.ts index 49522a7..c353120 100644 --- a/projects/pathway-browser/src/app/services/event.service.ts +++ b/projects/pathway-browser/src/app/services/event.service.ts @@ -360,7 +360,7 @@ export class EventService { : object.stId; this.dboService.setCurrentObj(object); - console.log('Build tree with selected event', idToBuild); + // console.log('Build tree with selected event', idToBuild); const ancestors = idToBuild ? this.fetchEventAncestors(idToBuild).pipe(map(ancestors => this.getFinalAncestor(ancestors, diagramId ? [diagramId] : undefined))) : from([[] as Event[]]); diff --git a/projects/pathway-browser/src/app/services/species.service.ts b/projects/pathway-browser/src/app/services/species.service.ts index 440b062..e735376 100644 --- a/projects/pathway-browser/src/app/services/species.service.ts +++ b/projects/pathway-browser/src/app/services/species.service.ts @@ -116,7 +116,7 @@ export class SpeciesService { newValue = newValue.replaceAll(initial, replacement || ''); } newValue = newValue.replaceAll(',""', '').replaceAll('""', '').trim() // Remove trailing commas and quotes from lists - console.log(value, " => ", newValue); + // console.log(value, " => ", newValue); if (newValue.length === 0) delete params[key]; else params[key] = JSON.parse(newValue); } diff --git a/projects/pathway-browser/src/app/services/url-state.service.ts b/projects/pathway-browser/src/app/services/url-state.service.ts index 238bcd4..aa98b5c 100644 --- a/projects/pathway-browser/src/app/services/url-state.service.ts +++ b/projects/pathway-browser/src/app/services/url-state.service.ts @@ -123,11 +123,11 @@ export class UrlStateService implements State { }); effect(() => { - console.log('Updating patwhayId to ', this.pathwayId()) + // console.log('Updating patwhayId to ', this.pathwayId()) //If in content/search do not navigate if (this.router.url.includes('content') || this.router.url.includes('query')) { - console.log('In content or search route, not navigating on pathwayId change'); + // console.log('In content or search route, not navigating on pathwayId change'); return; } @@ -214,9 +214,9 @@ export class UrlStateService implements State { if (typeof paramValue === 'string') paramValue = paramValue.replaceAll(' ', '__') queryParams[key] = isArray(paramValue) ? paramValue.join(';') : paramValue; } - console.log('Updating URL from state', queryParams) + // console.log('Updating URL from state', queryParams) if (this.router.url.includes('content') || this.router.url.includes('query')) { - console.log('In content or search route, not navigating on state change'); + // console.log('In content or search route, not navigating on state change'); return; } this.navigateTo(this.pathwayId() ?? null, {queryParams, preserveFragment: true}); diff --git a/projects/pathway-browser/src/app/shared/logger/logger.component.ts b/projects/pathway-browser/src/app/shared/logger/logger.component.ts index 396533e..3f9973a 100644 --- a/projects/pathway-browser/src/app/shared/logger/logger.component.ts +++ b/projects/pathway-browser/src/app/shared/logger/logger.component.ts @@ -11,7 +11,7 @@ export class LoggerComponent implements OnInit{ } ngOnInit(): void { - console.log('Building ', this.toLog()) + // console.log('Building ', this.toLog()) } diff --git a/projects/pathway-browser/src/app/utils/expandable-virtual-scroll/expandable-virtual-scroll.strategy.ts b/projects/pathway-browser/src/app/utils/expandable-virtual-scroll/expandable-virtual-scroll.strategy.ts index c8633db..61eb84e 100644 --- a/projects/pathway-browser/src/app/utils/expandable-virtual-scroll/expandable-virtual-scroll.strategy.ts +++ b/projects/pathway-browser/src/app/utils/expandable-virtual-scroll/expandable-virtual-scroll.strategy.ts @@ -11,14 +11,14 @@ export class ExpandableVirtualScrollStrategy extends FixedSizeVirtualScrollStrat private maxBufferPx: number = 200; constructor() { - console.log('Expandable Virtual Scroll Strategy.'); + // console.log('Expandable Virtual Scroll Strategy.'); super(28, 100, 200); } override attach(viewport: CdkVirtualScrollViewport): void { // super.attach(viewport); this.viewport = viewport; - console.log('Attempting to attach', viewport); + // console.log('Attempting to attach', viewport); } setItemHeight(index: number, height: number) { @@ -28,7 +28,7 @@ export class ExpandableVirtualScrollStrategy extends FixedSizeVirtualScrollStrat } else { this._indexToHeight.set(index, height); } - console.log(index, height); + // console.log(index, height); this._updateViewport(); } } diff --git a/projects/pathway-browser/src/app/viewport/analysis-form/qualitative-analysis/qualitative-analysis.component.ts b/projects/pathway-browser/src/app/viewport/analysis-form/qualitative-analysis/qualitative-analysis.component.ts index 955ab9c..c63f4d3 100644 --- a/projects/pathway-browser/src/app/viewport/analysis-form/qualitative-analysis/qualitative-analysis.component.ts +++ b/projects/pathway-browser/src/app/viewport/analysis-form/qualitative-analysis/qualitative-analysis.component.ts @@ -103,7 +103,7 @@ export class QualitativeAnalysisComponent implements AfterViewInit { }); effect(async () => { if (!this.lottieCanvas() || this.lottieEnd !== undefined) return; - console.log('Building lottie') + // console.log('Building lottie') this.lottieEnd = await this.lottieService.buildLottie({ renderConfig: { autoResize: true, diff --git a/projects/pathway-browser/src/app/viewport/search/search.component.ts b/projects/pathway-browser/src/app/viewport/search/search.component.ts index 57fa02e..c8ae515 100644 --- a/projects/pathway-browser/src/app/viewport/search/search.component.ts +++ b/projects/pathway-browser/src/app/viewport/search/search.component.ts @@ -193,7 +193,7 @@ export class SearchComponent { this.searchText.set(searchText); if (event) event.preventDefault(); this.collapsed.set('opened') - console.log('searching', searchText); + // console.log('searching', searchText); this.typeFilter.set([]) this.selectedResult.set(undefined) this.searchParams.set({ @@ -487,7 +487,9 @@ export namespace Search { isDisease: boolean; hasEHLD?: boolean; hasReferenceEntity: boolean; - disease: boolean + disease: boolean; + deleted: boolean; + date: number; } export interface EntryResult { diff --git a/projects/pathway-browser/src/app/viewport/viewport.component.ts b/projects/pathway-browser/src/app/viewport/viewport.component.ts index 038ac9b..69e6d74 100644 --- a/projects/pathway-browser/src/app/viewport/viewport.component.ts +++ b/projects/pathway-browser/src/app/viewport/viewport.component.ts @@ -245,7 +245,7 @@ export class ViewportComponent implements AfterViewInit { playSpeed = model(2); updateSpeed = effect(() => { - console.log('Update play speed', this.playSpeed()); + // console.log('Update play speed', this.playSpeed()); if (this.interval) { this.pause(); this.play(); diff --git a/projects/reactome-cytoscape-style/src/lib/interactivity.ts b/projects/reactome-cytoscape-style/src/lib/interactivity.ts index 1a09382..f9edc2a 100644 --- a/projects/reactome-cytoscape-style/src/lib/interactivity.ts +++ b/projects/reactome-cytoscape-style/src/lib/interactivity.ts @@ -15,7 +15,7 @@ export class Interactivity { isMobile = 'ontouchstart' in document || navigator.maxTouchPoints > 0; constructor(private cy: cytoscape.Core, private properties: Properties) { - console.log('is mobile', this.isMobile) + // console.log('is mobile', this.isMobile) // @ts-ignore cy.elements().ungrabify().panify(); this.initHover(cy); @@ -102,7 +102,7 @@ export class Interactivity { .on('mouseover', 'edge', e => { const mapped = mapper(e.target); - if (mapped !== e.target) console.log(mapped, mapped.connectedNodes('.reaction')) + // if (mapped !== e.target) console.log(mapped, mapped.connectedNodes('.reaction')) hoverReaction(mapped.connectedNodes('.reaction')) }) diff --git a/projects/reactome-cytoscape-style/src/lib/style.ts b/projects/reactome-cytoscape-style/src/lib/style.ts index dc34449..515cfa6 100644 --- a/projects/reactome-cytoscape-style/src/lib/style.ts +++ b/projects/reactome-cytoscape-style/src/lib/style.ts @@ -198,7 +198,7 @@ export class Style { css: { "background-color": (node) => { const exp = node.data('exp') as number[]; - console.log(node.data(), exp) + // console.log(node.data(), exp) return exp !== undefined ? this.currentPalette(exp[0]).hex() : this.pm('analysis', 'notFound', c => c)() }, "border-width": this.p('global', 'thickness'), diff --git a/projects/website-angular/src/app/article/article/article.component.scss b/projects/website-angular/src/app/article/article/article.component.scss index 79d01d2..90d6319 100644 --- a/projects/website-angular/src/app/article/article/article.component.scss +++ b/projects/website-angular/src/app/article/article/article.component.scss @@ -11,6 +11,7 @@ .article-title { margin: 0 0 1rem 0; + line-height: 32px; color: var(--primary); } diff --git a/projects/website-angular/src/app/breadcrumb/breadcrumb.component.ts b/projects/website-angular/src/app/breadcrumb/breadcrumb.component.ts index ef4cdcb..2442698 100644 --- a/projects/website-angular/src/app/breadcrumb/breadcrumb.component.ts +++ b/projects/website-angular/src/app/breadcrumb/breadcrumb.component.ts @@ -55,6 +55,12 @@ export class BreadcrumbComponent { this.updateBreadcrumbs(path_segments); } }) + } else if (path_segments.includes('faq') && path_segments.length > 2) { //In in an FAQ page, but not the main FAQ page + //Remove all segements after 'faq' and before the last segment (which is the question) + let faqIndex = path_segments.indexOf('faq'); + let modifiedSegments = [...path_segments]; + modifiedSegments.splice(faqIndex + 1, modifiedSegments.length - faqIndex - 2); + this.updateBreadcrumbs(modifiedSegments); } else { this.updateBreadcrumbs(path_segments); } diff --git a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.html b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.html index 7f45b04..bb5da43 100644 --- a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.html +++ b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.html @@ -5,7 +5,7 @@

Icon Library

Browse the Reactome icon library. Icons are used in Enhanced High Level Diagrams (EHLDs) - to represent biological entities and concepts. + to represent biological entities and concepts. Or take a look at the Figma!

@@ -90,7 +90,7 @@

Icon Library

@for (icon of icons; track icon.stId) { diff --git a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.scss b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.scss index be665b2..7412260 100644 --- a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.scss +++ b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.scss @@ -31,6 +31,11 @@ margin: 0; max-width: 640px; } + + .figma-link { + text-decoration: underline; + cursor: pointer; + } } // Search bar diff --git a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.ts b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.ts index 5f961e2..d5d75d0 100644 --- a/projects/website-angular/src/app/community/icon-lib/icon-lib.component.ts +++ b/projects/website-angular/src/app/community/icon-lib/icon-lib.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { Subject } from 'rxjs'; import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { PageLayoutComponent } from '../../page-layout/page-layout.component'; @@ -49,7 +49,7 @@ interface ParsedReference { @Component({ selector: 'app-icon-lib', - imports: [PageLayoutComponent], + imports: [PageLayoutComponent, RouterLink], templateUrl: './icon-lib.component.html', styleUrl: './icon-lib.component.scss' }) diff --git a/projects/website-angular/src/app/search/search.component.html b/projects/website-angular/src/app/search/search.component.html index b77b750..512355f 100644 --- a/projects/website-angular/src/app/search/search.component.html +++ b/projects/website-angular/src/app/search/search.component.html @@ -281,9 +281,33 @@

{{ group.typeName }} ({{ group.entriesCount }})

@for (entry of group.entries; track entry.dbId) {
- - {{ entry.stId }} + @if (entry.deleted) { + delete + + @if (entry.replacementStIds?.length) { + ⇒ + + {{ entry.replacementStIds[0] }} + + } + + } @else { + + + {{ entry.stId }} + } +
+ @if (entry.deleted && entry.date) { +
+ Deleted on + {{ entry.date | date:'MMM d, y, h:mm:ss a' }} +
+ } @if (entry.species?.length) {
@for (sp of entry.species; track sp; let last = $last) { diff --git a/projects/website-angular/src/app/search/search.component.scss b/projects/website-angular/src/app/search/search.component.scss index ed74920..b512da3 100644 --- a/projects/website-angular/src/app/search/search.component.scss +++ b/projects/website-angular/src/app/search/search.component.scss @@ -73,6 +73,20 @@ $border-radius: 8px; align-items: flex-start; } +.deleted { + text-decoration: line-through; + color: #555; +} + +.deleted-icon { + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + color: var(--error); +} + + // Facet sidebar .facet-sidebar { width: 260px; diff --git a/projects/website-angular/src/app/search/search.component.ts b/projects/website-angular/src/app/search/search.component.ts index 8f8abb4..6d10fc0 100644 --- a/projects/website-angular/src/app/search/search.component.ts +++ b/projects/website-angular/src/app/search/search.component.ts @@ -15,11 +15,13 @@ import { SearchFilters, FacetCount, } from '../../services/search.service'; +import { DatePipe } from '@angular/common'; +import { MatIcon } from "@angular/material/icon"; @Component({ selector: 'app-search', standalone: true, - imports: [PageLayoutComponent, TileComponent, RouterLink, SearchBarComponent, FormsModule], + imports: [PageLayoutComponent, TileComponent, RouterLink, SearchBarComponent, FormsModule, DatePipe, MatIcon], templateUrl: './search.component.html', styleUrl: './search.component.scss', }) @@ -199,9 +201,33 @@ export class SearchComponent implements OnInit, OnDestroy, AfterViewInit { this.hasNoResults = false; } else { // Successful API response - check if we have results - this.results = results as SearchResult; + const res = results as SearchResult; + const hasNonDeleted = res.results?.some(group => + group.entries.some(e => !e.deleted) + ); + res.results = res.results?.map(group => { + const entries = hasNonDeleted + ? group.entries.filter(e => !e.deleted) + : group.entries; + + return { + ...group, + entries, + entriesCount: entries.length + }; + }).filter(group => group.entries.length > 0) || []; + res.numberOfMatches = res.results.reduce( + (sum, g) => sum + g.entries.length, + 0 + ); + this.results = res; this.facets = facets; - this.totalPages = Math.ceil(((results as SearchResult).numberOfMatches || 0) / this.pageSize); + this.totalPages = this.totalPages = Math.max( + ...(results.results || []).map(group => + Math.ceil((group.entriesCount || 0) / this.pageSize) + ), + 0 + ); this.hasNoResults = ((results as SearchResult).numberOfMatches || 0) === 0; this.error = ''; } @@ -233,7 +259,7 @@ export class SearchComponent implements OnInit, OnDestroy, AfterViewInit { get allEntries(): SearchEntry[] { if (!this.results?.results) return []; - return this.results.results.flatMap(g => g.entries); + return this.results.results.flatMap(g => this.filterDeletedEntries(g.entries)); } toggleFacet(category: string, value: string): void { @@ -272,6 +298,17 @@ export class SearchComponent implements OnInit, OnDestroy, AfterViewInit { this.advancedMode = !this.advancedMode; } + private filterDeletedEntries(entries: SearchEntry[]): SearchEntry[] { + if (!entries?.length) return []; + + const nonDeleted = entries.filter(e => !e.deleted); + if (nonDeleted.length > 0) { + return nonDeleted; + } + + return entries; + } + private updateQueryParams(params: Record): void { this.router.navigate([], { relativeTo: this.route, diff --git a/projects/website-angular/src/scripts/generate-index.ts b/projects/website-angular/src/scripts/generate-index.ts index 61ffa4e..bbdc40e 100644 --- a/projects/website-angular/src/scripts/generate-index.ts +++ b/projects/website-angular/src/scripts/generate-index.ts @@ -5,6 +5,156 @@ import { SiteSearchIndexItem } from '../types/site-search'; import parseFrontmatter from '../utils/parseFrontmatter'; import truncateHtml from '../utils/truncateHtml'; +/** + * Strip markdown/MDX syntax to produce plain text for search indexing + */ +function stripMarkdown(md: string): string { + return md + .replace(/^---[\s\S]*?---\n?/, '') // frontmatter + .replace(/import\s+.*?from\s+['"].*?['"]\s*;?\n?/g, '') // ESM imports + .replace(/<[^>]+>/g, '') // HTML/JSX tags + .replace(/!\[.*?\]\(.*?\)/g, '') // images + .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') // links → text + .replace(/#{1,6}\s+/g, '') // headings + .replace(/(\*{1,3}|_{1,3})(.*?)\1/g, '$2') // bold/italic + .replace(/`{1,3}[^`]*`{1,3}/g, '') // inline/block code + .replace(/>\s?/gm, '') // blockquotes + .replace(/[-*+]\s+/gm, '') // list markers + .replace(/\d+\.\s+/gm, '') // ordered list markers + .replace(/\n{2,}/g, '\n') // collapse blank lines + .replace(/\s+/g, ' ') // normalize whitespace + .trim(); +} + +/** + * Recursively find all .mdx/.md files in a directory + */ +function findAllMdxFiles(dir: string): string[] { + const results: string[] = []; + if (!fs.existsSync(dir)) return results; + + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...findAllMdxFiles(fullPath)); + } else if (entry.name.endsWith('.mdx') || entry.name.endsWith('.md')) { + // Skip index.json artifacts + if (entry.name !== 'index.json') { + results.push(fullPath); + } + } + } + return results; +} + +/** + * Map a file path to its site URL + * e.g. content/about/what-is-reactome.mdx → /about/what-is-reactome + * content/about/news/article-1.mdx → /about/news/article-1 + * content/documentation/dev/index.mdx → /documentation/dev + */ +function filePathToUrl(filePath: string, contentRoot: string): string { + let relative = path.relative(contentRoot, filePath); + // Remove extension + relative = relative.replace(/\.(mdx|md)$/, ''); + // Remove trailing /index + relative = relative.replace(/\/index$/, ''); + // Convert to URL + return '/' + relative.replace(/\\/g, '/'); +} + +/** + * Infer a human-readable category from the top-level directory + */ +function inferCategory(url: string): string { + const categoryMap: Record = { + about: 'About', + content: 'Content', + documentation: 'Documentation', + community: 'Community', + tools: 'Tools', + }; + const topDir = url.split('/')[1] || ''; + // Special sub-categories + if (url.startsWith('/about/news/')) return 'News'; + if (url.startsWith('/content/reactome-research-spotlight/')) + return 'Research Spotlight'; + return categoryMap[topDir] || 'Other'; +} + +/** + * Generate a consolidated site search index covering all content + */ +function generateSiteSearchIndex(): void { + const contentRoot = path.resolve( + process.cwd(), + 'projects', + 'website-angular', + 'content' + ); + + if (!fs.existsSync(contentRoot)) { + console.warn('Content directory not found:', contentRoot); + return; + } + + const allFiles = findAllMdxFiles(contentRoot); + const items: SiteSearchIndexItem[] = []; + const seenUrls = new Set(); + let nextId = 1; + + for (const filePath of allFiles) { + const raw = fs.readFileSync(filePath, 'utf-8'); + const { frontmatter, body } = parseFrontmatter(raw); + + const url = filePathToUrl(filePath, contentRoot); + + // Skip duplicates (e.g. collaboration.mdx and collaboration/index.mdx) + if (seenUrls.has(url)) continue; + seenUrls.add(url); + const title = + (frontmatter['title'] as string) || + path + .basename(filePath) + .replace(/\.(mdx|md)$/, '') + .replace(/-/g, ' '); + const category = (frontmatter['category'] as string) + ? inferCategory(url) + : inferCategory(url); + const plainBody = stripMarkdown(body); + const excerpt = + plainBody.slice(0, 200) + (plainBody.length > 200 ? '...' : ''); + + items.push({ + id: nextId++, + title, + category, + url, + body: plainBody, + excerpt, + date: (frontmatter['date'] as string) || undefined, + }); + } + + // Write to public assets so it can be fetched at runtime + const outputDir = path.resolve( + process.cwd(), + 'projects', + 'website-angular', + 'public' + ); + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + const outputPath = path.join(outputDir, 'site-search-index.json'); + fs.writeFileSync(outputPath, JSON.stringify(items)); + // console.log( + // `Site search index generated: ${items.length} entries → ${outputPath}` + // ); +} + function loadNewsArticlesFromDir(dir: string): ArticleIndexItem[] { if (!fs.existsSync(dir)) return []; @@ -19,9 +169,7 @@ function loadNewsArticlesFromDir(dir: string): ArticleIndexItem[] { const { frontmatter, body } = parseFrontmatter(content); return { - title: - frontmatter['title'] || - filename.replace(/\.(mdx|md)$/, ''), + title: frontmatter['title'] || filename.replace(/\.(mdx|md)$/, ''), author: frontmatter['author'] || undefined, excerpt: truncateHtml(body || '', 50), date: frontmatter['date'] || new Date().toISOString(), @@ -36,11 +184,7 @@ function loadNewsArticlesFromDir(dir: string): ArticleIndexItem[] { : frontmatter['tags'], } as ArticleIndexItem; }) - .sort( - (a, b) => - new Date(b.date).getTime() - - new Date(a.date).getTime() - ); + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); } function buildRecursiveIndex(dir: string): any { @@ -73,10 +217,7 @@ function buildRecursiveIndex(dir: string): any { /** * Generate a JSON file with optional recursive indexing */ -function generateIndex( - directories: string[], - recursive: boolean = true -): void { +function generateIndex(directories: string[], recursive: boolean = true): void { const outputDir = path.resolve(process.cwd(), ...directories); if (!fs.existsSync(outputDir)) { @@ -94,5 +235,15 @@ function generateIndex( // Run on module load generateIndex(['projects', 'website-angular', 'content', 'about', 'news']); -generateIndex(['projects', 'website-angular', 'content', 'content', 'reactome-research-spotlight']); -generateIndex(['projects', 'website-angular', 'content', 'documentation', 'faq'], true); +generateIndex([ + 'projects', + 'website-angular', + 'content', + 'content', + 'reactome-research-spotlight', +]); +generateIndex( + ['projects', 'website-angular', 'content', 'documentation', 'faq'], + true +); +generateSiteSearchIndex(); diff --git a/projects/website-angular/src/server.ts b/projects/website-angular/src/server.ts index cf38c8c..695e5a1 100644 --- a/projects/website-angular/src/server.ts +++ b/projects/website-angular/src/server.ts @@ -75,7 +75,7 @@ function run(): void { // Start up the Node server const server = app(); server.listen(port, () => { - console.log(`Node Express server listening on http://localhost:${port}`); + // console.log(`Node Express server listening on http://localhost:${port}`); }); } diff --git a/projects/website-angular/src/services/search.service.ts b/projects/website-angular/src/services/search.service.ts index 2f51072..0c35f90 100644 --- a/projects/website-angular/src/services/search.service.ts +++ b/projects/website-angular/src/services/search.service.ts @@ -16,6 +16,8 @@ export interface SearchEntry { referenceIdentifier: string; databaseName: string; referenceURL: string; + deleted: boolean; + date: number; } export interface ResultGroup {