From f201760ba2d5fc57d51ce50a7bb552aec49a7fff Mon Sep 17 00:00:00 2001 From: AleksanderSklorz <115619721+AleksanderSklorz@users.noreply.github.com> Date: Fri, 18 Apr 2025 08:03:20 +0200 Subject: [PATCH] [ACS-9535] open links inside strings in md format as separated browser tab (#4521) * [ACS-9535] Opening embedded links in MD formatted answer as new tab * [ACS-9535] Unit tests for marked options --- .../aca-content/src/lib/aca-content.module.ts | 4 +- .../search-ai-marked-options.spec.ts | 61 +++++++++++++++++++ .../search-ai-marked-options.ts | 32 ++++++++++ .../search-ai-results.component.spec.ts | 7 ++- .../search-ai-results.component.ts | 13 +++- 5 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.spec.ts create mode 100644 projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.ts diff --git a/projects/aca-content/src/lib/aca-content.module.ts b/projects/aca-content/src/lib/aca-content.module.ts index 64ca2849b..bb20d2df2 100644 --- a/projects/aca-content/src/lib/aca-content.module.ts +++ b/projects/aca-content/src/lib/aca-content.module.ts @@ -79,7 +79,6 @@ import { SearchResultsRowComponent } from './components/search/search-results-ro import { BulkActionsDropdownComponent } from './components/bulk-actions-dropdown/bulk-actions-dropdown.component'; import { AgentsButtonComponent } from './components/knowledge-retrieval/search-ai/agents-button/agents-button.component'; import { SaveSearchSidenavComponent } from './components/search/search-save/sidenav/save-search-sidenav.component'; -import { MarkdownModule } from 'ngx-markdown'; @NgModule({ imports: [ @@ -101,8 +100,7 @@ import { MarkdownModule } from 'ngx-markdown'; AcaFolderRulesModule, CreateFromTemplateDialogComponent, OpenInAppComponent, - UploadFilesDialogComponent, - MarkdownModule.forRoot() + UploadFilesDialogComponent ], providers: [ { provide: ContentVersionService, useClass: ContentUrlService }, diff --git a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.spec.ts b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.spec.ts new file mode 100644 index 000000000..0478c25a8 --- /dev/null +++ b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.spec.ts @@ -0,0 +1,61 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { searchAiMarkedOptions } from './search-ai-marked-options'; + +describe('SearchAiMarkedOptions', () => { + let link = ''; + + beforeEach(() => { + link = searchAiMarkedOptions.renderer.link('https://example.com', 'Example', 'Example Link'); + }); + + it('should return a element', () => { + expect(link).toContain(' { + expect(link).toContain('href="https://example.com"'); + }); + + it('should returned link contain correct target', () => { + expect(link).toContain('target="_blank"'); + }); + + it('should returned link contain correct rel', () => { + expect(link).toContain('rel="noopener noreferrer"'); + }); + + it('should returned link contain correct title', () => { + expect(link).toContain('title="Example"'); + }); + + it('should returned link contain correct text', () => { + expect(link).toContain('>Example Link'); + }); + + it('should returned link contain correct title if title is null', () => { + expect(searchAiMarkedOptions.renderer.link('https://example.com', null, 'Example Link')).toContain('title=""'); + }); +}); diff --git a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.ts b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.ts new file mode 100644 index 000000000..fe170ed29 --- /dev/null +++ b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-marked-options.ts @@ -0,0 +1,32 @@ +/*! + * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * Alfresco Example Content Application + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * from Hyland Software. If not, see . + */ + +import { MarkedOptions, MarkedRenderer } from 'ngx-markdown'; + +const renderer = new MarkedRenderer(); +renderer.link = (href: string, title: string, text: string): string => + `${text}`; +export const searchAiMarkedOptions: MarkedOptions = { + renderer +}; diff --git a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.spec.ts b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.spec.ts index 272211235..f336ea711 100644 --- a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.spec.ts +++ b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.spec.ts @@ -41,7 +41,8 @@ import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { getAppSelection, getCurrentFolder, ViewNodeAction } from '@alfresco/aca-shared/store'; import { ViewerService } from '@alfresco/aca-content/viewer'; import { DebugElement } from '@angular/core'; -import { MarkdownComponent, MarkdownModule } from 'ngx-markdown'; +import { MarkdownComponent, MarkdownModule, MARKED_OPTIONS } from 'ngx-markdown'; +import { searchAiMarkedOptions } from './search-ai-marked-options'; const questionMock: QuestionModel = { question: 'test', questionId: 'testId', restrictionQuery: { nodesIds: [] } }; const getAiAnswerEntry = (noAnswer?: boolean): AiAnswerEntry => { @@ -361,6 +362,10 @@ describe('SearchAiResultsComponent', () => { answerEntry = getAiAnswerEntry(); }); + it('should have correct marked options', () => { + expect(fixture.debugElement.injector.get(MARKED_OPTIONS)).toBe(searchAiMarkedOptions); + }); + it('should be rendered when answer is loaded successfully', fakeAsync(() => { getAnswerSpyAnd.returnValues( throwError(() => 'error'), diff --git a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.ts b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.ts index c402f941e..230101cac 100644 --- a/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.ts +++ b/projects/aca-content/src/lib/components/knowledge-retrieval/search-ai/search-ai-results/search-ai-results.component.ts @@ -43,7 +43,8 @@ import { ModalAiService } from '../../../../services/modal-ai.service'; import { ViewNodeAction } from '@alfresco/aca-shared/store'; import { ViewerService } from '@alfresco/aca-content/viewer'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { MarkdownComponent } from 'ngx-markdown'; +import { MarkdownModule, MARKED_OPTIONS, provideMarkdown } from 'ngx-markdown'; +import { searchAiMarkedOptions } from './search-ai-marked-options'; @Component({ standalone: true, @@ -58,7 +59,15 @@ import { MarkdownComponent } from 'ngx-markdown'; EmptyContentComponent, MatCardModule, MatTooltipModule, - MarkdownComponent + MarkdownModule + ], + providers: [ + provideMarkdown({ + markedOptions: { + provide: MARKED_OPTIONS, + useValue: searchAiMarkedOptions + } + }) ], selector: 'aca-search-ai-results', templateUrl: './search-ai-results.component.html',