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',