From 2f73f58f853be5e46c4580cee11ca746a78da5ec Mon Sep 17 00:00:00 2001 From: Camille Moinier Date: Tue, 22 Aug 2023 14:34:46 +0200 Subject: [PATCH 1/2] (feat): make hyperlink fixable --- .../metadata-info/linkify.directive.spec.ts | 45 +++++++++++++++++++ .../linkify.directive.stories.ts | 41 +++++++++++++++++ .../lib/metadata-info/linkify.directive.ts | 39 ++++++++++++++++ .../metadata-info.component.html | 4 +- .../ui/elements/src/lib/ui-elements.module.ts | 2 + 5 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 libs/ui/elements/src/lib/metadata-info/linkify.directive.spec.ts create mode 100644 libs/ui/elements/src/lib/metadata-info/linkify.directive.stories.ts create mode 100644 libs/ui/elements/src/lib/metadata-info/linkify.directive.ts diff --git a/libs/ui/elements/src/lib/metadata-info/linkify.directive.spec.ts b/libs/ui/elements/src/lib/metadata-info/linkify.directive.spec.ts new file mode 100644 index 0000000000..09f9b93ecd --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-info/linkify.directive.spec.ts @@ -0,0 +1,45 @@ +import { TestBed, ComponentFixture, waitForAsync } from '@angular/core/testing' +import { Component, DebugElement } from '@angular/core' +import { By } from '@angular/platform-browser' +import { GnUiLinkifyDirective } from './linkify.directive' + +@Component({ + template: `
Click this link https://www.example.com
`, +}) +class TestComponent {} + +describe('GnUiLinkifyDirective', () => { + let fixture: ComponentFixture + let component: TestComponent + let debugElement: DebugElement + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [GnUiLinkifyDirective, TestComponent], + }) + + fixture = TestBed.createComponent(TestComponent) + component = fixture.componentInstance + debugElement = fixture.debugElement.query( + By.directive(GnUiLinkifyDirective) + ) + + fixture.detectChanges() + })) + + it('should create an anchor element with the correct href', () => { + fixture.detectChanges() + const anchorElement = debugElement.query(By.css('a')) + + const href = anchorElement.nativeElement.getAttribute('href') + expect(href).toBe('https://www.example.com') + }) + + it('should have the target attribute set to "_blank"', () => { + fixture.detectChanges() + const anchorElement = debugElement.query(By.css('a')) + + const target = anchorElement.nativeElement.getAttribute('target') + expect(target).toBe('_blank') + }) +}) diff --git a/libs/ui/elements/src/lib/metadata-info/linkify.directive.stories.ts b/libs/ui/elements/src/lib/metadata-info/linkify.directive.stories.ts new file mode 100644 index 0000000000..3bca5ce296 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-info/linkify.directive.stories.ts @@ -0,0 +1,41 @@ +import { + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from '@storybook/angular' +import { GnUiLinkifyDirective } from './linkify.directive' + +export default { + title: 'Elements/GnUiLinkifyDirective', + decorators: [ + moduleMetadata({ + declarations: [GnUiLinkifyDirective], + }), + ], +} as Meta + +export const Primary: StoryObj = { + args: { + htmlContent: `Région Hauts-de-France, Dreal, IGN BD Topo
+ + Les données produites s'appuient sur le modèle CNIG de juin 2018 relatif aux SCoT : http://cnig.gouv.fr/wp-content/uploads/2019/04/190315_Standard_CNIG_SCOT.pdf
+ + La structure a été modifiée au 03/2023 pour prendre en compte les évolutions du modèle CNIG du 10/06/2021 :
+ http://cnig.gouv.fr/IMG/pdf/210615_standard_cnig_nouveauscot.pdf
+ (il coexiste donc dans le modèle des champs liés aux deux modèles, par exemple sur les PADD pour les "anciens" SCoT, ou encore sur les PAS ou les DAAC pour les "nouveaux" SCoT)`, + }, + argTypes: { + htmlContent: { + control: 'text', + }, + }, + render: (args) => ({ + props: args, + template: ` +
+ ${args.htmlContent} +
`, + }), +} diff --git a/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts b/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts new file mode 100644 index 0000000000..e22a5e8565 --- /dev/null +++ b/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts @@ -0,0 +1,39 @@ +/* eslint-disable @angular-eslint/directive-selector */ +import { Directive, ElementRef, Renderer2, OnInit } from '@angular/core' + +@Directive({ + selector: '[gnUiLinkify]', +}) +export class GnUiLinkifyDirective implements OnInit { + constructor(private el: ElementRef, private renderer: Renderer2) {} + + ngOnInit() { + setTimeout(() => { + this.processLinks() + }, 0) + } + + private processLinks() { + const container = this.el.nativeElement + const linkRegex = /(\bhttps?:\/\/\S+\b)/g + + const nodes = Array.from(container.childNodes) + nodes.forEach((node) => { + if (node instanceof Text) { + const textNode = node as Text + const linkified = this.linkifyText(textNode.nodeValue) + const span = this.renderer.createElement('span') + span.innerHTML = linkified + container.insertBefore(span, textNode) + container.removeChild(textNode) + } + }) + } + + private linkifyText(text: string): string { + return text.replace(/(\bhttps?:\/\/\S+\b)/g, (match) => { + return `${match} open_in_new` + }) + } +} diff --git a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html index e37afb9ca7..fe1fb2cf6a 100644 --- a/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +++ b/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html @@ -32,7 +32,7 @@ *ngIf="metadata.lineage" [title]="'record.metadata.origin' | translate" > -

+

{{ metadata.lineage }}

record.metadata.isOpenData - + {{ constraint }} diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index d67290b3b3..74a897c6bb 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -23,6 +23,7 @@ import { UiInputsModule } from '@geonetwork-ui/ui/inputs' import { FormsModule } from '@angular/forms' import { AvatarComponent } from './avatar/avatar.component' import { UserPreviewComponent } from './user-preview/user-preview.component' +import { GnUiLinkifyDirective } from './metadata-info/linkify.directive' @NgModule({ imports: [ @@ -53,6 +54,7 @@ import { UserPreviewComponent } from './user-preview/user-preview.component' ThumbnailComponent, AvatarComponent, UserPreviewComponent, + GnUiLinkifyDirective, ], exports: [ MetadataInfoComponent, From 91f857eab58b2f796fe05d1c456dc17bc126e459 Mon Sep 17 00:00:00 2001 From: Camille Moinier Date: Tue, 22 Aug 2023 14:46:47 +0200 Subject: [PATCH 2/2] (fix): clean code --- libs/ui/elements/src/lib/metadata-info/linkify.directive.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts b/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts index e22a5e8565..3ef699bad1 100644 --- a/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts +++ b/libs/ui/elements/src/lib/metadata-info/linkify.directive.ts @@ -15,7 +15,6 @@ export class GnUiLinkifyDirective implements OnInit { private processLinks() { const container = this.el.nativeElement - const linkRegex = /(\bhttps?:\/\/\S+\b)/g const nodes = Array.from(container.childNodes) nodes.forEach((node) => {