diff --git a/frontend/src/app/declarations.d.ts b/frontend/src/app/declarations.d.ts index 43716dcf2f..1f0d94e0e0 100644 --- a/frontend/src/app/declarations.d.ts +++ b/frontend/src/app/declarations.d.ts @@ -18,3 +18,7 @@ declare module 'sortablejs' { export function create(element: any, options: any): Ref; } + +declare namespace marked { + export function escape(input: string): string; +} \ No newline at end of file diff --git a/frontend/src/app/features/rules/pages/rule/rule-page.component.ts b/frontend/src/app/features/rules/pages/rule/rule-page.component.ts index d816942fdb..cc275b7bc7 100644 --- a/frontend/src/app/features/rules/pages/rule/rule-page.component.ts +++ b/frontend/src/app/features/rules/pages/rule/rule-page.component.ts @@ -10,7 +10,7 @@ import { AbstractControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { debounceTime, Subscription } from 'rxjs'; import { ActionForm, ALL_TRIGGERS, MessageBus, ResourceOwner, RuleDto, RuleElementDto, RulesService, RulesState, SchemasState, TriggerForm, value$ } from '@app/shared'; -import { RuleConfigured } from '../messages'; +import { RuleConfigured } from './../messages'; @Component({ selector: 'sqx-rule-page', diff --git a/frontend/src/app/features/rules/pages/simulator/rule-simulator-page.component.ts b/frontend/src/app/features/rules/pages/simulator/rule-simulator-page.component.ts index dce7882905..6b920699b1 100644 --- a/frontend/src/app/features/rules/pages/simulator/rule-simulator-page.component.ts +++ b/frontend/src/app/features/rules/pages/simulator/rule-simulator-page.component.ts @@ -8,7 +8,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { MessageBus, ResourceOwner, RuleSimulatorState, SimulatedRuleEventDto } from '@app/shared'; -import { RuleConfigured } from '../messages'; +import { RuleConfigured } from './../messages'; @Component({ selector: 'sqx-simulator-events-page', diff --git a/frontend/src/app/features/rules/shared/actions/generic-action.component.html b/frontend/src/app/features/rules/shared/actions/generic-action.component.html index c519b96aea..dd78bf94e0 100644 --- a/frontend/src/app/features/rules/shared/actions/generic-action.component.html +++ b/frontend/src/app/features/rules/shared/actions/generic-action.component.html @@ -47,7 +47,7 @@ - +
{{ 'rules.advancedFormattingHint' | sqxTranslate }}: {{ 'common.documentation' | sqxTranslate }} diff --git a/frontend/src/app/framework/angular/markdown.directive.spec.ts b/frontend/src/app/framework/angular/markdown.directive.spec.ts new file mode 100644 index 0000000000..87f59a1153 --- /dev/null +++ b/frontend/src/app/framework/angular/markdown.directive.spec.ts @@ -0,0 +1,89 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Renderer2 } from '@angular/core'; +import { IMock, It, Mock, Times } from 'typemoq'; +import { MarkdownDirective } from './markdown.directive'; + +describe('MarkdownDirective', () => { + let renderer: IMock; + let markdownElement = {}; + let markdownDirective: MarkdownDirective; + + beforeEach(() => { + renderer = Mock.ofType(); + + markdownElement = {}; + markdownDirective = new MarkdownDirective(markdownElement as any, renderer.object); + }); + + it('should render empty text as text', () => { + markdownDirective.markdown = ''; + markdownDirective.ngOnChanges(); + + verifyTextRender(''); + }); + + it('should render as text if result has no tags', () => { + markdownDirective.inline = true; + markdownDirective.markdown = 'markdown'; + markdownDirective.ngOnChanges(); + + verifyTextRender('markdown'); + }); + + it('should render as text if optional', () => { + markdownDirective.optional = true; + markdownDirective.markdown = '**bold**'; + markdownDirective.ngOnChanges(); + + verifyTextRender('**bold**'); + }); + + it('should render if optional with exclamation', () => { + markdownDirective.optional = true; + markdownDirective.markdown = '!**bold**'; + markdownDirective.ngOnChanges(); + + verifyHtmlRender('bold'); + }); + + it('should render as HTML if allowed', () => { + markdownDirective.inline = false; + markdownDirective.markdown = '**bold**'; + markdownDirective.ngOnChanges(); + + verifyHtmlRender('

bold

\n'); + }); + + it('should render as inline HTML if allowed', () => { + markdownDirective.markdown = '!**bold**'; + markdownDirective.ngOnChanges(); + + verifyHtmlRender('bold'); + }); + + it('should render HTML escaped', () => { + markdownDirective.inline = false; + markdownDirective.markdown = '

Header

'; + markdownDirective.ngOnChanges(); + + verifyHtmlRender('

<h1>Header</h1>

\n'); + }); + + function verifyTextRender(text: string) { + renderer.verify(x => x.setProperty(It.isAny(), 'textContent', text), Times.once()); + + expect().nothing(); + } + + function verifyHtmlRender(text: string) { + renderer.verify(x => x.setProperty(It.isAny(), 'innerHTML', text), Times.once()); + + expect().nothing(); + } +}); \ No newline at end of file diff --git a/frontend/src/app/framework/angular/markdown.directive.ts b/frontend/src/app/framework/angular/markdown.directive.ts index 48dd250caa..2fefb6d7a2 100644 --- a/frontend/src/app/framework/angular/markdown.directive.ts +++ b/frontend/src/app/framework/angular/markdown.directive.ts @@ -6,24 +6,7 @@ */ import { Directive, ElementRef, Input, OnChanges, Renderer2 } from '@angular/core'; -import { marked } from 'marked'; - -const RENDERER_DEFAULT = new marked.Renderer(); -const RENDERER_INLINE = new marked.Renderer(); - -RENDERER_DEFAULT.link = (href, _, text) => { - if (href && href.startsWith('mailto')) { - return text; - } else { - return `${text} `; - } -}; - -RENDERER_INLINE.paragraph = (text) => { - return text; -}; - -RENDERER_INLINE.link = RENDERER_DEFAULT.link; +import { renderMarkdown } from '@app/framework/internal'; @Directive({ selector: '[sqxMarkdown]', @@ -35,9 +18,6 @@ export class MarkdownDirective implements OnChanges { @Input() public inline = true; - @Input() - public html = false; - @Input() public optional = false; @@ -50,22 +30,28 @@ export class MarkdownDirective implements OnChanges { public ngOnChanges() { let html = ''; - const markdown = this.markdown; + let markdown = this.markdown; + + const hasExclamation = markdown.indexOf('!') === 0; + + if (hasExclamation) { + markdown = markdown.substring(1); + } if (!markdown) { html = markdown; - } else if (this.optional && markdown.indexOf('!') !== 0) { + } else if (this.optional && !hasExclamation) { html = markdown; } else if (this.markdown) { - const renderer = this.inline ? RENDERER_INLINE : RENDERER_DEFAULT; - - html = marked(this.markdown, { renderer }); + html = renderMarkdown(markdown, this.inline); } - if (!this.html && (!html || html === this.markdown || html.indexOf('<') < 0)) { - this.renderer.setProperty(this.element.nativeElement, 'textContent', html); - } else { + const hasHtml = html.indexOf('<') >= 0; + + if (hasHtml) { this.renderer.setProperty(this.element.nativeElement, 'innerHTML', html); + } else { + this.renderer.setProperty(this.element.nativeElement, 'textContent', html); } } } diff --git a/frontend/src/app/framework/angular/pager.component.ts b/frontend/src/app/framework/angular/pager.component.ts index 8e79a3d5a8..27895531c7 100644 --- a/frontend/src/app/framework/angular/pager.component.ts +++ b/frontend/src/app/framework/angular/pager.component.ts @@ -6,7 +6,7 @@ */ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; -import { PagingInfo } from '../state'; +import { PagingInfo } from './../state'; export const PAGE_SIZES: ReadonlyArray = [10, 20, 30, 50]; diff --git a/frontend/src/app/framework/angular/pipes/markdown.pipe.spec.ts b/frontend/src/app/framework/angular/pipes/markdown.pipe.spec.ts index b1338502f7..2ba2308cb0 100644 --- a/frontend/src/app/framework/angular/pipes/markdown.pipe.spec.ts +++ b/frontend/src/app/framework/angular/pipes/markdown.pipe.spec.ts @@ -8,21 +8,29 @@ import { MarkdownInlinePipe, MarkdownPipe } from './markdown.pipe'; describe('MarkdownInlinePipe', () => { + const pipe = new MarkdownInlinePipe(); + it('should convert link to html', () => { - const actual = new MarkdownInlinePipe().transform('[link-name](link-url)'); + const actual = pipe.transform('[link-name](link-url)'); expect(actual).toBe('link-name '); }); it('should convert markdown to html', () => { - const actual = new MarkdownInlinePipe().transform('*bold*'); + const actual = pipe.transform('*bold*'); expect(actual).toBe('bold'); }); + it('should escape input html', () => { + const actual = pipe.transform('

Header

'); + + expect(actual).toBe('<h1>Header</h1>'); + }); + [null, undefined, ''].forEach(x => { it('should return empty string for invalid value', () => { - const actual = new MarkdownInlinePipe().transform(x); + const actual = pipe.transform(x); expect(actual).toBe(''); }); @@ -30,21 +38,29 @@ describe('MarkdownInlinePipe', () => { }); describe('MarkdownPipe', () => { + const pipe = new MarkdownPipe(); + it('should convert link to html', () => { - const actual = new MarkdownPipe().transform('[link-name](link-url)'); + const actual = pipe.transform('[link-name](link-url)'); expect(actual).toBe('

link-name

\n'); }); it('should convert markdown to html', () => { - const actual = new MarkdownPipe().transform('*bold*'); + const actual = pipe.transform('*bold*'); expect(actual).toBe('

bold

\n'); }); + it('should escape input html', () => { + const actual = pipe.transform('

Header

'); + + expect(actual).toBe('

<h1>Header</h1>

\n'); + }); + [null, undefined, ''].forEach(x => { it('should return empty string for invalid value', () => { - const actual = new MarkdownPipe().transform(x); + const actual = pipe.transform(x); expect(actual).toBe(''); }); diff --git a/frontend/src/app/framework/angular/pipes/markdown.pipe.ts b/frontend/src/app/framework/angular/pipes/markdown.pipe.ts index c78ffaa627..d47942017a 100644 --- a/frontend/src/app/framework/angular/pipes/markdown.pipe.ts +++ b/frontend/src/app/framework/angular/pipes/markdown.pipe.ts @@ -6,25 +6,7 @@ */ import { Pipe, PipeTransform } from '@angular/core'; -import { marked } from 'marked'; - -const renderer = new marked.Renderer(); - -renderer.link = (href, _, text) => { - if (href && href.startsWith('mailto')) { - return text; - } else { - return `${text} `; - } -}; - -const inlinerRenderer = new marked.Renderer(); - -inlinerRenderer.paragraph = (text) => { - return text; -}; - -inlinerRenderer.link = renderer.link; +import { renderMarkdown } from '@app/framework/internal'; @Pipe({ name: 'sqxMarkdown', @@ -32,11 +14,7 @@ inlinerRenderer.link = renderer.link; }) export class MarkdownPipe implements PipeTransform { public transform(text: string | undefined | null): string { - if (text) { - return marked(text, { renderer }); - } else { - return ''; - } + return renderMarkdown(text, false); } } @@ -46,10 +24,6 @@ export class MarkdownPipe implements PipeTransform { }) export class MarkdownInlinePipe implements PipeTransform { public transform(text: string | undefined | null): string { - if (text) { - return marked(text, { renderer: inlinerRenderer }); - } else { - return ''; - } + return renderMarkdown(text, true); } -} +} \ No newline at end of file diff --git a/frontend/src/app/framework/internal.ts b/frontend/src/app/framework/internal.ts index 166b4700d7..39759d4c45 100644 --- a/frontend/src/app/framework/internal.ts +++ b/frontend/src/app/framework/internal.ts @@ -33,6 +33,7 @@ export * from './utils/hateos'; export * from './utils/interpolator'; export * from './utils/keys'; export * from './utils/math-helper'; +export * from './utils/markdown'; export * from './utils/modal-positioner'; export * from './utils/modal-view'; export * from './utils/picasso'; diff --git a/frontend/src/app/framework/services/localizer.service.ts b/frontend/src/app/framework/services/localizer.service.ts index ca58ff8a51..6539d04c1f 100644 --- a/frontend/src/app/framework/services/localizer.service.ts +++ b/frontend/src/app/framework/services/localizer.service.ts @@ -6,7 +6,7 @@ */ import { Injectable } from '@angular/core'; -import { compareStrings } from '../utils/array-helper'; +import { compareStrings } from './../utils/array-helper'; @Injectable() export class LocalizerService { diff --git a/frontend/src/app/framework/utils/date-time.spec.ts b/frontend/src/app/framework/utils/date-time.spec.ts index dfabb261be..e69e57bdab 100644 --- a/frontend/src/app/framework/utils/date-time.spec.ts +++ b/frontend/src/app/framework/utils/date-time.spec.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { DateHelper } from '..'; +import { DateHelper } from './..'; import { DateTime } from './date-time'; describe('DateTime', () => { diff --git a/frontend/src/app/framework/utils/markdown.ts b/frontend/src/app/framework/utils/markdown.ts new file mode 100644 index 0000000000..aa9cf7df79 --- /dev/null +++ b/frontend/src/app/framework/utils/markdown.ts @@ -0,0 +1,61 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { marked } from 'marked'; + +function renderLink(href: string, _: string, text: string) { + if (href && href.startsWith('mailto')) { + return text; + } else { + return `${text} `; + } +} + +function renderInlineParagraph(text: string) { + return text; +} + +const RENDERER_DEFAULT = new marked.Renderer(); +const RENDERER_INLINE = new marked.Renderer(); + +RENDERER_INLINE.paragraph = renderInlineParagraph; +RENDERER_INLINE.link = renderLink; +RENDERER_DEFAULT.link = renderLink; + +export function renderMarkdown(input: string | undefined | null, inline: boolean) { + if (!input) { + return ''; + } + + input = escapeHTML(input); + + if (inline) { + return marked(input, { renderer: RENDERER_INLINE }); + } else { + return marked(input, { renderer: RENDERER_DEFAULT }); + } +} + +const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/; +const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, 'g'); +const escapeReplacements = { + '&' : '&', + '<' : '<', + '>' : '>', + '"' : '"', + '\'': ''', +}; + +const getEscapeReplacement = (ch: string) => escapeReplacements[ch]; + +export function escapeHTML(html: string) { + if (escapeTestNoEncode.test(html)) { + return html.replace(escapeReplaceNoEncode, getEscapeReplacement); + } + + return html; +} \ No newline at end of file diff --git a/frontend/src/app/framework/utils/text-measurer.ts b/frontend/src/app/framework/utils/text-measurer.ts index 74653d57b5..ffc493bd9d 100644 --- a/frontend/src/app/framework/utils/text-measurer.ts +++ b/frontend/src/app/framework/utils/text-measurer.ts @@ -6,7 +6,7 @@ */ import { ElementRef } from '@angular/core'; -import { Types } from '../internal'; +import { Types } from './../internal'; let CANVAS: HTMLCanvasElement | null = null; diff --git a/frontend/src/app/shared/services/history.service.spec.ts b/frontend/src/app/shared/services/history.service.spec.ts index e5279c27ed..c06ef02d5b 100644 --- a/frontend/src/app/shared/services/history.service.spec.ts +++ b/frontend/src/app/shared/services/history.service.spec.ts @@ -7,7 +7,71 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; -import { ApiUrlConfig, DateTime, HistoryEventDto, HistoryService, Version } from '@app/shared/internal'; +import { firstValueFrom, of } from 'rxjs'; +import { IMock, Mock } from 'typemoq'; +import { ApiUrlConfig, DateTime, formatHistoryMessage, HistoryEventDto, HistoryService, UsersProviderService, Version } from '@app/shared/internal'; + +describe('formatHistoryMessage', () => { + let userProvider: IMock; + + beforeEach(() => { + userProvider = Mock.ofType(); + }); + + it('should provide simple message', async () => { + const message = await firstValueFrom(formatHistoryMessage('message', userProvider.object)); + + expect(message).toEqual('message'); + }); + + it('should embed marker', async () => { + const message = await firstValueFrom(formatHistoryMessage('{Name}', userProvider.object)); + + expect(message).toEqual('Name'); + }); + + it('should embed marker ref and escape HTML', async () => { + const message = await firstValueFrom(formatHistoryMessage('{

HTML

}', userProvider.object)); + + expect(message).toEqual('<h1>HTML</h1>'); + }); + + it('should embed user ref with unknown type', async () => { + const message = await firstValueFrom(formatHistoryMessage('{unknown:User1}', userProvider.object)); + + expect(message).toEqual('User1'); + }); + + it('should embed user ref with client', async () => { + const message = await firstValueFrom(formatHistoryMessage('{user:unknown:Client1}', userProvider.object)); + + expect(message).toEqual('Client1-client'); + }); + + it('should embed user ref with client ending with client', async () => { + const message = await firstValueFrom(formatHistoryMessage('{user:client:Sample-Client}', userProvider.object)); + + expect(message).toEqual('Sample-Client'); + }); + + it('should embed user ref with subject', async () => { + userProvider.setup(x => x.getUser('1', null)) + .returns(() => of({ id: '1', displayName: 'User1' })); + + const message = await firstValueFrom(formatHistoryMessage('{user:subject:1}', userProvider.object)); + + expect(message).toEqual('User1'); + }); + + it('should embed user ref with id', async () => { + userProvider.setup(x => x.getUser('1', null)) + .returns(() => of({ id: '1', displayName: 'User1' })); + + const message = await firstValueFrom(formatHistoryMessage('{user:1}', userProvider.object)); + + expect(message).toEqual('User1'); + }); +}); describe('HistoryService', () => { beforeEach(() => { diff --git a/frontend/src/app/shared/services/history.service.ts b/frontend/src/app/shared/services/history.service.ts index 486eb2e340..883ee3b4a5 100644 --- a/frontend/src/app/shared/services/history.service.ts +++ b/frontend/src/app/shared/services/history.service.ts @@ -7,9 +7,9 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable, of } from 'rxjs'; +import { firstValueFrom, from, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { ApiUrlConfig, DateTime, pretifyError, Version } from '@app/framework'; +import { ApiUrlConfig, DateTime, escapeHTML, pretifyError, Version } from '@app/framework'; import { UsersProviderService } from './users-provider.service'; export class HistoryEventDto { @@ -24,43 +24,50 @@ export class HistoryEventDto { } } -const REPLACEMENT_TEMP = '$TEMP$'; - export function formatHistoryMessage(message: string, users: UsersProviderService): Observable { - const userName = (userId: string) => { - const parts = userId.split(':'); - - if (parts.length === 1) { - return users.getUser(parts[0], null).pipe(map(u => u.displayName)); - } else if (parts[0] === 'subject') { - return users.getUser(parts[1], null).pipe(map(u => u.displayName)); - } else if (parts[1].endsWith('client')) { - return of(parts[1]); - } else { - return of(`${parts[1]}-client`); - } - }; + async function getUserName(id: string): Promise { + const user = await firstValueFrom(users.getUser(id, null)); - let foundUserId: string | null = null; + return user.displayName; + } - message = message.replace(/{([^\s:]*):([^}]*)}/, (match: string, type: string, id: string) => { - if (type === 'user') { - foundUserId = id; - return REPLACEMENT_TEMP; - } else { - return id; + async function format(message: string): Promise { + const placeholderMatches = message.matchAll(/{(?[^\s:]*):(?[^}]*)}/g) || []; + const placeholderValues: string[] = []; + + for (const match of placeholderMatches) { + const { id, type } = match.groups!; + + if (type !== 'user') { + placeholderValues.push(id); + continue; + } + + const parts = id.split(':'); + + if (parts.length === 1) { + placeholderValues.push(await getUserName(parts[0])); + } else if (parts[0] === 'subject') { + placeholderValues.push(await getUserName(parts[1])); + } else if (parts[1].toLowerCase().endsWith('client')) { + placeholderValues.push(parts[1]); + } else { + placeholderValues.push(`${parts[1]}-client`); + } } - }); - message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => { - return `${marker}`; - }); + message = message.replace(/{([^\s:]*):([^}]*)}/, () => { + return `${escapeHTML(placeholderValues.shift() || '')}`; + }); + + message = message.replace(/{([^}]*)}/g, (match: string, marker: string) => { + return `${escapeHTML(marker)}`; + }); - if (foundUserId) { - return userName(foundUserId).pipe(map(t => message.replace(REPLACEMENT_TEMP, `${t}`))); + return message; } - return of(message); + return from(format(message)); } @Injectable() diff --git a/frontend/src/app/shared/services/rules.service.spec.ts b/frontend/src/app/shared/services/rules.service.spec.ts index 65d190898c..65379e1435 100644 --- a/frontend/src/app/shared/services/rules.service.spec.ts +++ b/frontend/src/app/shared/services/rules.service.spec.ts @@ -8,7 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { ApiUrlConfig, DateTime, Resource, ResourceLinks, RuleDto, RuleElementDto, RuleElementPropertyDto, RuleEventDto, RuleEventsDto, RulesDto, RulesService, Version } from '@app/shared/internal'; -import { RuleCompletions } from '..'; +import { RuleCompletions } from './..'; import { SimulatedRuleEventDto, SimulatedRuleEventsDto } from './rules.service'; describe('RulesService', () => { diff --git a/frontend/src/app/shared/services/schemas.service.spec.ts b/frontend/src/app/shared/services/schemas.service.spec.ts index e42a19e575..cb7a886dcf 100644 --- a/frontend/src/app/shared/services/schemas.service.spec.ts +++ b/frontend/src/app/shared/services/schemas.service.spec.ts @@ -8,7 +8,7 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { inject, TestBed } from '@angular/core/testing'; import { ApiUrlConfig, createProperties, DateTime, FieldRule, NestedFieldDto, Resource, ResourceLinks, RootFieldDto, SchemaDto, SchemaPropertiesDto, SchemasDto, SchemasService, Version } from '@app/shared/internal'; -import { SchemaCompletions } from '..'; +import { SchemaCompletions } from './..'; describe('SchemasService', () => { const version = new Version('1'); diff --git a/frontend/src/app/shared/state/asset-scripts.state.spec.ts b/frontend/src/app/shared/state/asset-scripts.state.spec.ts index 32d526c769..ddddd8a3f1 100644 --- a/frontend/src/app/shared/state/asset-scripts.state.spec.ts +++ b/frontend/src/app/shared/state/asset-scripts.state.spec.ts @@ -9,8 +9,8 @@ import { of, throwError } from 'rxjs'; import { onErrorResumeNext } from 'rxjs/operators'; import { IMock, It, Mock, Times } from 'typemoq'; import { DialogService, versioned } from '@app/shared/internal'; -import { AppsService, AssetScriptsPayload } from '../services/apps.service'; -import { createAssetScripts } from '../services/apps.service.spec'; +import { AppsService, AssetScriptsPayload } from './../services/apps.service'; +import { createAssetScripts } from './../services/apps.service.spec'; import { TestValues } from './_test-helpers'; import { AssetScriptsState } from './asset-scripts.state'; diff --git a/frontend/src/app/shared/state/asset-scripts.state.ts b/frontend/src/app/shared/state/asset-scripts.state.ts index fa57fd87e7..2f27e4da94 100644 --- a/frontend/src/app/shared/state/asset-scripts.state.ts +++ b/frontend/src/app/shared/state/asset-scripts.state.ts @@ -9,7 +9,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { finalize, tap } from 'rxjs/operators'; import { DialogService, LoadingState, Resource, shareSubscribed, State, Version } from '@app/framework'; -import { AppsService, AssetScripts, AssetScriptsPayload } from '../services/apps.service'; +import { AppsService, AssetScripts, AssetScriptsPayload } from './../services/apps.service'; import { AppsState } from './apps.state'; interface Snapshot extends LoadingState { diff --git a/frontend/src/app/shared/state/resolvers.spec.ts b/frontend/src/app/shared/state/resolvers.spec.ts index 8e0ce5a5ed..5f92e3335a 100644 --- a/frontend/src/app/shared/state/resolvers.spec.ts +++ b/frontend/src/app/shared/state/resolvers.spec.ts @@ -8,8 +8,8 @@ import { firstValueFrom, of, throwError } from 'rxjs'; import { IMock, Mock, Times } from 'typemoq'; import { UIOptions } from '@app/framework'; -import { ContentsService } from '../services/contents.service'; -import { createContent } from '../services/contents.service.spec'; +import { ContentsService } from './../services/contents.service'; +import { createContent } from './../services/contents.service.spec'; import { TestValues } from './_test-helpers'; import { ResolveContents } from './resolvers'; diff --git a/frontend/src/app/shared/state/rules.forms.ts b/frontend/src/app/shared/state/rules.forms.ts index ed72d8b11e..5b095aa008 100644 --- a/frontend/src/app/shared/state/rules.forms.ts +++ b/frontend/src/app/shared/state/rules.forms.ts @@ -7,7 +7,7 @@ import { AbstractControl, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { ExtendedFormGroup, Form, ValidatorsEx } from '@app/framework'; -import { RuleElementDto } from '../services/rules.service'; +import { RuleElementDto } from './../services/rules.service'; export class ActionForm extends Form { constructor(public readonly definition: RuleElementDto, diff --git a/frontend/src/app/shared/state/table-settings.spec.ts b/frontend/src/app/shared/state/table-settings.spec.ts index 415146d1d5..26ce1b3f8b 100644 --- a/frontend/src/app/shared/state/table-settings.spec.ts +++ b/frontend/src/app/shared/state/table-settings.spec.ts @@ -9,7 +9,7 @@ import { of } from 'rxjs'; import { IMock, It, Mock, Times } from 'typemoq'; import { DateTime, Version } from '@app/framework'; import { createProperties, FieldSizes, META_FIELDS, RootFieldDto, SchemaDto, TableSettings, UIState } from '@app/shared/internal'; -import { FieldWrappings } from '..'; +import { FieldWrappings } from './..'; describe('TableSettings', () => { let uiState: IMock;