From ec7359af2f126da107dfebd3eb7288188bc26111 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 26 Apr 2023 18:29:02 +0200 Subject: [PATCH] Fixes --- .../src/Squidex.Web/ClearCookiesAttribute.cs | 23 ------ .../Controllers/Account/AccountController.cs | 1 - .../assets/pages/assets-page.component.html | 8 +-- .../assets/pages/assets-page.component.ts | 11 ++- .../forms/editors/code-editor.stories.ts | 7 +- .../angular/long-hover.directive.stories.ts | 64 +++++++++++++++++ .../framework/angular/long-hover.directive.ts | 70 +++++++++++++++++++ frontend/src/app/framework/declarations.ts | 1 + frontend/src/app/framework/module.ts | 4 +- .../assets/asset-path.component.scss | 6 ++ .../assets/assets-list.component.html | 3 +- .../contents/content-value.component.scss | 2 + .../contents/content-value.component.ts | 5 +- .../app/shared/state/contents.forms.spec.ts | 4 +- .../state/contents.forms.visitors.spec.ts | 4 +- .../shared/state/contents.forms.visitors.ts | 6 +- 16 files changed, 173 insertions(+), 46 deletions(-) delete mode 100644 backend/src/Squidex.Web/ClearCookiesAttribute.cs create mode 100644 frontend/src/app/framework/angular/long-hover.directive.stories.ts create mode 100644 frontend/src/app/framework/angular/long-hover.directive.ts diff --git a/backend/src/Squidex.Web/ClearCookiesAttribute.cs b/backend/src/Squidex.Web/ClearCookiesAttribute.cs deleted file mode 100644 index e8167c529..000000000 --- a/backend/src/Squidex.Web/ClearCookiesAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.AspNetCore.Mvc.Filters; - -namespace Squidex.Web; - -public sealed class ClearCookiesAttribute : ActionFilterAttribute -{ - public override void OnActionExecuting(ActionExecutingContext context) - { - var cookies = context.HttpContext.Response.Cookies; - - foreach (var cookie in context.HttpContext.Request.Cookies.Keys) - { - cookies.Delete(cookie); - } - } -} diff --git a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs index d5ecf330e..64d87fec4 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs @@ -143,7 +143,6 @@ public sealed class AccountController : IdentityServerController [HttpGet] [Route("account/login/")] - [ClearCookies] public Task Login(string? returnUrl = null) { return LoginViewAsync(returnUrl, true, false); diff --git a/frontend/src/app/features/assets/pages/assets-page.component.html b/frontend/src/app/features/assets/pages/assets-page.component.html index f6d961a4e..8a6f0a002 100644 --- a/frontend/src/app/features/assets/pages/assets-page.component.html +++ b/frontend/src/app/features/assets/pages/assets-page.component.html @@ -22,7 +22,7 @@
@@ -33,10 +33,10 @@
- -
@@ -60,7 +60,7 @@ [assetsState]="assetsState" (edit)="editStart($event)" [isDisabled]="false" - [isListView]="isListView" + [isListView]="listMode" [showFolderIcon]="path.length === 0">
diff --git a/frontend/src/app/features/assets/pages/assets-page.component.ts b/frontend/src/app/features/assets/pages/assets-page.component.ts index 49eaceb7b..fe7259c3b 100644 --- a/frontend/src/app/features/assets/pages/assets-page.component.ts +++ b/frontend/src/app/features/assets/pages/assets-page.component.ts @@ -18,13 +18,12 @@ import { Settings } from '@app/shared/state/settings'; ], }) export class AssetsPageComponent extends ResourceOwner implements OnInit { - public queries = new Queries(this.uiState, 'assets'); - public editAsset?: AssetDto; - public addAssetFolderDialog = new DialogModel(); + public listQueries = new Queries(this.uiState, 'assets'); + public listMode = false; - public isListView = false; + public addAssetFolderDialog = new DialogModel(); constructor( public readonly assetsRoute: Router2State, @@ -34,7 +33,7 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit { ) { super(); - this.isListView = this.localStore.getBoolean(Settings.Local.ASSETS_MODE); + this.listMode = this.localStore.getBoolean(Settings.Local.ASSETS_MODE); } public ngOnInit() { @@ -84,7 +83,7 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit { } public changeView(isListView: boolean) { - this.isListView = isListView; + this.listMode = isListView; this.localStore.setBoolean(Settings.Local.ASSETS_MODE, isListView); } diff --git a/frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts b/frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts index b94a4d0b3..edd4a0697 100644 --- a/frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts +++ b/frontend/src/app/framework/angular/forms/editors/code-editor.stories.ts @@ -8,7 +8,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { moduleMetadata } from '@storybook/angular'; import { Meta, Story } from '@storybook/angular/types-6-0'; -import { CodeEditorComponent, SqxFrameworkModule } from '@app/framework'; +import { CodeEditorComponent, ScriptCompletions, SqxFrameworkModule } from '@app/framework'; export default { title: 'Framework/CodeEditor', @@ -74,15 +74,18 @@ const SingleLineTemplate: Story = (args: C `, }); -const COMPLETIONS = [{ +const COMPLETIONS: ScriptCompletions = [{ path: 'path1', description: 'Test1 Path', + type: 'Any', }, { path: 'path2', description: 'Test2 Path', + type: 'Array', }, { path: 'path3', description: 'Test3 Path', + type: 'String', }]; export const Default = Template.bind({}); diff --git a/frontend/src/app/framework/angular/long-hover.directive.stories.ts b/frontend/src/app/framework/angular/long-hover.directive.stories.ts new file mode 100644 index 000000000..67ef24156 --- /dev/null +++ b/frontend/src/app/framework/angular/long-hover.directive.stories.ts @@ -0,0 +1,64 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { action } from '@storybook/addon-actions'; +import { moduleMetadata } from '@storybook/angular'; +import { Meta, Story } from '@storybook/angular/types-6-0'; +import { CodeEditorComponent, LongHoverDirective, SqxFrameworkModule } from '@app/framework'; + +export default { + title: 'Framework/LongHover', + component: CodeEditorComponent, + argTypes: { + selector: { + control: 'text', + }, + hover: { + action: 'hover', + }, + cancelled: { + action: 'cancelled', + }, + }, + decorators: [ + moduleMetadata({ + imports: [ + BrowserAnimationsModule, + SqxFrameworkModule, + SqxFrameworkModule.forRoot(), + ], + }), + ], +} as Meta; + +const Template: Story = (args: LongHoverDirective & any) => ({ + props: args, + template: ` +
+
+ +
+
+ `, +}); + +export const Default = Template.bind({}); + +Default.args = { + hover: action('Hover') as any, + selector: '', + cancelled: action('Cancelled') as any, +}; + +export const Selector = Default.bind({}); + +Selector.args = { + hover: action('Hover') as any, + selector: 'button', + cancelled: action('Cancelled') as any, +}; \ No newline at end of file diff --git a/frontend/src/app/framework/angular/long-hover.directive.ts b/frontend/src/app/framework/angular/long-hover.directive.ts new file mode 100644 index 000000000..d8b4ade6c --- /dev/null +++ b/frontend/src/app/framework/angular/long-hover.directive.ts @@ -0,0 +1,70 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Directive, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core'; + +@Directive({ + selector: '[sqxLongHover]', +}) +export class LongHoverDirective { + private timerOut: Function | null = null; + private timer?: any; + + @Output('sqxLongHover') + public hover = new EventEmitter(); + + @Output('longHoverCancelled') + public cancelled = new EventEmitter(); + + @Input('longHoverSelector') + public selector?: string; + + @Input('longHoverDuration') + public duration = 2000; + + constructor( + private readonly renderer: Renderer2, + ) { + } + + @HostListener('mouseover', ['$event']) + public onMove(event: MouseEvent) { + if (!(event.target instanceof Element)) { + this.clearTimer(); + return; + } + + const isMatch = !this.selector || event.target.matches(this.selector); + + if (!isMatch) { + this.clearTimer(); + return; + } + + if (this.timer) { + return; + } + + this.timer = setTimeout(() => { + this.hover.emit(); + }, this.duration); + + this.timerOut = this.renderer.listen(event.target, 'mouseleave', () => { + this.clearTimer(); + }); + } + + private clearTimer() { + if (this.timer) { + clearTimeout(this.timer); + this.cancelled.emit(); + this.timer = null; + this.timerOut?.(); + this.timerOut = null; + } + } +} \ No newline at end of file diff --git a/frontend/src/app/framework/declarations.ts b/frontend/src/app/framework/declarations.ts index 8ea6f61b5..6f8fd1c94 100644 --- a/frontend/src/app/framework/declarations.ts +++ b/frontend/src/app/framework/declarations.ts @@ -53,6 +53,7 @@ export * from './angular/language-selector.component'; export * from './angular/layout-container.directive'; export * from './angular/layout.component'; export * from './angular/list-view.component'; +export * from './angular/long-hover.directive'; export * from './angular/loader.component'; export * from './angular/markdown.directive'; export * from './angular/modals/dialog-renderer.component'; diff --git a/frontend/src/app/framework/module.ts b/frontend/src/app/framework/module.ts index 69ee9d1e1..5d1ee49ae 100644 --- a/frontend/src/app/framework/module.ts +++ b/frontend/src/app/framework/module.ts @@ -11,7 +11,7 @@ import { ErrorHandler, ModuleWithProviders, NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { ColorPickerModule } from 'ngx-color-picker'; -import { AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, CompensateScrollbarDirective, ConfirmClickDirective, ControlErrorsComponent, ControlErrorsMessagesComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DropdownMenuComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, GlobalErrorHandler, HighlightPipe, HoverBackgroundDirective, IfOnceDirective, ImageSourceDirective, ImageUrlDirective, IndeterminateValueDirective, ISODatePipe, JoinPipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoaderComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, ProgressBarComponent, RadioGroupComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations'; +import { AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactivateGuard, CheckboxGroupComponent, ClipboardService, CodeComponent, CodeEditorComponent, ColorPickerComponent, CompensateScrollbarDirective, ConfirmClickDirective, ControlErrorsComponent, ControlErrorsMessagesComponent, CopyDirective, DarkenPipe, DatePipe, DateTimeEditorComponent, DayOfWeekPipe, DayPipe, DialogRendererComponent, DialogService, DisplayNamePipe, DropdownComponent, DropdownMenuComponent, DurationPipe, EditableTitleComponent, ExternalLinkDirective, FileDropDirective, FileSizePipe, FocusOnInitDirective, FormAlertComponent, FormErrorComponent, FormHintComponent, FromNowPipe, FullDateTimePipe, GlobalErrorHandler, HighlightPipe, HoverBackgroundDirective, IfOnceDirective, ImageSourceDirective, ImageUrlDirective, IndeterminateValueDirective, ISODatePipe, JoinPipe, KeysPipe, KNumberPipe, LanguageSelectorComponent, LayoutComponent, LayoutContainerDirective, LightenPipe, ListViewComponent, LoaderComponent, LoadingInterceptor, LoadingService, LocalizedInputComponent, LocalStoreService, LongHoverDirective, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, MessageBus, ModalDialogComponent, ModalDirective, ModalPlacementDirective, MonthPipe, OnboardingService, OnboardingTooltipComponent, PagerComponent, ParentLinkDirective, ProgressBarComponent, RadioGroupComponent, ResizedDirective, ResizeService, ResourceLoaderService, RootViewComponent, SafeHtmlPipe, SafeResourceUrlPipe, SafeUrlPipe, ScrollActiveDirective, ShortcutComponent, ShortcutDirective, ShortcutService, ShortDatePipe, ShortTimePipe, StarsComponent, StatusIconComponent, StopClickDirective, StopDragDirective, SyncScollingDirective, SyncWidthDirective, TabRouterlinkDirective, TagEditorComponent, TemplateWrapperDirective, TempService, TitleComponent, TitleService, ToggleComponent, ToolbarComponent, TooltipDirective, TransformInputDirective, TranslatePipe, VideoPlayerComponent } from './declarations'; @NgModule({ imports: [ @@ -70,6 +70,7 @@ import { AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactiva ListViewComponent, LoaderComponent, LocalizedInputComponent, + LongHoverDirective, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, @@ -159,6 +160,7 @@ import { AutocompleteComponent, AvatarComponent, CachingInterceptor, CanDeactiva ListViewComponent, LoaderComponent, LocalizedInputComponent, + LongHoverDirective, MarkdownDirective, MarkdownInlinePipe, MarkdownPipe, diff --git a/frontend/src/app/shared/components/assets/asset-path.component.scss b/frontend/src/app/shared/components/assets/asset-path.component.scss index e920153d0..2ea637e99 100644 --- a/frontend/src/app/shared/components/assets/asset-path.component.scss +++ b/frontend/src/app/shared/components/assets/asset-path.component.scss @@ -7,4 +7,10 @@ i { .first { padding-left: 0; +} + +.btn { + &:active { + border-color: transparent; + } } \ No newline at end of file diff --git a/frontend/src/app/shared/components/assets/assets-list.component.html b/frontend/src/app/shared/components/assets/assets-list.component.html index 9fcbbdf7e..5a340791a 100644 --- a/frontend/src/app/shared/components/assets/assets-list.component.html +++ b/frontend/src/app/shared/components/assets/assets-list.component.html @@ -63,7 +63,8 @@ [isDisabled]="isDisabled" [isListView]="isListView" (loadDone)="add(file, $event)" - (loadError)="remove(file)"> + (loadError)="remove(file)" + [folderId]="assetsState.parentId"> { @Input() public fields?: TableSettings; + @Output() + public preview = new EventEmitter(); + public get title() { return this.isString && this.isPlain ? this.value : undefined; } diff --git a/frontend/src/app/shared/state/contents.forms.spec.ts b/frontend/src/app/shared/state/contents.forms.spec.ts index 03e046758..8dbaea578 100644 --- a/frontend/src/app/shared/state/contents.forms.spec.ts +++ b/frontend/src/app/shared/state/contents.forms.spec.ts @@ -193,7 +193,7 @@ describe('GetContentValue', () => { const result = getContentValue(content, language, assetWithImageAndFileName); - expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue(' file13') }); + expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue(' file13', 'url/to/13') }); }); it('should resolve image url only from referenced asset', () => { @@ -209,7 +209,7 @@ describe('GetContentValue', () => { const result = getContentValue(content, language, assetWithImage); - expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue('') }); + expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue('', 'url/to/13') }); }); it('should resolve filename only from referenced asset', () => { diff --git a/frontend/src/app/shared/state/contents.forms.visitors.spec.ts b/frontend/src/app/shared/state/contents.forms.visitors.spec.ts index 94ac72fc0..c5f46514e 100644 --- a/frontend/src/app/shared/state/contents.forms.visitors.spec.ts +++ b/frontend/src/app/shared/state/contents.forms.visitors.spec.ts @@ -397,7 +397,7 @@ describe('StringField', () => { it('should format to preview image', () => { const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) }); - expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', true)).toEqual(new HtmlValue('')); + expect(FieldFormatter.format(field2, 'https://images.unsplash.com/123?x', true)).toEqual(new HtmlValue('', 'https://images.unsplash.com/123?x')); }); it('should not format to preview image if html not allowed', () => { @@ -409,7 +409,7 @@ describe('StringField', () => { it('should not format to preview image if not unsplash image', () => { const field2 = createField({ properties: createProperties('String', { editor: 'StockPhoto' }) }); - expect(FieldFormatter.format(field2, 'https://images.com/123?x', true)).toEqual(new HtmlValue('')); + expect(FieldFormatter.format(field2, 'https://images.com/123?x', true)).toEqual(new HtmlValue('', 'https://images.com/123?x')); }); it('should return default value from properties', () => { diff --git a/frontend/src/app/shared/state/contents.forms.visitors.ts b/frontend/src/app/shared/state/contents.forms.visitors.ts index e1f462482..f19440062 100644 --- a/frontend/src/app/shared/state/contents.forms.visitors.ts +++ b/frontend/src/app/shared/state/contents.forms.visitors.ts @@ -15,6 +15,7 @@ import { ArrayFieldPropertiesDto, AssetsFieldPropertiesDto, BooleanFieldProperti export class HtmlValue { constructor( public readonly html: string, + public readonly preview?: string, ) { } } @@ -52,9 +53,9 @@ export function getContentValue(content: ContentDto, language: LanguageDto, fiel const previewMode = field.properties['previewMode']; if (previewMode === 'ImageAndFileName') { - formatted = new HtmlValue(` ${value[1]}`); + formatted = new HtmlValue(` ${value[1]}`, value[0]); } else if (previewMode === 'Image') { - formatted = new HtmlValue(``); + formatted = new HtmlValue(``, value[0]); } else { formatted = value[1]; } @@ -63,7 +64,6 @@ export function getContentValue(content: ContentDto, language: LanguageDto, fiel } } } else { - // eslint-disable-next-line no-multi-assign value = formatted = '-'; }