@memberjunction/ng-search 6.1.3 → 6.2.0-edge.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/search-results.component.js +3 -3
- package/dist/lib/search-results.component.js.map +1 -1
- package/dist/lib/search-suggest.component.js +3 -3
- package/dist/lib/search-suggest.component.js.map +1 -1
- package/dist/lib/search-types.d.ts +4 -0
- package/dist/lib/search-types.d.ts.map +1 -1
- package/dist/lib/search-types.js.map +1 -1
- package/dist/lib/search.service.d.ts.map +1 -1
- package/dist/lib/search.service.js +48 -13
- package/dist/lib/search.service.js.map +1 -1
- package/package.json +9 -9
|
@@ -373,7 +373,7 @@ function SearchResultsComponent_ng_template_3_Conditional_14_Template(rf, ctx) {
|
|
|
373
373
|
i0.ɵɵadvance();
|
|
374
374
|
i0.ɵɵconditional(result_r12.Snippet ? 1 : -1);
|
|
375
375
|
i0.ɵɵadvance(5);
|
|
376
|
-
i0.ɵɵtextInterpolate(result_r12.EntityName);
|
|
376
|
+
i0.ɵɵtextInterpolate(result_r12.EntityDisplayName || result_r12.EntityName);
|
|
377
377
|
i0.ɵɵadvance(4);
|
|
378
378
|
i0.ɵɵtextInterpolate(result_r12.RecordID);
|
|
379
379
|
i0.ɵɵadvance(4);
|
|
@@ -418,7 +418,7 @@ function SearchResultsComponent_ng_template_3_Template(rf, ctx) { if (rf & 1) {
|
|
|
418
418
|
i0.ɵɵadvance(2);
|
|
419
419
|
i0.ɵɵproperty("innerHTML", ctx_r0.HighlightMatch(result_r12.RecordName || result_r12.Title), i0.ɵɵsanitizeHtml);
|
|
420
420
|
i0.ɵɵadvance();
|
|
421
|
-
i0.ɵɵproperty("innerHTML", ctx_r0.HighlightMatch(result_r12.EntityName), i0.ɵɵsanitizeHtml);
|
|
421
|
+
i0.ɵɵproperty("innerHTML", ctx_r0.HighlightMatch(result_r12.EntityDisplayName || result_r12.EntityName), i0.ɵɵsanitizeHtml);
|
|
422
422
|
i0.ɵɵadvance();
|
|
423
423
|
i0.ɵɵconditional(result_r12.Tags && result_r12.Tags.length > 0 ? 7 : -1);
|
|
424
424
|
i0.ɵɵadvance();
|
|
@@ -687,7 +687,7 @@ export class SearchResultsComponent extends BaseAngularComponent {
|
|
|
687
687
|
}
|
|
688
688
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SearchResultsComponent, [{
|
|
689
689
|
type: Component,
|
|
690
|
-
args: [{ standalone: false, selector: 'mj-search-results', template: "@if (IsLoading) {\n <div class=\"results-loading\">\n <mj-loading text=\"Searching...\" size=\"medium\"></mj-loading>\n </div>\n} @else if ((DisplayMode === 'flat' ? FlatResults.length : ResultGroups.length) === 0 && TotalCount === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Title=\"No results to display\" />\n} @else {\n <!-- Results summary -->\n @if (ShowSummary) {\n <div class=\"results-summary\">\n <span>{{ TotalCount }} results</span>\n @if (ElapsedMs > 0) {\n <span class=\"results-summary-time\"> in {{ FormatTime(ElapsedMs) }}</span>\n }\n </div>\n }\n\n @if (DisplayMode === 'flat') {\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 FLAT BLENDED LIST (sorted by score) \u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"results-scroll-area\">\n @for (result of PagedResults; track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n </div>\n\n <!-- Google-style pagination (sticky at bottom) -->\n @if (TotalPages > 1) {\n <div class=\"results-pager\">\n <button class=\"pager-btn pager-prev\" [disabled]=\"CurrentPage === 1\"\n (click)=\"GoToPage(CurrentPage - 1)\" aria-label=\"Previous page\">\n <i class=\"fa-solid fa-chevron-left\"></i>\n </button>\n @for (p of PageNumbers; track $index) {\n @if (p === -1) {\n <span class=\"pager-ellipsis\">...</span>\n } @else {\n <button class=\"pager-btn pager-num\" [class.pager-active]=\"p === CurrentPage\"\n (click)=\"GoToPage(p)\">{{ p }}</button>\n }\n }\n <button class=\"pager-btn pager-next\" [disabled]=\"CurrentPage === TotalPages\"\n (click)=\"GoToPage(CurrentPage + 1)\" aria-label=\"Next page\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n }\n } @else {\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 GROUPED BY SOURCE TYPE \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @for (group of ResultGroups; track group.SourceType) {\n <div class=\"results-group\">\n <div class=\"results-group-header\" (click)=\"ToggleGroup(group.SourceType)\">\n @if (ShowSourceIcons) {\n <i [class]=\"group.Icon + ' results-group-icon'\"></i>\n }\n <span class=\"results-group-label\">{{ group.Label }}</span>\n <span class=\"results-group-count\">({{ group.TotalCount }})</span>\n <i class=\"fa-solid results-group-chevron\"\n [class.fa-chevron-down]=\"!IsGroupExpanded(group.SourceType)\"\n [class.fa-chevron-up]=\"IsGroupExpanded(group.SourceType)\"></i>\n </div>\n\n @for (result of GetVisibleResults(group); track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n\n @if (HasMoreResults(group)) {\n <button class=\"results-show-more\" (click)=\"ToggleGroup(group.SourceType)\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n Show {{ GetHiddenCount(group) }} more results\n </button>\n }\n </div>\n }\n }\n}\n\n<!-- \u2550\u2550\u2550 Shared result card template \u2550\u2550\u2550 -->\n<ng-template #resultCardTpl let-result>\n <div class=\"result-card\" [class.result-card-expanded]=\"IsExpanded(result.ID)\">\n <!-- Main row -->\n <div class=\"result-card-main\" (click)=\"ToggleExpand(result)\">\n <div class=\"result-card-icon-wrap\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"result-card-body\">\n <div class=\"result-card-title\" [innerHTML]=\"HighlightMatch(result.RecordName || result.Title)\"></div>\n <div class=\"result-card-entity\" [innerHTML]=\"HighlightMatch(result.EntityName)\"></div>\n @if (result.Tags && result.Tags.length > 0) {\n <div class=\"result-card-tags\">\n @for (tag of result.Tags.slice(0, 5); track tag) {\n <span class=\"result-tag-chip\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n @if (result.Tags.length > 5) {\n <span class=\"result-tag-more\">+{{ result.Tags.length - 5 }}</span>\n }\n </div>\n }\n </div>\n @if (ShowScores) {\n <div class=\"result-card-score\">\n <div class=\"score-circle\" [class.score-high]=\"result.Score >= 0.40\" [class.score-medium]=\"result.Score >= 0.15 && result.Score < 0.40\" [class.score-low]=\"result.Score < 0.15\">\n <span class=\"score-value\">{{ FormatScore(result.Score) }}</span>\n </div>\n </div>\n }\n <div class=\"result-card-actions\">\n <button class=\"result-open-btn\" (click)=\"OnOpenRecord(result, $event)\"\n [title]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\"\n [attr.aria-label]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\">\n <i [class]=\"result.ResultType === 'storage-file' ? 'fa-solid fa-external-link' : 'fa-solid fa-arrow-up-right-from-square'\"></i>\n </button>\n <button class=\"result-expand-btn\" (click)=\"ToggleExpand(result, $event)\" title=\"Expand details\" aria-label=\"Expand details\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsExpanded(result.ID)\"\n [class.fa-chevron-up]=\"IsExpanded(result.ID)\"></i>\n </button>\n </div>\n </div>\n\n <!-- Expanded detail -->\n @if (IsExpanded(result.ID)) {\n <div class=\"result-card-detail\">\n @if (result.Snippet) {\n <div class=\"detail-section\">\n <span class=\"detail-label\">Preview</span>\n <p class=\"detail-snippet\" [innerHTML]=\"HighlightMatch(result.Snippet)\"></p>\n </div>\n }\n <div class=\"detail-meta\">\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>{{ result.EntityName }}</span>\n </div>\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-fingerprint\"></i>\n <span class=\"detail-record-id\">{{ result.RecordID }}</span>\n </div>\n <!-- SR-1: Show overall score consistently -->\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-signal\"></i>\n <span>Relevance: {{ FormatScore(result.Score) }}</span>\n </div>\n @if (result.ScoreBreakdown) {\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n <span>\n @if (result.ScoreBreakdown.Vector) { Vector: {{ FormatScore(result.ScoreBreakdown.Vector) }} }\n @if (result.ScoreBreakdown.FullText) { FTS: {{ FormatScore(result.ScoreBreakdown.FullText) }} }\n </span>\n </div>\n }\n </div>\n @if (ShowTags && result.Tags.length > 0) {\n <div class=\"detail-tags\">\n @for (tag of result.Tags; track tag) {\n <span class=\"result-card-tag\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n </div>\n }\n @if (GetResolvedLinks(result).length > 0) {\n <div class=\"detail-links\">\n @for (link of GetResolvedLinks(result); track link.Value) {\n @if (link.Type === 'URL') {\n <a [href]=\"link.Value\" target=\"_blank\" class=\"detail-link\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n {{ TruncateUrl(link.Value) }}\n </a>\n } @else if (link.Type === 'Email') {\n <a [href]=\"'mailto:' + link.Value\" class=\"detail-link detail-link-email\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ link.Value }}\n </a>\n }\n }\n </div>\n }\n <div class=\"detail-actions\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecord(result, $event)\">\n @if (result.ResultType === 'storage-file') {\n <i class=\"fa-solid fa-external-link\"></i> Open File\n } @else {\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Open Record\n }\n </button>\n <button class=\"detail-similar-btn\" (click)=\"OnMoreLikeThis(result, $event)\">\n <i class=\"fa-solid fa-shuffle\"></i> See Similar Items\n </button>\n </div>\n </div>\n }\n </div>\n</ng-template>\n", styles: ["/* Search Results Component */\n\n:host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n}\n\n.results-scroll-area {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n.results-loading {\n display: flex;\n justify-content: center;\n padding: 3rem 1rem;\n}\n\n.results-summary {\n padding: 0.75rem 1rem;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.results-summary-time {\n color: var(--mj-text-muted);\n}\n\n/* Group Header */\n.results-group {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.results-group-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.results-group-header:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.results-group-icon {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n.results-group-label {\n font-weight: 600;\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n flex: 1;\n}\n\n.results-group-count {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.results-group-chevron {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n transition: transform 0.2s ease;\n}\n\n/* Result Card \u2014 expandable */\n.result-card {\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n margin: 0 0 0.35rem 1.5rem;\n overflow: hidden;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n\n.result-card:hover {\n border-color: var(--mj-border-default);\n}\n\n.result-card-expanded {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.result-card-main {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.65rem 0.85rem;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.result-card-main:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.result-card-icon-wrap {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.8rem;\n flex-shrink: 0;\n}\n\n.result-card-body {\n flex: 1;\n min-width: 0;\n}\n\n.result-card-title {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.result-card-entity {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n.result-card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-top: 4px;\n}\n\n.result-tag-chip {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 10px;\n font-size: 0.65rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-border-default));\n}\n\n.result-tag-more {\n display: inline-block;\n padding: 1px 6px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.result-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n}\n\n.result-open-btn,\n.result-expand-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 11px;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.result-open-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.result-expand-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Expanded detail panel */\n.result-card-detail {\n padding: 0.75rem 1rem 0.85rem 3.5rem;\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n}\n\n.detail-section {\n margin-bottom: 0.65rem;\n}\n\n.detail-label {\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-bottom: 0.25rem;\n display: block;\n}\n\n.detail-snippet {\n margin: 0;\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n margin-bottom: 0.65rem;\n}\n\n.detail-meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.detail-meta-item i {\n font-size: 0.65rem;\n}\n\n.detail-record-id {\n font-family: monospace;\n font-size: 0.7rem;\n max-width: 180px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.detail-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-bottom: 0.65rem;\n}\n\n.detail-links {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-bottom: 8px;\n}\n\n.detail-link {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 0.72rem;\n color: var(--mj-text-link);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-border-default));\n text-decoration: none;\n transition: background 0.15s ease;\n}\n\n.detail-link:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n text-decoration: underline;\n}\n\n.detail-link i {\n font-size: 0.6rem;\n}\n\n.detail-actions {\n display: flex;\n gap: 8px;\n}\n\n.detail-open-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.detail-open-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.detail-similar-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.detail-similar-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.result-card-snippet {\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.result-card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n margin-top: 0.35rem;\n}\n\n.result-card-tag {\n display: inline-block;\n padding: 0.1rem 0.4rem;\n border-radius: 4px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n.result-card-tag-more {\n color: var(--mj-brand-primary);\n}\n\n.result-card-score {\n flex-shrink: 0;\n margin-top: 0.2rem;\n}\n\n.score-circle {\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n /* Default: neutral gray */\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n border: 2px solid color-mix(in srgb, var(--mj-text-muted) 25%, var(--mj-bg-surface));\n}\n.score-circle.score-high {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-success) 40%, var(--mj-bg-surface));\n}\n.score-circle.score-medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n}\n.score-circle.score-low {\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-text-muted) 25%, var(--mj-bg-surface));\n}\n\n.score-value {\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--mj-text-secondary);\n}\n.score-high .score-value {\n color: var(--mj-status-success-text, var(--mj-status-success));\n}\n.score-medium .score-value {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n}\n.score-low .score-value {\n color: var(--mj-text-muted);\n}\n\n/* Show More Button */\n.results-show-more {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.82rem;\n cursor: pointer;\n justify-content: center;\n transition: background 0.1s ease;\n}\n\n.results-show-more:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Google-style Pagination \u2014 pinned at bottom */\n.results-pager {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: 0.75rem 0;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.pager-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 32px;\n height: 32px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n padding: 0 6px;\n}\n\n.pager-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pager-btn:disabled {\n opacity: 0.35;\n cursor: default;\n}\n\n.pager-active {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.pager-active:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n color: var(--mj-text-inverse);\n}\n\n.pager-ellipsis {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n color: var(--mj-text-muted);\n font-size: 0.8rem;\n}\n\n.pager-prev i,\n.pager-next i {\n font-size: 0.7rem;\n}\n\n/* Search text highlighting */\n:host ::ng-deep .search-highlight,\n.search-highlight {\n background: color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n color: inherit;\n padding: 1px 2px;\n border-radius: 2px;\n}\n"] }]
|
|
690
|
+
args: [{ standalone: false, selector: 'mj-search-results', template: "@if (IsLoading) {\n <div class=\"results-loading\">\n <mj-loading text=\"Searching...\" size=\"medium\"></mj-loading>\n </div>\n} @else if ((DisplayMode === 'flat' ? FlatResults.length : ResultGroups.length) === 0 && TotalCount === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Title=\"No results to display\" />\n} @else {\n <!-- Results summary -->\n @if (ShowSummary) {\n <div class=\"results-summary\">\n <span>{{ TotalCount }} results</span>\n @if (ElapsedMs > 0) {\n <span class=\"results-summary-time\"> in {{ FormatTime(ElapsedMs) }}</span>\n }\n </div>\n }\n\n @if (DisplayMode === 'flat') {\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 FLAT BLENDED LIST (sorted by score) \u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"results-scroll-area\">\n @for (result of PagedResults; track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n </div>\n\n <!-- Google-style pagination (sticky at bottom) -->\n @if (TotalPages > 1) {\n <div class=\"results-pager\">\n <button class=\"pager-btn pager-prev\" [disabled]=\"CurrentPage === 1\"\n (click)=\"GoToPage(CurrentPage - 1)\" aria-label=\"Previous page\">\n <i class=\"fa-solid fa-chevron-left\"></i>\n </button>\n @for (p of PageNumbers; track $index) {\n @if (p === -1) {\n <span class=\"pager-ellipsis\">...</span>\n } @else {\n <button class=\"pager-btn pager-num\" [class.pager-active]=\"p === CurrentPage\"\n (click)=\"GoToPage(p)\">{{ p }}</button>\n }\n }\n <button class=\"pager-btn pager-next\" [disabled]=\"CurrentPage === TotalPages\"\n (click)=\"GoToPage(CurrentPage + 1)\" aria-label=\"Next page\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n }\n } @else {\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550 GROUPED BY SOURCE TYPE \u2550\u2550\u2550\u2550\u2550\u2550 -->\n @for (group of ResultGroups; track group.SourceType) {\n <div class=\"results-group\">\n <div class=\"results-group-header\" (click)=\"ToggleGroup(group.SourceType)\">\n @if (ShowSourceIcons) {\n <i [class]=\"group.Icon + ' results-group-icon'\"></i>\n }\n <span class=\"results-group-label\">{{ group.Label }}</span>\n <span class=\"results-group-count\">({{ group.TotalCount }})</span>\n <i class=\"fa-solid results-group-chevron\"\n [class.fa-chevron-down]=\"!IsGroupExpanded(group.SourceType)\"\n [class.fa-chevron-up]=\"IsGroupExpanded(group.SourceType)\"></i>\n </div>\n\n @for (result of GetVisibleResults(group); track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n\n @if (HasMoreResults(group)) {\n <button class=\"results-show-more\" (click)=\"ToggleGroup(group.SourceType)\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n Show {{ GetHiddenCount(group) }} more results\n </button>\n }\n </div>\n }\n }\n}\n\n<!-- \u2550\u2550\u2550 Shared result card template \u2550\u2550\u2550 -->\n<ng-template #resultCardTpl let-result>\n <div class=\"result-card\" [class.result-card-expanded]=\"IsExpanded(result.ID)\">\n <!-- Main row -->\n <div class=\"result-card-main\" (click)=\"ToggleExpand(result)\">\n <div class=\"result-card-icon-wrap\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"result-card-body\">\n <div class=\"result-card-title\" [innerHTML]=\"HighlightMatch(result.RecordName || result.Title)\"></div>\n <div class=\"result-card-entity\" [innerHTML]=\"HighlightMatch(result.EntityDisplayName || result.EntityName)\"></div>\n @if (result.Tags && result.Tags.length > 0) {\n <div class=\"result-card-tags\">\n @for (tag of result.Tags.slice(0, 5); track tag) {\n <span class=\"result-tag-chip\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n @if (result.Tags.length > 5) {\n <span class=\"result-tag-more\">+{{ result.Tags.length - 5 }}</span>\n }\n </div>\n }\n </div>\n @if (ShowScores) {\n <div class=\"result-card-score\">\n <div class=\"score-circle\" [class.score-high]=\"result.Score >= 0.40\" [class.score-medium]=\"result.Score >= 0.15 && result.Score < 0.40\" [class.score-low]=\"result.Score < 0.15\">\n <span class=\"score-value\">{{ FormatScore(result.Score) }}</span>\n </div>\n </div>\n }\n <div class=\"result-card-actions\">\n <button class=\"result-open-btn\" (click)=\"OnOpenRecord(result, $event)\"\n [title]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\"\n [attr.aria-label]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\">\n <i [class]=\"result.ResultType === 'storage-file' ? 'fa-solid fa-external-link' : 'fa-solid fa-arrow-up-right-from-square'\"></i>\n </button>\n <button class=\"result-expand-btn\" (click)=\"ToggleExpand(result, $event)\" title=\"Expand details\" aria-label=\"Expand details\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsExpanded(result.ID)\"\n [class.fa-chevron-up]=\"IsExpanded(result.ID)\"></i>\n </button>\n </div>\n </div>\n\n <!-- Expanded detail -->\n @if (IsExpanded(result.ID)) {\n <div class=\"result-card-detail\">\n @if (result.Snippet) {\n <div class=\"detail-section\">\n <span class=\"detail-label\">Preview</span>\n <p class=\"detail-snippet\" [innerHTML]=\"HighlightMatch(result.Snippet)\"></p>\n </div>\n }\n <div class=\"detail-meta\">\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>{{ result.EntityDisplayName || result.EntityName }}</span>\n </div>\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-fingerprint\"></i>\n <span class=\"detail-record-id\">{{ result.RecordID }}</span>\n </div>\n <!-- SR-1: Show overall score consistently -->\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-signal\"></i>\n <span>Relevance: {{ FormatScore(result.Score) }}</span>\n </div>\n @if (result.ScoreBreakdown) {\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n <span>\n @if (result.ScoreBreakdown.Vector) { Vector: {{ FormatScore(result.ScoreBreakdown.Vector) }} }\n @if (result.ScoreBreakdown.FullText) { FTS: {{ FormatScore(result.ScoreBreakdown.FullText) }} }\n </span>\n </div>\n }\n </div>\n @if (ShowTags && result.Tags.length > 0) {\n <div class=\"detail-tags\">\n @for (tag of result.Tags; track tag) {\n <span class=\"result-card-tag\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n </div>\n }\n @if (GetResolvedLinks(result).length > 0) {\n <div class=\"detail-links\">\n @for (link of GetResolvedLinks(result); track link.Value) {\n @if (link.Type === 'URL') {\n <a [href]=\"link.Value\" target=\"_blank\" class=\"detail-link\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n {{ TruncateUrl(link.Value) }}\n </a>\n } @else if (link.Type === 'Email') {\n <a [href]=\"'mailto:' + link.Value\" class=\"detail-link detail-link-email\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ link.Value }}\n </a>\n }\n }\n </div>\n }\n <div class=\"detail-actions\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecord(result, $event)\">\n @if (result.ResultType === 'storage-file') {\n <i class=\"fa-solid fa-external-link\"></i> Open File\n } @else {\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Open Record\n }\n </button>\n <button class=\"detail-similar-btn\" (click)=\"OnMoreLikeThis(result, $event)\">\n <i class=\"fa-solid fa-shuffle\"></i> See Similar Items\n </button>\n </div>\n </div>\n }\n </div>\n</ng-template>\n", styles: ["/* Search Results Component */\n\n:host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n flex: 1;\n}\n\n.results-scroll-area {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n.results-loading {\n display: flex;\n justify-content: center;\n padding: 3rem 1rem;\n}\n\n.results-summary {\n padding: 0.75rem 1rem;\n color: var(--mj-text-secondary);\n font-size: 0.85rem;\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.results-summary-time {\n color: var(--mj-text-muted);\n}\n\n/* Group Header */\n.results-group {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.results-group-header {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1rem;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.results-group-header:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.results-group-icon {\n color: var(--mj-brand-primary);\n font-size: 0.85rem;\n}\n\n.results-group-label {\n font-weight: 600;\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n flex: 1;\n}\n\n.results-group-count {\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.results-group-chevron {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n transition: transform 0.2s ease;\n}\n\n/* Result Card \u2014 expandable */\n.result-card {\n border: 1px solid var(--mj-border-subtle);\n border-radius: 8px;\n margin: 0 0 0.35rem 1.5rem;\n overflow: hidden;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n\n.result-card:hover {\n border-color: var(--mj-border-default);\n}\n\n.result-card-expanded {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.result-card-main {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.65rem 0.85rem;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.result-card-main:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.result-card-icon-wrap {\n width: 2rem;\n height: 2rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.8rem;\n flex-shrink: 0;\n}\n\n.result-card-body {\n flex: 1;\n min-width: 0;\n}\n\n.result-card-title {\n color: var(--mj-text-primary);\n font-weight: 600;\n font-size: 0.85rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.result-card-entity {\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n.result-card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-top: 4px;\n}\n\n.result-tag-chip {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 10px;\n font-size: 0.65rem;\n font-weight: 500;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-border-default));\n}\n\n.result-tag-more {\n display: inline-block;\n padding: 1px 6px;\n font-size: 0.6rem;\n color: var(--mj-text-muted);\n}\n\n.result-card-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n}\n\n.result-open-btn,\n.result-expand-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-muted);\n font-size: 11px;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.result-open-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.result-expand-btn:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n/* Expanded detail panel */\n.result-card-detail {\n padding: 0.75rem 1rem 0.85rem 3.5rem;\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n}\n\n.detail-section {\n margin-bottom: 0.65rem;\n}\n\n.detail-label {\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n margin-bottom: 0.25rem;\n display: block;\n}\n\n.detail-snippet {\n margin: 0;\n font-size: 0.82rem;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.75rem;\n margin-bottom: 0.65rem;\n}\n\n.detail-meta-item {\n display: flex;\n align-items: center;\n gap: 5px;\n font-size: 0.75rem;\n color: var(--mj-text-muted);\n}\n\n.detail-meta-item i {\n font-size: 0.65rem;\n}\n\n.detail-record-id {\n font-family: monospace;\n font-size: 0.7rem;\n max-width: 180px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.detail-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin-bottom: 0.65rem;\n}\n\n.detail-links {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-bottom: 8px;\n}\n\n.detail-link {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 0.72rem;\n color: var(--mj-text-link);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-border-default));\n text-decoration: none;\n transition: background 0.15s ease;\n}\n\n.detail-link:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n text-decoration: underline;\n}\n\n.detail-link i {\n font-size: 0.6rem;\n}\n\n.detail-actions {\n display: flex;\n gap: 8px;\n}\n\n.detail-open-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: 1px solid var(--mj-brand-primary);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.78rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.detail-open-btn:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n}\n\n.detail-similar-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: transparent;\n color: var(--mj-text-secondary);\n font-size: 0.78rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s;\n}\n\n.detail-similar-btn:hover {\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, var(--mj-bg-surface));\n}\n\n.result-card-snippet {\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n line-height: 1.4;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.result-card-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n margin-top: 0.35rem;\n}\n\n.result-card-tag {\n display: inline-block;\n padding: 0.1rem 0.4rem;\n border-radius: 4px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n font-size: 0.7rem;\n}\n\n.result-card-tag-more {\n color: var(--mj-brand-primary);\n}\n\n.result-card-score {\n flex-shrink: 0;\n margin-top: 0.2rem;\n}\n\n.score-circle {\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n /* Default: neutral gray */\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n border: 2px solid color-mix(in srgb, var(--mj-text-muted) 25%, var(--mj-bg-surface));\n}\n.score-circle.score-high {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-success) 40%, var(--mj-bg-surface));\n}\n.score-circle.score-medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n}\n.score-circle.score-low {\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-text-muted) 25%, var(--mj-bg-surface));\n}\n\n.score-value {\n font-size: 0.7rem;\n font-weight: 700;\n color: var(--mj-text-secondary);\n}\n.score-high .score-value {\n color: var(--mj-status-success-text, var(--mj-status-success));\n}\n.score-medium .score-value {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n}\n.score-low .score-value {\n color: var(--mj-text-muted);\n}\n\n/* Show More Button */\n.results-show-more {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n width: 100%;\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.82rem;\n cursor: pointer;\n justify-content: center;\n transition: background 0.1s ease;\n}\n\n.results-show-more:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n/* Google-style Pagination \u2014 pinned at bottom */\n.results-pager {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: 0.75rem 0;\n flex-shrink: 0;\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface);\n}\n\n.pager-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 32px;\n height: 32px;\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 0.82rem;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n padding: 0 6px;\n}\n\n.pager-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n.pager-btn:disabled {\n opacity: 0.35;\n cursor: default;\n}\n\n.pager-active {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.pager-active:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n color: var(--mj-text-inverse);\n}\n\n.pager-ellipsis {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n color: var(--mj-text-muted);\n font-size: 0.8rem;\n}\n\n.pager-prev i,\n.pager-next i {\n font-size: 0.7rem;\n}\n\n/* Search text highlighting */\n:host ::ng-deep .search-highlight,\n.search-highlight {\n background: color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n color: inherit;\n padding: 1px 2px;\n border-radius: 2px;\n}\n"] }]
|
|
691
691
|
}], null, { ResultGroups: [{
|
|
692
692
|
type: Input
|
|
693
693
|
}], FlatResults: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-results.component.js","sourceRoot":"","sources":["../../src/lib/search-results.component.ts","../../src/lib/search-results.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Search Results Component\n *\n * Displays search results grouped by source type with expandable sections.\n * Used both within the search overlay and as a standalone results view\n * in the Knowledge Hub search dashboard.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n Output,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport { SearchResultItem, SearchResultGroup, SearchResultSelectedEvent } from './search-types';\nimport { EscapeHTML, HighlightSearchMatches } from '@memberjunction/global';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n\n@Component({\n standalone: false,\n selector: 'mj-search-results',\n templateUrl: './search-results.component.html',\n styleUrls: ['./search-results.component.css']\n})\nexport class SearchResultsComponent extends BaseAngularComponent {\n private cdr = inject(ChangeDetectorRef);\n\n /** Grouped search results to display (used when DisplayMode is 'grouped') */\n @Input() ResultGroups: SearchResultGroup[] = [];\n\n /** Flat list of all results sorted by score (used when DisplayMode is 'flat') */\n @Input() FlatResults: SearchResultItem[] = [];\n\n /** Display mode: 'flat' for blended list sorted by score, 'grouped' for source-type groups */\n @Input() DisplayMode: 'flat' | 'grouped' = 'flat';\n\n /** Whether results are currently loading */\n @Input() IsLoading = false;\n\n /** Total result count */\n @Input() TotalCount = 0;\n\n /** Elapsed search time in ms */\n @Input() ElapsedMs = 0;\n\n /** Maximum results per group before \"Show more\" (grouped mode only) */\n @Input() MaxPerGroup = 5;\n\n /** Results per page in flat mode */\n @Input() PageSize = 10;\n\n /** Whether to show the results summary line (count + time). Set false when parent provides its own header. */\n @Input() ShowSummary = true;\n\n /** Whether to show score badges */\n @Input() ShowScores = true;\n\n /** Whether to show tags */\n @Input() ShowTags = true;\n\n /** Whether to show source type icons */\n @Input() ShowSourceIcons = true;\n\n /** Text to highlight within result cards (e.g. from a client-side filter) */\n @Input() HighlightText = '';\n\n @Output() ResultSelected = new EventEmitter<SearchResultSelectedEvent>();\n\n /** Emitted when user clicks \"Open Record\" — parent handles navigation */\n @Output() OpenRecordRequested = new EventEmitter<SearchResultItem>();\n\n /** Emitted when user clicks \"See Similar Items\" — parent runs a \"more like this\" search */\n @Output() MoreLikeThisRequested = new EventEmitter<SearchResultItem>();\n\n /** Current page (1-based) for flat mode pagination */\n public CurrentPage = 1;\n\n /** Tracks which groups are expanded */\n public ExpandedGroups = new Set<string>();\n\n /** Set of expanded result card IDs — multiple can be open simultaneously */\n public ExpandedResultIDs = new Set<string>();\n\n // ── Flat mode pagination ──\n\n /** Get results for the current page in flat mode */\n public get PagedResults(): SearchResultItem[] {\n const start = (this.CurrentPage - 1) * this.PageSize;\n return this.FlatResults.slice(start, start + this.PageSize);\n }\n\n /** Total number of pages */\n public get TotalPages(): number {\n return Math.ceil(this.FlatResults.length / this.PageSize);\n }\n\n /** Page numbers to display in the pager */\n public get PageNumbers(): number[] {\n const total = this.TotalPages;\n if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);\n\n const pages: number[] = [1];\n const start = Math.max(2, this.CurrentPage - 2);\n const end = Math.min(total - 1, this.CurrentPage + 2);\n if (start > 2) pages.push(-1); // ellipsis marker\n for (let i = start; i <= end; i++) pages.push(i);\n if (end < total - 1) pages.push(-1); // ellipsis marker\n pages.push(total);\n return pages;\n }\n\n /** Navigate to a specific page */\n public GoToPage(page: number): void {\n if (page < 1 || page > this.TotalPages) return;\n this.CurrentPage = page;\n this.cdr.detectChanges();\n }\n\n // ── Grouped mode ──\n\n /** Toggle group expansion */\n public ToggleGroup(sourceType: string): void {\n if (this.ExpandedGroups.has(sourceType)) {\n this.ExpandedGroups.delete(sourceType);\n } else {\n this.ExpandedGroups.add(sourceType);\n }\n this.cdr.detectChanges();\n }\n\n /** Whether a group is expanded */\n public IsGroupExpanded(sourceType: string): boolean {\n return this.ExpandedGroups.has(sourceType);\n }\n\n /** Get visible results for a group (respecting max per group unless expanded) */\n public GetVisibleResults(group: SearchResultGroup): SearchResultItem[] {\n if (this.IsGroupExpanded(group.SourceType)) {\n return group.Results;\n }\n return group.Results.slice(0, this.MaxPerGroup);\n }\n\n /** Whether a group has more results than shown */\n public HasMoreResults(group: SearchResultGroup): boolean {\n return group.Results.length > this.MaxPerGroup && !this.IsGroupExpanded(group.SourceType);\n }\n\n /** Get the count of hidden results */\n public GetHiddenCount(group: SearchResultGroup): number {\n return group.Results.length - this.MaxPerGroup;\n }\n\n /** Handle clicking a result */\n public OnResultClick(result: SearchResultItem, event: MouseEvent): void {\n this.ResultSelected.emit({\n Result: result,\n OpenInNewTab: event.metaKey || event.ctrlKey\n });\n }\n\n /** Toggle expand/collapse of a result card — multiple can be open */\n public ToggleExpand(result: SearchResultItem, event?: MouseEvent): void {\n if (event) event.stopPropagation();\n if (this.ExpandedResultIDs.has(result.ID)) {\n this.ExpandedResultIDs.delete(result.ID);\n } else {\n this.ExpandedResultIDs.add(result.ID);\n }\n this.cdr.detectChanges();\n }\n\n /** Check if a result card is expanded */\n public IsExpanded(resultID: string): boolean {\n return this.ExpandedResultIDs.has(resultID);\n }\n\n /** Handle \"Open Record\" button click */\n public OnOpenRecord(result: SearchResultItem, event: MouseEvent): void {\n event.stopPropagation();\n this.OpenRecordRequested.emit(result);\n }\n\n /** Handle \"See Similar Items\" button click — emits the result for a \"more like this\" search */\n public OnMoreLikeThis(result: SearchResultItem, event: MouseEvent): void {\n event.stopPropagation();\n this.MoreLikeThisRequested.emit(result);\n }\n\n /**\n * Wrap substrings matching HighlightText in <mark> tags for visual emphasis.\n * The input is regex-escaped so user text is treated as a literal string.\n */\n public HighlightMatch(text: string): string {\n return HighlightSearchMatches(text, this.HighlightText, 'search-highlight');\n }\n\n /** Format a score as a percentage */\n public FormatScore(score: number): string {\n return `${Math.round(score * 100)}%`;\n }\n\n /** Format elapsed time */\n public FormatTime(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n }\n\n /** Resolved link cache per result ID */\n private resolvedLinksCache = new Map<string, { Value: string; Label: string; Type: 'URL' | 'Email' }[]>();\n\n /**\n * Resolve URL and Email fields from the result's RawMetadata using entity ExtendedType metadata.\n * Returns clickable link entries with proper type classification.\n */\n public GetResolvedLinks(result: SearchResultItem): { Value: string; Label: string; Type: 'URL' | 'Email' }[] {\n if (this.resolvedLinksCache.has(result.ID)) {\n return this.resolvedLinksCache.get(result.ID)!;\n }\n\n const links: { Value: string; Label: string; Type: 'URL' | 'Email' }[] = [];\n\n if (result.RawMetadata) {\n try {\n const meta = JSON.parse(result.RawMetadata) as Record<string, unknown>;\n const linkFields = this.GetLinkFields(result.EntityName);\n\n for (const field of linkFields) {\n const value = meta[field.Name];\n if (value && typeof value === 'string' && value.trim().length > 0) {\n links.push({\n Value: value,\n Label: field.DisplayName,\n Type: field.Type\n });\n }\n }\n } catch { /* ignore parse errors */ }\n }\n\n // Fallback: extract URLs from snippet if no ExtendedType fields found\n if (links.length === 0) {\n const urls = this.ExtractUrls(result.Snippet);\n for (const url of urls) {\n links.push({ Value: url, Label: '', Type: 'URL' });\n }\n }\n\n this.resolvedLinksCache.set(result.ID, links);\n return links;\n }\n\n /** Extract URLs from snippet text */\n public ExtractUrls(snippet: string): string[] {\n if (!snippet) return [];\n const urlRegex = /https?:\\/\\/[^\\s,;)>\"']+/g;\n const matches = snippet.match(urlRegex);\n return matches ? [...new Set(matches)] : [];\n }\n\n /** Truncate a URL for display */\n public TruncateUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const path = parsed.pathname.length > 30\n ? parsed.pathname.substring(0, 30) + '...'\n : parsed.pathname;\n return parsed.hostname + path;\n } catch {\n return url.length > 50 ? url.substring(0, 50) + '...' : url;\n }\n }\n\n /** Cache of URL/Email field info per entity name */\n private linkFieldsCache = new Map<string, { Name: string; DisplayName: string; Type: 'URL' | 'Email' }[]>();\n\n /**\n * Get URL and Email fields for an entity using ExtendedType from entity metadata.\n * Returns fields with ExtendedType='URL' or ExtendedType='Email'.\n */\n public GetLinkFields(entityName: string): { Name: string; DisplayName: string; Type: 'URL' | 'Email' }[] {\n if (this.linkFieldsCache.has(entityName)) {\n return this.linkFieldsCache.get(entityName)!;\n }\n\n try {\n const md = this.ProviderToUse;\n const entityInfo = md.Entities.find(e => e.Name === entityName);\n if (!entityInfo) {\n this.linkFieldsCache.set(entityName, []);\n return [];\n }\n\n const linkFields = entityInfo.Fields\n .filter(f => f.ExtendedType === 'URL' || f.ExtendedType === 'Email')\n .map(f => ({\n Name: f.Name,\n DisplayName: f.DisplayNameOrName,\n Type: f.ExtendedType as 'URL' | 'Email'\n }));\n\n this.linkFieldsCache.set(entityName, linkFields);\n return linkFields;\n } catch {\n this.linkFieldsCache.set(entityName, []);\n return [];\n }\n }\n}\n","@if (IsLoading) {\n <div class=\"results-loading\">\n <mj-loading text=\"Searching...\" size=\"medium\"></mj-loading>\n </div>\n} @else if ((DisplayMode === 'flat' ? FlatResults.length : ResultGroups.length) === 0 && TotalCount === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Title=\"No results to display\" />\n} @else {\n <!-- Results summary -->\n @if (ShowSummary) {\n <div class=\"results-summary\">\n <span>{{ TotalCount }} results</span>\n @if (ElapsedMs > 0) {\n <span class=\"results-summary-time\"> in {{ FormatTime(ElapsedMs) }}</span>\n }\n </div>\n }\n\n @if (DisplayMode === 'flat') {\n <!-- ══════ FLAT BLENDED LIST (sorted by score) ══════ -->\n <div class=\"results-scroll-area\">\n @for (result of PagedResults; track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n </div>\n\n <!-- Google-style pagination (sticky at bottom) -->\n @if (TotalPages > 1) {\n <div class=\"results-pager\">\n <button class=\"pager-btn pager-prev\" [disabled]=\"CurrentPage === 1\"\n (click)=\"GoToPage(CurrentPage - 1)\" aria-label=\"Previous page\">\n <i class=\"fa-solid fa-chevron-left\"></i>\n </button>\n @for (p of PageNumbers; track $index) {\n @if (p === -1) {\n <span class=\"pager-ellipsis\">...</span>\n } @else {\n <button class=\"pager-btn pager-num\" [class.pager-active]=\"p === CurrentPage\"\n (click)=\"GoToPage(p)\">{{ p }}</button>\n }\n }\n <button class=\"pager-btn pager-next\" [disabled]=\"CurrentPage === TotalPages\"\n (click)=\"GoToPage(CurrentPage + 1)\" aria-label=\"Next page\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n }\n } @else {\n <!-- ══════ GROUPED BY SOURCE TYPE ══════ -->\n @for (group of ResultGroups; track group.SourceType) {\n <div class=\"results-group\">\n <div class=\"results-group-header\" (click)=\"ToggleGroup(group.SourceType)\">\n @if (ShowSourceIcons) {\n <i [class]=\"group.Icon + ' results-group-icon'\"></i>\n }\n <span class=\"results-group-label\">{{ group.Label }}</span>\n <span class=\"results-group-count\">({{ group.TotalCount }})</span>\n <i class=\"fa-solid results-group-chevron\"\n [class.fa-chevron-down]=\"!IsGroupExpanded(group.SourceType)\"\n [class.fa-chevron-up]=\"IsGroupExpanded(group.SourceType)\"></i>\n </div>\n\n @for (result of GetVisibleResults(group); track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n\n @if (HasMoreResults(group)) {\n <button class=\"results-show-more\" (click)=\"ToggleGroup(group.SourceType)\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n Show {{ GetHiddenCount(group) }} more results\n </button>\n }\n </div>\n }\n }\n}\n\n<!-- ═══ Shared result card template ═══ -->\n<ng-template #resultCardTpl let-result>\n <div class=\"result-card\" [class.result-card-expanded]=\"IsExpanded(result.ID)\">\n <!-- Main row -->\n <div class=\"result-card-main\" (click)=\"ToggleExpand(result)\">\n <div class=\"result-card-icon-wrap\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"result-card-body\">\n <div class=\"result-card-title\" [innerHTML]=\"HighlightMatch(result.RecordName || result.Title)\"></div>\n <div class=\"result-card-entity\" [innerHTML]=\"HighlightMatch(result.EntityName)\"></div>\n @if (result.Tags && result.Tags.length > 0) {\n <div class=\"result-card-tags\">\n @for (tag of result.Tags.slice(0, 5); track tag) {\n <span class=\"result-tag-chip\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n @if (result.Tags.length > 5) {\n <span class=\"result-tag-more\">+{{ result.Tags.length - 5 }}</span>\n }\n </div>\n }\n </div>\n @if (ShowScores) {\n <div class=\"result-card-score\">\n <div class=\"score-circle\" [class.score-high]=\"result.Score >= 0.40\" [class.score-medium]=\"result.Score >= 0.15 && result.Score < 0.40\" [class.score-low]=\"result.Score < 0.15\">\n <span class=\"score-value\">{{ FormatScore(result.Score) }}</span>\n </div>\n </div>\n }\n <div class=\"result-card-actions\">\n <button class=\"result-open-btn\" (click)=\"OnOpenRecord(result, $event)\"\n [title]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\"\n [attr.aria-label]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\">\n <i [class]=\"result.ResultType === 'storage-file' ? 'fa-solid fa-external-link' : 'fa-solid fa-arrow-up-right-from-square'\"></i>\n </button>\n <button class=\"result-expand-btn\" (click)=\"ToggleExpand(result, $event)\" title=\"Expand details\" aria-label=\"Expand details\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsExpanded(result.ID)\"\n [class.fa-chevron-up]=\"IsExpanded(result.ID)\"></i>\n </button>\n </div>\n </div>\n\n <!-- Expanded detail -->\n @if (IsExpanded(result.ID)) {\n <div class=\"result-card-detail\">\n @if (result.Snippet) {\n <div class=\"detail-section\">\n <span class=\"detail-label\">Preview</span>\n <p class=\"detail-snippet\" [innerHTML]=\"HighlightMatch(result.Snippet)\"></p>\n </div>\n }\n <div class=\"detail-meta\">\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>{{ result.EntityName }}</span>\n </div>\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-fingerprint\"></i>\n <span class=\"detail-record-id\">{{ result.RecordID }}</span>\n </div>\n <!-- SR-1: Show overall score consistently -->\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-signal\"></i>\n <span>Relevance: {{ FormatScore(result.Score) }}</span>\n </div>\n @if (result.ScoreBreakdown) {\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n <span>\n @if (result.ScoreBreakdown.Vector) { Vector: {{ FormatScore(result.ScoreBreakdown.Vector) }} }\n @if (result.ScoreBreakdown.FullText) { FTS: {{ FormatScore(result.ScoreBreakdown.FullText) }} }\n </span>\n </div>\n }\n </div>\n @if (ShowTags && result.Tags.length > 0) {\n <div class=\"detail-tags\">\n @for (tag of result.Tags; track tag) {\n <span class=\"result-card-tag\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n </div>\n }\n @if (GetResolvedLinks(result).length > 0) {\n <div class=\"detail-links\">\n @for (link of GetResolvedLinks(result); track link.Value) {\n @if (link.Type === 'URL') {\n <a [href]=\"link.Value\" target=\"_blank\" class=\"detail-link\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n {{ TruncateUrl(link.Value) }}\n </a>\n } @else if (link.Type === 'Email') {\n <a [href]=\"'mailto:' + link.Value\" class=\"detail-link detail-link-email\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ link.Value }}\n </a>\n }\n }\n </div>\n }\n <div class=\"detail-actions\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecord(result, $event)\">\n @if (result.ResultType === 'storage-file') {\n <i class=\"fa-solid fa-external-link\"></i> Open File\n } @else {\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Open Record\n }\n </button>\n <button class=\"detail-similar-btn\" (click)=\"OnMoreLikeThis(result, $event)\">\n <i class=\"fa-solid fa-shuffle\"></i> See Similar Items\n </button>\n </div>\n </div>\n }\n </div>\n</ng-template>\n"]}
|
|
1
|
+
{"version":3,"file":"search-results.component.js","sourceRoot":"","sources":["../../src/lib/search-results.component.ts","../../src/lib/search-results.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Search Results Component\n *\n * Displays search results grouped by source type with expandable sections.\n * Used both within the search overlay and as a standalone results view\n * in the Knowledge Hub search dashboard.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n Output,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport { SearchResultItem, SearchResultGroup, SearchResultSelectedEvent } from './search-types';\nimport { EscapeHTML, HighlightSearchMatches } from '@memberjunction/global';\nimport { BaseAngularComponent } from '@memberjunction/ng-base-types';\n\n@Component({\n standalone: false,\n selector: 'mj-search-results',\n templateUrl: './search-results.component.html',\n styleUrls: ['./search-results.component.css']\n})\nexport class SearchResultsComponent extends BaseAngularComponent {\n private cdr = inject(ChangeDetectorRef);\n\n /** Grouped search results to display (used when DisplayMode is 'grouped') */\n @Input() ResultGroups: SearchResultGroup[] = [];\n\n /** Flat list of all results sorted by score (used when DisplayMode is 'flat') */\n @Input() FlatResults: SearchResultItem[] = [];\n\n /** Display mode: 'flat' for blended list sorted by score, 'grouped' for source-type groups */\n @Input() DisplayMode: 'flat' | 'grouped' = 'flat';\n\n /** Whether results are currently loading */\n @Input() IsLoading = false;\n\n /** Total result count */\n @Input() TotalCount = 0;\n\n /** Elapsed search time in ms */\n @Input() ElapsedMs = 0;\n\n /** Maximum results per group before \"Show more\" (grouped mode only) */\n @Input() MaxPerGroup = 5;\n\n /** Results per page in flat mode */\n @Input() PageSize = 10;\n\n /** Whether to show the results summary line (count + time). Set false when parent provides its own header. */\n @Input() ShowSummary = true;\n\n /** Whether to show score badges */\n @Input() ShowScores = true;\n\n /** Whether to show tags */\n @Input() ShowTags = true;\n\n /** Whether to show source type icons */\n @Input() ShowSourceIcons = true;\n\n /** Text to highlight within result cards (e.g. from a client-side filter) */\n @Input() HighlightText = '';\n\n @Output() ResultSelected = new EventEmitter<SearchResultSelectedEvent>();\n\n /** Emitted when user clicks \"Open Record\" — parent handles navigation */\n @Output() OpenRecordRequested = new EventEmitter<SearchResultItem>();\n\n /** Emitted when user clicks \"See Similar Items\" — parent runs a \"more like this\" search */\n @Output() MoreLikeThisRequested = new EventEmitter<SearchResultItem>();\n\n /** Current page (1-based) for flat mode pagination */\n public CurrentPage = 1;\n\n /** Tracks which groups are expanded */\n public ExpandedGroups = new Set<string>();\n\n /** Set of expanded result card IDs — multiple can be open simultaneously */\n public ExpandedResultIDs = new Set<string>();\n\n // ── Flat mode pagination ──\n\n /** Get results for the current page in flat mode */\n public get PagedResults(): SearchResultItem[] {\n const start = (this.CurrentPage - 1) * this.PageSize;\n return this.FlatResults.slice(start, start + this.PageSize);\n }\n\n /** Total number of pages */\n public get TotalPages(): number {\n return Math.ceil(this.FlatResults.length / this.PageSize);\n }\n\n /** Page numbers to display in the pager */\n public get PageNumbers(): number[] {\n const total = this.TotalPages;\n if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);\n\n const pages: number[] = [1];\n const start = Math.max(2, this.CurrentPage - 2);\n const end = Math.min(total - 1, this.CurrentPage + 2);\n if (start > 2) pages.push(-1); // ellipsis marker\n for (let i = start; i <= end; i++) pages.push(i);\n if (end < total - 1) pages.push(-1); // ellipsis marker\n pages.push(total);\n return pages;\n }\n\n /** Navigate to a specific page */\n public GoToPage(page: number): void {\n if (page < 1 || page > this.TotalPages) return;\n this.CurrentPage = page;\n this.cdr.detectChanges();\n }\n\n // ── Grouped mode ──\n\n /** Toggle group expansion */\n public ToggleGroup(sourceType: string): void {\n if (this.ExpandedGroups.has(sourceType)) {\n this.ExpandedGroups.delete(sourceType);\n } else {\n this.ExpandedGroups.add(sourceType);\n }\n this.cdr.detectChanges();\n }\n\n /** Whether a group is expanded */\n public IsGroupExpanded(sourceType: string): boolean {\n return this.ExpandedGroups.has(sourceType);\n }\n\n /** Get visible results for a group (respecting max per group unless expanded) */\n public GetVisibleResults(group: SearchResultGroup): SearchResultItem[] {\n if (this.IsGroupExpanded(group.SourceType)) {\n return group.Results;\n }\n return group.Results.slice(0, this.MaxPerGroup);\n }\n\n /** Whether a group has more results than shown */\n public HasMoreResults(group: SearchResultGroup): boolean {\n return group.Results.length > this.MaxPerGroup && !this.IsGroupExpanded(group.SourceType);\n }\n\n /** Get the count of hidden results */\n public GetHiddenCount(group: SearchResultGroup): number {\n return group.Results.length - this.MaxPerGroup;\n }\n\n /** Handle clicking a result */\n public OnResultClick(result: SearchResultItem, event: MouseEvent): void {\n this.ResultSelected.emit({\n Result: result,\n OpenInNewTab: event.metaKey || event.ctrlKey\n });\n }\n\n /** Toggle expand/collapse of a result card — multiple can be open */\n public ToggleExpand(result: SearchResultItem, event?: MouseEvent): void {\n if (event) event.stopPropagation();\n if (this.ExpandedResultIDs.has(result.ID)) {\n this.ExpandedResultIDs.delete(result.ID);\n } else {\n this.ExpandedResultIDs.add(result.ID);\n }\n this.cdr.detectChanges();\n }\n\n /** Check if a result card is expanded */\n public IsExpanded(resultID: string): boolean {\n return this.ExpandedResultIDs.has(resultID);\n }\n\n /** Handle \"Open Record\" button click */\n public OnOpenRecord(result: SearchResultItem, event: MouseEvent): void {\n event.stopPropagation();\n this.OpenRecordRequested.emit(result);\n }\n\n /** Handle \"See Similar Items\" button click — emits the result for a \"more like this\" search */\n public OnMoreLikeThis(result: SearchResultItem, event: MouseEvent): void {\n event.stopPropagation();\n this.MoreLikeThisRequested.emit(result);\n }\n\n /**\n * Wrap substrings matching HighlightText in <mark> tags for visual emphasis.\n * The input is regex-escaped so user text is treated as a literal string.\n */\n public HighlightMatch(text: string): string {\n return HighlightSearchMatches(text, this.HighlightText, 'search-highlight');\n }\n\n /** Format a score as a percentage */\n public FormatScore(score: number): string {\n return `${Math.round(score * 100)}%`;\n }\n\n /** Format elapsed time */\n public FormatTime(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n return `${(ms / 1000).toFixed(1)}s`;\n }\n\n /** Resolved link cache per result ID */\n private resolvedLinksCache = new Map<string, { Value: string; Label: string; Type: 'URL' | 'Email' }[]>();\n\n /**\n * Resolve URL and Email fields from the result's RawMetadata using entity ExtendedType metadata.\n * Returns clickable link entries with proper type classification.\n */\n public GetResolvedLinks(result: SearchResultItem): { Value: string; Label: string; Type: 'URL' | 'Email' }[] {\n if (this.resolvedLinksCache.has(result.ID)) {\n return this.resolvedLinksCache.get(result.ID)!;\n }\n\n const links: { Value: string; Label: string; Type: 'URL' | 'Email' }[] = [];\n\n if (result.RawMetadata) {\n try {\n const meta = JSON.parse(result.RawMetadata) as Record<string, unknown>;\n const linkFields = this.GetLinkFields(result.EntityName);\n\n for (const field of linkFields) {\n const value = meta[field.Name];\n if (value && typeof value === 'string' && value.trim().length > 0) {\n links.push({\n Value: value,\n Label: field.DisplayName,\n Type: field.Type\n });\n }\n }\n } catch { /* ignore parse errors */ }\n }\n\n // Fallback: extract URLs from snippet if no ExtendedType fields found\n if (links.length === 0) {\n const urls = this.ExtractUrls(result.Snippet);\n for (const url of urls) {\n links.push({ Value: url, Label: '', Type: 'URL' });\n }\n }\n\n this.resolvedLinksCache.set(result.ID, links);\n return links;\n }\n\n /** Extract URLs from snippet text */\n public ExtractUrls(snippet: string): string[] {\n if (!snippet) return [];\n const urlRegex = /https?:\\/\\/[^\\s,;)>\"']+/g;\n const matches = snippet.match(urlRegex);\n return matches ? [...new Set(matches)] : [];\n }\n\n /** Truncate a URL for display */\n public TruncateUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const path = parsed.pathname.length > 30\n ? parsed.pathname.substring(0, 30) + '...'\n : parsed.pathname;\n return parsed.hostname + path;\n } catch {\n return url.length > 50 ? url.substring(0, 50) + '...' : url;\n }\n }\n\n /** Cache of URL/Email field info per entity name */\n private linkFieldsCache = new Map<string, { Name: string; DisplayName: string; Type: 'URL' | 'Email' }[]>();\n\n /**\n * Get URL and Email fields for an entity using ExtendedType from entity metadata.\n * Returns fields with ExtendedType='URL' or ExtendedType='Email'.\n */\n public GetLinkFields(entityName: string): { Name: string; DisplayName: string; Type: 'URL' | 'Email' }[] {\n if (this.linkFieldsCache.has(entityName)) {\n return this.linkFieldsCache.get(entityName)!;\n }\n\n try {\n const md = this.ProviderToUse;\n const entityInfo = md.Entities.find(e => e.Name === entityName);\n if (!entityInfo) {\n this.linkFieldsCache.set(entityName, []);\n return [];\n }\n\n const linkFields = entityInfo.Fields\n .filter(f => f.ExtendedType === 'URL' || f.ExtendedType === 'Email')\n .map(f => ({\n Name: f.Name,\n DisplayName: f.DisplayNameOrName,\n Type: f.ExtendedType as 'URL' | 'Email'\n }));\n\n this.linkFieldsCache.set(entityName, linkFields);\n return linkFields;\n } catch {\n this.linkFieldsCache.set(entityName, []);\n return [];\n }\n }\n}\n","@if (IsLoading) {\n <div class=\"results-loading\">\n <mj-loading text=\"Searching...\" size=\"medium\"></mj-loading>\n </div>\n} @else if ((DisplayMode === 'flat' ? FlatResults.length : ResultGroups.length) === 0 && TotalCount === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Title=\"No results to display\" />\n} @else {\n <!-- Results summary -->\n @if (ShowSummary) {\n <div class=\"results-summary\">\n <span>{{ TotalCount }} results</span>\n @if (ElapsedMs > 0) {\n <span class=\"results-summary-time\"> in {{ FormatTime(ElapsedMs) }}</span>\n }\n </div>\n }\n\n @if (DisplayMode === 'flat') {\n <!-- ══════ FLAT BLENDED LIST (sorted by score) ══════ -->\n <div class=\"results-scroll-area\">\n @for (result of PagedResults; track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n </div>\n\n <!-- Google-style pagination (sticky at bottom) -->\n @if (TotalPages > 1) {\n <div class=\"results-pager\">\n <button class=\"pager-btn pager-prev\" [disabled]=\"CurrentPage === 1\"\n (click)=\"GoToPage(CurrentPage - 1)\" aria-label=\"Previous page\">\n <i class=\"fa-solid fa-chevron-left\"></i>\n </button>\n @for (p of PageNumbers; track $index) {\n @if (p === -1) {\n <span class=\"pager-ellipsis\">...</span>\n } @else {\n <button class=\"pager-btn pager-num\" [class.pager-active]=\"p === CurrentPage\"\n (click)=\"GoToPage(p)\">{{ p }}</button>\n }\n }\n <button class=\"pager-btn pager-next\" [disabled]=\"CurrentPage === TotalPages\"\n (click)=\"GoToPage(CurrentPage + 1)\" aria-label=\"Next page\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n }\n } @else {\n <!-- ══════ GROUPED BY SOURCE TYPE ══════ -->\n @for (group of ResultGroups; track group.SourceType) {\n <div class=\"results-group\">\n <div class=\"results-group-header\" (click)=\"ToggleGroup(group.SourceType)\">\n @if (ShowSourceIcons) {\n <i [class]=\"group.Icon + ' results-group-icon'\"></i>\n }\n <span class=\"results-group-label\">{{ group.Label }}</span>\n <span class=\"results-group-count\">({{ group.TotalCount }})</span>\n <i class=\"fa-solid results-group-chevron\"\n [class.fa-chevron-down]=\"!IsGroupExpanded(group.SourceType)\"\n [class.fa-chevron-up]=\"IsGroupExpanded(group.SourceType)\"></i>\n </div>\n\n @for (result of GetVisibleResults(group); track result.ID) {\n <ng-container *ngTemplateOutlet=\"resultCardTpl; context: { $implicit: result }\"></ng-container>\n }\n\n @if (HasMoreResults(group)) {\n <button class=\"results-show-more\" (click)=\"ToggleGroup(group.SourceType)\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n Show {{ GetHiddenCount(group) }} more results\n </button>\n }\n </div>\n }\n }\n}\n\n<!-- ═══ Shared result card template ═══ -->\n<ng-template #resultCardTpl let-result>\n <div class=\"result-card\" [class.result-card-expanded]=\"IsExpanded(result.ID)\">\n <!-- Main row -->\n <div class=\"result-card-main\" (click)=\"ToggleExpand(result)\">\n <div class=\"result-card-icon-wrap\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"result-card-body\">\n <div class=\"result-card-title\" [innerHTML]=\"HighlightMatch(result.RecordName || result.Title)\"></div>\n <div class=\"result-card-entity\" [innerHTML]=\"HighlightMatch(result.EntityDisplayName || result.EntityName)\"></div>\n @if (result.Tags && result.Tags.length > 0) {\n <div class=\"result-card-tags\">\n @for (tag of result.Tags.slice(0, 5); track tag) {\n <span class=\"result-tag-chip\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n @if (result.Tags.length > 5) {\n <span class=\"result-tag-more\">+{{ result.Tags.length - 5 }}</span>\n }\n </div>\n }\n </div>\n @if (ShowScores) {\n <div class=\"result-card-score\">\n <div class=\"score-circle\" [class.score-high]=\"result.Score >= 0.40\" [class.score-medium]=\"result.Score >= 0.15 && result.Score < 0.40\" [class.score-low]=\"result.Score < 0.15\">\n <span class=\"score-value\">{{ FormatScore(result.Score) }}</span>\n </div>\n </div>\n }\n <div class=\"result-card-actions\">\n <button class=\"result-open-btn\" (click)=\"OnOpenRecord(result, $event)\"\n [title]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\"\n [attr.aria-label]=\"result.ResultType === 'storage-file' ? 'Open file' : 'Open record'\">\n <i [class]=\"result.ResultType === 'storage-file' ? 'fa-solid fa-external-link' : 'fa-solid fa-arrow-up-right-from-square'\"></i>\n </button>\n <button class=\"result-expand-btn\" (click)=\"ToggleExpand(result, $event)\" title=\"Expand details\" aria-label=\"Expand details\">\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"!IsExpanded(result.ID)\"\n [class.fa-chevron-up]=\"IsExpanded(result.ID)\"></i>\n </button>\n </div>\n </div>\n\n <!-- Expanded detail -->\n @if (IsExpanded(result.ID)) {\n <div class=\"result-card-detail\">\n @if (result.Snippet) {\n <div class=\"detail-section\">\n <span class=\"detail-label\">Preview</span>\n <p class=\"detail-snippet\" [innerHTML]=\"HighlightMatch(result.Snippet)\"></p>\n </div>\n }\n <div class=\"detail-meta\">\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-layer-group\"></i>\n <span>{{ result.EntityDisplayName || result.EntityName }}</span>\n </div>\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-fingerprint\"></i>\n <span class=\"detail-record-id\">{{ result.RecordID }}</span>\n </div>\n <!-- SR-1: Show overall score consistently -->\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-signal\"></i>\n <span>Relevance: {{ FormatScore(result.Score) }}</span>\n </div>\n @if (result.ScoreBreakdown) {\n <div class=\"detail-meta-item\">\n <i class=\"fa-solid fa-chart-bar\"></i>\n <span>\n @if (result.ScoreBreakdown.Vector) { Vector: {{ FormatScore(result.ScoreBreakdown.Vector) }} }\n @if (result.ScoreBreakdown.FullText) { FTS: {{ FormatScore(result.ScoreBreakdown.FullText) }} }\n </span>\n </div>\n }\n </div>\n @if (ShowTags && result.Tags.length > 0) {\n <div class=\"detail-tags\">\n @for (tag of result.Tags; track tag) {\n <span class=\"result-card-tag\" [innerHTML]=\"HighlightMatch(tag)\"></span>\n }\n </div>\n }\n @if (GetResolvedLinks(result).length > 0) {\n <div class=\"detail-links\">\n @for (link of GetResolvedLinks(result); track link.Value) {\n @if (link.Type === 'URL') {\n <a [href]=\"link.Value\" target=\"_blank\" class=\"detail-link\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n {{ TruncateUrl(link.Value) }}\n </a>\n } @else if (link.Type === 'Email') {\n <a [href]=\"'mailto:' + link.Value\" class=\"detail-link detail-link-email\" (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ link.Value }}\n </a>\n }\n }\n </div>\n }\n <div class=\"detail-actions\">\n <button class=\"detail-open-btn\" (click)=\"OnOpenRecord(result, $event)\">\n @if (result.ResultType === 'storage-file') {\n <i class=\"fa-solid fa-external-link\"></i> Open File\n } @else {\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i> Open Record\n }\n </button>\n <button class=\"detail-similar-btn\" (click)=\"OnMoreLikeThis(result, $event)\">\n <i class=\"fa-solid fa-shuffle\"></i> See Similar Items\n </button>\n </div>\n </div>\n }\n </div>\n</ng-template>\n"]}
|
|
@@ -78,7 +78,7 @@ function SearchSuggestComponent_Conditional_0_Conditional_3_For_6_Template(rf, c
|
|
|
78
78
|
i0.ɵɵadvance(3);
|
|
79
79
|
i0.ɵɵtextInterpolate(record_r7.RecordName);
|
|
80
80
|
i0.ɵɵadvance(2);
|
|
81
|
-
i0.ɵɵtextInterpolate(record_r7.EntityName);
|
|
81
|
+
i0.ɵɵtextInterpolate(record_r7.EntityDisplayName || record_r7.EntityName);
|
|
82
82
|
} }
|
|
83
83
|
function SearchSuggestComponent_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
84
84
|
i0.ɵɵelementStart(0, "div", 2)(1, "div", 7);
|
|
@@ -119,7 +119,7 @@ function SearchSuggestComponent_Conditional_0_Conditional_4_Conditional_0_For_2_
|
|
|
119
119
|
i0.ɵɵadvance(3);
|
|
120
120
|
i0.ɵɵtextInterpolate(result_r10.RecordName || result_r10.Title);
|
|
121
121
|
i0.ɵɵadvance(2);
|
|
122
|
-
i0.ɵɵtextInterpolate(result_r10.EntityName);
|
|
122
|
+
i0.ɵɵtextInterpolate(result_r10.EntityDisplayName || result_r10.EntityName);
|
|
123
123
|
i0.ɵɵadvance();
|
|
124
124
|
i0.ɵɵclassMap("suggest-result-score " + ctx_r1.GetScoreClass(result_r10.Score));
|
|
125
125
|
i0.ɵɵadvance();
|
|
@@ -586,7 +586,7 @@ export class SearchSuggestComponent {
|
|
|
586
586
|
}
|
|
587
587
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SearchSuggestComponent, [{
|
|
588
588
|
type: Component,
|
|
589
|
-
args: [{ standalone: false, selector: 'mj-search-suggest', template: "@if (IsOpen) {\n <div class=\"suggest-dropdown\">\n @if (IsLoading && showPreviewSection) {\n <div class=\"suggest-loading\">\n <i class=\"fa-solid fa-spinner fa-spin suggest-loading-icon\"></i>\n <span>Searching...</span>\n </div>\n }\n\n <!-- Recent searches section -->\n @if (showRecentSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock suggest-section-icon\"></i>\n <span>Recent Searches</span>\n <button type=\"button\" class=\"suggest-clear-link\" aria-label=\"Clear recent searches\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnClearRecentClick($event)\">Clear</button>\n </div>\n @for (recent of VisibleRecentSearches; track recent.Query; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted(i)\"\n (mousedown)=\"OnRecentClick(recent); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = i\"\n >\n <i class=\"fa-solid fa-clock suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ recent.Query }}</span>\n <span class=\"suggest-item-meta\">{{ recent.ResultCount }} results</span>\n </div>\n }\n </div>\n }\n\n <!-- Recently opened records section (idle state) -->\n @if (showRecentRecordsSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock-rotate-left suggest-section-icon\"></i>\n <span>Recently Opened</span>\n </div>\n @for (record of VisibleRecentRecords; track record.EntityName + record.RecordID; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnRecentRecordClick(record); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <i class=\"fa-solid fa-file-lines suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ record.RecordName }}</span>\n <span class=\"suggest-item-meta\">{{ record.EntityName }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Preview results section -->\n @if (showPreviewSection && !IsLoading) {\n @if (VisiblePreviewResults.length > 0) {\n <div class=\"suggest-section\">\n @for (result of VisiblePreviewResults; track result.ID; let i = $index) {\n <div\n class=\"suggest-item suggest-result-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnResultClick(result); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <div class=\"suggest-result-icon\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"suggest-result-body\">\n <div class=\"suggest-result-title\">{{ result.RecordName || result.Title }}</div>\n <div class=\"suggest-result-entity\">{{ result.EntityName }}</div>\n </div>\n <span class=\"suggest-result-score\" [class]=\"'suggest-result-score ' + GetScoreClass(result.Score)\">{{ FormatScore(result.Score) }}</span>\n </div>\n }\n </div>\n } @else if (!IsLoading) {\n <div class=\"suggest-empty\">\n <span>No results found</span>\n </div>\n }\n\n <!-- See all footer -->\n @if (showSeeAllFooter) {\n <div\n class=\"suggest-see-all\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length)\"\n (mousedown)=\"OnSeeAllClick(); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length\"\n >\n <i class=\"fa-solid fa-arrow-right suggest-see-all-icon\"></i>\n <span>See all {{ TotalCount }} results</span>\n </div>\n }\n }\n\n <!-- Min relevance filter bar -->\n @if (showPreviewSection) {\n <div class=\"suggest-filter-bar\">\n <div class=\"suggest-filter-toggle\" (mousedown)=\"ToggleFilter($event)\">\n <i class=\"fa-solid fa-sliders suggest-filter-icon\"></i>\n <span class=\"suggest-filter-label\">\n @if (MinRelevancePercent > 0) {\n Min relevance: {{ MinRelevancePercent }}%\n } @else {\n Showing all results\n }\n </span>\n <i class=\"fa-solid fa-chevron-down suggest-filter-chevron\" [class.suggest-filter-chevron-open]=\"IsFilterOpen\"></i>\n </div>\n\n @if (IsFilterOpen) {\n <div class=\"suggest-filter-popover\">\n <div class=\"suggest-filter-popover-header\">Min Relevance</div>\n @for (option of MinRelevanceOptions; track option) {\n <div\n class=\"suggest-filter-option\"\n [class.suggest-filter-option-active]=\"IsSelectedRelevance(option)\"\n (mousedown)=\"SetMinRelevance(option, $event)\"\n >\n <span>{{ FormatRelevanceOption(option) }}</span>\n @if (IsSelectedRelevance(option)) {\n <i class=\"fa-solid fa-check suggest-filter-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Empty state when no recents of either kind and query too short -->\n @if (!showRecentSection && !showRecentRecordsSection && !showPreviewSection) {\n <div class=\"suggest-empty\">\n <span>Type at least {{ MinQueryLength }} characters to search</span>\n </div>\n }\n\n <!-- Host-supplied promo row (e.g. command palette nudge) -->\n @if (ShowPromo && PromoText) {\n <div class=\"suggest-promo\">\n <i class=\"fa-solid fa-wand-magic-sparkles suggest-promo-icon\"></i>\n <span class=\"suggest-promo-text\">{{ PromoText }}</span>\n <button type=\"button\" class=\"suggest-promo-action\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoAccept()\">{{ PromoActionLabel }}</button>\n <button type=\"button\" class=\"suggest-promo-dismiss\" aria-label=\"Dismiss\" title=\"Dismiss\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoDismiss(); $event.stopPropagation()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n }\n </div>\n}\n", styles: ["/* Search Suggest Dropdown */\n\n.suggest-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 99999;\n margin-top: 4px;\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n overflow: visible;\n animation: suggestSlideIn 0.15s ease;\n}\n\n@keyframes suggestSlideIn {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Sections */\n.suggest-section {\n padding: 0.25rem 0;\n}\n\n.suggest-section + .suggest-section {\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.suggest-section-header {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 0.75rem 0.3rem;\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.suggest-section-icon {\n font-size: 0.65rem;\n}\n\n.suggest-clear-link {\n margin-left: auto;\n padding: 0;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: 0.68rem;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-link, var(--mj-brand-primary));\n cursor: pointer;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.suggest-clear-link:hover {\n text-decoration: underline;\n}\n\n.suggest-clear-link:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n border-radius: var(--mj-radius-sm);\n}\n\n/* Items */\n.suggest-item {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.45rem 0.75rem;\n cursor: pointer;\n transition: background 0.08s ease;\n}\n\n.suggest-item:hover,\n.suggest-item-highlighted {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-item-icon {\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n width: 1rem;\n text-align: center;\n flex-shrink: 0;\n}\n\n.suggest-item-text {\n flex: 1;\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.suggest-item-meta {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n}\n\n/* Result items */\n.suggest-result-item {\n gap: 0.65rem;\n}\n\n.suggest-result-icon {\n width: 1.75rem;\n height: 1.75rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.72rem;\n flex-shrink: 0;\n}\n\n.suggest-result-body {\n flex: 1;\n min-width: 0;\n}\n\n.suggest-result-title {\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.suggest-result-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n/* Score badge \u2014 base styles */\n.suggest-result-score {\n font-size: 0.68rem;\n font-weight: 600;\n padding: 0.1rem 0.4rem;\n border-radius: 9999px;\n flex-shrink: 0;\n /* Default: neutral gray */\n color: var(--mj-text-muted);\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 high relevance (green, \u226540%) */\n.suggest-score-high {\n color: var(--mj-status-success-text, var(--mj-status-success));\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 medium relevance (amber, 15\u201339%) */\n.suggest-score-medium {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 low relevance (gray, <15%) */\n.suggest-score-low {\n color: var(--mj-text-muted);\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n}\n\n/* Loading */\n.suggest-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.85rem 0.75rem;\n color: var(--mj-text-muted);\n font-size: 0.82rem;\n}\n\n.suggest-loading-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.suggest-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem 0.75rem;\n color: var(--mj-text-muted);\n font-size: 0.82rem;\n}\n\n/* See all footer */\n.suggest-see-all {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.55rem 0.75rem;\n border-top: 1px solid var(--mj-border-subtle);\n cursor: pointer;\n color: var(--mj-brand-primary);\n font-size: 0.82rem;\n font-weight: 500;\n transition: background 0.08s ease;\n}\n\n.suggest-see-all:hover,\n.suggest-see-all.suggest-item-highlighted {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-see-all-icon {\n font-size: 0.72rem;\n}\n\n/* --- Min Relevance Filter Bar --- */\n.suggest-filter-bar {\n position: relative;\n border-top: 1px solid var(--mj-border-subtle);\n padding: 0.35rem 0.75rem;\n}\n\n.suggest-filter-toggle {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n cursor: pointer;\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n transition: color 0.1s ease;\n user-select: none;\n}\n\n.suggest-filter-toggle:hover {\n color: var(--mj-text-secondary);\n}\n\n.suggest-filter-icon {\n font-size: 0.65rem;\n}\n\n.suggest-filter-label {\n flex: 1;\n}\n\n.suggest-filter-chevron {\n font-size: 0.55rem;\n transition: transform 0.15s ease;\n}\n\n.suggest-filter-chevron-open {\n transform: rotate(180deg);\n}\n\n/* Filter popover */\n.suggest-filter-popover {\n position: absolute;\n top: 100%;\n left: 0.5rem;\n right: 0.5rem;\n margin-top: 4px;\n background: var(--mj-bg-surface-elevated, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n overflow: hidden;\n z-index: 10;\n animation: suggestSlideIn 0.12s ease;\n}\n\n.suggest-filter-popover-header {\n padding: 0.4rem 0.6rem 0.25rem;\n font-size: 0.65rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.suggest-filter-option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.35rem 0.6rem;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.08s ease;\n}\n\n.suggest-filter-option:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-filter-option-active {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n.suggest-filter-check {\n font-size: 0.65rem;\n color: var(--mj-brand-primary);\n}\n\n/* Host-supplied promo row \u2014 a quiet whisper pinned to the dropdown's bottom\n edge, never a banner. */\n.suggest-promo {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-3);\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.suggest-promo-icon {\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.suggest-promo-text {\n flex: 1;\n min-width: 0;\n}\n\n.suggest-promo-action {\n flex-shrink: 0;\n padding: 0.2rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n font-weight: var(--mj-font-medium);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.suggest-promo-action:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.suggest-promo-dismiss {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: var(--mj-radius-full);\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 0.75rem;\n cursor: pointer;\n transition: var(--mj-transition-colors);\n flex-shrink: 0;\n}\n\n.suggest-promo-dismiss:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n"] }]
|
|
589
|
+
args: [{ standalone: false, selector: 'mj-search-suggest', template: "@if (IsOpen) {\n <div class=\"suggest-dropdown\">\n @if (IsLoading && showPreviewSection) {\n <div class=\"suggest-loading\">\n <i class=\"fa-solid fa-spinner fa-spin suggest-loading-icon\"></i>\n <span>Searching...</span>\n </div>\n }\n\n <!-- Recent searches section -->\n @if (showRecentSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock suggest-section-icon\"></i>\n <span>Recent Searches</span>\n <button type=\"button\" class=\"suggest-clear-link\" aria-label=\"Clear recent searches\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnClearRecentClick($event)\">Clear</button>\n </div>\n @for (recent of VisibleRecentSearches; track recent.Query; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted(i)\"\n (mousedown)=\"OnRecentClick(recent); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = i\"\n >\n <i class=\"fa-solid fa-clock suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ recent.Query }}</span>\n <span class=\"suggest-item-meta\">{{ recent.ResultCount }} results</span>\n </div>\n }\n </div>\n }\n\n <!-- Recently opened records section (idle state) -->\n @if (showRecentRecordsSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock-rotate-left suggest-section-icon\"></i>\n <span>Recently Opened</span>\n </div>\n @for (record of VisibleRecentRecords; track record.EntityName + record.RecordID; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnRecentRecordClick(record); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <i class=\"fa-solid fa-file-lines suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ record.RecordName }}</span>\n <span class=\"suggest-item-meta\">{{ record.EntityDisplayName || record.EntityName }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Preview results section -->\n @if (showPreviewSection && !IsLoading) {\n @if (VisiblePreviewResults.length > 0) {\n <div class=\"suggest-section\">\n @for (result of VisiblePreviewResults; track result.ID; let i = $index) {\n <div\n class=\"suggest-item suggest-result-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnResultClick(result); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <div class=\"suggest-result-icon\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"suggest-result-body\">\n <div class=\"suggest-result-title\">{{ result.RecordName || result.Title }}</div>\n <div class=\"suggest-result-entity\">{{ result.EntityDisplayName || result.EntityName }}</div>\n </div>\n <span class=\"suggest-result-score\" [class]=\"'suggest-result-score ' + GetScoreClass(result.Score)\">{{ FormatScore(result.Score) }}</span>\n </div>\n }\n </div>\n } @else if (!IsLoading) {\n <div class=\"suggest-empty\">\n <span>No results found</span>\n </div>\n }\n\n <!-- See all footer -->\n @if (showSeeAllFooter) {\n <div\n class=\"suggest-see-all\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length)\"\n (mousedown)=\"OnSeeAllClick(); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length\"\n >\n <i class=\"fa-solid fa-arrow-right suggest-see-all-icon\"></i>\n <span>See all {{ TotalCount }} results</span>\n </div>\n }\n }\n\n <!-- Min relevance filter bar -->\n @if (showPreviewSection) {\n <div class=\"suggest-filter-bar\">\n <div class=\"suggest-filter-toggle\" (mousedown)=\"ToggleFilter($event)\">\n <i class=\"fa-solid fa-sliders suggest-filter-icon\"></i>\n <span class=\"suggest-filter-label\">\n @if (MinRelevancePercent > 0) {\n Min relevance: {{ MinRelevancePercent }}%\n } @else {\n Showing all results\n }\n </span>\n <i class=\"fa-solid fa-chevron-down suggest-filter-chevron\" [class.suggest-filter-chevron-open]=\"IsFilterOpen\"></i>\n </div>\n\n @if (IsFilterOpen) {\n <div class=\"suggest-filter-popover\">\n <div class=\"suggest-filter-popover-header\">Min Relevance</div>\n @for (option of MinRelevanceOptions; track option) {\n <div\n class=\"suggest-filter-option\"\n [class.suggest-filter-option-active]=\"IsSelectedRelevance(option)\"\n (mousedown)=\"SetMinRelevance(option, $event)\"\n >\n <span>{{ FormatRelevanceOption(option) }}</span>\n @if (IsSelectedRelevance(option)) {\n <i class=\"fa-solid fa-check suggest-filter-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Empty state when no recents of either kind and query too short -->\n @if (!showRecentSection && !showRecentRecordsSection && !showPreviewSection) {\n <div class=\"suggest-empty\">\n <span>Type at least {{ MinQueryLength }} characters to search</span>\n </div>\n }\n\n <!-- Host-supplied promo row (e.g. command palette nudge) -->\n @if (ShowPromo && PromoText) {\n <div class=\"suggest-promo\">\n <i class=\"fa-solid fa-wand-magic-sparkles suggest-promo-icon\"></i>\n <span class=\"suggest-promo-text\">{{ PromoText }}</span>\n <button type=\"button\" class=\"suggest-promo-action\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoAccept()\">{{ PromoActionLabel }}</button>\n <button type=\"button\" class=\"suggest-promo-dismiss\" aria-label=\"Dismiss\" title=\"Dismiss\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoDismiss(); $event.stopPropagation()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n }\n </div>\n}\n", styles: ["/* Search Suggest Dropdown */\n\n.suggest-dropdown {\n position: absolute;\n top: 100%;\n left: 0;\n right: 0;\n z-index: 99999;\n margin-top: 4px;\n border: 1px solid var(--mj-border-default);\n border-radius: 10px;\n background: var(--mj-bg-surface);\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);\n overflow: visible;\n animation: suggestSlideIn 0.15s ease;\n}\n\n@keyframes suggestSlideIn {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* Sections */\n.suggest-section {\n padding: 0.25rem 0;\n}\n\n.suggest-section + .suggest-section {\n border-top: 1px solid var(--mj-border-subtle);\n}\n\n.suggest-section-header {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n padding: 0.5rem 0.75rem 0.3rem;\n font-size: 0.7rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.suggest-section-icon {\n font-size: 0.65rem;\n}\n\n.suggest-clear-link {\n margin-left: auto;\n padding: 0;\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: 0.68rem;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-link, var(--mj-brand-primary));\n cursor: pointer;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.suggest-clear-link:hover {\n text-decoration: underline;\n}\n\n.suggest-clear-link:focus-visible {\n outline: 2px solid var(--mj-border-focus);\n outline-offset: 2px;\n border-radius: var(--mj-radius-sm);\n}\n\n/* Items */\n.suggest-item {\n display: flex;\n align-items: center;\n gap: 0.6rem;\n padding: 0.45rem 0.75rem;\n cursor: pointer;\n transition: background 0.08s ease;\n}\n\n.suggest-item:hover,\n.suggest-item-highlighted {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-item-icon {\n color: var(--mj-text-muted);\n font-size: 0.75rem;\n width: 1rem;\n text-align: center;\n flex-shrink: 0;\n}\n\n.suggest-item-text {\n flex: 1;\n font-size: 0.85rem;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.suggest-item-meta {\n font-size: 0.72rem;\n color: var(--mj-text-muted);\n flex-shrink: 0;\n}\n\n/* Result items */\n.suggest-result-item {\n gap: 0.65rem;\n}\n\n.suggest-result-icon {\n width: 1.75rem;\n height: 1.75rem;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n font-size: 0.72rem;\n flex-shrink: 0;\n}\n\n.suggest-result-body {\n flex: 1;\n min-width: 0;\n}\n\n.suggest-result-title {\n font-size: 0.85rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.suggest-result-entity {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n\n/* Score badge \u2014 base styles */\n.suggest-result-score {\n font-size: 0.68rem;\n font-weight: 600;\n padding: 0.1rem 0.4rem;\n border-radius: 9999px;\n flex-shrink: 0;\n /* Default: neutral gray */\n color: var(--mj-text-muted);\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 high relevance (green, \u226540%) */\n.suggest-score-high {\n color: var(--mj-status-success-text, var(--mj-status-success));\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 medium relevance (amber, 15\u201339%) */\n.suggest-score-medium {\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n}\n\n/* Score badge \u2014 low relevance (gray, <15%) */\n.suggest-score-low {\n color: var(--mj-text-muted);\n background: color-mix(in srgb, var(--mj-text-muted) 10%, var(--mj-bg-surface));\n}\n\n/* Loading */\n.suggest-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n padding: 0.85rem 0.75rem;\n color: var(--mj-text-muted);\n font-size: 0.82rem;\n}\n\n.suggest-loading-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Empty state */\n.suggest-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 1rem 0.75rem;\n color: var(--mj-text-muted);\n font-size: 0.82rem;\n}\n\n/* See all footer */\n.suggest-see-all {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.55rem 0.75rem;\n border-top: 1px solid var(--mj-border-subtle);\n cursor: pointer;\n color: var(--mj-brand-primary);\n font-size: 0.82rem;\n font-weight: 500;\n transition: background 0.08s ease;\n}\n\n.suggest-see-all:hover,\n.suggest-see-all.suggest-item-highlighted {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-see-all-icon {\n font-size: 0.72rem;\n}\n\n/* --- Min Relevance Filter Bar --- */\n.suggest-filter-bar {\n position: relative;\n border-top: 1px solid var(--mj-border-subtle);\n padding: 0.35rem 0.75rem;\n}\n\n.suggest-filter-toggle {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n cursor: pointer;\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n transition: color 0.1s ease;\n user-select: none;\n}\n\n.suggest-filter-toggle:hover {\n color: var(--mj-text-secondary);\n}\n\n.suggest-filter-icon {\n font-size: 0.65rem;\n}\n\n.suggest-filter-label {\n flex: 1;\n}\n\n.suggest-filter-chevron {\n font-size: 0.55rem;\n transition: transform 0.15s ease;\n}\n\n.suggest-filter-chevron-open {\n transform: rotate(180deg);\n}\n\n/* Filter popover */\n.suggest-filter-popover {\n position: absolute;\n top: 100%;\n left: 0.5rem;\n right: 0.5rem;\n margin-top: 4px;\n background: var(--mj-bg-surface-elevated, var(--mj-bg-surface));\n border: 1px solid var(--mj-border-default);\n border-radius: 8px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n overflow: hidden;\n z-index: 10;\n animation: suggestSlideIn 0.12s ease;\n}\n\n.suggest-filter-popover-header {\n padding: 0.4rem 0.6rem 0.25rem;\n font-size: 0.65rem;\n font-weight: 600;\n color: var(--mj-text-muted);\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.suggest-filter-option {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.35rem 0.6rem;\n cursor: pointer;\n font-size: 0.78rem;\n color: var(--mj-text-primary);\n transition: background 0.08s ease;\n}\n\n.suggest-filter-option:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.suggest-filter-option-active {\n color: var(--mj-brand-primary);\n font-weight: 600;\n}\n\n.suggest-filter-check {\n font-size: 0.65rem;\n color: var(--mj-brand-primary);\n}\n\n/* Host-supplied promo row \u2014 a quiet whisper pinned to the dropdown's bottom\n edge, never a banner. */\n.suggest-promo {\n display: flex;\n align-items: center;\n gap: var(--mj-space-2);\n padding: var(--mj-space-2) var(--mj-space-3);\n border-top: 1px solid var(--mj-border-subtle);\n background: var(--mj-bg-surface-card);\n font-size: 0.78rem;\n color: var(--mj-text-muted);\n}\n\n.suggest-promo-icon {\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.suggest-promo-text {\n flex: 1;\n min-width: 0;\n}\n\n.suggest-promo-action {\n flex-shrink: 0;\n padding: 0.2rem 0.6rem;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: transparent;\n color: var(--mj-brand-primary);\n font-size: 0.75rem;\n font-weight: var(--mj-font-medium);\n cursor: pointer;\n transition: var(--mj-transition-colors);\n}\n\n.suggest-promo-action:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n}\n\n.suggest-promo-dismiss {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n padding: 0;\n border: none;\n border-radius: var(--mj-radius-full);\n background: transparent;\n color: var(--mj-text-disabled);\n font-size: 0.75rem;\n cursor: pointer;\n transition: var(--mj-transition-colors);\n flex-shrink: 0;\n}\n\n.suggest-promo-dismiss:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n"] }]
|
|
590
590
|
}], null, { IsOpen: [{
|
|
591
591
|
type: Input
|
|
592
592
|
}], PreviewResults: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-suggest.component.js","sourceRoot":"","sources":["../../src/lib/search-suggest.component.ts","../../src/lib/search-suggest.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Search Suggest Component\n *\n * Autocomplete dropdown showing preview results and recent searches.\n * Supports keyboard navigation (up/down/enter) for accessibility.\n * Designed to be paired with SearchInputComponent via SearchCompositeComponent.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n OnInit,\n Output,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport { StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { SearchResultItem, RecentRecordItem } from './search-types';\nimport { RecentSearch } from './search.service';\nimport { MJEventType, MJGlobal } from '@memberjunction/global';\n\n/** User setting key for persisting the preview min relevance threshold */\nconst PREVIEW_MIN_RELEVANCE_KEY = 'search.previewMinRelevance';\n\n/** Preset options for the min relevance filter */\nconst MIN_RELEVANCE_OPTIONS = [0, 15, 30, 50, 70];\n\n@Component({\n standalone: false,\n selector: 'mj-search-suggest',\n templateUrl: './search-suggest.component.html',\n styleUrls: ['./search-suggest.component.css']\n})\nexport class SearchSuggestComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n // --- Configuration Inputs ---\n\n /** Whether the suggest dropdown is visible */\n @Input() IsOpen = false;\n\n /** Preview search results to display */\n @Input() PreviewResults: SearchResultItem[] = [];\n\n /** Recent search entries to show when query is empty/short */\n @Input() RecentSearches: RecentSearch[] = [];\n\n /** Total count of results for the current query (may exceed PreviewResults.length) */\n @Input() TotalCount = 0;\n\n /** Whether a search is currently loading */\n @Input() IsLoading = false;\n\n /** Current query text — used to determine whether to show recent vs preview */\n @Input() Query = '';\n\n /** Minimum query length before preview results are shown */\n @Input() MinQueryLength = 2;\n\n /** Maximum number of preview results to display */\n @Input() MaxPreviewResults = 8;\n\n /** Whether to show the recent searches section */\n @Input() ShowRecent = true;\n\n /**\n * Recently opened records (host-loaded, e.g. from SearchService\n * .GetRecentlyOpenedRecords). Rendered as a \"Recently Opened\" section in\n * the idle/empty state so users can jump straight back to a record they\n * just had open — the complement to Recent Searches (queries).\n */\n @Input() RecentRecords: RecentRecordItem[] = [];\n\n /**\n * Optional promo row pinned to the dropdown's bottom edge — a host-supplied\n * nudge (e.g. Explorer advertising its command palette). The component is\n * deliberately ignorant of WHAT is being promoted; the host provides the\n * copy and handles both outcomes.\n */\n @Input() ShowPromo = false;\n\n /** Promo copy (one sentence). Required when ShowPromo is true. */\n @Input() PromoText = '';\n\n /** Label for the promo's accept action. */\n @Input() PromoActionLabel = 'Turn on';\n\n // --- Outputs ---\n\n /** Emitted when the user selects a preview result */\n @Output() ResultSelected = new EventEmitter<SearchResultItem>();\n\n /** Emitted when the user selects a recent search */\n @Output() RecentSelected = new EventEmitter<string>();\n\n /** Emitted when the user clicks \"See all N results\" */\n @Output() SeeAllRequested = new EventEmitter<string>();\n\n /** Emitted when the user clicks \"Clear\" on recent searches */\n @Output() ClearRecentRequested = new EventEmitter<void>();\n\n /** Emitted when the user selects a recently opened record */\n @Output() RecentRecordSelected = new EventEmitter<RecentRecordItem>();\n\n /** Promo accept — carries the current query so the host can hand it off. */\n @Output() PromoAccepted = new EventEmitter<string>();\n\n /** Promo dismissed — the host should persist this and stop showing it. */\n @Output() PromoDismissed = new EventEmitter<void>();\n\n // --- Internal state ---\n\n /** Currently keyboard-highlighted index (-1 for none) */\n public HighlightedIndex = -1;\n\n /** Min relevance percentage (0-100) for filtering preview results */\n public MinRelevancePercent = 0;\n\n /** Whether the filter popover is open */\n public IsFilterOpen = false;\n\n /** Available min relevance presets */\n public readonly MinRelevanceOptions = MIN_RELEVANCE_OPTIONS;\n\n // --- Lifecycle ---\n\n async ngOnInit(): Promise<void> {\n await this.loadMinRelevanceSetting();\n }\n\n // --- Public Methods ---\n\n /** Move keyboard highlight up */\n public NavigateUp(): void {\n const maxIndex = this.getNavigableItemCount() - 1;\n if (maxIndex < 0) return;\n\n this.HighlightedIndex--;\n if (this.HighlightedIndex < 0) {\n this.HighlightedIndex = maxIndex;\n }\n this.cdr.detectChanges();\n }\n\n /** Move keyboard highlight down */\n public NavigateDown(): void {\n const maxIndex = this.getNavigableItemCount() - 1;\n if (maxIndex < 0) return;\n\n this.HighlightedIndex++;\n if (this.HighlightedIndex > maxIndex) {\n this.HighlightedIndex = 0;\n }\n this.cdr.detectChanges();\n }\n\n /** Select the currently highlighted item */\n public SelectHighlighted(): void {\n if (this.HighlightedIndex < 0) return;\n\n if (this.showRecentSection) {\n // Navigating recent searches\n const visibleRecent = this.VisibleRecentSearches;\n if (this.HighlightedIndex < visibleRecent.length) {\n this.OnRecentClick(visibleRecent[this.HighlightedIndex]);\n return;\n }\n }\n\n if (this.showRecentRecordsSection) {\n // Navigating recently opened records (indexes continue after recents)\n const offset = this.showRecentSection ? this.VisibleRecentSearches.length : 0;\n const recordIndex = this.HighlightedIndex - offset;\n const visibleRecords = this.VisibleRecentRecords;\n if (recordIndex >= 0 && recordIndex < visibleRecords.length) {\n this.OnRecentRecordClick(visibleRecords[recordIndex]);\n return;\n }\n }\n\n if (this.showPreviewSection) {\n // Navigating preview results\n const offset = this.showRecentSection ? this.VisibleRecentSearches.length : 0;\n const resultIndex = this.HighlightedIndex - offset;\n const visibleResults = this.VisiblePreviewResults;\n if (resultIndex >= 0 && resultIndex < visibleResults.length) {\n this.OnResultClick(visibleResults[resultIndex]);\n return;\n }\n\n // \"See all\" footer\n if (resultIndex === visibleResults.length && this.showSeeAllFooter) {\n this.OnSeeAllClick();\n }\n }\n }\n\n /** Reset the highlighted index */\n public ResetHighlight(): void {\n this.HighlightedIndex = -1;\n }\n\n /** Number of keyboard-navigable rows currently visible (recents + recently\n opened + preview results + see-all). Public so the composite's Tab\n handling knows when the highlight has walked off the end of the list. */\n public get NavigableItemCount(): number {\n return this.getNavigableItemCount();\n }\n\n // --- Template getters ---\n\n /** Whether to show recent searches section */\n public get showRecentSection(): boolean {\n return this.ShowRecent && this.Query.length < this.MinQueryLength && this.RecentSearches.length > 0;\n }\n\n /** Whether to show preview results section */\n public get showPreviewSection(): boolean {\n return this.Query.length >= this.MinQueryLength;\n }\n\n /** Whether to show the recently-opened-records section (idle state only) */\n public get showRecentRecordsSection(): boolean {\n return this.Query.length < this.MinQueryLength && this.RecentRecords.length > 0;\n }\n\n /** Visible recently opened records (capped at 3) */\n public get VisibleRecentRecords(): RecentRecordItem[] {\n return this.RecentRecords.slice(0, 3);\n }\n\n /** Whether to show the \"See all N results\" footer */\n public get showSeeAllFooter(): boolean {\n return this.showPreviewSection && this.TotalCount > 0;\n }\n\n /** Visible recent searches (capped at 5) */\n public get VisibleRecentSearches(): RecentSearch[] {\n return this.RecentSearches.slice(0, 5);\n }\n\n /** Visible preview results filtered by min relevance, sorted by score descending, capped at MaxPreviewResults */\n public get VisiblePreviewResults(): SearchResultItem[] {\n const minScore = this.MinRelevancePercent / 100;\n return [...this.PreviewResults]\n .filter(r => r.Score >= minScore)\n .sort((a, b) => b.Score - a.Score)\n .slice(0, this.MaxPreviewResults);\n }\n\n // --- Template event handlers ---\n\n /** Handle clicking a preview result */\n public OnResultClick(result: SearchResultItem): void {\n this.ResultSelected.emit(result);\n }\n\n /** Handle clicking a recent search */\n public OnRecentClick(recent: RecentSearch): void {\n this.RecentSelected.emit(recent.Query);\n }\n\n /** Handle clicking a recently opened record */\n public OnRecentRecordClick(record: RecentRecordItem): void {\n this.RecentRecordSelected.emit(record);\n }\n\n /** Handle clicking \"See all\" */\n public OnSeeAllClick(): void {\n this.SeeAllRequested.emit(this.Query);\n }\n\n /** Promo accept — fired on click so keyboard Enter/Space work too; the\n template's mousedown-preventDefault keeps the input from blurring first. */\n public OnPromoAccept(): void {\n this.PromoAccepted.emit(this.Query);\n }\n\n /** Promo dismiss. */\n public OnPromoDismiss(): void {\n this.PromoDismissed.emit();\n }\n\n /** \"Clear\" on recent searches — click-driven so keyboard Enter/Space work;\n the template's mousedown-preventDefault keeps the input from blurring. */\n public OnClearRecentClick(event: MouseEvent): void {\n event.stopPropagation();\n this.ClearRecentRequested.emit();\n }\n\n /** Check if an item at the given index is highlighted */\n public IsHighlighted(index: number): boolean {\n return this.HighlightedIndex === index;\n }\n\n /** Format a score value as a percentage */\n public FormatScore(score: number): string {\n return `${Math.round(score * 100)}%`;\n }\n\n /** Get the CSS class for a score value: high (green), medium (amber), low (gray) */\n public GetScoreClass(score: number): string {\n if (score >= 0.40) return 'suggest-score-high';\n if (score >= 0.15) return 'suggest-score-medium';\n return 'suggest-score-low';\n }\n\n /** Toggle the filter popover */\n public ToggleFilter(event: MouseEvent): void {\n event.stopPropagation();\n event.preventDefault();\n this.IsFilterOpen = !this.IsFilterOpen;\n this.cdr.detectChanges();\n }\n\n /** Set the min relevance and persist */\n public SetMinRelevance(percent: number, event: MouseEvent): void {\n event.stopPropagation();\n event.preventDefault();\n this.MinRelevancePercent = percent;\n this.IsFilterOpen = false;\n this.persistMinRelevanceSetting(percent);\n this.cdr.detectChanges();\n }\n\n /** Whether a given option is the currently selected min relevance */\n public IsSelectedRelevance(percent: number): boolean {\n return this.MinRelevancePercent === percent;\n }\n\n /** Format a relevance option for display */\n public FormatRelevanceOption(percent: number): string {\n return percent === 0 ? 'Show all' : `≥ ${percent}%`;\n }\n\n // --- Private ---\n\n private getNavigableItemCount(): number {\n let count = 0;\n if (this.showRecentSection) {\n count += this.VisibleRecentSearches.length;\n }\n if (this.showRecentRecordsSection) {\n count += this.VisibleRecentRecords.length;\n }\n if (this.showPreviewSection) {\n count += this.VisiblePreviewResults.length;\n if (this.showSeeAllFooter) {\n count += 1; // \"See all\" item\n }\n }\n return count;\n }\n\n private async loadMinRelevanceSetting(): Promise<void> {\n try {\n // Wait for all startup engines (including UserInfoEngine) to finish loading\n MJGlobal.Instance.GetEventListener(true).subscribe( async (event) => {\n switch (event.event) {\n case MJEventType.LoggedIn:\n // Wait for StartupManager to complete before refreshing \n await StartupManager.Instance.Startup();\n\n const engine = UserInfoEngine.Instance;\n const saved = engine.GetSetting(PREVIEW_MIN_RELEVANCE_KEY);\n\n if (saved != null) {\n const parsed = parseInt(saved, 10);\n if (!isNaN(parsed) && parsed >= 0 && parsed <= 100) {\n this.MinRelevancePercent = parsed;\n this.cdr.detectChanges();\n }\n }\n }\n });\n } catch (err) {\n console.error('[SearchSuggest] loadMinRelevanceSetting error:', err);\n }\n }\n\n private persistMinRelevanceSetting(percent: number): void {\n try {\n UserInfoEngine.Instance.SetSettingDebounced(PREVIEW_MIN_RELEVANCE_KEY, String(percent));\n } catch {\n // Silently fail if UserInfoEngine not available\n }\n }\n}\n","@if (IsOpen) {\n <div class=\"suggest-dropdown\">\n @if (IsLoading && showPreviewSection) {\n <div class=\"suggest-loading\">\n <i class=\"fa-solid fa-spinner fa-spin suggest-loading-icon\"></i>\n <span>Searching...</span>\n </div>\n }\n\n <!-- Recent searches section -->\n @if (showRecentSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock suggest-section-icon\"></i>\n <span>Recent Searches</span>\n <button type=\"button\" class=\"suggest-clear-link\" aria-label=\"Clear recent searches\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnClearRecentClick($event)\">Clear</button>\n </div>\n @for (recent of VisibleRecentSearches; track recent.Query; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted(i)\"\n (mousedown)=\"OnRecentClick(recent); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = i\"\n >\n <i class=\"fa-solid fa-clock suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ recent.Query }}</span>\n <span class=\"suggest-item-meta\">{{ recent.ResultCount }} results</span>\n </div>\n }\n </div>\n }\n\n <!-- Recently opened records section (idle state) -->\n @if (showRecentRecordsSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock-rotate-left suggest-section-icon\"></i>\n <span>Recently Opened</span>\n </div>\n @for (record of VisibleRecentRecords; track record.EntityName + record.RecordID; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnRecentRecordClick(record); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <i class=\"fa-solid fa-file-lines suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ record.RecordName }}</span>\n <span class=\"suggest-item-meta\">{{ record.EntityName }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Preview results section -->\n @if (showPreviewSection && !IsLoading) {\n @if (VisiblePreviewResults.length > 0) {\n <div class=\"suggest-section\">\n @for (result of VisiblePreviewResults; track result.ID; let i = $index) {\n <div\n class=\"suggest-item suggest-result-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnResultClick(result); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <div class=\"suggest-result-icon\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"suggest-result-body\">\n <div class=\"suggest-result-title\">{{ result.RecordName || result.Title }}</div>\n <div class=\"suggest-result-entity\">{{ result.EntityName }}</div>\n </div>\n <span class=\"suggest-result-score\" [class]=\"'suggest-result-score ' + GetScoreClass(result.Score)\">{{ FormatScore(result.Score) }}</span>\n </div>\n }\n </div>\n } @else if (!IsLoading) {\n <div class=\"suggest-empty\">\n <span>No results found</span>\n </div>\n }\n\n <!-- See all footer -->\n @if (showSeeAllFooter) {\n <div\n class=\"suggest-see-all\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length)\"\n (mousedown)=\"OnSeeAllClick(); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length\"\n >\n <i class=\"fa-solid fa-arrow-right suggest-see-all-icon\"></i>\n <span>See all {{ TotalCount }} results</span>\n </div>\n }\n }\n\n <!-- Min relevance filter bar -->\n @if (showPreviewSection) {\n <div class=\"suggest-filter-bar\">\n <div class=\"suggest-filter-toggle\" (mousedown)=\"ToggleFilter($event)\">\n <i class=\"fa-solid fa-sliders suggest-filter-icon\"></i>\n <span class=\"suggest-filter-label\">\n @if (MinRelevancePercent > 0) {\n Min relevance: {{ MinRelevancePercent }}%\n } @else {\n Showing all results\n }\n </span>\n <i class=\"fa-solid fa-chevron-down suggest-filter-chevron\" [class.suggest-filter-chevron-open]=\"IsFilterOpen\"></i>\n </div>\n\n @if (IsFilterOpen) {\n <div class=\"suggest-filter-popover\">\n <div class=\"suggest-filter-popover-header\">Min Relevance</div>\n @for (option of MinRelevanceOptions; track option) {\n <div\n class=\"suggest-filter-option\"\n [class.suggest-filter-option-active]=\"IsSelectedRelevance(option)\"\n (mousedown)=\"SetMinRelevance(option, $event)\"\n >\n <span>{{ FormatRelevanceOption(option) }}</span>\n @if (IsSelectedRelevance(option)) {\n <i class=\"fa-solid fa-check suggest-filter-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Empty state when no recents of either kind and query too short -->\n @if (!showRecentSection && !showRecentRecordsSection && !showPreviewSection) {\n <div class=\"suggest-empty\">\n <span>Type at least {{ MinQueryLength }} characters to search</span>\n </div>\n }\n\n <!-- Host-supplied promo row (e.g. command palette nudge) -->\n @if (ShowPromo && PromoText) {\n <div class=\"suggest-promo\">\n <i class=\"fa-solid fa-wand-magic-sparkles suggest-promo-icon\"></i>\n <span class=\"suggest-promo-text\">{{ PromoText }}</span>\n <button type=\"button\" class=\"suggest-promo-action\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoAccept()\">{{ PromoActionLabel }}</button>\n <button type=\"button\" class=\"suggest-promo-dismiss\" aria-label=\"Dismiss\" title=\"Dismiss\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoDismiss(); $event.stopPropagation()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n }\n </div>\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search-suggest.component.js","sourceRoot":"","sources":["../../src/lib/search-suggest.component.ts","../../src/lib/search-suggest.component.html"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Search Suggest Component\n *\n * Autocomplete dropdown showing preview results and recent searches.\n * Supports keyboard navigation (up/down/enter) for accessibility.\n * Designed to be paired with SearchInputComponent via SearchCompositeComponent.\n */\n\nimport {\n Component,\n EventEmitter,\n Input,\n OnInit,\n Output,\n ChangeDetectorRef,\n inject\n} from '@angular/core';\nimport { StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { SearchResultItem, RecentRecordItem } from './search-types';\nimport { RecentSearch } from './search.service';\nimport { MJEventType, MJGlobal } from '@memberjunction/global';\n\n/** User setting key for persisting the preview min relevance threshold */\nconst PREVIEW_MIN_RELEVANCE_KEY = 'search.previewMinRelevance';\n\n/** Preset options for the min relevance filter */\nconst MIN_RELEVANCE_OPTIONS = [0, 15, 30, 50, 70];\n\n@Component({\n standalone: false,\n selector: 'mj-search-suggest',\n templateUrl: './search-suggest.component.html',\n styleUrls: ['./search-suggest.component.css']\n})\nexport class SearchSuggestComponent implements OnInit {\n private cdr = inject(ChangeDetectorRef);\n\n // --- Configuration Inputs ---\n\n /** Whether the suggest dropdown is visible */\n @Input() IsOpen = false;\n\n /** Preview search results to display */\n @Input() PreviewResults: SearchResultItem[] = [];\n\n /** Recent search entries to show when query is empty/short */\n @Input() RecentSearches: RecentSearch[] = [];\n\n /** Total count of results for the current query (may exceed PreviewResults.length) */\n @Input() TotalCount = 0;\n\n /** Whether a search is currently loading */\n @Input() IsLoading = false;\n\n /** Current query text — used to determine whether to show recent vs preview */\n @Input() Query = '';\n\n /** Minimum query length before preview results are shown */\n @Input() MinQueryLength = 2;\n\n /** Maximum number of preview results to display */\n @Input() MaxPreviewResults = 8;\n\n /** Whether to show the recent searches section */\n @Input() ShowRecent = true;\n\n /**\n * Recently opened records (host-loaded, e.g. from SearchService\n * .GetRecentlyOpenedRecords). Rendered as a \"Recently Opened\" section in\n * the idle/empty state so users can jump straight back to a record they\n * just had open — the complement to Recent Searches (queries).\n */\n @Input() RecentRecords: RecentRecordItem[] = [];\n\n /**\n * Optional promo row pinned to the dropdown's bottom edge — a host-supplied\n * nudge (e.g. Explorer advertising its command palette). The component is\n * deliberately ignorant of WHAT is being promoted; the host provides the\n * copy and handles both outcomes.\n */\n @Input() ShowPromo = false;\n\n /** Promo copy (one sentence). Required when ShowPromo is true. */\n @Input() PromoText = '';\n\n /** Label for the promo's accept action. */\n @Input() PromoActionLabel = 'Turn on';\n\n // --- Outputs ---\n\n /** Emitted when the user selects a preview result */\n @Output() ResultSelected = new EventEmitter<SearchResultItem>();\n\n /** Emitted when the user selects a recent search */\n @Output() RecentSelected = new EventEmitter<string>();\n\n /** Emitted when the user clicks \"See all N results\" */\n @Output() SeeAllRequested = new EventEmitter<string>();\n\n /** Emitted when the user clicks \"Clear\" on recent searches */\n @Output() ClearRecentRequested = new EventEmitter<void>();\n\n /** Emitted when the user selects a recently opened record */\n @Output() RecentRecordSelected = new EventEmitter<RecentRecordItem>();\n\n /** Promo accept — carries the current query so the host can hand it off. */\n @Output() PromoAccepted = new EventEmitter<string>();\n\n /** Promo dismissed — the host should persist this and stop showing it. */\n @Output() PromoDismissed = new EventEmitter<void>();\n\n // --- Internal state ---\n\n /** Currently keyboard-highlighted index (-1 for none) */\n public HighlightedIndex = -1;\n\n /** Min relevance percentage (0-100) for filtering preview results */\n public MinRelevancePercent = 0;\n\n /** Whether the filter popover is open */\n public IsFilterOpen = false;\n\n /** Available min relevance presets */\n public readonly MinRelevanceOptions = MIN_RELEVANCE_OPTIONS;\n\n // --- Lifecycle ---\n\n async ngOnInit(): Promise<void> {\n await this.loadMinRelevanceSetting();\n }\n\n // --- Public Methods ---\n\n /** Move keyboard highlight up */\n public NavigateUp(): void {\n const maxIndex = this.getNavigableItemCount() - 1;\n if (maxIndex < 0) return;\n\n this.HighlightedIndex--;\n if (this.HighlightedIndex < 0) {\n this.HighlightedIndex = maxIndex;\n }\n this.cdr.detectChanges();\n }\n\n /** Move keyboard highlight down */\n public NavigateDown(): void {\n const maxIndex = this.getNavigableItemCount() - 1;\n if (maxIndex < 0) return;\n\n this.HighlightedIndex++;\n if (this.HighlightedIndex > maxIndex) {\n this.HighlightedIndex = 0;\n }\n this.cdr.detectChanges();\n }\n\n /** Select the currently highlighted item */\n public SelectHighlighted(): void {\n if (this.HighlightedIndex < 0) return;\n\n if (this.showRecentSection) {\n // Navigating recent searches\n const visibleRecent = this.VisibleRecentSearches;\n if (this.HighlightedIndex < visibleRecent.length) {\n this.OnRecentClick(visibleRecent[this.HighlightedIndex]);\n return;\n }\n }\n\n if (this.showRecentRecordsSection) {\n // Navigating recently opened records (indexes continue after recents)\n const offset = this.showRecentSection ? this.VisibleRecentSearches.length : 0;\n const recordIndex = this.HighlightedIndex - offset;\n const visibleRecords = this.VisibleRecentRecords;\n if (recordIndex >= 0 && recordIndex < visibleRecords.length) {\n this.OnRecentRecordClick(visibleRecords[recordIndex]);\n return;\n }\n }\n\n if (this.showPreviewSection) {\n // Navigating preview results\n const offset = this.showRecentSection ? this.VisibleRecentSearches.length : 0;\n const resultIndex = this.HighlightedIndex - offset;\n const visibleResults = this.VisiblePreviewResults;\n if (resultIndex >= 0 && resultIndex < visibleResults.length) {\n this.OnResultClick(visibleResults[resultIndex]);\n return;\n }\n\n // \"See all\" footer\n if (resultIndex === visibleResults.length && this.showSeeAllFooter) {\n this.OnSeeAllClick();\n }\n }\n }\n\n /** Reset the highlighted index */\n public ResetHighlight(): void {\n this.HighlightedIndex = -1;\n }\n\n /** Number of keyboard-navigable rows currently visible (recents + recently\n opened + preview results + see-all). Public so the composite's Tab\n handling knows when the highlight has walked off the end of the list. */\n public get NavigableItemCount(): number {\n return this.getNavigableItemCount();\n }\n\n // --- Template getters ---\n\n /** Whether to show recent searches section */\n public get showRecentSection(): boolean {\n return this.ShowRecent && this.Query.length < this.MinQueryLength && this.RecentSearches.length > 0;\n }\n\n /** Whether to show preview results section */\n public get showPreviewSection(): boolean {\n return this.Query.length >= this.MinQueryLength;\n }\n\n /** Whether to show the recently-opened-records section (idle state only) */\n public get showRecentRecordsSection(): boolean {\n return this.Query.length < this.MinQueryLength && this.RecentRecords.length > 0;\n }\n\n /** Visible recently opened records (capped at 3) */\n public get VisibleRecentRecords(): RecentRecordItem[] {\n return this.RecentRecords.slice(0, 3);\n }\n\n /** Whether to show the \"See all N results\" footer */\n public get showSeeAllFooter(): boolean {\n return this.showPreviewSection && this.TotalCount > 0;\n }\n\n /** Visible recent searches (capped at 5) */\n public get VisibleRecentSearches(): RecentSearch[] {\n return this.RecentSearches.slice(0, 5);\n }\n\n /** Visible preview results filtered by min relevance, sorted by score descending, capped at MaxPreviewResults */\n public get VisiblePreviewResults(): SearchResultItem[] {\n const minScore = this.MinRelevancePercent / 100;\n return [...this.PreviewResults]\n .filter(r => r.Score >= minScore)\n .sort((a, b) => b.Score - a.Score)\n .slice(0, this.MaxPreviewResults);\n }\n\n // --- Template event handlers ---\n\n /** Handle clicking a preview result */\n public OnResultClick(result: SearchResultItem): void {\n this.ResultSelected.emit(result);\n }\n\n /** Handle clicking a recent search */\n public OnRecentClick(recent: RecentSearch): void {\n this.RecentSelected.emit(recent.Query);\n }\n\n /** Handle clicking a recently opened record */\n public OnRecentRecordClick(record: RecentRecordItem): void {\n this.RecentRecordSelected.emit(record);\n }\n\n /** Handle clicking \"See all\" */\n public OnSeeAllClick(): void {\n this.SeeAllRequested.emit(this.Query);\n }\n\n /** Promo accept — fired on click so keyboard Enter/Space work too; the\n template's mousedown-preventDefault keeps the input from blurring first. */\n public OnPromoAccept(): void {\n this.PromoAccepted.emit(this.Query);\n }\n\n /** Promo dismiss. */\n public OnPromoDismiss(): void {\n this.PromoDismissed.emit();\n }\n\n /** \"Clear\" on recent searches — click-driven so keyboard Enter/Space work;\n the template's mousedown-preventDefault keeps the input from blurring. */\n public OnClearRecentClick(event: MouseEvent): void {\n event.stopPropagation();\n this.ClearRecentRequested.emit();\n }\n\n /** Check if an item at the given index is highlighted */\n public IsHighlighted(index: number): boolean {\n return this.HighlightedIndex === index;\n }\n\n /** Format a score value as a percentage */\n public FormatScore(score: number): string {\n return `${Math.round(score * 100)}%`;\n }\n\n /** Get the CSS class for a score value: high (green), medium (amber), low (gray) */\n public GetScoreClass(score: number): string {\n if (score >= 0.40) return 'suggest-score-high';\n if (score >= 0.15) return 'suggest-score-medium';\n return 'suggest-score-low';\n }\n\n /** Toggle the filter popover */\n public ToggleFilter(event: MouseEvent): void {\n event.stopPropagation();\n event.preventDefault();\n this.IsFilterOpen = !this.IsFilterOpen;\n this.cdr.detectChanges();\n }\n\n /** Set the min relevance and persist */\n public SetMinRelevance(percent: number, event: MouseEvent): void {\n event.stopPropagation();\n event.preventDefault();\n this.MinRelevancePercent = percent;\n this.IsFilterOpen = false;\n this.persistMinRelevanceSetting(percent);\n this.cdr.detectChanges();\n }\n\n /** Whether a given option is the currently selected min relevance */\n public IsSelectedRelevance(percent: number): boolean {\n return this.MinRelevancePercent === percent;\n }\n\n /** Format a relevance option for display */\n public FormatRelevanceOption(percent: number): string {\n return percent === 0 ? 'Show all' : `≥ ${percent}%`;\n }\n\n // --- Private ---\n\n private getNavigableItemCount(): number {\n let count = 0;\n if (this.showRecentSection) {\n count += this.VisibleRecentSearches.length;\n }\n if (this.showRecentRecordsSection) {\n count += this.VisibleRecentRecords.length;\n }\n if (this.showPreviewSection) {\n count += this.VisiblePreviewResults.length;\n if (this.showSeeAllFooter) {\n count += 1; // \"See all\" item\n }\n }\n return count;\n }\n\n private async loadMinRelevanceSetting(): Promise<void> {\n try {\n // Wait for all startup engines (including UserInfoEngine) to finish loading\n MJGlobal.Instance.GetEventListener(true).subscribe( async (event) => {\n switch (event.event) {\n case MJEventType.LoggedIn:\n // Wait for StartupManager to complete before refreshing \n await StartupManager.Instance.Startup();\n\n const engine = UserInfoEngine.Instance;\n const saved = engine.GetSetting(PREVIEW_MIN_RELEVANCE_KEY);\n\n if (saved != null) {\n const parsed = parseInt(saved, 10);\n if (!isNaN(parsed) && parsed >= 0 && parsed <= 100) {\n this.MinRelevancePercent = parsed;\n this.cdr.detectChanges();\n }\n }\n }\n });\n } catch (err) {\n console.error('[SearchSuggest] loadMinRelevanceSetting error:', err);\n }\n }\n\n private persistMinRelevanceSetting(percent: number): void {\n try {\n UserInfoEngine.Instance.SetSettingDebounced(PREVIEW_MIN_RELEVANCE_KEY, String(percent));\n } catch {\n // Silently fail if UserInfoEngine not available\n }\n }\n}\n","@if (IsOpen) {\n <div class=\"suggest-dropdown\">\n @if (IsLoading && showPreviewSection) {\n <div class=\"suggest-loading\">\n <i class=\"fa-solid fa-spinner fa-spin suggest-loading-icon\"></i>\n <span>Searching...</span>\n </div>\n }\n\n <!-- Recent searches section -->\n @if (showRecentSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock suggest-section-icon\"></i>\n <span>Recent Searches</span>\n <button type=\"button\" class=\"suggest-clear-link\" aria-label=\"Clear recent searches\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnClearRecentClick($event)\">Clear</button>\n </div>\n @for (recent of VisibleRecentSearches; track recent.Query; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted(i)\"\n (mousedown)=\"OnRecentClick(recent); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = i\"\n >\n <i class=\"fa-solid fa-clock suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ recent.Query }}</span>\n <span class=\"suggest-item-meta\">{{ recent.ResultCount }} results</span>\n </div>\n }\n </div>\n }\n\n <!-- Recently opened records section (idle state) -->\n @if (showRecentRecordsSection) {\n <div class=\"suggest-section\">\n <div class=\"suggest-section-header\">\n <i class=\"fa-solid fa-clock-rotate-left suggest-section-icon\"></i>\n <span>Recently Opened</span>\n </div>\n @for (record of VisibleRecentRecords; track record.EntityName + record.RecordID; let i = $index) {\n <div\n class=\"suggest-item suggest-recent-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnRecentRecordClick(record); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <i class=\"fa-solid fa-file-lines suggest-item-icon\"></i>\n <span class=\"suggest-item-text\">{{ record.RecordName }}</span>\n <span class=\"suggest-item-meta\">{{ record.EntityDisplayName || record.EntityName }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Preview results section -->\n @if (showPreviewSection && !IsLoading) {\n @if (VisiblePreviewResults.length > 0) {\n <div class=\"suggest-section\">\n @for (result of VisiblePreviewResults; track result.ID; let i = $index) {\n <div\n class=\"suggest-item suggest-result-item\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + i)\"\n (mousedown)=\"OnResultClick(result); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + i\"\n >\n <div class=\"suggest-result-icon\">\n <i [class]=\"result.EntityIcon || result.SourceIcon || 'fa-solid fa-database'\"></i>\n </div>\n <div class=\"suggest-result-body\">\n <div class=\"suggest-result-title\">{{ result.RecordName || result.Title }}</div>\n <div class=\"suggest-result-entity\">{{ result.EntityDisplayName || result.EntityName }}</div>\n </div>\n <span class=\"suggest-result-score\" [class]=\"'suggest-result-score ' + GetScoreClass(result.Score)\">{{ FormatScore(result.Score) }}</span>\n </div>\n }\n </div>\n } @else if (!IsLoading) {\n <div class=\"suggest-empty\">\n <span>No results found</span>\n </div>\n }\n\n <!-- See all footer -->\n @if (showSeeAllFooter) {\n <div\n class=\"suggest-see-all\"\n [class.suggest-item-highlighted]=\"IsHighlighted((showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length)\"\n (mousedown)=\"OnSeeAllClick(); $event.preventDefault()\"\n (mouseenter)=\"HighlightedIndex = (showRecentSection ? VisibleRecentSearches.length : 0) + VisiblePreviewResults.length\"\n >\n <i class=\"fa-solid fa-arrow-right suggest-see-all-icon\"></i>\n <span>See all {{ TotalCount }} results</span>\n </div>\n }\n }\n\n <!-- Min relevance filter bar -->\n @if (showPreviewSection) {\n <div class=\"suggest-filter-bar\">\n <div class=\"suggest-filter-toggle\" (mousedown)=\"ToggleFilter($event)\">\n <i class=\"fa-solid fa-sliders suggest-filter-icon\"></i>\n <span class=\"suggest-filter-label\">\n @if (MinRelevancePercent > 0) {\n Min relevance: {{ MinRelevancePercent }}%\n } @else {\n Showing all results\n }\n </span>\n <i class=\"fa-solid fa-chevron-down suggest-filter-chevron\" [class.suggest-filter-chevron-open]=\"IsFilterOpen\"></i>\n </div>\n\n @if (IsFilterOpen) {\n <div class=\"suggest-filter-popover\">\n <div class=\"suggest-filter-popover-header\">Min Relevance</div>\n @for (option of MinRelevanceOptions; track option) {\n <div\n class=\"suggest-filter-option\"\n [class.suggest-filter-option-active]=\"IsSelectedRelevance(option)\"\n (mousedown)=\"SetMinRelevance(option, $event)\"\n >\n <span>{{ FormatRelevanceOption(option) }}</span>\n @if (IsSelectedRelevance(option)) {\n <i class=\"fa-solid fa-check suggest-filter-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Empty state when no recents of either kind and query too short -->\n @if (!showRecentSection && !showRecentRecordsSection && !showPreviewSection) {\n <div class=\"suggest-empty\">\n <span>Type at least {{ MinQueryLength }} characters to search</span>\n </div>\n }\n\n <!-- Host-supplied promo row (e.g. command palette nudge) -->\n @if (ShowPromo && PromoText) {\n <div class=\"suggest-promo\">\n <i class=\"fa-solid fa-wand-magic-sparkles suggest-promo-icon\"></i>\n <span class=\"suggest-promo-text\">{{ PromoText }}</span>\n <button type=\"button\" class=\"suggest-promo-action\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoAccept()\">{{ PromoActionLabel }}</button>\n <button type=\"button\" class=\"suggest-promo-dismiss\" aria-label=\"Dismiss\" title=\"Dismiss\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"OnPromoDismiss(); $event.stopPropagation()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n }\n </div>\n}\n"]}
|
|
@@ -14,6 +14,8 @@ export interface SearchResultItem {
|
|
|
14
14
|
Snippet: string;
|
|
15
15
|
/** The entity name this result belongs to */
|
|
16
16
|
EntityName: string;
|
|
17
|
+
/** Human-readable display name for the entity (e.g., "People" instead of "MJ_BizApps_Common: People") */
|
|
18
|
+
EntityDisplayName?: string;
|
|
17
19
|
/** The primary key of the source record */
|
|
18
20
|
RecordID: string;
|
|
19
21
|
/** Classification of the result source */
|
|
@@ -189,6 +191,8 @@ export interface SearchExecutedEvent {
|
|
|
189
191
|
export interface RecentRecordItem {
|
|
190
192
|
/** Entity name (e.g. 'MJ: AI Prompt Runs') */
|
|
191
193
|
EntityName: string;
|
|
194
|
+
/** Human-readable display name for the entity (e.g., 'AI Prompt Runs') */
|
|
195
|
+
EntityDisplayName?: string;
|
|
192
196
|
/** Record primary key value */
|
|
193
197
|
RecordID: string;
|
|
194
198
|
/** Resolved display name (falls back to the ID when resolution fails) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search-types.d.ts","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */\n ResultType?: string;\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n /** Raw vector metadata JSON — contains all entity fields stored in the vector DB */\n RawMetadata?: string;\n /** ID of the SearchProvider metadata record that produced this result */\n ProviderId?: string;\n /** Display label from the SearchProvider metadata (e.g., \"Database\", \"Semantic Search\") */\n ProviderLabel?: string;\n /** Font Awesome icon class from the SearchProvider metadata */\n ProviderIcon?: string;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n /**\n * Optional array of `MJ: Search Scopes` record IDs selected in the UI scope selector.\n * When provided, the server resolves each scope and runs only those constrained providers.\n * When omitted (or all selected scopes are `IsGlobal`), behavior is equivalent to the\n * pre-scope unconstrained search.\n */\n ScopeIDs?: string[];\n}\n\n/** Lightweight scope metadata returned by the `SearchScopes` GraphQL query. */\nexport interface SearchScopeInfo {\n ID: string;\n Name: string;\n Description?: string;\n Icon?: string;\n IsGlobal: boolean;\n IsDefault: boolean;\n /** True when the scope has an OwnerUserID — renders as a personal scope in the UI. */\n IsPersonal: boolean;\n}\n\n/** Metadata about an active search provider from the server */\nexport interface SearchProviderInfo {\n /** SearchProvider record ID */\n ID: string;\n /** Provider name */\n Name: string;\n /** UI display label */\n DisplayName: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The SourceType key this provider uses */\n SourceType: string;\n /** Priority (lower = higher) */\n Priority: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number; Storage: number };\n /** Active search providers metadata (for UI labels and icons) */\n Providers: SearchProviderInfo[];\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n\n/** A recently opened record (from MJ: User Record Logs) surfaced in search empty states */\nexport interface RecentRecordItem {\n /** Entity name (e.g. 'MJ: AI Prompt Runs') */\n EntityName: string;\n /** Record primary key value */\n RecordID: string;\n /** Resolved display name (falls back to the ID when resolution fails) */\n RecordName: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search-types.js","sourceRoot":"","sources":["../../src/lib/search-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * @fileoverview Shared types for the ng-search package.\n *\n * Defines the data contracts for search results, filters,\n * and events used across all search components.\n */\n\n/** Represents a single search result item */\nexport interface SearchResultItem {\n /** Unique identifier */\n ID: string;\n /** Display title */\n Title: string;\n /** Short text snippet showing match context */\n Snippet: string;\n /** The entity name this result belongs to */\n EntityName: string;\n /** Human-readable display name for the entity (e.g., \"People\" instead of \"MJ_BizApps_Common: People\") */\n EntityDisplayName?: string;\n /** The primary key of the source record */\n RecordID: string;\n /** Classification of the result source */\n SourceType: 'entity' | 'content-item' | 'file' | 'web-page';\n /** Discriminator for UI rendering: 'entity-record', 'storage-file', or 'content-item' */\n ResultType?: string;\n /** Relevance score between 0 and 1 */\n Score: number;\n /** Breakdown of how the score was computed */\n ScoreBreakdown: ScoreBreakdown;\n /** Human-readable tags */\n Tags: string[];\n /** Font Awesome icon class for the source type */\n SourceIcon: string;\n /** Entity-specific icon from EntityInfo.Icon (e.g., \"fa-solid fa-robot\") */\n EntityIcon?: string;\n /** Human-readable record name resolved from EntityRecordName */\n RecordName?: string;\n /** When this result was matched */\n MatchedAt: Date;\n /** Raw vector metadata JSON — contains all entity fields stored in the vector DB */\n RawMetadata?: string;\n /** ID of the SearchProvider metadata record that produced this result */\n ProviderId?: string;\n /** Display label from the SearchProvider metadata (e.g., \"Database\", \"Semantic Search\") */\n ProviderLabel?: string;\n /** Font Awesome icon class from the SearchProvider metadata */\n ProviderIcon?: string;\n}\n\n/** Breakdown of score contribution from each search source */\nexport interface ScoreBreakdown {\n Vector?: number;\n FullText?: number;\n Entity?: number;\n}\n\n/** Grouped search results by source type */\nexport interface SearchResultGroup {\n /** The source type label */\n Label: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The source type key */\n SourceType: string;\n /** Results in this group */\n Results: SearchResultItem[];\n /** Total count for this source (may exceed Results.length if paginated) */\n TotalCount: number;\n}\n\n/** Faceted filter configuration */\nexport interface SearchFilter {\n /** Filter category name */\n Category: string;\n /** Filter options within this category */\n Options: SearchFilterOption[];\n /** Whether multiple options can be selected */\n MultiSelect: boolean;\n}\n\n/** Single filter option within a category */\nexport interface SearchFilterOption {\n /** Display label */\n Label: string;\n /** Value to filter by */\n Value: string;\n /** Count of results matching this option */\n Count: number;\n /** Whether this option is currently selected */\n IsSelected: boolean;\n /** Font Awesome icon class */\n Icon?: string;\n}\n\n/** Search request parameters */\nexport interface SearchRequest {\n /** The search query string */\n Query: string;\n /** Maximum number of results to return */\n MaxResults: number;\n /** Active filter selections */\n ActiveFilters: Record<string, string[]>;\n /** Which search sources to include */\n IncludeSources: ('vector' | 'fulltext' | 'entity' | 'storage')[];\n /**\n * Minimum relevance score (0-1) to include in results.\n * Results below this threshold are filtered out.\n * Default: 0.35 (35%).\n */\n MinScore?: number;\n /**\n * Optional array of `MJ: Search Scopes` record IDs selected in the UI scope selector.\n * When provided, the server resolves each scope and runs only those constrained providers.\n * When omitted (or all selected scopes are `IsGlobal`), behavior is equivalent to the\n * pre-scope unconstrained search.\n */\n ScopeIDs?: string[];\n}\n\n/** Lightweight scope metadata returned by the `SearchScopes` GraphQL query. */\nexport interface SearchScopeInfo {\n ID: string;\n Name: string;\n Description?: string;\n Icon?: string;\n IsGlobal: boolean;\n IsDefault: boolean;\n /** True when the scope has an OwnerUserID — renders as a personal scope in the UI. */\n IsPersonal: boolean;\n}\n\n/** Metadata about an active search provider from the server */\nexport interface SearchProviderInfo {\n /** SearchProvider record ID */\n ID: string;\n /** Provider name */\n Name: string;\n /** UI display label */\n DisplayName: string;\n /** Font Awesome icon class */\n Icon: string;\n /** The SourceType key this provider uses */\n SourceType: string;\n /** Priority (lower = higher) */\n Priority: number;\n}\n\n/** Search response from the service */\nexport interface SearchResponse {\n /** Whether the search succeeded */\n Success: boolean;\n /** The search results */\n Results: SearchResultItem[];\n /** Grouped results by source type */\n Groups: SearchResultGroup[];\n /** Available filters based on results */\n Filters: SearchFilter[];\n /** Total result count across all sources */\n TotalCount: number;\n /** Time taken in milliseconds */\n ElapsedMs: number;\n /** Count of results from each source */\n SourceCounts: { Vector: number; FullText: number; Entity: number; Storage: number };\n /** Active search providers metadata (for UI labels and icons) */\n Providers: SearchProviderInfo[];\n /** Error message if Success is false */\n ErrorMessage?: string;\n}\n\n/** Event emitted when a search result is selected */\nexport interface SearchResultSelectedEvent {\n /** The selected result item */\n Result: SearchResultItem;\n /** Whether to open in a new tab */\n OpenInNewTab: boolean;\n}\n\n/** Event emitted when filters change */\nexport interface SearchFilterChangeEvent {\n /** The filter category that changed */\n Category: string;\n /** The new selected values */\n SelectedValues: string[];\n}\n\n/** Event emitted when search is executed */\nexport interface SearchExecutedEvent {\n /** The search query */\n Query: string;\n /** Active filters */\n Filters: Record<string, string[]>;\n /** Number of results returned */\n ResultCount: number;\n /** Time taken in ms */\n ElapsedMs: number;\n}\n\n/** A recently opened record (from MJ: User Record Logs) surfaced in search empty states */\nexport interface RecentRecordItem {\n /** Entity name (e.g. 'MJ: AI Prompt Runs') */\n EntityName: string;\n /** Human-readable display name for the entity (e.g., 'AI Prompt Runs') */\n EntityDisplayName?: string;\n /** Record primary key value */\n RecordID: string;\n /** Resolved display name (falls back to the ID when resolution fails) */\n RecordName: string;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,OAAO,EAAc,MAAM,MAAM,CAAC;AAExE,OAAO,EAAgB,iBAAiB,EAA4B,MAAM,sBAAsB,CAAC;AASjG,OAAO,EACH,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EAGZ,eAAe,EACf,gBAAgB,EACnB,MAAM,gBAAgB,CAAC;;AA8BxB,sCAAsC;AACtC,MAAM,WAAW,YAAY;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACvB;AASD;;;GAGG;AACH,qBACa,aAAa;IACtB,OAAO,CAAC,SAAS,CAAkC;IACnD,IAAW,QAAQ,IAAI,iBAAiB,CAEvC;IACD,IAAW,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,EAElD;IAED,8BAA8B;IACvB,cAAc,yCAAoD;IAEzE,sCAAsC;IAC/B,YAAY,2BAAuC;IAE1D,2EAA2E;IACpE,eAAe,kCAA2C;IAEjE,mBAAmB;IACZ,OAAO,kBAAyB;IAEvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAM;IACxC,OAAO,CAAC,oBAAoB,CAAS;IAErC,oFAAoF;IACpF,OAAO,CAAC,qBAAqB,CAAyC;IAEtE,0FAA0F;IACnF,OAAO,qCAA8C;IAC5D,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,kBAAkB,CAA2C;IAErE;;OAEG;IACU,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IAuB3E;;;;;;;;;;;;;;;;;OAiBG;IACI,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,UAAU,CAAC;QACpD,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;QAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IAuBF;;;;OAIG;IACU,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,MAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAoB1F,uCAAuC;IAChC,YAAY,IAAI,IAAI;IAI3B,yDAAyD;IAClD,mBAAmB,IAAI,IAAI;IAKlC;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,EAAE;IA0BrE;;OAEG;IACI,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,YAAY,EAAE;IAShE,2CAA2C;IACpC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAIhD,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,qBAAqB;
|
|
1
|
+
{"version":3,"file":"search.service.d.ts","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -227,17 +227,24 @@ export class SearchService {
|
|
|
227
227
|
fileCount++;
|
|
228
228
|
}
|
|
229
229
|
else {
|
|
230
|
-
|
|
230
|
+
const existing = counts.get(r.EntityName);
|
|
231
|
+
const displayName = r.EntityDisplayName || r.EntityName;
|
|
232
|
+
if (existing) {
|
|
233
|
+
existing.count++;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
counts.set(r.EntityName, { count: 1, displayName, icon: r.EntityIcon });
|
|
237
|
+
}
|
|
231
238
|
}
|
|
232
239
|
}
|
|
233
240
|
const options = Array.from(counts.entries())
|
|
234
|
-
.sort((a, b) => b[1] - a[1])
|
|
235
|
-
.map(([name,
|
|
236
|
-
Label:
|
|
241
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
242
|
+
.map(([name, data]) => ({
|
|
243
|
+
Label: data.displayName,
|
|
237
244
|
Value: name,
|
|
238
|
-
Count: count,
|
|
245
|
+
Count: data.count,
|
|
239
246
|
IsSelected: false,
|
|
240
|
-
Icon: 'fa-solid fa-table'
|
|
247
|
+
Icon: data.icon ?? 'fa-solid fa-table'
|
|
241
248
|
}));
|
|
242
249
|
// Add a single "Files" entry for all storage file results
|
|
243
250
|
if (fileCount > 0) {
|
|
@@ -367,11 +374,21 @@ export class SearchService {
|
|
|
367
374
|
// name lookup is cosmetic — IDs are an acceptable fallback
|
|
368
375
|
}
|
|
369
376
|
}
|
|
370
|
-
return logs.map((log) =>
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
377
|
+
return logs.map((log) => {
|
|
378
|
+
let entityInfo;
|
|
379
|
+
try {
|
|
380
|
+
entityInfo = this.Provider?.EntityByName ? this.Provider.EntityByName(log.Entity) : undefined;
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
// Non-fatal if metadata cache is uninitialized
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
EntityName: log.Entity,
|
|
387
|
+
EntityDisplayName: entityInfo?.DisplayName || log.Entity,
|
|
388
|
+
RecordID: log.RecordID,
|
|
389
|
+
RecordName: this.recentRecordNameCache.get(`${log.Entity}||${log.RecordID}`) ?? log.RecordID,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
375
392
|
}
|
|
376
393
|
catch {
|
|
377
394
|
return [];
|
|
@@ -484,19 +501,37 @@ export class SearchService {
|
|
|
484
501
|
}
|
|
485
502
|
/** Map a single SearchClientResultItem to a SearchResultItem */
|
|
486
503
|
mapClientResultItem(r) {
|
|
504
|
+
let entityDisplayName = r.EntityDisplayName;
|
|
505
|
+
let entityIcon = r.EntityIcon;
|
|
506
|
+
try {
|
|
507
|
+
const entity = this.Provider?.EntityByName?.(r.EntityName);
|
|
508
|
+
if (!entityDisplayName && entity) {
|
|
509
|
+
entityDisplayName = entity.DisplayName || entity.Name;
|
|
510
|
+
}
|
|
511
|
+
if (!entityIcon && entity?.Icon) {
|
|
512
|
+
entityIcon = entity.Icon;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
// Non-fatal if metadata cache is uninitialized (e.g. in unit tests)
|
|
517
|
+
}
|
|
518
|
+
if (!entityDisplayName) {
|
|
519
|
+
entityDisplayName = r.EntityName;
|
|
520
|
+
}
|
|
487
521
|
return {
|
|
488
522
|
ID: r.ID,
|
|
489
523
|
Title: r.RecordName || r.Title,
|
|
490
524
|
Snippet: r.Snippet,
|
|
491
525
|
EntityName: r.EntityName,
|
|
526
|
+
EntityDisplayName: entityDisplayName,
|
|
492
527
|
RecordID: r.RecordID,
|
|
493
528
|
SourceType: r.SourceType || 'entity',
|
|
494
529
|
ResultType: r.ResultType,
|
|
495
530
|
Score: r.Score,
|
|
496
531
|
ScoreBreakdown: r.ScoreBreakdown ?? {},
|
|
497
532
|
Tags: r.Tags ?? [],
|
|
498
|
-
SourceIcon:
|
|
499
|
-
EntityIcon:
|
|
533
|
+
SourceIcon: entityIcon || r.ProviderIcon || FALLBACK_SOURCE_ICONS[r.SourceType] || 'fa-solid fa-database',
|
|
534
|
+
EntityIcon: entityIcon,
|
|
500
535
|
RecordName: r.RecordName,
|
|
501
536
|
MatchedAt: new Date(r.MatchedAt),
|
|
502
537
|
RawMetadata: r.RawMetadata,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Search service that connects to the UnifiedSearchService via GraphQL.\n *\n * Provides a reactive interface for executing searches, managing search history,\n * and transforming raw search responses into display-ready grouped results.\n */\n\nimport { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, Subject, throwError } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { CompositeKey, IMetadataProvider, Metadata, StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { MJEventType, MJGlobal } from '@memberjunction/global';\nimport {\n GraphQLSearchClient,\n GraphQLDataProvider,\n SearchClientResponse,\n SearchClientResultItem\n} from '@memberjunction/graphql-dataprovider';\nimport {\n SearchRequest,\n SearchResponse,\n SearchResultItem,\n SearchResultGroup,\n SearchFilter,\n SearchFilterOption,\n SearchProviderInfo,\n SearchScopeInfo,\n RecentRecordItem\n} from './search-types';\n\n/** Default minimum relevance score threshold (0-1). Results below this are filtered out. */\nconst DEFAULT_MIN_SCORE = 0.35;\n\n/** Fallback icon mapping for source types (used when provider metadata is not available) */\nconst FALLBACK_SOURCE_ICONS: Record<string, string> = {\n 'entity': 'fa-solid fa-database',\n 'vector': 'fa-solid fa-brain',\n 'fulltext': 'fa-solid fa-magnifying-glass',\n 'content-item': 'fa-solid fa-file-lines',\n 'file': 'fa-solid fa-file',\n 'web-page': 'fa-solid fa-globe',\n 'storage': 'fa-solid fa-folder-open',\n};\n\n/** Fallback labels for source types (used when provider metadata is not available) */\nconst FALLBACK_SOURCE_LABELS: Record<string, string> = {\n 'entity': 'Database',\n 'vector': 'Semantic Search',\n 'fulltext': 'Full-Text Search',\n 'content-item': 'Content Items',\n 'file': 'Documents',\n 'web-page': 'Web Pages',\n 'storage': 'File Storage',\n};\n\n/** User setting key for persisting recent searches */\nconst RECENT_SEARCHES_KEY = 'search.recentSearches';\n\n/** Recent search entry for history */\nexport interface RecentSearch {\n Query: string;\n Timestamp: Date;\n ResultCount: number;\n}\n\n/** JSON-safe shape for persistence (Date → ISO string) */\ninterface RecentSearchJson {\n Query: string;\n Timestamp: string;\n ResultCount: number;\n}\n\n/**\n * Multi-provider note: callers under a non-default provider should set\n * `service.Provider = component.ProviderToUse` before invoking any methods.\n */\n@Injectable({ providedIn: 'root' })\nexport class SearchService {\n private _provider: IMetadataProvider | null = null;\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n /** Current search response */\n public SearchResults$ = new BehaviorSubject<SearchResponse | null>(null);\n\n /** Whether a search is in progress */\n public IsSearching$ = new BehaviorSubject<boolean>(false);\n\n /** Recent search history (persisted via UserInfoEngine, max 20 entries) */\n public RecentSearches$ = new BehaviorSubject<RecentSearch[]>([]);\n\n /** Error stream */\n public Errors$ = new Subject<string>();\n\n private readonly maxRecentSearches = 20;\n private recentSearchesLoaded = false;\n\n /** Cached provider metadata keyed by SourceType, populated from search responses */\n private providersBySourceType = new Map<string, SearchProviderInfo>();\n\n /** Cached list of scopes the current user can see. Populated lazily by `LoadScopes()`. */\n public Scopes$ = new BehaviorSubject<SearchScopeInfo[]>([]);\n private scopesLoaded = false;\n private scopesLoadInFlight: Promise<SearchScopeInfo[]> | null = null;\n\n /**\n * Execute a search request via the SearchKnowledge GraphQL mutation.\n */\n public async ExecuteSearch(request: SearchRequest): Promise<SearchResponse> {\n this.IsSearching$.next(true);\n const startTime = Date.now();\n\n try {\n const response = await this.executeGraphQLSearch(request);\n response.ElapsedMs = Date.now() - startTime;\n\n this.SearchResults$.next(response);\n this.addToRecentSearches(request.Query, response.TotalCount);\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Search failed';\n this.Errors$.next(errorMessage);\n const errorResponse = this.createEmptyResponse(errorMessage);\n this.SearchResults$.next(errorResponse);\n return errorResponse;\n } finally {\n this.IsSearching$.next(false);\n }\n }\n\n /**\n * Phase 2C streaming search. Returns an Observable that emits one\n * notification per provider as it returns, plus a 'fused' / 'reranked'\n * / 'final' terminal event. Same auth + permission behavior as\n * ExecuteSearch — denials surface as an error on the Observable.\n *\n * Consumers (typically the search composite component) should:\n * - render skeleton rows immediately when the Observable is created\n * - on each 'provider' event: append the rows for that source\n * - on 'final': replace the partials with the canonical fused list\n * - on error: show the failure message + clear the skeleton\n *\n * Returns rxjs Observable rather than awaiting a Promise — callers\n * decide whether to subscribe + populate state, or unsubscribe on\n * navigation. The service does NOT update its own SearchResults$\n * subject for streaming calls; the caller owns the partial-render\n * state model.\n */\n public StreamSearch(request: SearchRequest): Observable<{\n Phase: string;\n ProviderName?: string;\n Results?: SearchResultItem[];\n ElapsedMs?: number;\n ErrorMessage?: string;\n }> {\n const provider = Metadata.Provider; // global-provider-ok: streaming search service consumes the GraphQL channel of the global default provider\n if (!(provider instanceof GraphQLDataProvider)) {\n return throwError(() => new Error('GraphQL provider not available'));\n }\n const client = new GraphQLSearchClient(provider);\n const filters = this.buildClientFilters(request);\n const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;\n return client.StreamSearch({\n Query: request.Query,\n MaxResults: request.MaxResults || 20,\n MinScore: minScore > 0 ? minScore : undefined,\n Filters: filters,\n ScopeIDs: request.ScopeIDs && request.ScopeIDs.length > 0 ? request.ScopeIDs : undefined,\n }).pipe(map((ev) => ({\n Phase: ev.Phase,\n ProviderName: ev.ProviderName,\n Results: ev.Results ? ev.Results.map(r => this.mapClientResultItem(r)) : undefined,\n ElapsedMs: ev.ElapsedMs,\n ErrorMessage: ev.ErrorMessage,\n })));\n }\n\n /**\n * Lightweight preview search for autocomplete/typeahead.\n * Does NOT update SearchResults$ or IsSearching$ observables,\n * and does NOT add to recent search history.\n */\n public async PreviewSearch(query: string, maxResults: number = 8): Promise<SearchResponse> {\n try {\n if (!query.trim()) {\n return this.createEmptyResponse();\n }\n\n const provider = this.Provider;\n if (!(provider instanceof GraphQLDataProvider)) {\n return this.createEmptyResponse('GraphQL provider not available');\n }\n\n const client = new GraphQLSearchClient(provider);\n const clientResponse = await client.PreviewSearch(query, maxResults);\n return this.mapClientResponseToSearchResponse(clientResponse);\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Preview search failed';\n return this.createEmptyResponse(errorMessage);\n }\n }\n\n /** Clear the current search results */\n public ClearResults(): void {\n this.SearchResults$.next(null);\n }\n\n /** Clear recent search history (memory and persisted) */\n public ClearRecentSearches(): void {\n this.RecentSearches$.next([]);\n this.persistRecentSearches([]);\n }\n\n /**\n * Group flat results by source type for display.\n */\n public GroupResults(results: SearchResultItem[]): SearchResultGroup[] {\n const groupMap = new Map<string, SearchResultItem[]>();\n\n for (const result of results) {\n const key = result.SourceType;\n const existing = groupMap.get(key);\n if (existing) {\n existing.push(result);\n } else {\n groupMap.set(key, [result]);\n }\n }\n\n return Array.from(groupMap.entries()).map(([sourceType, items]) => {\n // Priority: cached provider metadata > per-result metadata > fallback maps\n const cached = this.providersBySourceType.get(sourceType);\n return {\n Label: cached?.DisplayName ?? items[0]?.ProviderLabel ?? FALLBACK_SOURCE_LABELS[sourceType] ?? sourceType,\n Icon: cached?.Icon ?? items[0]?.ProviderIcon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle',\n SourceType: sourceType,\n Results: items,\n TotalCount: items.length\n };\n });\n }\n\n /**\n * Build filter facets from a set of results.\n */\n public BuildFilters(results: SearchResultItem[]): SearchFilter[] {\n return [\n this.buildSourceTypeFilter(results),\n this.buildEntityNameFilter(results),\n this.buildFileTypeFilter(results),\n this.buildTagFilter(results)\n ].filter(f => f.Options.length > 0); // Hide categories with no options\n }\n\n /** Get the icon for a given source type */\n public GetSourceIcon(sourceType: string): string {\n return this.providersBySourceType.get(sourceType)?.Icon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle';\n }\n\n private buildSourceTypeFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n counts.set(r.SourceType, (counts.get(r.SourceType) ?? 0) + 1);\n }\n const options: SearchFilterOption[] = Array.from(counts.entries()).map(([type, count]) => {\n const cached = this.providersBySourceType.get(type);\n return {\n Label: cached?.DisplayName ?? FALLBACK_SOURCE_LABELS[type] ?? type,\n Value: type,\n Count: count,\n IsSelected: false,\n Icon: cached?.Icon ?? FALLBACK_SOURCE_ICONS[type]\n };\n });\n return { Category: 'Source', Options: options, MultiSelect: true };\n }\n\n private buildEntityNameFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n let fileCount = 0;\n for (const r of results) {\n if (r.ResultType === 'storage-file') {\n fileCount++;\n } else {\n counts.set(r.EntityName, (counts.get(r.EntityName) ?? 0) + 1);\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([name, count]) => ({\n Label: name,\n Value: name,\n Count: count,\n IsSelected: false,\n Icon: 'fa-solid fa-table'\n }));\n\n // Add a single \"Files\" entry for all storage file results\n if (fileCount > 0) {\n options.push({\n Label: 'Files',\n Value: '__storage-files__',\n Count: fileCount,\n IsSelected: false,\n Icon: 'fa-solid fa-file'\n });\n }\n\n return { Category: 'Entity', Options: options, MultiSelect: true };\n }\n\n private buildFileTypeFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n if (r.ResultType !== 'storage-file') continue;\n const ext = r.Title?.split('.').pop()?.toUpperCase();\n if (ext && ext.length <= 6) {\n counts.set(ext, (counts.get(ext) ?? 0) + 1);\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([ext, count]) => ({\n Label: ext,\n Value: ext,\n Count: count,\n IsSelected: false,\n Icon: 'fa-solid fa-file'\n }));\n return { Category: 'File Type', Options: options, MultiSelect: true };\n }\n\n private buildTagFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n for (const tag of r.Tags) {\n counts.set(tag, (counts.get(tag) ?? 0) + 1);\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)\n .map(([tag, count]) => ({\n Label: tag,\n Value: tag,\n Count: count,\n IsSelected: false,\n Icon: 'fa-solid fa-tag'\n }));\n return { Category: 'Tags', Options: options, MultiSelect: true };\n }\n\n /**\n * Load persisted recent searches from UserInfoEngine.\n * Safe to call multiple times — only loads once.\n */\n public async LoadRecentSearches(): Promise<void> {\n if (this.recentSearchesLoaded) return;\n\n try {\n MJGlobal.Instance.GetEventListener(true).subscribe(async (event) => {\n if (event.event === MJEventType.LoggedIn) {\n await StartupManager.Instance.Startup();\n\n const engine = UserInfoEngine.Instance;\n\n const json = engine.GetSetting(RECENT_SEARCHES_KEY);\n\n if (json) {\n const parsed = JSON.parse(json) as RecentSearchJson[];\n const searches: RecentSearch[] = parsed.map(s => ({\n Query: s.Query,\n Timestamp: new Date(s.Timestamp),\n ResultCount: s.ResultCount\n }));\n this.RecentSearches$.next(searches);\n }\n }\n });\n } catch (err) {\n console.error('[SearchService] LoadRecentSearches error:', err);\n }\n this.recentSearchesLoaded = true;\n }\n\n /**\n * Record a query into recent searches WITHOUT running a search. For surfaces\n * (e.g. the omnibar palette) where the user types a query but executes a\n * suggestion that navigates directly — no full ExecuteSearch ever runs, so\n * the automatic recording inside ExecuteSearch never fires for that query.\n * Deduplicates by query text and persists via UserInfoEngine like all recents.\n */\n /** Resolved record-name memo — GetEntityRecordNames round-trips are cosmetic\n and stable, so resolve each entity/record pair once per session. */\n private recentRecordNameCache = new Map<string, string>();\n\n /**\n * Recently opened records for search empty states, newest first — same\n * source as the omnibar's '#' empty state (MJ: User Record Logs via\n * UserInfoEngine). Fail-soft: any error yields [] rather than blocking\n * the surface. Display names resolve via GetEntityRecordNames with the\n * record ID as fallback.\n */\n public async GetRecentlyOpenedRecords(max = 3): Promise<RecentRecordItem[]> {\n try {\n const engine = UserInfoEngine.Instance;\n await engine.Config(false, this.Provider.CurrentUser ?? undefined); // no-op when loaded\n const logs = [...engine.UserRecordLogs]\n .sort((a, b) => new Date(b.LatestAt).getTime() - new Date(a.LatestAt).getTime())\n .slice(0, max);\n if (logs.length === 0) {\n return [];\n }\n const unresolved = logs.filter((l) => !this.recentRecordNameCache.has(`${l.Entity}||${l.RecordID}`));\n if (unresolved.length > 0) {\n try {\n // Recents span arbitrary entities whose key column can have any name; `FromID`\n // assumed `ID` and left those records showing a raw id instead of a name.\n const results = await this.Provider.GetEntityRecordNames(\n unresolved.map((l) => ({\n EntityName: l.Entity,\n CompositeKey: CompositeKey.FromURLSegment(this.Provider.EntityByName(l.Entity), l.RecordID),\n })),\n this.Provider.CurrentUser ?? undefined,\n );\n for (const r of results ?? []) {\n if (r.Success && r.RecordName) {\n this.recentRecordNameCache.set(`${r.EntityName}||${r.CompositeKey.GetValueByIndex(0)}`, r.RecordName);\n }\n }\n } catch {\n // name lookup is cosmetic — IDs are an acceptable fallback\n }\n }\n return logs.map((log) => ({\n EntityName: log.Entity,\n RecordID: log.RecordID,\n RecordName: this.recentRecordNameCache.get(`${log.Entity}||${log.RecordID}`) ?? log.RecordID,\n }));\n } catch {\n return [];\n }\n }\n\n public RecordRecentSearch(query: string, resultCount = 0): void {\n const trimmed = query.trim();\n if (trimmed.length > 0) {\n this.addToRecentSearches(trimmed, resultCount);\n }\n }\n\n private addToRecentSearches(query: string, resultCount: number): void {\n const current = this.RecentSearches$.value;\n const filtered = current.filter(s => s.Query !== query);\n const updated = [\n { Query: query, Timestamp: new Date(), ResultCount: resultCount },\n ...filtered\n ].slice(0, this.maxRecentSearches);\n this.RecentSearches$.next(updated);\n this.persistRecentSearches(updated);\n }\n\n private persistRecentSearches(searches: RecentSearch[]): void {\n try {\n const json: RecentSearchJson[] = searches.map(s => ({\n Query: s.Query,\n Timestamp: s.Timestamp.toISOString(),\n ResultCount: s.ResultCount\n }));\n UserInfoEngine.Instance.SetSettingDebounced(RECENT_SEARCHES_KEY, JSON.stringify(json));\n } catch {\n // Silently fail if UserInfoEngine not available\n }\n }\n\n /** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */\n private cacheProviders(providers: SearchProviderInfo[]): void {\n for (const p of providers) {\n this.providersBySourceType.set(p.SourceType, p);\n }\n }\n\n private createEmptyResponse(errorMessage?: string): SearchResponse {\n return {\n Success: !errorMessage,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 0,\n ElapsedMs: 0,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },\n Providers: [],\n ErrorMessage: errorMessage\n };\n }\n\n /**\n * Execute search via the GraphQLSearchClient.\n */\n private async executeGraphQLSearch(request: SearchRequest): Promise<SearchResponse> {\n if (!request.Query.trim()) {\n return this.createEmptyResponse();\n }\n\n const provider = this.Provider;\n if (!(provider instanceof GraphQLDataProvider)) {\n return this.createEmptyResponse('GraphQL provider not available');\n }\n\n const client = new GraphQLSearchClient(provider);\n const filters = this.buildClientFilters(request);\n const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;\n\n const clientResponse = await client.ExecuteSearch({\n Query: request.Query,\n MaxResults: request.MaxResults || 20,\n MinScore: minScore > 0 ? minScore : undefined,\n Filters: filters,\n ScopeIDs: request.ScopeIDs && request.ScopeIDs.length > 0 ? request.ScopeIDs : undefined\n });\n\n return this.mapClientResponseToSearchResponse(clientResponse);\n }\n\n /** Convert a SearchClientResponse into the SearchResponse shape used by this service */\n private mapClientResponseToSearchResponse(clientResponse: SearchClientResponse): SearchResponse {\n if (!clientResponse.Success) {\n return this.createEmptyResponse(clientResponse.ErrorMessage ?? 'Search failed');\n }\n\n // Cache provider metadata for use in GroupResults/BuildFilters\n const providers: SearchProviderInfo[] = (clientResponse.Providers ?? []).map(p => ({\n ID: p.ID,\n Name: p.Name,\n DisplayName: p.DisplayName,\n Icon: p.Icon,\n SourceType: p.SourceType,\n Priority: p.Priority,\n }));\n this.cacheProviders(providers);\n\n const results: SearchResultItem[] = (clientResponse.Results ?? []).map(r => this.mapClientResultItem(r));\n\n const groups = this.GroupResults(results);\n const filters = this.BuildFilters(results);\n\n return {\n Success: true,\n Results: results,\n Groups: groups,\n Filters: filters,\n TotalCount: clientResponse.TotalCount,\n ElapsedMs: clientResponse.ElapsedMs,\n SourceCounts: {\n Vector: clientResponse.SourceCounts.Vector,\n FullText: clientResponse.SourceCounts.FullText,\n Entity: clientResponse.SourceCounts.Entity,\n Storage: clientResponse.SourceCounts.Storage\n },\n Providers: providers,\n };\n }\n\n /** Map a single SearchClientResultItem to a SearchResultItem */\n private mapClientResultItem(r: SearchClientResultItem): SearchResultItem {\n return {\n ID: r.ID,\n Title: r.RecordName || r.Title,\n Snippet: r.Snippet,\n EntityName: r.EntityName,\n RecordID: r.RecordID,\n SourceType: (r.SourceType as SearchResultItem['SourceType']) || 'entity',\n ResultType: r.ResultType,\n Score: r.Score,\n ScoreBreakdown: r.ScoreBreakdown ?? {},\n Tags: r.Tags ?? [],\n SourceIcon: r.EntityIcon || r.ProviderIcon || FALLBACK_SOURCE_ICONS[r.SourceType] || 'fa-solid fa-database',\n EntityIcon: r.EntityIcon,\n RecordName: r.RecordName,\n MatchedAt: new Date(r.MatchedAt),\n RawMetadata: r.RawMetadata,\n ProviderId: r.ProviderId,\n ProviderLabel: r.ProviderLabel,\n ProviderIcon: r.ProviderIcon,\n };\n }\n\n /**\n * Load the list of search scopes available to the current user.\n * Cached after the first successful call; pass `force=true` to reload.\n */\n public async LoadScopes(force: boolean = false): Promise<SearchScopeInfo[]> {\n if (this.scopesLoaded && !force) return this.Scopes$.value;\n if (this.scopesLoadInFlight && !force) return this.scopesLoadInFlight;\n\n const load = (async (): Promise<SearchScopeInfo[]> => {\n try {\n const provider = Metadata.Provider; // global-provider-ok: scope discovery consumes the GraphQL channel of the global default provider\n if (!(provider instanceof GraphQLDataProvider)) {\n this.Scopes$.next([]);\n return [];\n }\n const client = new GraphQLSearchClient(provider);\n const scopes = await client.GetSearchScopes();\n this.Scopes$.next(scopes);\n this.scopesLoaded = true;\n return scopes;\n } catch {\n this.Scopes$.next([]);\n return [];\n } finally {\n this.scopesLoadInFlight = null;\n }\n })();\n\n this.scopesLoadInFlight = load;\n return load;\n }\n\n /** Build the client filters from active filter selections */\n private buildClientFilters(request: SearchRequest): { EntityNames?: string[]; SourceTypes?: string[]; Tags?: string[] } | undefined {\n if (!request.ActiveFilters || Object.keys(request.ActiveFilters).length === 0) {\n return undefined;\n }\n\n const result: { EntityNames?: string[]; SourceTypes?: string[]; Tags?: string[] } = {};\n\n const entityFilters = request.ActiveFilters['Entity'];\n if (entityFilters?.length) {\n result.EntityNames = entityFilters;\n }\n\n const sourceFilters = request.ActiveFilters['Source'];\n if (sourceFilters?.length) {\n result.SourceTypes = sourceFilters;\n }\n\n const tagFilters = request.ActiveFilters['Tags'];\n if (tagFilters?.length) {\n result.Tags = tagFilters;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../src/lib/search.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAc,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACxE,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,YAAY,EAAqB,QAAQ,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EACH,mBAAmB,EACnB,mBAAmB,EAGtB,MAAM,sCAAsC,CAAC;;AAa9C,4FAA4F;AAC5F,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,4FAA4F;AAC5F,MAAM,qBAAqB,GAA2B;IAClD,QAAQ,EAAE,sBAAsB;IAChC,QAAQ,EAAE,mBAAmB;IAC7B,UAAU,EAAE,8BAA8B;IAC1C,cAAc,EAAE,wBAAwB;IACxC,MAAM,EAAE,kBAAkB;IAC1B,UAAU,EAAE,mBAAmB;IAC/B,SAAS,EAAE,yBAAyB;CACvC,CAAC;AAEF,sFAAsF;AACtF,MAAM,sBAAsB,GAA2B;IACnD,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,iBAAiB;IAC3B,UAAU,EAAE,kBAAkB;IAC9B,cAAc,EAAE,eAAe;IAC/B,MAAM,EAAE,WAAW;IACnB,UAAU,EAAE,WAAW;IACvB,SAAS,EAAE,cAAc;CAC5B,CAAC;AAEF,sDAAsD;AACtD,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AAgBpD;;;GAGG;AAEH,MAAM,OAAO,aAAa;IACd,SAAS,GAA6B,IAAI,CAAC;IACnD,IAAW,QAAQ;QACf,OAAO,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC;IAC/C,CAAC;IACD,IAAW,QAAQ,CAAC,KAA+B;QAC/C,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED,8BAA8B;IACvB,cAAc,GAAG,IAAI,eAAe,CAAwB,IAAI,CAAC,CAAC;IAEzE,sCAAsC;IAC/B,YAAY,GAAG,IAAI,eAAe,CAAU,KAAK,CAAC,CAAC;IAE1D,2EAA2E;IACpE,eAAe,GAAG,IAAI,eAAe,CAAiB,EAAE,CAAC,CAAC;IAEjE,mBAAmB;IACZ,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;IAEtB,iBAAiB,GAAG,EAAE,CAAC;IAChC,oBAAoB,GAAG,KAAK,CAAC;IAErC,oFAAoF;IAC5E,qBAAqB,GAAG,IAAI,GAAG,EAA8B,CAAC;IAEtE,0FAA0F;IACnF,OAAO,GAAG,IAAI,eAAe,CAAoB,EAAE,CAAC,CAAC;IACpD,YAAY,GAAG,KAAK,CAAC;IACrB,kBAAkB,GAAsC,IAAI,CAAC;IAErE;;OAEG;IACI,KAAK,CAAC,aAAa,CAAC,OAAsB;QAC7C,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC1D,QAAQ,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE5C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;YAE7D,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YAC1E,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAChC,MAAM,aAAa,GAAG,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;YAC7D,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACxC,OAAO,aAAa,CAAC;QACzB,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACI,YAAY,CAAC,OAAsB;QAOtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,2GAA2G;QAC/I,IAAI,CAAC,CAAC,QAAQ,YAAY,mBAAmB,CAAC,EAAE,CAAC;YAC7C,OAAO,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACvD,OAAO,MAAM,CAAC,YAAY,CAAC;YACvB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;YACpC,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;YAC7C,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;SAC3F,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACjB,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,YAAY,EAAE,EAAE,CAAC,YAAY;YAC7B,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;YAClF,SAAS,EAAE,EAAE,CAAC,SAAS;YACvB,YAAY,EAAE,EAAE,CAAC,YAAY;SAChC,CAAC,CAAC,CAAC,CAAC;IACT,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,aAAqB,CAAC;QAC5D,IAAI,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChB,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;YACtC,CAAC;YAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC/B,IAAI,CAAC,CAAC,QAAQ,YAAY,mBAAmB,CAAC,EAAE,CAAC;gBAC7C,OAAO,IAAI,CAAC,mBAAmB,CAAC,gCAAgC,CAAC,CAAC;YACtE,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YACjD,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YACrE,OAAO,IAAI,CAAC,iCAAiC,CAAC,cAAc,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC;YAClF,OAAO,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;QAClD,CAAC;IACL,CAAC;IAED,uCAAuC;IAChC,YAAY;QACf,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,yDAAyD;IAClD,mBAAmB;QACtB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,OAA2B;QAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA8B,CAAC;QAEvD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC;YAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,QAAQ,EAAE,CAAC;gBACX,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACJ,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;YAChC,CAAC;QACL,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE;YAC9D,2EAA2E;YAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC1D,OAAO;gBACH,KAAK,EAAE,MAAM,EAAE,WAAW,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,aAAa,IAAI,sBAAsB,CAAC,UAAU,CAAC,IAAI,UAAU;gBACzG,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,IAAI,qBAAqB,CAAC,UAAU,CAAC,IAAI,oBAAoB;gBACzG,UAAU,EAAE,UAAU;gBACtB,OAAO,EAAE,KAAK;gBACd,UAAU,EAAE,KAAK,CAAC,MAAM;aAC3B,CAAC;QACN,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;OAEG;IACI,YAAY,CAAC,OAA2B;QAC3C,OAAO;YACH,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC;YACnC,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC;YACnC,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC;YACjC,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;SAC/B,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,kCAAkC;IAC3E,CAAC;IAED,2CAA2C;IACpC,aAAa,CAAC,UAAkB;QACnC,OAAO,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,IAAI,IAAI,qBAAqB,CAAC,UAAU,CAAC,IAAI,oBAAoB,CAAC;IACzH,CAAC;IAEO,qBAAqB,CAAC,OAA2B;QACrD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,OAAO,GAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YACrF,MAAM,MAAM,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO;gBACH,KAAK,EAAE,MAAM,EAAE,WAAW,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,IAAI;gBAClE,KAAK,EAAE,IAAI;gBACX,KAAK,EAAE,KAAK;gBACZ,UAAU,EAAE,KAAK;gBACjB,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,qBAAqB,CAAC,IAAI,CAAC;aACpD,CAAC;QACN,CAAC,CAAC,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACvE,CAAC;IAEO,qBAAqB,CAAC,OAA2B;QACrD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAiE,CAAC;QACxF,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,UAAU,KAAK,cAAc,EAAE,CAAC;gBAClC,SAAS,EAAE,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACJ,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;gBAC1C,MAAM,WAAW,GAAG,CAAC,CAAC,iBAAiB,IAAI,CAAC,CAAC,UAAU,CAAC;gBACxD,IAAI,QAAQ,EAAE,CAAC;oBACX,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;qBAAM,CAAC;oBACJ,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;gBAC5E,CAAC;YACL,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;aAC7D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;aACvC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACpB,KAAK,EAAE,IAAI,CAAC,WAAW;YACvB,KAAK,EAAE,IAAI;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,mBAAmB;SACzC,CAAC,CAAC,CAAC;QAER,0DAA0D;QAC1D,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC;gBACT,KAAK,EAAE,OAAO;gBACd,KAAK,EAAE,mBAAmB;gBAC1B,KAAK,EAAE,SAAS;gBAChB,UAAU,EAAE,KAAK;gBACjB,IAAI,EAAE,kBAAkB;aAC3B,CAAC,CAAC;QACP,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACvE,CAAC;IAEO,mBAAmB,CAAC,OAA2B;QACnD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,UAAU,KAAK,cAAc;gBAAE,SAAS;YAC9C,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,CAAC;YACrD,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBACzB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAChD,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;aAC7D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACpB,KAAK,EAAE,GAAG;YACV,KAAK,EAAE,GAAG;YACV,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,kBAAkB;SAC3B,CAAC,CAAC,CAAC;QACR,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC1E,CAAC;IAEO,cAAc,CAAC,OAA2B;QAC9C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACtB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;gBACvB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAChD,CAAC;QACL,CAAC;QACD,MAAM,OAAO,GAAyB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;aAC7D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aAC3B,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;aACZ,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACpB,KAAK,EAAE,GAAG;YACV,KAAK,EAAE,GAAG;YACV,KAAK,EAAE,KAAK;YACZ,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,iBAAiB;SAC1B,CAAC,CAAC,CAAC;QACR,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACrE,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,kBAAkB;QAC3B,IAAI,IAAI,CAAC,oBAAoB;YAAE,OAAO;QAEtC,IAAI,CAAC;YACD,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;gBAC/D,IAAI,KAAK,CAAC,KAAK,KAAK,WAAW,CAAC,QAAQ,EAAE,CAAC;oBACvC,MAAM,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;oBAExC,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC;oBAEvC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;oBAEpD,IAAI,IAAI,EAAE,CAAC;wBACP,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;wBACtD,MAAM,QAAQ,GAAmB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;4BAC9C,KAAK,EAAE,CAAC,CAAC,KAAK;4BACd,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;4BAChC,WAAW,EAAE,CAAC,CAAC,WAAW;yBAC7B,CAAC,CAAC,CAAC;wBACJ,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;oBACxC,CAAC;gBACL,CAAC;YACL,CAAC,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;IACrC,CAAC;IAED;;;;;;OAMG;IACH;2EACuE;IAC/D,qBAAqB,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1D;;;;;;OAMG;IACI,KAAK,CAAC,wBAAwB,CAAC,GAAG,GAAG,CAAC;QACzC,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC;YACvC,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC,CAAC,oBAAoB;YACxF,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC;iBAClC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC;iBAC/E,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACnB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAC;YACd,CAAC;YACD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;YACrG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxB,IAAI,CAAC;oBACD,+EAA+E;oBAC/E,0EAA0E;oBAC1E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CACpD,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;wBACnB,UAAU,EAAE,CAAC,CAAC,MAAM;wBACpB,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC;qBAC9F,CAAC,CAAC,EACH,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,SAAS,CACzC,CAAC;oBACF,KAAK,MAAM,CAAC,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;wBAC5B,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;4BAC5B,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,UAAU,KAAK,CAAC,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;wBAC1G,CAAC;oBACL,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACL,2DAA2D;gBAC/D,CAAC;YACL,CAAC;YACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACpB,IAAI,UAAU,CAAC;gBACf,IAAI,CAAC;oBACD,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBAClG,CAAC;gBAAC,MAAM,CAAC;oBACL,+CAA+C;gBACnD,CAAC;gBACD,OAAO;oBACH,UAAU,EAAE,GAAG,CAAC,MAAM;oBACtB,iBAAiB,EAAE,UAAU,EAAE,WAAW,IAAI,GAAG,CAAC,MAAM;oBACxD,QAAQ,EAAE,GAAG,CAAC,QAAQ;oBACtB,UAAU,EAAE,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,QAAQ;iBAC/F,CAAC;YACN,CAAC,CAAC,CAAC;QACP,CAAC;QAAC,MAAM,CAAC;YACL,OAAO,EAAE,CAAC;QACd,CAAC;IACL,CAAC;IAEM,kBAAkB,CAAC,KAAa,EAAE,WAAW,GAAG,CAAC;QACpD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;QACnD,CAAC;IACL,CAAC;IAEO,mBAAmB,CAAC,KAAa,EAAE,WAAmB;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;QAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG;YACZ,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;YACjE,GAAG,QAAQ;SACd,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACnC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAEO,qBAAqB,CAAC,QAAwB;QAClD,IAAI,CAAC;YACD,MAAM,IAAI,GAAuB,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChD,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;gBACpC,WAAW,EAAE,CAAC,CAAC,WAAW;aAC7B,CAAC,CAAC,CAAC;YACJ,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CAAC,mBAAmB,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3F,CAAC;QAAC,MAAM,CAAC;YACL,gDAAgD;QACpD,CAAC;IACL,CAAC;IAED,iFAAiF;IACzE,cAAc,CAAC,SAA+B;QAClD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YACxB,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACpD,CAAC;IACL,CAAC;IAEO,mBAAmB,CAAC,YAAqB;QAC7C,OAAO;YACH,OAAO,EAAE,CAAC,YAAY;YACtB,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,EAAE;YACV,OAAO,EAAE,EAAE;YACX,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;YACZ,YAAY,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE;YAC/D,SAAS,EAAE,EAAE;YACb,YAAY,EAAE,YAAY;SAC7B,CAAC;IACN,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB,CAAC,OAAsB;QACrD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACxB,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACtC,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,CAAC,QAAQ,YAAY,mBAAmB,CAAC,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC,mBAAmB,CAAC,gCAAgC,CAAC,CAAC;QACtE,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QAEvD,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC;YAC9C,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;YACpC,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;YAC7C,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;SAC3F,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,iCAAiC,CAAC,cAAc,CAAC,CAAC;IAClE,CAAC;IAED,wFAAwF;IAChF,iCAAiC,CAAC,cAAoC;QAC1E,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAC,mBAAmB,CAAC,cAAc,CAAC,YAAY,IAAI,eAAe,CAAC,CAAC;QACpF,CAAC;QAED,+DAA+D;QAC/D,MAAM,SAAS,GAAyB,CAAC,cAAc,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/E,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACvB,CAAC,CAAC,CAAC;QACJ,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAE/B,MAAM,OAAO,GAAuB,CAAC,cAAc,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;QAEzG,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAE3C,OAAO;YACH,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,OAAO;YAChB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE,cAAc,CAAC,UAAU;YACrC,SAAS,EAAE,cAAc,CAAC,SAAS;YACnC,YAAY,EAAE;gBACV,MAAM,EAAE,cAAc,CAAC,YAAY,CAAC,MAAM;gBAC1C,QAAQ,EAAE,cAAc,CAAC,YAAY,CAAC,QAAQ;gBAC9C,MAAM,EAAE,cAAc,CAAC,YAAY,CAAC,MAAM;gBAC1C,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,OAAO;aAC/C;YACD,SAAS,EAAE,SAAS;SACvB,CAAC;IACN,CAAC;IAED,gEAAgE;IACxD,mBAAmB,CAAC,CAAyB;QACjD,IAAI,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAAC;QAC5C,IAAI,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;QAC9B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC3D,IAAI,CAAC,iBAAiB,IAAI,MAAM,EAAE,CAAC;gBAC/B,iBAAiB,GAAG,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC;YAC1D,CAAC;YACD,IAAI,CAAC,UAAU,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;gBAC9B,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;YAC7B,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,oEAAoE;QACxE,CAAC;QACD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACrB,iBAAiB,GAAG,CAAC,CAAC,UAAU,CAAC;QACrC,CAAC;QAED,OAAO;YACH,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,KAAK;YAC9B,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,iBAAiB,EAAE,iBAAiB;YACpC,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,UAAU,EAAG,CAAC,CAAC,UAA6C,IAAI,QAAQ;YACxE,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,cAAc,EAAE,CAAC,CAAC,cAAc,IAAI,EAAE;YACtC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE;YAClB,UAAU,EAAE,UAAU,IAAI,CAAC,CAAC,YAAY,IAAI,qBAAqB,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,sBAAsB;YACzG,UAAU,EAAE,UAAU;YACtB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YAChC,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,YAAY,EAAE,CAAC,CAAC,YAAY;SAC/B,CAAC;IACN,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,UAAU,CAAC,QAAiB,KAAK;QAC1C,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAC3D,IAAI,IAAI,CAAC,kBAAkB,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,kBAAkB,CAAC;QAEtE,MAAM,IAAI,GAAG,CAAC,KAAK,IAAgC,EAAE;YACjD,IAAI,CAAC;gBACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,kGAAkG;gBACtI,IAAI,CAAC,CAAC,QAAQ,YAAY,mBAAmB,CAAC,EAAE,CAAC;oBAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACtB,OAAO,EAAE,CAAC;gBACd,CAAC;gBACD,MAAM,MAAM,GAAG,IAAI,mBAAmB,CAAC,QAAQ,CAAC,CAAC;gBACjD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;gBAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,OAAO,MAAM,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACL,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACtB,OAAO,EAAE,CAAC;YACd,CAAC;oBAAS,CAAC;gBACP,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YACnC,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;QAEL,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QAC/B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,6DAA6D;IACrD,kBAAkB,CAAC,OAAsB;QAC7C,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5E,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,MAAM,MAAM,GAAwE,EAAE,CAAC;QAEvF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,aAAa,EAAE,MAAM,EAAE,CAAC;YACxB,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC;QACvC,CAAC;QAED,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,aAAa,EAAE,MAAM,EAAE,CAAC;YACxB,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC;QACvC,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,GAAG,UAAU,CAAC;QAC7B,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,CAAC;uGA5lBQ,aAAa;gEAAb,aAAa,WAAb,aAAa,mBADA,MAAM;;iFACnB,aAAa;cADzB,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["/**\n * @fileoverview Search service that connects to the UnifiedSearchService via GraphQL.\n *\n * Provides a reactive interface for executing searches, managing search history,\n * and transforming raw search responses into display-ready grouped results.\n */\n\nimport { Injectable } from '@angular/core';\nimport { BehaviorSubject, Observable, Subject, throwError } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { CompositeKey, IMetadataProvider, Metadata, StartupManager } from '@memberjunction/core';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\nimport { MJEventType, MJGlobal } from '@memberjunction/global';\nimport {\n GraphQLSearchClient,\n GraphQLDataProvider,\n SearchClientResponse,\n SearchClientResultItem\n} from '@memberjunction/graphql-dataprovider';\nimport {\n SearchRequest,\n SearchResponse,\n SearchResultItem,\n SearchResultGroup,\n SearchFilter,\n SearchFilterOption,\n SearchProviderInfo,\n SearchScopeInfo,\n RecentRecordItem\n} from './search-types';\n\n/** Default minimum relevance score threshold (0-1). Results below this are filtered out. */\nconst DEFAULT_MIN_SCORE = 0.35;\n\n/** Fallback icon mapping for source types (used when provider metadata is not available) */\nconst FALLBACK_SOURCE_ICONS: Record<string, string> = {\n 'entity': 'fa-solid fa-database',\n 'vector': 'fa-solid fa-brain',\n 'fulltext': 'fa-solid fa-magnifying-glass',\n 'content-item': 'fa-solid fa-file-lines',\n 'file': 'fa-solid fa-file',\n 'web-page': 'fa-solid fa-globe',\n 'storage': 'fa-solid fa-folder-open',\n};\n\n/** Fallback labels for source types (used when provider metadata is not available) */\nconst FALLBACK_SOURCE_LABELS: Record<string, string> = {\n 'entity': 'Database',\n 'vector': 'Semantic Search',\n 'fulltext': 'Full-Text Search',\n 'content-item': 'Content Items',\n 'file': 'Documents',\n 'web-page': 'Web Pages',\n 'storage': 'File Storage',\n};\n\n/** User setting key for persisting recent searches */\nconst RECENT_SEARCHES_KEY = 'search.recentSearches';\n\n/** Recent search entry for history */\nexport interface RecentSearch {\n Query: string;\n Timestamp: Date;\n ResultCount: number;\n}\n\n/** JSON-safe shape for persistence (Date → ISO string) */\ninterface RecentSearchJson {\n Query: string;\n Timestamp: string;\n ResultCount: number;\n}\n\n/**\n * Multi-provider note: callers under a non-default provider should set\n * `service.Provider = component.ProviderToUse` before invoking any methods.\n */\n@Injectable({ providedIn: 'root' })\nexport class SearchService {\n private _provider: IMetadataProvider | null = null;\n public get Provider(): IMetadataProvider {\n return this._provider ?? Metadata.Provider;\n }\n public set Provider(value: IMetadataProvider | null) {\n this._provider = value;\n }\n\n /** Current search response */\n public SearchResults$ = new BehaviorSubject<SearchResponse | null>(null);\n\n /** Whether a search is in progress */\n public IsSearching$ = new BehaviorSubject<boolean>(false);\n\n /** Recent search history (persisted via UserInfoEngine, max 20 entries) */\n public RecentSearches$ = new BehaviorSubject<RecentSearch[]>([]);\n\n /** Error stream */\n public Errors$ = new Subject<string>();\n\n private readonly maxRecentSearches = 20;\n private recentSearchesLoaded = false;\n\n /** Cached provider metadata keyed by SourceType, populated from search responses */\n private providersBySourceType = new Map<string, SearchProviderInfo>();\n\n /** Cached list of scopes the current user can see. Populated lazily by `LoadScopes()`. */\n public Scopes$ = new BehaviorSubject<SearchScopeInfo[]>([]);\n private scopesLoaded = false;\n private scopesLoadInFlight: Promise<SearchScopeInfo[]> | null = null;\n\n /**\n * Execute a search request via the SearchKnowledge GraphQL mutation.\n */\n public async ExecuteSearch(request: SearchRequest): Promise<SearchResponse> {\n this.IsSearching$.next(true);\n const startTime = Date.now();\n\n try {\n const response = await this.executeGraphQLSearch(request);\n response.ElapsedMs = Date.now() - startTime;\n\n this.SearchResults$.next(response);\n this.addToRecentSearches(request.Query, response.TotalCount);\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Search failed';\n this.Errors$.next(errorMessage);\n const errorResponse = this.createEmptyResponse(errorMessage);\n this.SearchResults$.next(errorResponse);\n return errorResponse;\n } finally {\n this.IsSearching$.next(false);\n }\n }\n\n /**\n * Phase 2C streaming search. Returns an Observable that emits one\n * notification per provider as it returns, plus a 'fused' / 'reranked'\n * / 'final' terminal event. Same auth + permission behavior as\n * ExecuteSearch — denials surface as an error on the Observable.\n *\n * Consumers (typically the search composite component) should:\n * - render skeleton rows immediately when the Observable is created\n * - on each 'provider' event: append the rows for that source\n * - on 'final': replace the partials with the canonical fused list\n * - on error: show the failure message + clear the skeleton\n *\n * Returns rxjs Observable rather than awaiting a Promise — callers\n * decide whether to subscribe + populate state, or unsubscribe on\n * navigation. The service does NOT update its own SearchResults$\n * subject for streaming calls; the caller owns the partial-render\n * state model.\n */\n public StreamSearch(request: SearchRequest): Observable<{\n Phase: string;\n ProviderName?: string;\n Results?: SearchResultItem[];\n ElapsedMs?: number;\n ErrorMessage?: string;\n }> {\n const provider = Metadata.Provider; // global-provider-ok: streaming search service consumes the GraphQL channel of the global default provider\n if (!(provider instanceof GraphQLDataProvider)) {\n return throwError(() => new Error('GraphQL provider not available'));\n }\n const client = new GraphQLSearchClient(provider);\n const filters = this.buildClientFilters(request);\n const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;\n return client.StreamSearch({\n Query: request.Query,\n MaxResults: request.MaxResults || 20,\n MinScore: minScore > 0 ? minScore : undefined,\n Filters: filters,\n ScopeIDs: request.ScopeIDs && request.ScopeIDs.length > 0 ? request.ScopeIDs : undefined,\n }).pipe(map((ev) => ({\n Phase: ev.Phase,\n ProviderName: ev.ProviderName,\n Results: ev.Results ? ev.Results.map(r => this.mapClientResultItem(r)) : undefined,\n ElapsedMs: ev.ElapsedMs,\n ErrorMessage: ev.ErrorMessage,\n })));\n }\n\n /**\n * Lightweight preview search for autocomplete/typeahead.\n * Does NOT update SearchResults$ or IsSearching$ observables,\n * and does NOT add to recent search history.\n */\n public async PreviewSearch(query: string, maxResults: number = 8): Promise<SearchResponse> {\n try {\n if (!query.trim()) {\n return this.createEmptyResponse();\n }\n\n const provider = this.Provider;\n if (!(provider instanceof GraphQLDataProvider)) {\n return this.createEmptyResponse('GraphQL provider not available');\n }\n\n const client = new GraphQLSearchClient(provider);\n const clientResponse = await client.PreviewSearch(query, maxResults);\n return this.mapClientResponseToSearchResponse(clientResponse);\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Preview search failed';\n return this.createEmptyResponse(errorMessage);\n }\n }\n\n /** Clear the current search results */\n public ClearResults(): void {\n this.SearchResults$.next(null);\n }\n\n /** Clear recent search history (memory and persisted) */\n public ClearRecentSearches(): void {\n this.RecentSearches$.next([]);\n this.persistRecentSearches([]);\n }\n\n /**\n * Group flat results by source type for display.\n */\n public GroupResults(results: SearchResultItem[]): SearchResultGroup[] {\n const groupMap = new Map<string, SearchResultItem[]>();\n\n for (const result of results) {\n const key = result.SourceType;\n const existing = groupMap.get(key);\n if (existing) {\n existing.push(result);\n } else {\n groupMap.set(key, [result]);\n }\n }\n\n return Array.from(groupMap.entries()).map(([sourceType, items]) => {\n // Priority: cached provider metadata > per-result metadata > fallback maps\n const cached = this.providersBySourceType.get(sourceType);\n return {\n Label: cached?.DisplayName ?? items[0]?.ProviderLabel ?? FALLBACK_SOURCE_LABELS[sourceType] ?? sourceType,\n Icon: cached?.Icon ?? items[0]?.ProviderIcon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle',\n SourceType: sourceType,\n Results: items,\n TotalCount: items.length\n };\n });\n }\n\n /**\n * Build filter facets from a set of results.\n */\n public BuildFilters(results: SearchResultItem[]): SearchFilter[] {\n return [\n this.buildSourceTypeFilter(results),\n this.buildEntityNameFilter(results),\n this.buildFileTypeFilter(results),\n this.buildTagFilter(results)\n ].filter(f => f.Options.length > 0); // Hide categories with no options\n }\n\n /** Get the icon for a given source type */\n public GetSourceIcon(sourceType: string): string {\n return this.providersBySourceType.get(sourceType)?.Icon ?? FALLBACK_SOURCE_ICONS[sourceType] ?? 'fa-solid fa-circle';\n }\n\n private buildSourceTypeFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n counts.set(r.SourceType, (counts.get(r.SourceType) ?? 0) + 1);\n }\n const options: SearchFilterOption[] = Array.from(counts.entries()).map(([type, count]) => {\n const cached = this.providersBySourceType.get(type);\n return {\n Label: cached?.DisplayName ?? FALLBACK_SOURCE_LABELS[type] ?? type,\n Value: type,\n Count: count,\n IsSelected: false,\n Icon: cached?.Icon ?? FALLBACK_SOURCE_ICONS[type]\n };\n });\n return { Category: 'Source', Options: options, MultiSelect: true };\n }\n\n private buildEntityNameFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, { count: number; displayName: string; icon?: string }>();\n let fileCount = 0;\n for (const r of results) {\n if (r.ResultType === 'storage-file') {\n fileCount++;\n } else {\n const existing = counts.get(r.EntityName);\n const displayName = r.EntityDisplayName || r.EntityName;\n if (existing) {\n existing.count++;\n } else {\n counts.set(r.EntityName, { count: 1, displayName, icon: r.EntityIcon });\n }\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1].count - a[1].count)\n .map(([name, data]) => ({\n Label: data.displayName,\n Value: name,\n Count: data.count,\n IsSelected: false,\n Icon: data.icon ?? 'fa-solid fa-table'\n }));\n\n // Add a single \"Files\" entry for all storage file results\n if (fileCount > 0) {\n options.push({\n Label: 'Files',\n Value: '__storage-files__',\n Count: fileCount,\n IsSelected: false,\n Icon: 'fa-solid fa-file'\n });\n }\n\n return { Category: 'Entity', Options: options, MultiSelect: true };\n }\n\n private buildFileTypeFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n if (r.ResultType !== 'storage-file') continue;\n const ext = r.Title?.split('.').pop()?.toUpperCase();\n if (ext && ext.length <= 6) {\n counts.set(ext, (counts.get(ext) ?? 0) + 1);\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .map(([ext, count]) => ({\n Label: ext,\n Value: ext,\n Count: count,\n IsSelected: false,\n Icon: 'fa-solid fa-file'\n }));\n return { Category: 'File Type', Options: options, MultiSelect: true };\n }\n\n private buildTagFilter(results: SearchResultItem[]): SearchFilter {\n const counts = new Map<string, number>();\n for (const r of results) {\n for (const tag of r.Tags) {\n counts.set(tag, (counts.get(tag) ?? 0) + 1);\n }\n }\n const options: SearchFilterOption[] = Array.from(counts.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)\n .map(([tag, count]) => ({\n Label: tag,\n Value: tag,\n Count: count,\n IsSelected: false,\n Icon: 'fa-solid fa-tag'\n }));\n return { Category: 'Tags', Options: options, MultiSelect: true };\n }\n\n /**\n * Load persisted recent searches from UserInfoEngine.\n * Safe to call multiple times — only loads once.\n */\n public async LoadRecentSearches(): Promise<void> {\n if (this.recentSearchesLoaded) return;\n\n try {\n MJGlobal.Instance.GetEventListener(true).subscribe(async (event) => {\n if (event.event === MJEventType.LoggedIn) {\n await StartupManager.Instance.Startup();\n\n const engine = UserInfoEngine.Instance;\n\n const json = engine.GetSetting(RECENT_SEARCHES_KEY);\n\n if (json) {\n const parsed = JSON.parse(json) as RecentSearchJson[];\n const searches: RecentSearch[] = parsed.map(s => ({\n Query: s.Query,\n Timestamp: new Date(s.Timestamp),\n ResultCount: s.ResultCount\n }));\n this.RecentSearches$.next(searches);\n }\n }\n });\n } catch (err) {\n console.error('[SearchService] LoadRecentSearches error:', err);\n }\n this.recentSearchesLoaded = true;\n }\n\n /**\n * Record a query into recent searches WITHOUT running a search. For surfaces\n * (e.g. the omnibar palette) where the user types a query but executes a\n * suggestion that navigates directly — no full ExecuteSearch ever runs, so\n * the automatic recording inside ExecuteSearch never fires for that query.\n * Deduplicates by query text and persists via UserInfoEngine like all recents.\n */\n /** Resolved record-name memo — GetEntityRecordNames round-trips are cosmetic\n and stable, so resolve each entity/record pair once per session. */\n private recentRecordNameCache = new Map<string, string>();\n\n /**\n * Recently opened records for search empty states, newest first — same\n * source as the omnibar's '#' empty state (MJ: User Record Logs via\n * UserInfoEngine). Fail-soft: any error yields [] rather than blocking\n * the surface. Display names resolve via GetEntityRecordNames with the\n * record ID as fallback.\n */\n public async GetRecentlyOpenedRecords(max = 3): Promise<RecentRecordItem[]> {\n try {\n const engine = UserInfoEngine.Instance;\n await engine.Config(false, this.Provider.CurrentUser ?? undefined); // no-op when loaded\n const logs = [...engine.UserRecordLogs]\n .sort((a, b) => new Date(b.LatestAt).getTime() - new Date(a.LatestAt).getTime())\n .slice(0, max);\n if (logs.length === 0) {\n return [];\n }\n const unresolved = logs.filter((l) => !this.recentRecordNameCache.has(`${l.Entity}||${l.RecordID}`));\n if (unresolved.length > 0) {\n try {\n // Recents span arbitrary entities whose key column can have any name; `FromID`\n // assumed `ID` and left those records showing a raw id instead of a name.\n const results = await this.Provider.GetEntityRecordNames(\n unresolved.map((l) => ({\n EntityName: l.Entity,\n CompositeKey: CompositeKey.FromURLSegment(this.Provider.EntityByName(l.Entity), l.RecordID),\n })),\n this.Provider.CurrentUser ?? undefined,\n );\n for (const r of results ?? []) {\n if (r.Success && r.RecordName) {\n this.recentRecordNameCache.set(`${r.EntityName}||${r.CompositeKey.GetValueByIndex(0)}`, r.RecordName);\n }\n }\n } catch {\n // name lookup is cosmetic — IDs are an acceptable fallback\n }\n }\n return logs.map((log) => {\n let entityInfo;\n try {\n entityInfo = this.Provider?.EntityByName ? this.Provider.EntityByName(log.Entity) : undefined;\n } catch {\n // Non-fatal if metadata cache is uninitialized\n }\n return {\n EntityName: log.Entity,\n EntityDisplayName: entityInfo?.DisplayName || log.Entity,\n RecordID: log.RecordID,\n RecordName: this.recentRecordNameCache.get(`${log.Entity}||${log.RecordID}`) ?? log.RecordID,\n };\n });\n } catch {\n return [];\n }\n }\n\n public RecordRecentSearch(query: string, resultCount = 0): void {\n const trimmed = query.trim();\n if (trimmed.length > 0) {\n this.addToRecentSearches(trimmed, resultCount);\n }\n }\n\n private addToRecentSearches(query: string, resultCount: number): void {\n const current = this.RecentSearches$.value;\n const filtered = current.filter(s => s.Query !== query);\n const updated = [\n { Query: query, Timestamp: new Date(), ResultCount: resultCount },\n ...filtered\n ].slice(0, this.maxRecentSearches);\n this.RecentSearches$.next(updated);\n this.persistRecentSearches(updated);\n }\n\n private persistRecentSearches(searches: RecentSearch[]): void {\n try {\n const json: RecentSearchJson[] = searches.map(s => ({\n Query: s.Query,\n Timestamp: s.Timestamp.toISOString(),\n ResultCount: s.ResultCount\n }));\n UserInfoEngine.Instance.SetSettingDebounced(RECENT_SEARCHES_KEY, JSON.stringify(json));\n } catch {\n // Silently fail if UserInfoEngine not available\n }\n }\n\n /** Cache provider metadata by SourceType for use in GroupResults/BuildFilters */\n private cacheProviders(providers: SearchProviderInfo[]): void {\n for (const p of providers) {\n this.providersBySourceType.set(p.SourceType, p);\n }\n }\n\n private createEmptyResponse(errorMessage?: string): SearchResponse {\n return {\n Success: !errorMessage,\n Results: [],\n Groups: [],\n Filters: [],\n TotalCount: 0,\n ElapsedMs: 0,\n SourceCounts: { Vector: 0, FullText: 0, Entity: 0, Storage: 0 },\n Providers: [],\n ErrorMessage: errorMessage\n };\n }\n\n /**\n * Execute search via the GraphQLSearchClient.\n */\n private async executeGraphQLSearch(request: SearchRequest): Promise<SearchResponse> {\n if (!request.Query.trim()) {\n return this.createEmptyResponse();\n }\n\n const provider = this.Provider;\n if (!(provider instanceof GraphQLDataProvider)) {\n return this.createEmptyResponse('GraphQL provider not available');\n }\n\n const client = new GraphQLSearchClient(provider);\n const filters = this.buildClientFilters(request);\n const minScore = request.MinScore ?? DEFAULT_MIN_SCORE;\n\n const clientResponse = await client.ExecuteSearch({\n Query: request.Query,\n MaxResults: request.MaxResults || 20,\n MinScore: minScore > 0 ? minScore : undefined,\n Filters: filters,\n ScopeIDs: request.ScopeIDs && request.ScopeIDs.length > 0 ? request.ScopeIDs : undefined\n });\n\n return this.mapClientResponseToSearchResponse(clientResponse);\n }\n\n /** Convert a SearchClientResponse into the SearchResponse shape used by this service */\n private mapClientResponseToSearchResponse(clientResponse: SearchClientResponse): SearchResponse {\n if (!clientResponse.Success) {\n return this.createEmptyResponse(clientResponse.ErrorMessage ?? 'Search failed');\n }\n\n // Cache provider metadata for use in GroupResults/BuildFilters\n const providers: SearchProviderInfo[] = (clientResponse.Providers ?? []).map(p => ({\n ID: p.ID,\n Name: p.Name,\n DisplayName: p.DisplayName,\n Icon: p.Icon,\n SourceType: p.SourceType,\n Priority: p.Priority,\n }));\n this.cacheProviders(providers);\n\n const results: SearchResultItem[] = (clientResponse.Results ?? []).map(r => this.mapClientResultItem(r));\n\n const groups = this.GroupResults(results);\n const filters = this.BuildFilters(results);\n\n return {\n Success: true,\n Results: results,\n Groups: groups,\n Filters: filters,\n TotalCount: clientResponse.TotalCount,\n ElapsedMs: clientResponse.ElapsedMs,\n SourceCounts: {\n Vector: clientResponse.SourceCounts.Vector,\n FullText: clientResponse.SourceCounts.FullText,\n Entity: clientResponse.SourceCounts.Entity,\n Storage: clientResponse.SourceCounts.Storage\n },\n Providers: providers,\n };\n }\n\n /** Map a single SearchClientResultItem to a SearchResultItem */\n private mapClientResultItem(r: SearchClientResultItem): SearchResultItem {\n let entityDisplayName = r.EntityDisplayName;\n let entityIcon = r.EntityIcon;\n try {\n const entity = this.Provider?.EntityByName?.(r.EntityName);\n if (!entityDisplayName && entity) {\n entityDisplayName = entity.DisplayName || entity.Name;\n }\n if (!entityIcon && entity?.Icon) {\n entityIcon = entity.Icon;\n }\n } catch {\n // Non-fatal if metadata cache is uninitialized (e.g. in unit tests)\n }\n if (!entityDisplayName) {\n entityDisplayName = r.EntityName;\n }\n\n return {\n ID: r.ID,\n Title: r.RecordName || r.Title,\n Snippet: r.Snippet,\n EntityName: r.EntityName,\n EntityDisplayName: entityDisplayName,\n RecordID: r.RecordID,\n SourceType: (r.SourceType as SearchResultItem['SourceType']) || 'entity',\n ResultType: r.ResultType,\n Score: r.Score,\n ScoreBreakdown: r.ScoreBreakdown ?? {},\n Tags: r.Tags ?? [],\n SourceIcon: entityIcon || r.ProviderIcon || FALLBACK_SOURCE_ICONS[r.SourceType] || 'fa-solid fa-database',\n EntityIcon: entityIcon,\n RecordName: r.RecordName,\n MatchedAt: new Date(r.MatchedAt),\n RawMetadata: r.RawMetadata,\n ProviderId: r.ProviderId,\n ProviderLabel: r.ProviderLabel,\n ProviderIcon: r.ProviderIcon,\n };\n }\n\n /**\n * Load the list of search scopes available to the current user.\n * Cached after the first successful call; pass `force=true` to reload.\n */\n public async LoadScopes(force: boolean = false): Promise<SearchScopeInfo[]> {\n if (this.scopesLoaded && !force) return this.Scopes$.value;\n if (this.scopesLoadInFlight && !force) return this.scopesLoadInFlight;\n\n const load = (async (): Promise<SearchScopeInfo[]> => {\n try {\n const provider = Metadata.Provider; // global-provider-ok: scope discovery consumes the GraphQL channel of the global default provider\n if (!(provider instanceof GraphQLDataProvider)) {\n this.Scopes$.next([]);\n return [];\n }\n const client = new GraphQLSearchClient(provider);\n const scopes = await client.GetSearchScopes();\n this.Scopes$.next(scopes);\n this.scopesLoaded = true;\n return scopes;\n } catch {\n this.Scopes$.next([]);\n return [];\n } finally {\n this.scopesLoadInFlight = null;\n }\n })();\n\n this.scopesLoadInFlight = load;\n return load;\n }\n\n /** Build the client filters from active filter selections */\n private buildClientFilters(request: SearchRequest): { EntityNames?: string[]; SourceTypes?: string[]; Tags?: string[] } | undefined {\n if (!request.ActiveFilters || Object.keys(request.ActiveFilters).length === 0) {\n return undefined;\n }\n\n const result: { EntityNames?: string[]; SourceTypes?: string[]; Tags?: string[] } = {};\n\n const entityFilters = request.ActiveFilters['Entity'];\n if (entityFilters?.length) {\n result.EntityNames = entityFilters;\n }\n\n const sourceFilters = request.ActiveFilters['Source'];\n if (sourceFilters?.length) {\n result.SourceTypes = sourceFilters;\n }\n\n const tagFilters = request.ActiveFilters['Tags'];\n if (tagFilters?.length) {\n result.Tags = tagFilters;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-search",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0-edge.0",
|
|
4
4
|
"description": "MemberJunction: Reusable search overlay and results components - command palette / Spotlight style search",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@angular/compiler": "21.2.22",
|
|
15
15
|
"@angular/compiler-cli": "21.2.22",
|
|
16
|
-
"@memberjunction/ng-test-utils": "6.
|
|
16
|
+
"@memberjunction/ng-test-utils": "6.2.0-edge.0",
|
|
17
17
|
"vitest": "^4.0.18"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"@angular/forms": "^21.2.22"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@memberjunction/ng-ui-components": "6.
|
|
26
|
-
"@memberjunction/core": "6.
|
|
27
|
-
"@memberjunction/ng-base-types": "6.
|
|
28
|
-
"@memberjunction/global": "6.
|
|
29
|
-
"@memberjunction/graphql-dataprovider": "6.
|
|
30
|
-
"@memberjunction/core-entities": "6.
|
|
31
|
-
"@memberjunction/ng-shared-generic": "6.
|
|
25
|
+
"@memberjunction/ng-ui-components": "6.2.0-edge.0",
|
|
26
|
+
"@memberjunction/core": "6.2.0-edge.0",
|
|
27
|
+
"@memberjunction/ng-base-types": "6.2.0-edge.0",
|
|
28
|
+
"@memberjunction/global": "6.2.0-edge.0",
|
|
29
|
+
"@memberjunction/graphql-dataprovider": "6.2.0-edge.0",
|
|
30
|
+
"@memberjunction/core-entities": "6.2.0-edge.0",
|
|
31
|
+
"@memberjunction/ng-shared-generic": "6.2.0-edge.0",
|
|
32
32
|
"rxjs": "^7.8.2",
|
|
33
33
|
"tslib": "^2.8.1"
|
|
34
34
|
},
|