Browse Source

Styling fixes and markdown support for errors and notifications.

release/3.x
Sebastian 7 years ago
parent
commit
7e880aee10
  1. 4
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  2. 2
      src/Squidex/app/framework/angular/forms/form-error.component.ts
  3. 4
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.html
  4. 30
      src/Squidex/app/framework/angular/pipes/markdown.pipe.spec.ts
  5. 29
      src/Squidex/app/framework/angular/pipes/markdown.pipe.ts
  6. 1
      src/Squidex/app/framework/declarations.ts
  7. 3
      src/Squidex/app/framework/module.ts
  8. 4
      src/Squidex/app/framework/utils/error.spec.ts
  9. 40
      src/Squidex/app/framework/utils/error.ts
  10. 36
      src/Squidex/app/shared/components/help-markdown.pipe.spec.ts
  11. 6
      src/Squidex/app/shared/components/help-markdown.pipe.ts
  12. 6
      src/Squidex/app/theme/_forms.scss

4
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -156,7 +156,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
} }
public canDeactivate(): Observable<boolean> { public canDeactivate(): Observable<boolean> {
return this.checkPendingChanges('close current content view').pipe( return this.checkPendingChanges('close the current content view').pipe(
tap(confirmed => { tap(confirmed => {
if (confirmed) { if (confirmed) {
this.autoSaveService.remove(this.autoSaveKey); this.autoSaveService.remove(this.autoSaveKey);
@ -267,7 +267,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
private checkPendingChanges(action: string) { private checkPendingChanges(action: string) {
return this.contentForm.hasChanged() ? return this.contentForm.hasChanged() ?
this.dialogs.confirm('Unsaved changes', `You have unsaved changes. When you ${action} you will loose them.<br />Do you want to continue anyway?`) : this.dialogs.confirm('Unsaved changes', `You have unsaved changes.\n\nWhen you ${action} you will loose them.\n\n*Do you want to continue anyway?*`) :
of(true); of(true);
} }

2
src/Squidex/app/framework/angular/forms/form-error.component.ts

@ -18,7 +18,7 @@ import { ErrorDto } from '@app/framework/internal';
<a class="form-alert-close" (click)="close()"> <a class="form-alert-close" (click)="close()">
<i class="icon-close"></i> <i class="icon-close"></i>
</a> </a>
<div [innerHTML]="error?.displayMessage"></div> <div [innerHTML]="error?.displayMessage | sqxMarkdown"></div>
</div> </div>
</div> </div>
</ng-container>`, </ng-container>`,

4
src/Squidex/app/framework/angular/modals/dialog-renderer.component.html

@ -7,7 +7,7 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<span [innerHTML]="snapshot.dialogRequest?.text"></span> <span [innerHTML]="snapshot.dialogRequest?.text | sqxMarkdown"></span>
</ng-container> </ng-container>
<ng-container footer> <ng-container footer>
@ -21,7 +21,7 @@
<div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of snapshot.notifications" (click)="close(notification)" @fade> <div class="alert alert-dismissible alert-{{notification.messageType}}" *ngFor="let notification of snapshot.notifications" (click)="close(notification)" @fade>
<button type="button" class="close" data-dismiss="alert" (close)="close(notification)">&times;</button> <button type="button" class="close" data-dismiss="alert" (close)="close(notification)">&times;</button>
<span [innerHTML]="notification.message"></span> <span [innerHTML]="notification.message | sqxMarkdown"></span>
</div> </div>
</div> </div>

30
src/Squidex/app/framework/angular/pipes/markdown.pipe.spec.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { MarkdownPipe } from './markdown.pipe';
describe('MarkdownPipe', () => {
it('should convert link to html', () => {
const actual = new MarkdownPipe().transform('[link-name](link-url)');
expect(actual).toBe('<p><a href="link-url" target="_blank", rel="noopener">link-name <i class="icon-external-link"></i></a></p>\n');
});
it('should convert markdown to html', () => {
const actual = new MarkdownPipe().transform('*bold*');
expect(actual).toBe('<p><em>bold</em></p>\n');
});
[null, undefined, ''].map(x => {
it('should return empty string for invalid value', () => {
const actual = new MarkdownPipe().transform(x);
expect(actual).toBe('');
});
});
});

29
src/Squidex/app/framework/angular/pipes/markdown.pipe.ts

@ -0,0 +1,29 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Pipe, PipeTransform } from '@angular/core';
import Marked from 'marked';
const renderer = new Marked.Renderer();
renderer.link = (href, _, text) => {
return `<a href="${href}" target="_blank", rel="noopener">${text} <i class="icon-external-link"></i></a>`;
};
@Pipe({
name: 'sqxMarkdown',
pure: true
})
export class MarkdownPipe implements PipeTransform {
public transform(text: string | null | undefined): string {
if (text) {
return Marked(text, { renderer });
} else {
return '';
}
}
}

1
src/Squidex/app/framework/declarations.ts

@ -46,6 +46,7 @@ export * from './angular/modals/tooltip.directive';
export * from './angular/pipes/colors.pipes'; export * from './angular/pipes/colors.pipes';
export * from './angular/pipes/date-time.pipes'; export * from './angular/pipes/date-time.pipes';
export * from './angular/pipes/keys.pipe'; export * from './angular/pipes/keys.pipe';
export * from './angular/pipes/markdown.pipe';
export * from './angular/pipes/money.pipe'; export * from './angular/pipes/money.pipe';
export * from './angular/pipes/name.pipe'; export * from './angular/pipes/name.pipe';
export * from './angular/pipes/numbers.pipes'; export * from './angular/pipes/numbers.pipes';

3
src/Squidex/app/framework/module.ts

@ -59,6 +59,7 @@ import {
LoadingInterceptor, LoadingInterceptor,
LoadingService, LoadingService,
LocalStoreService, LocalStoreService,
MarkdownPipe,
MessageBus, MessageBus,
ModalDialogComponent, ModalDialogComponent,
ModalDirective, ModalDirective,
@ -142,6 +143,7 @@ import {
KeysPipe, KeysPipe,
KNumberPipe, KNumberPipe,
LightenPipe, LightenPipe,
MarkdownPipe,
ModalDialogComponent, ModalDialogComponent,
ModalDirective, ModalDirective,
ModalPlacementDirective, ModalPlacementDirective,
@ -214,6 +216,7 @@ import {
KeysPipe, KeysPipe,
KNumberPipe, KNumberPipe,
LightenPipe, LightenPipe,
MarkdownPipe,
ModalDirective, ModalDirective,
ModalDialogComponent, ModalDialogComponent,
ModalPlacementDirective, ModalPlacementDirective,

4
src/Squidex/app/framework/utils/error.spec.ts

@ -29,12 +29,12 @@ describe('ErrorDto', () => {
it('Should create append do to simple message when detail has one item', () => { it('Should create append do to simple message when detail has one item', () => {
const error = new ErrorDto(500, 'Error Message', ['Detail Message']); const error = new ErrorDto(500, 'Error Message', ['Detail Message']);
expect(error.displayMessage).toBe('Error Message: Detail Message.'); expect(error.displayMessage).toBe('Error Message.\n\n * Detail Message.\n');
}); });
it('Should create html list when error has more items.', () => { it('Should create html list when error has more items.', () => {
const error = new ErrorDto(500, 'Error Message', ['Detail1.', 'Detail2.']); const error = new ErrorDto(500, 'Error Message', ['Detail1.', 'Detail2.']);
expect(error.displayMessage).toBe('Error Message.<ul><li>Detail1.</li><li>Detail2.</li></ul>'); expect(error.displayMessage).toBe('Error Message.\n\n * Detail1.\n * Detail2.\n');
}); });
}); });

40
src/Squidex/app/framework/utils/error.ts

@ -23,39 +23,25 @@ export class ErrorDto {
} }
function formatMessage(message: string, details?: ReadonlyArray<string>) { function formatMessage(message: string, details?: ReadonlyArray<string>) {
const appendLast = (row: string, char: string) => { let result = appendLast(message, '.');
const last = row[row.length - 1];
if (last !== char) { if (details && details.length > 0) {
return row + char; result += '\n\n';
} else {
return row;
}
};
const removeLast = (row: string, char: string) => {
const last = row[row.length - 1];
if (last === char) {
return row.substr(0, row.length - 1);
} else {
return row;
}
};
if (details && details.length > 1) {
let result = appendLast(message, '.') + '<ul>';
for (const detail of details) { for (const detail of details) {
result += `<li>${appendLast(detail, '.')}</li>`; result += ` * ${appendLast(detail, '.')}\n`;
} }
}
return result;
}
result = result + '</ul>'; function appendLast(row: string, char: string) {
const last = row[row.length - 1];
return result; if (last !== char) {
} else if (details && details.length === 1) { return row + char;
return `${appendLast(removeLast(message, '.'), ':')} ${appendLast(details[0], '.')}`;
} else { } else {
return appendLast(message, '.'); return row;
} }
} }

36
src/Squidex/app/shared/components/help-markdown.pipe.spec.ts

@ -0,0 +1,36 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { HelpMarkdownPipe } from './help-markdown.pipe';
describe('MarkdownPipe', () => {
it('should convert absolute link to html', () => {
const actual = new HelpMarkdownPipe().transform('[link-name](https://squidex.io)');
expect(actual).toBe('<p><a href="https://squidex.io" target="_blank", rel="noopener">link-name <i class="icon-external-link"></i></a></p>\n');
});
it('should convert relative link to html', () => {
const actual = new HelpMarkdownPipe().transform('[link-name](link-url)');
expect(actual).toBe('<p><a href="https://docs.squidex.io/link-url" target="_blank", rel="noopener">link-name <i class="icon-external-link"></i></a></p>\n');
});
it('should convert markdown to html', () => {
const actual = new HelpMarkdownPipe().transform('*bold*');
expect(actual).toBe('<p><em>bold</em></p>\n');
});
[null, undefined, ''].map(x => {
it('should return empty string for invalid value', () => {
const actual = new HelpMarkdownPipe().transform(x);
expect(actual).toBe('');
});
});
});

6
src/Squidex/app/shared/components/help-markdown.pipe.ts

@ -10,12 +10,12 @@ import * as Marked from 'marked';
const renderer = new Marked.Renderer(); const renderer = new Marked.Renderer();
renderer.link = (href, title, text) => { renderer.link = (href, _, text) => {
if (!href.startsWith('http')) { if (!href.startsWith('http')) {
href = `https://docs.squidex.io/${href}`; href = `https://docs.squidex.io/${href}`;
} }
return `<a href="${href}" title="${title}" target="_blank", rel="noopener">${text} <i class="icon-external-link"></i></a>`; return `<a href="${href}" target="_blank", rel="noopener">${text} <i class="icon-external-link"></i></a>`;
}; };
@Pipe({ @Pipe({
@ -23,7 +23,7 @@ renderer.link = (href, title, text) => {
pure: true pure: true
}) })
export class HelpMarkdownPipe implements PipeTransform { export class HelpMarkdownPipe implements PipeTransform {
public transform(text: string | null): string { public transform(text: string | null | undefined): string {
if (text) { if (text) {
return Marked(text, { renderer }); return Marked(text, { renderer });
} else { } else {

6
src/Squidex/app/theme/_forms.scss

@ -116,11 +116,11 @@
@include absolute(.25rem, 0, auto, auto); @include absolute(.25rem, 0, auto, auto);
padding: 1rem; padding: 1rem;
min-width: 200px; min-width: 200px;
max-width: 400px; max-width: 600px;
font-size: 1rem; font-size: 1rem;
font-weight: normal; font-weight: normal;
text-align: left; text-align: left;
z-index: 1000; z-index: 2000;
&::after { &::after {
@include absolute(-.75rem, .625rem, auto, auto); @include absolute(-.75rem, .625rem, auto, auto);
@ -184,7 +184,7 @@
label { label {
& { & {
color: black; color: $color-title;
} }
.hint { .hint {

Loading…
Cancel
Save