diff --git a/projects/dsp-ui/src/lib/action/action.module.ts b/projects/dsp-ui/src/lib/action/action.module.ts index a65c34e44..5720e089e 100644 --- a/projects/dsp-ui/src/lib/action/action.module.ts +++ b/projects/dsp-ui/src/lib/action/action.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatCardModule } from '@angular/material/card'; @@ -10,7 +11,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; import { MatMenuModule } from '@angular/material/menu'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ConfirmationDialogComponent } from './components/confirmation-dialog/confirmation-dialog.component'; @@ -18,6 +18,8 @@ import { ConfirmationMessageComponent } from './components/confirmation-dialog/c import { LoginFormComponent } from './components/login-form/login-form.component'; import { MessageComponent } from './components/message/message.component'; import { ProgressIndicatorComponent } from './components/progress-indicator/progress-indicator.component'; +import { SelectProjectComponent } from './components/select-project/select-project.component'; +import { SelectedResourcesComponent } from './components/selected-resources/selected-resources.component'; import { SortButtonComponent } from './components/sort-button/sort-button.component'; import { StringLiteralInputComponent } from './components/string-literal-input/string-literal-input.component'; import { AdminImageDirective } from './directives/admin-image/admin-image.directive'; @@ -27,69 +29,70 @@ import { ReversePipe } from './pipes/array-transformation/reverse.pipe'; import { SortByPipe } from './pipes/array-transformation/sort-by.pipe'; import { FormattedBooleanPipe } from './pipes/formatting/formatted-boolean.pipe'; import { KnoraDatePipe } from './pipes/formatting/knoradate.pipe'; +import { LinkifyPipe } from './pipes/string-transformation/linkify.pipe'; import { StringifyStringLiteralPipe } from './pipes/string-transformation/stringify-string-literal.pipe'; import { TruncatePipe } from './pipes/string-transformation/truncate.pipe'; -import { SelectProjectComponent } from './components/select-project/select-project.component'; -import { SelectedResourcesComponent } from './components/selected-resources/selected-resources.component'; @NgModule({ - declarations: [ - AdminImageDirective, - ConfirmationDialogComponent, - ConfirmationMessageComponent, - ExistingNameDirective, - FormattedBooleanPipe, - GndDirective, - KnoraDatePipe, - LoginFormComponent, - MessageComponent, - ProgressIndicatorComponent, - ReversePipe, - SortButtonComponent, - SortByPipe, - StringifyStringLiteralPipe, - StringLiteralInputComponent, - SelectProjectComponent, - TruncatePipe, - SelectedResourcesComponent - ], - imports: [ - BrowserAnimationsModule, - CommonModule, - MatButtonModule, - MatButtonToggleModule, - MatCardModule, - MatDialogModule, - MatFormFieldModule, - MatIconModule, - MatInputModule, - MatListModule, - MatAutocompleteModule, - FormsModule, - ReactiveFormsModule, - MatMenuModule, - MatSnackBarModule - ], - exports: [ - AdminImageDirective, - ConfirmationDialogComponent, - ConfirmationMessageComponent, - ExistingNameDirective, - FormattedBooleanPipe, - GndDirective, - KnoraDatePipe, - LoginFormComponent, - MessageComponent, - ProgressIndicatorComponent, - ReversePipe, - SortButtonComponent, - SortByPipe, - StringifyStringLiteralPipe, - StringLiteralInputComponent, - SelectProjectComponent, - TruncatePipe, - SelectedResourcesComponent - ] + declarations: [ + AdminImageDirective, + ConfirmationDialogComponent, + ConfirmationMessageComponent, + ExistingNameDirective, + FormattedBooleanPipe, + GndDirective, + KnoraDatePipe, + LoginFormComponent, + MessageComponent, + ProgressIndicatorComponent, + ReversePipe, + SortButtonComponent, + SortByPipe, + StringifyStringLiteralPipe, + StringLiteralInputComponent, + SelectProjectComponent, + LinkifyPipe, + TruncatePipe, + SelectedResourcesComponent + ], + imports: [ + BrowserAnimationsModule, + CommonModule, + MatButtonModule, + MatButtonToggleModule, + MatCardModule, + MatDialogModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatListModule, + MatAutocompleteModule, + FormsModule, + ReactiveFormsModule, + MatMenuModule, + MatSnackBarModule + ], + exports: [ + AdminImageDirective, + ConfirmationDialogComponent, + ConfirmationMessageComponent, + ExistingNameDirective, + FormattedBooleanPipe, + GndDirective, + KnoraDatePipe, + LoginFormComponent, + MessageComponent, + ProgressIndicatorComponent, + ReversePipe, + SortButtonComponent, + SortByPipe, + StringifyStringLiteralPipe, + StringLiteralInputComponent, + SelectProjectComponent, + LinkifyPipe, + TruncatePipe, + SelectedResourcesComponent + ] }) export class DspActionModule { } diff --git a/projects/dsp-ui/src/lib/action/index.ts b/projects/dsp-ui/src/lib/action/index.ts index b7ebae74a..d26affbdd 100644 --- a/projects/dsp-ui/src/lib/action/index.ts +++ b/projects/dsp-ui/src/lib/action/index.ts @@ -5,6 +5,7 @@ export * from './pipes/formatting/formatted-boolean.pipe'; export * from './pipes/formatting/knoradate.pipe'; export * from './pipes/array-transformation/reverse.pipe'; export * from './pipes/array-transformation/sort-by.pipe'; +export * from './pipes/string-transformation/linkify.pipe'; export * from './pipes/string-transformation/truncate.pipe'; export * from './pipes/string-transformation/stringify-string-literal.pipe'; diff --git a/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.spec.ts b/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.spec.ts new file mode 100644 index 000000000..71b052fe8 --- /dev/null +++ b/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.spec.ts @@ -0,0 +1,32 @@ +import { LinkifyPipe } from './linkify.pipe'; + +describe('LinkifyPipe', () => { + + let pipe: LinkifyPipe; + + beforeEach(() => { + pipe = new LinkifyPipe(); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('should recognize the url without protocol but with hashtag at the end', () => { + const text = 'You can visit the app on app.dasch.swiss/#'; + const linkifiedSnippet = pipe.transform(text); + expect(linkifiedSnippet).toEqual('You can visit the app on app.dasch.swiss/#'); + }); + + it('should recognize the url with protocol followed by full stop', () => { + const text = 'You can visit the app on https://app.dasch.swiss.'; + const linkifiedSnippet = pipe.transform(text); + expect(linkifiedSnippet).toEqual('You can visit the app on https://app.dasch.swiss.'); + }); + + it('should recognize both urls in the example text', () => { + const text = 'You can visit the app on https://app.dasch.swiss and the documentation on docs.dasch.swiss.'; + const linkifiedSnippet = pipe.transform(text); + expect(linkifiedSnippet).toEqual('You can visit the app on https://app.dasch.swiss and the documentation on docs.dasch.swiss.'); + }); +}); diff --git a/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.ts b/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.ts new file mode 100644 index 000000000..4c862f026 --- /dev/null +++ b/projects/dsp-ui/src/lib/action/pipes/string-transformation/linkify.pipe.ts @@ -0,0 +1,57 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +/** + * This pipe analyses a string and converts any url into a href tag + * + */ +@Pipe({ + name: 'dspLinkify' +}) +export class LinkifyPipe implements PipeTransform { + + transform(value: string): string { + let stylizedText: string = ''; + if (value && value.length > 0) { + for (let str of value.split(' ')) { + // if string/url ends with a full stop '.' or colon ':' or comma ',' or semicolon ';' the pipe will not recognize the url + const lastChar = str.substring(str.length - 1); + const endsWithFullStop = (lastChar === '.' || lastChar === ':' || lastChar === ',' || lastChar === ';'); + let end = ' '; + if (endsWithFullStop) { + str = str.slice(0, -1); + end = '.' + } + if (this._recognizeUrl(str)) { + const url = this._setProtocol(str); + stylizedText += `${str}${end}`; + } else { + stylizedText += str + end; + } + } + return stylizedText.trim(); + } else { + return value; + } + } + + private _recognizeUrl(str: string): boolean { + const pattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', + 'i' + ); // fragment locator + return pattern.test(str); + } + + private _setProtocol(url: string): string { + if (!/^(?:f|ht)tps?\:\/\//.test(url)) { + url = 'http://' + url; + } + return url; + } + +} diff --git a/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-html/text-value-as-html.component.html b/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-html/text-value-as-html.component.html index cf25186ae..c740bee76 100644 --- a/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-html/text-value-as-html.component.html +++ b/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-html/text-value-as-html.component.html @@ -1,4 +1,4 @@ -
+This is a very simple HTML document with a link
'; + hostCompDe = testHostFixture.debugElement; - testHostComponent.displayInputVal = inputVal; + }); - testHostFixture.detectChanges(); + it('should display an existing value', () => { - expect(testHostComponent.inputValueComponent).toBeTruthy(); + const inputVal: ReadTextValueAsHtml = new ReadTextValueAsHtml(); - const valueComponentDe = hostCompDe.query(By.directive(TextValueAsHtmlComponent)); + inputVal.hasPermissions = 'CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser'; + inputVal.userHasPermission = 'CR'; + inputVal.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue'; + inputVal.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw/values/TEST_ID'; + inputVal.html = + 'This is a very simple HTML document with a link
'; - const valueParagraph = valueComponentDe.query(By.css('div.value')); - const valueParagraphNativeElement = valueParagraph.nativeElement; + testHostComponent.displayInputVal = inputVal; - expect(testHostComponent.inputValueComponent.displayValue.html) - .toEqual('This is a very simple HTML document with a link
'); + testHostFixture.detectChanges(); - expect(testHostComponent.inputValueComponent.mode).toEqual('read'); + expect(testHostComponent.inputValueComponent).toBeTruthy(); - expect(valueParagraphNativeElement.innerHTML) - .toEqual('This is a very simple HTML document with a link
'); + const valueComponentDe = hostCompDe.query(By.directive(TextValueAsHtmlComponent)); - const commentSpan = valueComponentDe.query(By.css('span.comment')); + const valueParagraph = valueComponentDe.query(By.css('div.value')); + const valueParagraphNativeElement = valueParagraph.nativeElement; - expect(commentSpan).toBe(null); + expect(testHostComponent.inputValueComponent.displayValue.html) + .toEqual('This is a very simple HTML document with a link
'); - }); + expect(testHostComponent.inputValueComponent.mode).toEqual('read'); + + expect(valueParagraphNativeElement.innerHTML) + .toEqual('This is a very simple HTML document with a link
'); + + const commentSpan = valueComponentDe.query(By.css('span.comment')); + + expect(commentSpan).toBe(null); + + }); - it('should display an existing value with a comment', () => { + it('should display an existing value with a comment', () => { - const inputVal: ReadTextValueAsHtml = new ReadTextValueAsHtml(); + const inputVal: ReadTextValueAsHtml = new ReadTextValueAsHtml(); - inputVal.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue'; - inputVal.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw/values/TEST_ID'; - inputVal.html = - 'This is a very simple HTML document with a link and a comment
'; - inputVal.valueHasComment = 'very interesting'; + inputVal.type = 'http://api.knora.org/ontology/knora-api/v2#TextValue'; + inputVal.id = 'http://rdfh.ch/0001/H6gBWUuJSuuO-CilHV8kQw/values/TEST_ID'; + inputVal.html = + 'This is a very simple HTML document with a link and a comment
'; + inputVal.valueHasComment = 'very interesting'; - testHostComponent.displayInputVal = inputVal; + testHostComponent.displayInputVal = inputVal; - testHostFixture.detectChanges(); + testHostFixture.detectChanges(); - const valueComponentDe = hostCompDe.query(By.directive(TextValueAsHtmlComponent)); + const valueComponentDe = hostCompDe.query(By.directive(TextValueAsHtmlComponent)); - const valueParagraph = valueComponentDe.query(By.css('div.value')); - const valueParagraphNativeElement = valueParagraph.nativeElement; + const valueParagraph = valueComponentDe.query(By.css('div.value')); + const valueParagraphNativeElement = valueParagraph.nativeElement; - const commentSpan = valueComponentDe.query(By.css('span.comment')); - const commentSpanNativeElement = commentSpan.nativeElement; + const commentSpan = valueComponentDe.query(By.css('span.comment')); + const commentSpanNativeElement = commentSpan.nativeElement; - expect(testHostComponent.inputValueComponent.displayValue.html) - .toEqual('This is a very simple HTML document with a link and a comment
'); + expect(testHostComponent.inputValueComponent.displayValue.html) + .toEqual('This is a very simple HTML document with a link and a comment
'); - expect(testHostComponent.inputValueComponent.mode).toEqual('read'); + expect(testHostComponent.inputValueComponent.mode).toEqual('read'); - expect(valueParagraphNativeElement.innerHTML) - .toEqual('This is a very simple HTML document with a link and a comment
'); + expect(valueParagraphNativeElement.innerHTML) + .toEqual('This is a very simple HTML document with a link and a comment
'); - expect(commentSpanNativeElement.innerText).toEqual('very interesting'); + expect(commentSpanNativeElement.innerText).toEqual('very interesting'); + }); }); - }); }); diff --git a/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-string/text-value-as-string.component.html b/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-string/text-value-as-string.component.html index c8335f09a..777646272 100644 --- a/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-string/text-value-as-string.component.html +++ b/projects/dsp-ui/src/lib/viewer/values/text-value/text-value-as-string/text-value-as-string.component.html @@ -1,5 +1,5 @@ - {{valueFormControl.value}} + {{commentFormControl.value}}