From 74d8a70c74341677d7c29927a18060f528d695b9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 19 May 2023 00:59:28 +0200 Subject: [PATCH 01/50] Add editor icons --- .../app/components/atoms/icons/icon/icons.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/client-v2/src/app/components/atoms/icons/icon/icons.ts b/client-v2/src/app/components/atoms/icons/icon/icons.ts index 9441e126..5c25763b 100644 --- a/client-v2/src/app/components/atoms/icons/icon/icons.ts +++ b/client-v2/src/app/components/atoms/icons/icon/icons.ts @@ -26,8 +26,40 @@ const extraIcons = { logout: 'fas fa-sign-out-alt', workspace: 'fas fa-garage', loading: 'fad fa-spinner-third animate-spin', + image: 'fas fa-image', + chevronRight: 'fas fa-chevron-right', chevronLeft: 'fas fa-chevron-left', + 'caret-down': 'fas fa-caret-down', + 'caret-up': 'fas fa-caret-up', + 'ellipsis-h': 'fas fa-ellipsis-h', + 'ellipsis-v': 'fas fa-ellipsis-v', + + editor: { + undo: 'far fa-undo-alt', + redo: 'far fa-redo-alt', + indent: 'far fa-indent', + outdent: 'far fa-outdent', + paragrapgh: 'far fa-paragraph', + text: 'far fa-text', + heading: 'far fa-heading', + heading1: 'far fa-h1', + heading2: 'far fa-h2', + heading3: 'far fa-h3', + heading4: 'far fa-h4', + bold: 'far fa-bold', + italic: 'far fa-italic', + strike: 'far fa-strikethrough', + link: 'far fa-link', + bulletList: 'far fa-list-ul', + orderedList: 'far fa-list-ol', + taskList: 'far fa-tasks', + horizontalRule: 'far fa-horizontal-rule', + code: 'far fa-code', + codeBlock: 'far fa-laptop-code', + quote: 'fas fa-quote-right', + table: 'fas fa-table', + }, } export const entityStateIcons = { From a77a2a562193fd62765bfbbfeab0777924d5cb84 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 19 May 2023 02:16:07 +0200 Subject: [PATCH 02/50] Extract common components, directives, etc. into modules, add `keycombo.component` --- client-v2/src/app/app.module.ts | 22 ++--- .../breadcrumbs/breadcrumbs.component.test.ts | 11 +-- .../breadcrumbs/breadcrumbs.component.ts | 2 +- .../entity-view/entity-view.component.test.ts | 5 +- .../entity-view/entity-view.component.ts | 2 +- .../task-view/task-view.component.test.ts | 7 +- .../tasklist-view.component.test.ts | 7 +- .../organisms/task/task.component.test.ts | 15 +--- .../organisms/task/task.component.ts | 2 +- .../user-menu/user-menu.component.ts | 2 +- .../drop-down/drop-down.component.css | 7 ++ .../drop-down/drop-down.component.html | 36 ++++++--- .../drop-down/drop-down.component.spec.ts | 0 .../drop-down/drop-down.component.test.ts | 0 .../drop-down/drop-down.component.ts | 23 ++++-- client-v2/src/app/dropdown/dropdown.module.ts | 14 ++++ .../src/app/keyboard/key-combo/combos.spec.ts | 28 +++++++ .../src/app/keyboard/key-combo/combos.ts | 80 +++++++++++++++++++ .../keyboard/key-combo/key-combo.component.ts | 22 +++++ client-v2/src/app/keyboard/keyboard.module.ts | 10 +++ .../component-playground.component.ts | 2 +- .../src/app/pages/home/home.component.ts | 2 +- client-v2/src/app/rx/rx.module.ts | 10 +++ client-v2/src/app/shared/entity-menu-items.ts | 2 +- .../tooltip.directive.spec.ts | 0 .../tooltip.directive.test.ts | 2 +- .../tooltip.directive.ts | 2 +- client-v2/src/app/tooltip/tooltip.module.ts | 11 +++ .../tooltip/tooltip.component.css | 0 .../tooltip/tooltip.component.html | 0 .../tooltip/tooltip.component.spec.ts | 0 .../tooltip/tooltip.component.ts | 0 client-v2/src/app/utils/menu-item.helpers.ts | 2 +- client-v2/src/css/components.css | 4 + 34 files changed, 261 insertions(+), 71 deletions(-) rename client-v2/src/app/{components/molecules => dropdown}/drop-down/drop-down.component.css (55%) rename client-v2/src/app/{components/molecules => dropdown}/drop-down/drop-down.component.html (70%) rename client-v2/src/app/{components/molecules => dropdown}/drop-down/drop-down.component.spec.ts (100%) rename client-v2/src/app/{components/molecules => dropdown}/drop-down/drop-down.component.test.ts (100%) rename client-v2/src/app/{components/molecules => dropdown}/drop-down/drop-down.component.ts (63%) create mode 100644 client-v2/src/app/dropdown/dropdown.module.ts create mode 100644 client-v2/src/app/keyboard/key-combo/combos.spec.ts create mode 100644 client-v2/src/app/keyboard/key-combo/combos.ts create mode 100644 client-v2/src/app/keyboard/key-combo/key-combo.component.ts create mode 100644 client-v2/src/app/keyboard/keyboard.module.ts create mode 100644 client-v2/src/app/rx/rx.module.ts rename client-v2/src/app/{directives => tooltip}/tooltip.directive.spec.ts (100%) rename client-v2/src/app/{directives => tooltip}/tooltip.directive.test.ts (97%) rename client-v2/src/app/{directives => tooltip}/tooltip.directive.ts (97%) create mode 100644 client-v2/src/app/tooltip/tooltip.module.ts rename client-v2/src/app/{components/atoms => tooltip}/tooltip/tooltip.component.css (100%) rename client-v2/src/app/{components/atoms => tooltip}/tooltip/tooltip.component.html (100%) rename client-v2/src/app/{components/atoms => tooltip}/tooltip/tooltip.component.spec.ts (100%) rename client-v2/src/app/{components/atoms => tooltip}/tooltip/tooltip.component.ts (100%) diff --git a/client-v2/src/app/app.module.ts b/client-v2/src/app/app.module.ts index 43aaed0c..4b198ee9 100644 --- a/client-v2/src/app/app.module.ts +++ b/client-v2/src/app/app.module.ts @@ -33,12 +33,9 @@ import { SettingsAccountComponent } from './pages/settings/account/account.compo import { AuthComponent } from './pages/auth/auth.component' import { LoginLoadingComponent } from './pages/auth/login-loading/login-loading.component' import { CdkMenuModule } from '@angular/cdk/menu' -import { DropDownComponent } from './components/molecules/drop-down/drop-down.component' import { ModalModule } from './modal/modal.module' import { IconsModule } from './components/atoms/icons/icons.module' import { OverlayModule } from '@angular/cdk/overlay' -import { TooltipDirective } from './directives/tooltip.directive' -import { TooltipComponent } from './components/atoms/tooltip/tooltip.component' import { CdkTreeModule } from '@angular/cdk/tree' import { EntityPageLabelComponent } from './components/atoms/entity-page-label/entity-page-label.component' import { EntityPageComponent } from './pages/home/entity-page/entity-page.component' @@ -58,10 +55,6 @@ import { LayoutModule } from '@angular/cdk/layout' import { IntersectionDirective } from './directives/intersection.directive' import { EntityDescriptionComponent } from './components/molecules/entity-description/entity-description.component' import { ApplicationinsightsAngularpluginErrorService } from '@microsoft/applicationinsights-angularplugin-js' -import { LetModule } from '@rx-angular/template/let' -import { IfModule } from '@rx-angular/template/if' -import { ForModule } from '@rx-angular/template/for' -import { PushModule } from '@rx-angular/template/push' import { SearchComponent } from './pages/home/search/search.component' import { HighlightPipe } from './pipes/highlight.pipe' import { TaskNestingDemoComponent } from './pages/landing-page/demos/task-nesting-demo.component' @@ -71,6 +64,10 @@ import { TaskDescriptionDemoComponent } from './pages/landing-page/demos/task-de import { TermsOfServiceComponent } from './pages/terms-of-service/terms-of-service.component' import { PrivacyPolicyComponent } from './pages/privacy-policy/privacy-policy.component' import { ContactComponent } from './pages/contact/contact.component' +import { RxModule } from './rx/rx.module' +import { TooltipModule } from './tooltip/tooltip.module' +import { DropdownModule } from './dropdown/dropdown.module' +import { KeyboardModule } from './keyboard/keyboard.module' @NgModule({ declarations: [ @@ -96,9 +93,6 @@ import { ContactComponent } from './pages/contact/contact.component' SettingsAccountComponent, AuthComponent, LoginLoadingComponent, - DropDownComponent, - TooltipDirective, - TooltipComponent, EntityPageLabelComponent, EntityPageComponent, BreadcrumbsComponent, @@ -163,10 +157,10 @@ import { ContactComponent } from './pages/contact/contact.component' OverlayModule, CdkTreeModule, LayoutModule, - LetModule, - IfModule, - ForModule, - PushModule, + RxModule, + TooltipModule, + DropdownModule, + KeyboardModule, ], providers: [ { diff --git a/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.test.ts b/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.test.ts index 37287612..80074a80 100644 --- a/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.test.ts +++ b/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.test.ts @@ -2,20 +2,21 @@ import { CdkMenuModule } from '@angular/cdk/menu' import { testName } from 'cypress/support/helpers' import { EntityPageLabelComponent } from '../../atoms/entity-page-label/entity-page-label.component' import { IconsModule } from '../../atoms/icons/icons.module' -import { DropDownComponent, MenuItem } from '../drop-down/drop-down.component' +import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' import { Breadcrumb, BreadcrumbsComponent } from './breadcrumbs.component' import { OverlayModule } from '@angular/cdk/overlay' import { DeviceService } from 'src/app/services/device.service' -import { TooltipComponent } from '../../atoms/tooltip/tooltip.component' -import { ForModule } from '@rx-angular/template/for' import { menuServiceMock } from 'src/app/utils/unit-test.mocks' +import { RxModule } from 'src/app/rx/rx.module' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' +import { TooltipModule } from 'src/app/tooltip/tooltip.module' const defaultTemplate = `` const setupComponent = (breadcrumbs: Breadcrumb[], template = defaultTemplate) => { cy.mount(template, { componentProperties: { breadcrumbs }, - imports: [CdkMenuModule, IconsModule, OverlayModule, ForModule], - declarations: [BreadcrumbsComponent, EntityPageLabelComponent, DropDownComponent, TooltipComponent], + imports: [CdkMenuModule, IconsModule, OverlayModule, RxModule, DropdownModule, TooltipModule], + declarations: [BreadcrumbsComponent, EntityPageLabelComponent], providers: [DeviceService, menuServiceMock], }) } diff --git a/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.ts b/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.ts index eb11440f..5aed3cee 100644 --- a/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.ts +++ b/client-v2/src/app/components/molecules/breadcrumbs/breadcrumbs.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { IconKey } from '../../atoms/icons/icon/icons' -import { MenuItem } from '../drop-down/drop-down.component' +import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' import { BehaviorSubject, combineLatestWith, distinctUntilChanged, map, timer } from 'rxjs' import { moveToMacroQueue } from 'src/app/utils' import { DeviceService } from 'src/app/services/device.service' diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts index 75faa031..ba8c6d8e 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts @@ -5,11 +5,11 @@ import { EntityPreviewRecursive, EntityType } from 'src/app/fullstack-shared-mod import { TasklistDetail } from 'src/app/fullstack-shared-models/list.model' import { storeMock } from 'src/app/utils/unit-test.mocks' import { EntityPageLabelComponent } from '../../atoms/entity-page-label/entity-page-label.component' -import { DropDownComponent } from '../../molecules/drop-down/drop-down.component' import { EditableEntityTitleComponent } from '../../molecules/editable-entity-heading/editable-entity-title.component' import { EntityViewComponent, entityViewComponentMap } from './entity-view.component' import { TaskViewComponent } from './views/task-view/task-view.component' import { TasklistViewComponent } from './views/tasklist-view/tasklist-view.component' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' const defaultTemplate = ` @@ -25,13 +25,12 @@ const setupComponent = (template = defaultTemplate) => { activeEntity$: new BehaviorSubject(null), entityOptionsMap$: new BehaviorSubject(null), }, - imports: [CdkMenuModule], + imports: [CdkMenuModule, DropdownModule], declarations: [ EntityViewComponent, ...entityViewComponents, EditableEntityTitleComponent, EntityPageLabelComponent, - DropDownComponent, ], providers: [ storeMock, diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts b/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts index 1d288167..1a1689a4 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts @@ -27,7 +27,7 @@ import { AppState } from 'src/app/store' import { entitiesActions } from 'src/app/store/entities/entities.actions' import { useTaskForActiveItems } from 'src/app/utils/menu-item.helpers' import { EntityMenuItemsMap } from '../../../shared/entity-menu-items' -import { MenuItem } from '../../molecules/drop-down/drop-down.component' +import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' import { TaskViewComponent } from './views/task-view/task-view.component' import { TasklistViewComponent } from './views/tasklist-view/tasklist-view.component' diff --git a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.test.ts index f593ca04..889c8eb6 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.test.ts @@ -3,7 +3,6 @@ import { testName } from 'cypress/support/helpers' import { BehaviorSubject, of } from 'rxjs' import { EntityPageLabelComponent } from 'src/app/components/atoms/entity-page-label/entity-page-label.component' import { InlineEditorComponent } from 'src/app/components/atoms/inline-editor/inline-editor.component' -import { DropDownComponent } from 'src/app/components/molecules/drop-down/drop-down.component' import { EditableEntityTitleComponent } from 'src/app/components/molecules/editable-entity-heading/editable-entity-title.component' import { PageProgressBarComponent } from 'src/app/components/molecules/page-progress-bar/page-progress-bar.component' import { FocusableDirective } from 'src/app/directives/focusable.directive' @@ -14,8 +13,9 @@ import { TaskTreeMap } from 'src/app/store/entities/entities.state' import { actionsMock, storeMock } from 'src/app/utils/unit-test.mocks' import { EntityViewComponent, EntityViewData, ENTITY_VIEW_DATA } from '../../entity-view.component' import { TaskViewComponent } from './task-view.component' -import { PushModule } from '@rx-angular/template/push' import { HighlightPipe } from 'src/app/pipes/highlight.pipe' +import { RxModule } from 'src/app/rx/rx.module' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' const setupComponent = (viewData: EntityViewData, taskTreeMap: TaskTreeMap = {}) => { const store = { @@ -29,7 +29,7 @@ const setupComponent = (viewData: EntityViewData, taskTreeMap: TaskT } cy.mount(` `, { componentProperties: {}, - imports: [CdkMenuModule, PushModule], + imports: [CdkMenuModule, RxModule, DropdownModule], declarations: [ TaskViewComponent, MutationDirective, @@ -37,7 +37,6 @@ const setupComponent = (viewData: EntityViewData, taskTreeMap: TaskT EditableEntityTitleComponent, EntityPageLabelComponent, PageProgressBarComponent, - DropDownComponent, InlineEditorComponent, HighlightPipe, ], diff --git a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts index 8640f76f..e4f22c2f 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts @@ -3,7 +3,6 @@ import { testName } from 'cypress/support/helpers' import { BehaviorSubject, of } from 'rxjs' import { EntityPageLabelComponent } from 'src/app/components/atoms/entity-page-label/entity-page-label.component' import { InlineEditorComponent } from 'src/app/components/atoms/inline-editor/inline-editor.component' -import { DropDownComponent } from 'src/app/components/molecules/drop-down/drop-down.component' import { EditableEntityTitleComponent } from 'src/app/components/molecules/editable-entity-heading/editable-entity-title.component' import { EntityDescriptionComponent } from 'src/app/components/molecules/entity-description/entity-description.component' import { FocusableDirective } from 'src/app/directives/focusable.directive' @@ -14,8 +13,9 @@ import { TaskTreeMap } from 'src/app/store/entities/entities.state' import { actionsMock, storeMock } from 'src/app/utils/unit-test.mocks' import { EntityViewComponent, EntityViewData, ENTITY_VIEW_DATA } from '../../entity-view.component' import { TasklistViewComponent } from './tasklist-view.component' -import { PushModule } from '@rx-angular/template/push' import { HighlightPipe } from 'src/app/pipes/highlight.pipe' +import { RxModule } from 'src/app/rx/rx.module' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' const setupComponent = (viewData: EntityViewData, taskTreeMap: TaskTreeMap = {}) => { const store = { @@ -29,14 +29,13 @@ const setupComponent = (viewData: EntityViewData, taskTreeMap: T } cy.mount(` `, { componentProperties: {}, - imports: [CdkMenuModule, PushModule], + imports: [CdkMenuModule, RxModule, DropdownModule], declarations: [ TasklistViewComponent, MutationDirective, FocusableDirective, EditableEntityTitleComponent, EntityPageLabelComponent, - DropDownComponent, InlineEditorComponent, EntityDescriptionComponent, HighlightPipe, diff --git a/client-v2/src/app/components/organisms/task/task.component.test.ts b/client-v2/src/app/components/organisms/task/task.component.test.ts index b998caac..9ee5d912 100644 --- a/client-v2/src/app/components/organisms/task/task.component.test.ts +++ b/client-v2/src/app/components/organisms/task/task.component.test.ts @@ -9,11 +9,11 @@ import { getEntityMenuItemsMap } from 'src/app/shared/entity-menu-items' import { AppState } from 'src/app/store' import { IconsModule } from '../../atoms/icons/icons.module' import { InlineEditorComponent } from '../../atoms/inline-editor/inline-editor.component' -import { DropDownComponent } from '../../molecules/drop-down/drop-down.component' import { TaskTreeNode } from '../task-tree/task-tree.component' import { TaskComponent } from './task.component' import { HighlightPipe } from 'src/app/pipes/highlight.pipe' -import { PushModule } from '@rx-angular/template/push' +import { RxModule } from 'src/app/rx/rx.module' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' const taskMenuItems = getEntityMenuItemsMap({} as unknown as Store)[EntityType.TASK] @@ -40,15 +40,8 @@ const setupComponent = ( ...listeners, menuItems: taskMenuItems.map(useStubsForActions(menuItemStubsMap)), }, - imports: [CdkMenuModule, IconsModule, PushModule], - declarations: [ - TaskComponent, - DropDownComponent, - InlineEditorComponent, - FocusableDirective, - MutationDirective, - HighlightPipe, - ], + imports: [CdkMenuModule, IconsModule, RxModule, DropdownModule], + declarations: [TaskComponent, InlineEditorComponent, FocusableDirective, MutationDirective, HighlightPipe], } ) } diff --git a/client-v2/src/app/components/organisms/task/task.component.ts b/client-v2/src/app/components/organisms/task/task.component.ts index 1ec25f53..baf5180d 100644 --- a/client-v2/src/app/components/organisms/task/task.component.ts +++ b/client-v2/src/app/components/organisms/task/task.component.ts @@ -7,7 +7,7 @@ import { ENTITY_TITLE_DEFAULTS } from 'src/app/shared/defaults' import { insertElementAfter, moveToMacroQueue } from 'src/app/utils' import { TaskPreviewFlattend, TaskPriority, TaskStatus } from '../../../fullstack-shared-models/task.model' import { EntityState } from '../../atoms/icons/icon/icons' -import { MenuItem } from '../../molecules/drop-down/drop-down.component' +import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' import { TaskTreeNode } from '../task-tree/task-tree.component' @UntilDestroy() diff --git a/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts b/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts index 3a780d41..dd088b28 100644 --- a/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts +++ b/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts @@ -3,7 +3,7 @@ import { Store } from '@ngrx/store' import { AppState } from 'src/app/store' import { authActions } from 'src/app/store/user/user.actions' import { userFeature } from 'src/app/store/user/user.selectors' -import { MenuItem, MenuItemVariant } from '../../molecules/drop-down/drop-down.component' +import { MenuItem, MenuItemVariant } from '../../../dropdown/drop-down/drop-down.component' @Component({ selector: 'user-menu', diff --git a/client-v2/src/app/components/molecules/drop-down/drop-down.component.css b/client-v2/src/app/dropdown/drop-down/drop-down.component.css similarity index 55% rename from client-v2/src/app/components/molecules/drop-down/drop-down.component.css rename to client-v2/src/app/dropdown/drop-down/drop-down.component.css index c063770d..0bdd11c5 100644 --- a/client-v2/src/app/components/molecules/drop-down/drop-down.component.css +++ b/client-v2/src/app/dropdown/drop-down/drop-down.component.css @@ -4,3 +4,10 @@ :where(.route-active) { @apply text-primary-300; } + +.menu-item { + @apply inline-flex justify-between; +} +.menu-item.item-active { + @apply !bg-tinted-600; +} diff --git a/client-v2/src/app/components/molecules/drop-down/drop-down.component.html b/client-v2/src/app/dropdown/drop-down/drop-down.component.html similarity index 70% rename from client-v2/src/app/components/molecules/drop-down/drop-down.component.html rename to client-v2/src/app/dropdown/drop-down/drop-down.component.html index 79b69888..dc575a7d 100644 --- a/client-v2/src/app/components/molecules/drop-down/drop-down.component.html +++ b/client-v2/src/app/dropdown/drop-down/drop-down.component.html @@ -12,9 +12,15 @@ *ngIf="menuItem.icon" [icon]="menuItem.icon" class="mr-1 inline-block scale-[1.3]" - [class.text-tinted-400]="!menuItem.variant || menuItem.variant == MenuItemVariant.DEFAULT" + [ngClass]="{ + 'text-tinted-400 [.item-active_&]:text-tinted-300': + !menuItem.variant || menuItem.variant == MenuItemVariant.DEFAULT + }" > + + + - - {{ menuItem.title }} + + + {{ menuItem.title }} + + - - {{ menuItem.title }} + + + {{ menuItem.title }} + + + + + + + {{ getTitle(control) }} + + + + +
+ + diff --git a/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.spec.ts b/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.spec.ts new file mode 100644 index 00000000..a911f979 --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { RtEditorToolbarComponent } from './rt-editor-toolbar.component' + +describe('FormattingOptionswComponent', () => { + let component: RtEditorToolbarComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RtEditorToolbarComponent], + }).compileComponents() + + fixture = TestBed.createComponent(RtEditorToolbarComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.ts b/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.ts new file mode 100644 index 00000000..36a1c5f9 --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor-toolbar/rt-editor-toolbar.component.ts @@ -0,0 +1,88 @@ +import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core' +import { ChainedCommands, Editor } from '@tiptap/core' +import { formattingControlGroups } from '../formatting-controls' +import { BehaviorSubject, debounceTime, map } from 'rxjs' +import { EditorConfig, FormattingControl, FormattingControlGroup } from '../types' +import { IconKey } from 'src/app/components/atoms/icons/icon/icons' + +@Component({ + selector: 'app-rt-editor-toolbar', + templateUrl: './rt-editor-toolbar.component.html', + styleUrls: ['./rt-editor-toolbar.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RtEditorToolbarComponent implements OnInit, OnDestroy { + ngOnInit(): void { + this.editor?.on('selectionUpdate', this.selectionUpdateCallback) + this.editor?.on('update', this.selectionUpdateCallback) + } + ngOnDestroy(): void { + this.editor?.off('selectionUpdate', this.selectionUpdateCallback) + this.editor?.off('update', this.selectionUpdateCallback) + } + + @Input() editor!: Editor + @Input() config: Record = { + basic: true, + bold: true, + italic: true, + strike: true, + link: true, + blocks: true, + headings: true, + lists: true, + taskLists: true, + rule: true, + code: true, + codeBlock: true, + undoRedo: true, + } + + execEditorChain(callback: (chain: ChainedCommands, editor: Editor) => ChainedCommands, autoFocus = true) { + if (!this.editor) return + + callback(this.getChain(autoFocus), this.editor).run() + } + getChain(autoFocus = true) { + let chain = this.editor.chain() + if (autoFocus) chain = chain.focus() + return chain + } + + groups = formattingControlGroups + .filter(({ configKey }) => + typeof configKey == 'string' ? this.config[configKey] : configKey.some(key => this.config[key]) + ) + .map(group => ({ + ...group, + controls: group.controls.filter(({ configKey }) => + typeof configKey == 'string' ? this.config[configKey] : configKey.some(key => this.config[key]) + ), + })) + + selectionUpdateCallback = (() => this.updates$.next(null)).bind(this) + updates$ = new BehaviorSubject(null) + formattingControls$ = this.updates$.pipe( + debounceTime(120), + // must be new objects to trigger change detection + map(() => this.groups.map(group => ({ ...group }))) + ) + + controlGroupTrackBy(index: number, item: FormattingControlGroup) { + return index + item.configKey.toString() + } + controlTrackBy(index: number, item: FormattingControl) { + if (!this.editor) return index + return '' + index + this.getTitle(item) + this.getIcon(item) + item.isActive?.(this.editor) + } + getIcon(item: FormattingControl) { + if (!this.editor) return '' as IconKey + const icon = typeof item.icon == 'string' ? item.icon : item.icon(this.editor) + return icon + } + getTitle(item: FormattingControl) { + if (!this.editor) return '' as IconKey + const title = typeof item.title == 'string' ? item.title : item.title(this.editor) + return title + } +} diff --git a/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.css b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.css new file mode 100644 index 00000000..0f34a587 --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.css @@ -0,0 +1,62 @@ +.ProseMirror, +.rendered-content { + @apply pb-6 outline-none; +} + +:is(.ProseMirror, .rendered-content) p { + margin: 0; +} +:is(.ProseMirror, .rendered-content) code { + @apply text-md; +} +:is(.ProseMirror, .rendered-content) a { + @apply underline/* */ cursor-pointer not-hover:text-submit-600; +} + +:is(.ProseMirror, .rendered-content) :is(ul, ol):not(*:not(ul, ol) ~ :is(ul, ol)) { + @apply pl-0; +} + +:is(.ProseMirror, .rendered-content) code { + @apply rounded-md bg-tinted-800 px-1 py-0.5 text-tinted-300; +} +:is(.ProseMirror, .rendered-content) pre code { + @apply my-2 block px-3 py-2; +} +:is(.ProseMirror, .rendered-content) h1 { + font-size: 1.8rem; +} + +:is(.ProseMirror, .rendered-content) ul[data-type='taskList'] { + @apply ml-0.5 list-none; +} +:is(.ProseMirror, .rendered-content) ul[data-type='taskList'] li { + display: flex; +} +:is(.ProseMirror, .rendered-content) ul[data-type='taskList'] input { + @apply accent-submit-400; +} +:is(.ProseMirror, .rendered-content) li p { + @apply inline; +} +:is(.ProseMirror, .rendered-content) ul[data-type='taskList'] li > label { + flex: 0 0 auto; + @apply mr-2 select-none; +} + +:is(.ProseMirror, .rendered-content) ul[data-type='taskList'] li > div { + flex: 1 1 auto; +} + +.ProseMirror :is(p.is-editor-empty:first-child, .is-empty)::before { + @apply text-tinted-400; + + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; +} + +button.isActive { + @apply !bg-primary-400 font-bold text-tinted-900; +} diff --git a/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.html b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.html new file mode 100644 index 00000000..4ca9baf2 --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.spec.ts b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.spec.ts new file mode 100644 index 00000000..14ac3eed --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { RtEditorComponent } from './rt-editor.component' + +describe('RichTextEditorComponent', () => { + let component: RtEditorComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RtEditorComponent], + }).compileComponents() + }) + + beforeEach(() => { + fixture = TestBed.createComponent(RtEditorComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.ts b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.ts new file mode 100644 index 00000000..edd3940c --- /dev/null +++ b/client-v2/src/app/rich-text-editor/rt-editor/rt-editor.component.ts @@ -0,0 +1,174 @@ +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnDestroy, + Output, + ViewEncapsulation, + inject, +} from '@angular/core' +import { ChainedCommands, Editor } from '@tiptap/core' +import Placeholder from '@tiptap/extension-placeholder' +import TaskItem from '@tiptap/extension-task-item' +import TaskList from '@tiptap/extension-task-list' +import StarterKit from '@tiptap/starter-kit' +import Heading from '@tiptap/extension-heading' +import Blockquote from '@tiptap/extension-blockquote' +import Strike from '@tiptap/extension-strike' +import Link from '@tiptap/extension-link' +import Typography from '@tiptap/extension-typography' +import { Indentation } from '../extension-indentation' +import { BehaviorSubject, shareReplay, Subject, timer } from 'rxjs' +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' +import { coalesceWith } from '@rx-angular/cdk/coalescing' +import { formattingControlGroups } from '../formatting-controls' +import { createEventEmitter } from 'src/app/utils/observable.helpers' +import { HotToastService } from '@ngneat/hot-toast' + +@UntilDestroy() +@Component({ + selector: 'app-rt-editor', + templateUrl: './rt-editor.component.html', + styleUrls: ['./rt-editor.component.css'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + class: 'block', + }, +}) +export class RtEditorComponent implements OnDestroy { + ngOnDestroy(): void { + this.editor.destroy() + } + + @Input() set value(value: string) { + this.value$.next(value) + } + value$ = new Subject() + valueSub = this.value$.pipe(untilDestroyed(this)).subscribe(value => { + this.editor?.commands.setContent(value, false) + }) + @Output() blur = new EventEmitter() + @Output() focus = new EventEmitter() + + @Input() disabled = false + @Input() placeholder = '' + + @Output() valueChanges = new EventEmitter() + @Output() contentChanges = new EventEmitter<{ html: string; text: string }>() + + private isFocused$_ = new BehaviorSubject(false) + isFocused$ = this.isFocused$_.pipe( + coalesceWith(timer(70)), + shareReplay({ bufferSize: 1, refCount: true }), + untilDestroyed(this) + ) + @Output() isFocused = createEventEmitter(this.isFocused$) + + toast = inject(HotToastService) + + editor = new Editor({ + parseOptions: {}, + editable: !this.disabled, + extensions: [ + StarterKit.configure({ strike: false }), + Strike.extend({ + addKeyboardShortcuts(this) { + const toggleStrike = () => this.editor.chain().focus().toggleStrike().run() + return { + 'Mod-shift-S': toggleStrike, + 'Mod-shift-X': toggleStrike, + } + }, + }), + Link.extend({ + addKeyboardShortcuts: () => { + const control = formattingControlGroups + .map(g => g.controls) + .flat() + .find(c => c.configKey === 'link') + if (!control?.registerKeybinding) return {} + + return { + [control.registerKeybinding]: () => + control.action ? this.execEditorChain(control.action) : false, + } + }, + }), + Typography.configure({ + emDash: false, + // ellipsis ?? + openDoubleQuote: false, + closeDoubleQuote: false, + openSingleQuote: false, + closeSingleQuote: false, + notEqual: false, + }), + Placeholder.configure({ + placeholder: ({ node, editor }) => { + if (editor.isEmpty) return this.placeholder + if (node.type.name == Heading.name) return 'Heading ' + node.attrs['level'] + if (node.type.name == Blockquote.name) return 'Quote' + + // const parentNode = editor.state.selection.$anchor.parent + // console.log(node.type.name) + // console.log(parentNode.type.name) + // if (parentNode.type.name == 'bulletList') return 'List' + // if (parentNode.type.name == 'taskList') return 'Task' + // if (parentNode.type.name == 'codeBlock') return "Write some code let's goo!" + + // if (node.type.name == Paragraph.name) return 'Type "/" for commands' + + return '' + }, + showOnlyCurrent: false, + }), + TaskList, + TaskItem.configure({ nested: true }), + Indentation, + ], + onFocus: ({ event }) => { + this.focus.emit(event) + this.isFocused$_.next(true) + }, + onBlur: ({ event }) => { + const clickedElem = event.relatedTarget as HTMLElement | undefined + const wasControlClicked = + clickedElem?.className?.includes('format-control-item') || + clickedElem?.className?.includes('format-controls-container') || + clickedElem?.className?.includes('keep-editor-focus') || + clickedElem?.parentElement?.className?.includes('format-controls-container') || + clickedElem?.parentElement?.className?.includes('keep-editor-focus') || + false + + if (!wasControlClicked) { + this.blur.emit(event) + this.isFocused$_.next(false) + this.deselectEditor() + } + }, + onUpdate: ({ editor }) => { + this.valueChanges.emit(editor.getHTML()) + this.contentChanges.emit({ html: editor.getHTML(), text: editor.getText() }) + }, + editorProps: { + handleKeyDown: (view, event) => { + // @TODO: watch out for `metaKey` on windows & linux + if (event.key === 'Enter' && event.metaKey && !event.shiftKey) { + this.editor.commands.blur() + return true + } + return false + }, + }, + }) + + execEditorChain(callback: (chain: ChainedCommands, editor: Editor) => ChainedCommands): boolean { + return callback(this.editor.chain().focus(), this.editor).run() + } + + deselectEditor() { + window.getSelection()?.removeAllRanges() + } +} diff --git a/client-v2/src/app/rich-text-editor/types.ts b/client-v2/src/app/rich-text-editor/types.ts new file mode 100644 index 00000000..05177009 --- /dev/null +++ b/client-v2/src/app/rich-text-editor/types.ts @@ -0,0 +1,46 @@ +import type { ChainedCommands, Editor } from '@tiptap/core' +import { IconKey } from '../components/atoms/icons/icon/icons' +import { MenuItem } from '../dropdown/drop-down/drop-down.component' + +export interface EditorConfig { + basic?: boolean + bold?: boolean + italic?: boolean + strike?: boolean + link?: boolean + blocks?: boolean + headings?: boolean + lists?: boolean + taskLists?: boolean + code?: boolean + codeBlock?: boolean + rule?: boolean + undoRedo?: boolean +} + +export type ConfigKey = keyof EditorConfig | (keyof EditorConfig)[] + +export interface FormattingControlGroup { + configKey: ConfigKey + controls: FormattingControl[] +} +export type FormattingControlMenuItem = MenuItem<{ + chain: (autoFocus?: boolean) => ChainedCommands + editor: Editor + data: TDropdownData +}> + +export interface FormattingControl { + configKey: ConfigKey + title: string | ((editor: Editor) => string) + icon: IconKey | ((editor: Editor) => IconKey) + displayKeybinding?: string + registerKeybinding?: string + dropdown?: { + items: FormattingControlMenuItem[] + data?: TDropdownData + } + isActive?: (editor: Editor) => boolean + action?: (chain: ChainedCommands, editor: Editor) => ChainedCommands + testName: string +} diff --git a/client-v2/src/css/components.css b/client-v2/src/css/components.css index 379efd01..2ba31dc7 100644 --- a/client-v2/src/css/components.css +++ b/client-v2/src/css/components.css @@ -17,7 +17,7 @@ } .button-naked { - @apply rounded-lg py-1 px-2 transition-colors hover:bg-tinted-700; + @apply rounded-lg py-1 px-2 transition-colors hover:bg-tinted-700 duration-75; /* hover:text-tinted-100 */ } .button-naked i { @@ -32,7 +32,7 @@ @apply m-0 inline-block aspect-square rounded py-0.5 px-2.5 text-base transition-colors hover:bg-tinted-700; } .icon-btn { - @apply inline-flex aspect-square min-w-[1.75rem] items-center justify-center rounded-lg px-1 transition-colors hover:bg-tinted-500; + @apply inline-flex h-7 min-w-[1.75rem] items-center justify-center rounded-lg px-1 transition-colors hover:bg-tinted-500; } .button--submit { From ab6737dd36d859b5f1d1eb265324b8f1c23b4d91 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 19 May 2023 23:44:19 +0200 Subject: [PATCH 04/50] Fix tooltip directive import --- client-v2/src/app/tooltip/tooltip/tooltip.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-v2/src/app/tooltip/tooltip/tooltip.component.spec.ts b/client-v2/src/app/tooltip/tooltip/tooltip.component.spec.ts index 60cc32e6..d2cb303a 100644 --- a/client-v2/src/app/tooltip/tooltip/tooltip.component.spec.ts +++ b/client-v2/src/app/tooltip/tooltip/tooltip.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { TooltipDirective } from 'src/app/directives/tooltip.directive' import { TooltipComponent, TOOLTIP_DATA } from './tooltip.component' +import { TooltipDirective } from '../tooltip.directive' describe('TooltipComponent', () => { let component: TooltipComponent From beec9f731c4c4219f198d046e2f51fd21bd0dac9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 19 May 2023 23:50:13 +0200 Subject: [PATCH 05/50] Integrate rt-editor into entity-description and task --- .../entity-description.component.css | 3 + .../entity-description.component.html | 28 ++-- .../entity-description.component.spec.ts | 6 +- .../entity-description.component.ts | 6 +- .../organisms/task/task.component.css | 10 +- .../organisms/task/task.component.html | 126 ++++++++++-------- .../organisms/task/task.component.ts | 41 +++--- .../app/tooltip/tooltip/tooltip.component.css | 15 ++- 8 files changed, 134 insertions(+), 101 deletions(-) diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.css b/client-v2/src/app/components/molecules/entity-description/entity-description.component.css index e69de29b..47292f7f 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.css +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.css @@ -0,0 +1,3 @@ +:host { + @apply relative block; +} diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html index 85a8e2a3..834f5a0f 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html @@ -1,15 +1,17 @@ -
+ + + +
+ + +> diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.spec.ts b/client-v2/src/app/components/molecules/entity-description/entity-description.component.spec.ts index b4fc9a82..3903f9ca 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.spec.ts +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.spec.ts @@ -1,7 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' -import { FocusableDirective } from 'src/app/directives/focusable.directive' import { EntityDescriptionComponent } from './entity-description.component' +import { RichTextEditorModule } from 'src/app/rich-text-editor/rich-text-editor.module' +import { RxModule } from 'src/app/rx/rx.module' describe('EntityDescriptionComponent', () => { let component: EntityDescriptionComponent @@ -9,7 +10,8 @@ describe('EntityDescriptionComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [EntityDescriptionComponent, FocusableDirective], + imports: [RichTextEditorModule, RxModule], + declarations: [EntityDescriptionComponent], }).compileComponents() fixture = TestBed.createComponent(EntityDescriptionComponent) diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts index 6e1380cc..8b21b85e 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts @@ -21,7 +21,7 @@ export class EntityDescriptionComponent { descriptionChanges$ = new BehaviorSubject(null) blurEvents$ = new Subject() - @Output() blur = createEventEmitter(this.blurEvents$.pipe(tap(this.deselectEditor), untilDestroyed(this))) + @Output() blur = createEventEmitter(this.blurEvents$.pipe(untilDestroyed(this))) descriptionDomState$ = merge( this.descriptionChanges$, @@ -46,8 +46,4 @@ export class EntityDescriptionComponent { untilDestroyed(this) ) ) - - deselectEditor() { - window.getSelection()?.removeAllRanges() - } } diff --git a/client-v2/src/app/components/organisms/task/task.component.css b/client-v2/src/app/components/organisms/task/task.component.css index 4d0b6bba..37f33d91 100644 --- a/client-v2/src/app/components/organisms/task/task.component.css +++ b/client-v2/src/app/components/organisms/task/task.component.css @@ -11,9 +11,9 @@ } /* [description] highlight if not focused */ - .task:is(.task, .titleHasFocus) .description:not(:focus) { + /* .task:is(.task, .titleHasFocus) .description:not(:focus, .hasFocus) { @apply hover:bg-tinted-800; - } + } */ } @supports selector(A:has(B)) { /* [task] highlight only if description are NOT hovered */ @@ -28,9 +28,9 @@ } /* [description] highlight if not focused */ - .task:is(.task, .titleHasFocus, .isHovered) .description:not(:focus) { + /* .task:is(.task, .titleHasFocus, .isHovered) .description:not(:focus, .hasFocus) { @apply hover:bg-tinted-700; - } + } */ } /* [task] */ @@ -65,7 +65,7 @@ @apply mx-1 h-3.5 w-px translate-y-[5px] bg-current brightness-75; } -.task.completed .icon-btn { +.task.completed .icon-btn:not(app-toolbar .icon-btn) { @apply hover:bg-submit-600; } diff --git a/client-v2/src/app/components/organisms/task/task.component.html b/client-v2/src/app/components/organisms/task/task.component.html index 3e753d44..033569c4 100644 --- a/client-v2/src/app/components/organisms/task/task.component.html +++ b/client-v2/src/app/components/organisms/task/task.component.html @@ -1,12 +1,12 @@
  • -
    - - Priority: - {{ task.priority }} - - -
    - - - + + + + + Priority: + {{ task.priority }} + + + + - - + + +
    +
    + + + + + + + + - - + +
    + + + +
    +
    -
    - +
  • diff --git a/client-v2/src/app/components/organisms/task/task.component.ts b/client-v2/src/app/components/organisms/task/task.component.ts index baf5180d..a9ff494f 100644 --- a/client-v2/src/app/components/organisms/task/task.component.ts +++ b/client-v2/src/app/components/organisms/task/task.component.ts @@ -1,6 +1,17 @@ -import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core' +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' -import { BehaviorSubject, distinctUntilChanged, filter, first, map, merge, shareReplay, Subject, tap } from 'rxjs' +import { + BehaviorSubject, + distinctUntilChanged, + filter, + first, + map, + merge, + shareReplay, + Subject, + switchMap, + tap, +} from 'rxjs' import { EntityType } from 'src/app/fullstack-shared-models/entities.model' import { colors, taskStatusColorMap } from 'src/app/shared/colors' import { ENTITY_TITLE_DEFAULTS } from 'src/app/shared/defaults' @@ -9,6 +20,7 @@ import { TaskPreviewFlattend, TaskPriority, TaskStatus } from '../../../fullstac import { EntityState } from '../../atoms/icons/icon/icons' import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' import { TaskTreeNode } from '../task-tree/task-tree.component' +import { RtEditorComponent } from '../../../rich-text-editor/rt-editor/rt-editor.component' @UntilDestroy() @Component({ @@ -105,6 +117,8 @@ export class TaskComponent { isHovered = false + descriptionChanges$ = new BehaviorSubject<{ html: string; text: string } | null>(null) + descriptionExpansionChanges$ = new Subject<{ emit: boolean; isExpanded: boolean }>() isDescriptionExpanded$ = merge( this.nodeData$.pipe(map(nodeData => ({ emit: false, isExpanded: nodeData?.isDescriptionExpanded ?? false }))), @@ -124,15 +138,15 @@ export class TaskComponent { shareReplay({ bufferSize: 1, refCount: true }) ) - @ViewChild('description') descriptionRef!: ElementRef + @ViewChild('descriptionEditor') descriptionEditor!: RtEditorComponent addDescription() { this.descriptionExpansionChanges$.next({ emit: false, isExpanded: true }) moveToMacroQueue(() => { - this.descriptionRef.nativeElement.focus() + this.descriptionEditor.editor.commands.focus() }) } resetDescription() { - this.descriptionRef.nativeElement.innerHTML = this.task$.value?.description || '' + this.descriptionEditor.editor.commands.setContent(this.task$.value?.description || '') } toggleDescription() { this.isDescriptionExpanded$.pipe(first()).subscribe(isExpanded => { @@ -140,15 +154,13 @@ export class TaskComponent { }) } + isDescriptionFocused$ = new BehaviorSubject(false) descriptionBlurEvents$ = new Subject() descriptionUpdatesSub = this.descriptionBlurEvents$ .pipe( - map(() => ({ - html: this.descriptionRef.nativeElement.innerHTML.trim(), - text: this.descriptionRef.nativeElement.innerText.trim(), - })), + switchMap(() => this.descriptionChanges$.pipe(first())), + filter(Boolean), tap(({ text }) => { - this.deselectEditor() if (!text) this.descriptionExpansionChanges$.next({ emit: true, isExpanded: false }) }), filter(({ text, html }) => { @@ -158,11 +170,6 @@ export class TaskComponent { return true }), - // distinctUntilChanged((prev, curr) => { - // const isTextSame = prev.text == curr.text - // const isHtmlSame = prev.html == curr.html - // return isTextSame && isHtmlSame - // }), tap(({ text, html }) => { this.descriptionChange.emit(text ? html : '') @@ -171,8 +178,4 @@ export class TaskComponent { untilDestroyed(this) ) .subscribe() - - deselectEditor() { - window.getSelection()?.removeAllRanges() - } } diff --git a/client-v2/src/app/tooltip/tooltip/tooltip.component.css b/client-v2/src/app/tooltip/tooltip/tooltip.component.css index e3562e9e..f1515bc6 100644 --- a/client-v2/src/app/tooltip/tooltip/tooltip.component.css +++ b/client-v2/src/app/tooltip/tooltip/tooltip.component.css @@ -2,7 +2,20 @@ --reveal-delay: 0ms; } :host { - @apply box-border block max-w-xs rounded-lg border border-tinted-600/60 bg-tinted-700 py-1 px-2 text-sm text-tinted-100 shadow-xl glass overflow-hidden; + @apply box-border + block + max-w-xs + overflow-hidden + rounded-lg + border + border-tinted-600/60 + bg-tinted-700 + py-1 + px-2 + text-sm + text-tinted-100 + shadow-xl + glass; animation: reveal 130ms var(--reveal-delay, 280ms) forwards; scale: 0.8; From 241b7a1225b6b2160442669bbb827f82af19605e Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 19 May 2023 23:57:55 +0200 Subject: [PATCH 06/50] Bump initial budget to 1.5mb --- client-v2/angular.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-v2/angular.json b/client-v2/angular.json index 4b11ad42..fc5fc2f0 100644 --- a/client-v2/angular.json +++ b/client-v2/angular.json @@ -39,7 +39,7 @@ { "type": "initial", "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumError": "1.5mb" }, { "type": "anyComponentStyle", From a4230879a3cd46cb9436165c3969270e13b755b9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sat, 20 May 2023 00:11:18 +0200 Subject: [PATCH 07/50] Fix task component tests --- .../src/app/components/organisms/task/task.component.html | 6 +++++- .../app/components/organisms/task/task.component.test.ts | 6 ++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client-v2/src/app/components/organisms/task/task.component.html b/client-v2/src/app/components/organisms/task/task.component.html index 033569c4..231b9d5c 100644 --- a/client-v2/src/app/components/organisms/task/task.component.html +++ b/client-v2/src/app/components/organisms/task/task.component.html @@ -81,7 +81,11 @@ - -
    -
    - - - - - - - - + + + +
    +
    + + + - -
    - - +
    - + >
    diff --git a/client-v2/src/app/components/organisms/task/task.component.ts b/client-v2/src/app/components/organisms/task/task.component.ts index a9ff494f..3567b454 100644 --- a/client-v2/src/app/components/organisms/task/task.component.ts +++ b/client-v2/src/app/components/organisms/task/task.component.ts @@ -1,26 +1,74 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, +} from '@angular/core' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' +import { createDocument, getSchema } from '@tiptap/core' import { BehaviorSubject, + Subject, distinctUntilChanged, + distinctUntilKeyChanged, filter, first, map, merge, + mergeWith, + share, shareReplay, - Subject, - switchMap, - tap, + startWith, + throttleTime, + withLatestFrom, } from 'rxjs' import { EntityType } from 'src/app/fullstack-shared-models/entities.model' +import { + createCounterWidget, + updateCounterWidget, +} from 'src/app/rich-text-editor/editor-features/extensions/checklist-counter.extension' +import { + getDefaultEditorFeatures, + getDefaultEditorLayout, + provideEditorFeatures, +} from 'src/app/rich-text-editor/features' +import { ChecklistCount, countChecklistItems } from 'src/app/rich-text-editor/helpers' +import { TipTapEditorComponent } from 'src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component' +import { DeviceService } from 'src/app/services/device.service' import { colors, taskStatusColorMap } from 'src/app/shared/colors' import { ENTITY_TITLE_DEFAULTS } from 'src/app/shared/defaults' import { insertElementAfter, moveToMacroQueue } from 'src/app/utils' -import { TaskPreviewFlattend, TaskPriority, TaskStatus } from '../../../fullstack-shared-models/task.model' -import { EntityState } from '../../atoms/icons/icon/icons' import { MenuItem } from '../../../dropdown/drop-down/drop-down.component' +import { + TaskPreview, + TaskPreviewFlattend, + TaskPreviewRecursive, + TaskPriority, + TaskStatus, +} from '../../../fullstack-shared-models/task.model' +import { EntityState } from '../../atoms/icons/icon/icons' import { TaskTreeNode } from '../task-tree/task-tree.component' -import { RtEditorComponent } from '../../../rich-text-editor/rt-editor/rt-editor.component' + +@Component({ + selector: 'app-elem-container', + template: '', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ElemContainerComponent implements OnInit { + constructor(private elemRef: ElementRef) {} + + @Input() elem: HTMLElement | null = null + ngOnInit(): void { + if (this.elem) this.elemRef.nativeElement.appendChild(this.elem) + } +} + +const editorFeatures = getDefaultEditorFeatures({ checklistCounterFeature: false }) +const editorSchema = getSchema(editorFeatures.flatMap(feature => feature.extensions)) @UntilDestroy() @Component({ @@ -28,13 +76,18 @@ import { RtEditorComponent } from '../../../rich-text-editor/rt-editor/rt-editor templateUrl: './task.component.html', styleUrls: ['./task.component.css'], changeDetection: ChangeDetectionStrategy.OnPush, + viewProviders: [provideEditorFeatures(editorFeatures)], }) export class TaskComponent { + constructor(private deviceService: DeviceService) {} + EntityType = EntityType TaskStatus = TaskStatus TaskPriority = TaskPriority PLACEHOLDER = ENTITY_TITLE_DEFAULTS[EntityType.TASK] + toolbarLayout$ = this.deviceService.isTouchPrimary$.pipe(map(getDefaultEditorLayout)) + statusColorMap = { ...taskStatusColorMap, [TaskStatus.OPEN]: 'text-tinted-100', @@ -49,9 +102,9 @@ export class TaskComponent { [TaskStatus.COMPLETED]: colors.submit[600], } - highlightQuery$ = new BehaviorSubject(null) - @Input() set highlightQuery(value: string) { - this.highlightQuery$.next(value) + searchTerm$ = new BehaviorSubject(null) + @Input() set searchTerm(value: string) { + this.searchTerm$.next(value) } task$ = new BehaviorSubject(null) @@ -62,10 +115,10 @@ export class TaskComponent { } @Input() set menuItems(items: MenuItem[]) { - this.menuItems_$.next(items) + this.menuItemsInput$.next(items) } - menuItems_$ = new BehaviorSubject(null) - menuItems$ = this.menuItems_$.pipe( + menuItemsInput$ = new BehaviorSubject(null) + menuItems$ = this.menuItemsInput$.pipe( map(items => { if (this.readonly) return null if (!items || this.task$.value?.description) return items @@ -73,7 +126,7 @@ export class TaskComponent { const descriptionItem: MenuItem = { title: 'Add description', icon: 'description', - action: () => this.addDescription(), + action: () => this.openDescription(), } const insertAfterIndex = items.findIndex(({ title }) => title && /Rename/.test(title)) @@ -85,13 +138,13 @@ export class TaskComponent { shareReplay(1) ) - statusMenuItems$ = this.menuItems_$.pipe( + statusMenuItems$ = this.menuItemsInput$.pipe( map(items => { if (this.readonly) return null return items?.find(({ title }) => title == 'Status')?.children }) ) - priorityMenuItems$ = this.menuItems_$.pipe( + priorityMenuItems$ = this.menuItemsInput$.pipe( map(items => { if (this.readonly) return null return items?.find(({ title }) => title == 'Priority')?.children @@ -101,8 +154,6 @@ export class TaskComponent { @Output() expansionChange = new EventEmitter() @Output() titleChange = new EventEmitter() - @Output() descriptionChange = new EventEmitter() - @Output() descriptionExpansionChange = new EventEmitter() @Output() statusChange = new EventEmitter() @Output() priorityChange = new EventEmitter() @@ -115,67 +166,126 @@ export class TaskComponent { @Input() readonly = false - isHovered = false + isSelected = false - descriptionChanges$ = new BehaviorSubject<{ html: string; text: string } | null>(null) + @Output('descriptionChange') descriptionUpdateOnBlur$ = new EventEmitter() - descriptionExpansionChanges$ = new Subject<{ emit: boolean; isExpanded: boolean }>() - isDescriptionExpanded$ = merge( - this.nodeData$.pipe(map(nodeData => ({ emit: false, isExpanded: nodeData?.isDescriptionExpanded ?? false }))), - this.descriptionExpansionChanges$ - ).pipe( - distinctUntilChanged((prev, curr) => { - if (curr.emit) return false + descriptionUpdates$ = new Subject() + descriptionState$ = merge( + this.descriptionUpdates$, + this.task$.pipe( + map(task => task?.description), + filter(description => description !== undefined && description !== null), + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + map(description => description!) + ) + ).pipe(share({ resetOnRefCountZero: true })) - return prev.isExpanded == curr.isExpanded - }), - tap(({ emit, isExpanded }) => { - if (!emit) return + @Output() isDescriptionActive$ = new BehaviorSubject(false) + descriptionBlur$ = new Subject() - this.descriptionExpansionChange.emit(isExpanded) - }), + isDescriptionExpandedInput$ = new Subject<{ + emit: boolean + isExpanded: boolean + }>() + isDescriptionExpandedBus$ = this.nodeData$.pipe( + map(nodeData => ({ emit: false, isExpanded: nodeData?.isDescriptionExpanded ?? false })), + mergeWith(this.isDescriptionExpandedInput$), + mergeWith( + this.descriptionBlur$.pipe( + withLatestFrom(this.descriptionState$.pipe(startWith(null))), + map(([, latestEditorState]) => ({ emit: true, isExpanded: !!latestEditorState })) + ) + ), + startWith({ emit: false, isExpanded: false }), + shareReplay({ bufferSize: 1, refCount: true }) + ) + isDescriptionExpanded$ = this.isDescriptionExpandedBus$.pipe( map(({ isExpanded }) => isExpanded), + distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }) ) - @ViewChild('descriptionEditor') descriptionEditor!: RtEditorComponent - addDescription() { - this.descriptionExpansionChanges$.next({ emit: false, isExpanded: true }) + @Output('descriptionExpansionChange') isDescriptionExpandedOutput = this.isDescriptionExpandedBus$.pipe( + filter(({ emit }) => emit), + map(({ isExpanded }) => isExpanded), + distinctUntilChanged() + ) + + bindConfig$ = this.task$.pipe( + filter(Boolean), + distinctUntilKeyChanged('id'), + map(task => { + const description$ = this.task$.pipe( + map(task => task?.description || ''), + distinctUntilChanged() + ) + return { input$: description$, context: task.id } + }) + ) + + @ViewChild(TipTapEditorComponent) descriptionEditor?: TipTapEditorComponent + + private counterWidgetId!: string + private counterWidget: HTMLDivElement | null = null + + getChecklistCounterWidget() { + if (!this.task$.value) return null + if (this.counterWidget) return this.counterWidget + + this.counterWidgetId = 'checklist-counter' + this.task$.value.id + + const checklistCount = countChecklistItems( + this.descriptionEditor?.editor.state.doc || createDocument(this.task$.value.description, editorSchema) + ) + + this.counterWidget = createCounterWidget({ + widgetId: this.counterWidgetId, + sticky: false, + withLabel: false, + style: { + display: checklistCount.totalItems == 0 ? 'none' : 'flex', + }, + overrideStyles: true, + checklistCount, + }) + + this.descriptionState$ + .pipe(untilDestroyed(this), throttleTime(400, undefined, { leading: true, trailing: true })) + .subscribe(description => { + if (!this.task$.value) return + + const checklistCount = countChecklistItems( + this.descriptionEditor?.editor.state.doc || createDocument(description, editorSchema) + ) + + updateCounterWidget({ + widgetId: this.counterWidgetId, + checklistCount, + sticky: false, + overrideStyles: true, + style: { + display: checklistCount.totalItems == 0 ? 'none' : 'flex', + }, + }) + }) + + return this.counterWidget + } + + openDescription() { + this.isDescriptionExpandedInput$.next({ emit: false, isExpanded: true }) moveToMacroQueue(() => { - this.descriptionEditor.editor.commands.focus() + this.descriptionEditor?.editor.commands.focus() }) } resetDescription() { - this.descriptionEditor.editor.commands.setContent(this.task$.value?.description || '') + // @TODO: this needs be updated + this.descriptionEditor?.editor.commands.setContent(this.task$.value?.description || '') } toggleDescription() { this.isDescriptionExpanded$.pipe(first()).subscribe(isExpanded => { - this.descriptionExpansionChanges$.next({ emit: true, isExpanded: !isExpanded }) + this.isDescriptionExpandedInput$.next({ emit: true, isExpanded: !isExpanded }) }) } - - isDescriptionFocused$ = new BehaviorSubject(false) - descriptionBlurEvents$ = new Subject() - descriptionUpdatesSub = this.descriptionBlurEvents$ - .pipe( - switchMap(() => this.descriptionChanges$.pipe(first())), - filter(Boolean), - tap(({ text }) => { - if (!text) this.descriptionExpansionChanges$.next({ emit: true, isExpanded: false }) - }), - filter(({ text, html }) => { - if (!text && !this.task$.value?.description) return false - // if (this.task$.value?.description == text) return false - if (this.task$.value?.description == html) return false - - return true - }), - tap(({ text, html }) => { - this.descriptionChange.emit(text ? html : '') - - if (text) this.descriptionExpansionChanges$.next({ emit: true, isExpanded: true }) - }), - untilDestroyed(this) - ) - .subscribe() } diff --git a/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts b/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts index dd088b28..1bb8c839 100644 --- a/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts +++ b/client-v2/src/app/components/organisms/user-menu/user-menu.component.ts @@ -39,7 +39,7 @@ export class UserMenuComponent { // title: 'Garbage', // icon: 'fas fa-trash' as IconKey, // }, - { isSeperator: true }, + { isSeparator: true }, { title: 'Logout', icon: 'logout', diff --git a/client-v2/src/app/dropdown/drop-down/drop-down.component.html b/client-v2/src/app/dropdown/drop-down/drop-down.component.html index dc575a7d..c5b558ec 100644 --- a/client-v2/src/app/dropdown/drop-down/drop-down.component.html +++ b/client-v2/src/app/dropdown/drop-down/drop-down.component.html @@ -1,4 +1,4 @@ - @@ -134,8 +135,8 @@ class="icon-btn" (click)="$event.stopPropagation(); nodeTooltip.hideTooltip()" [cdkMenuTriggerFor]="nodeDropDown" - (cdkMenuOpened)="isHovered[node.id] = true" - (cdkMenuClosed)="isHovered[node.id] = false" + (cdkMenuOpened)="isSelected[node.id] = true" + (cdkMenuClosed)="isSelected[node.id] = false" data-test-name="open-menu" > diff --git a/client-v2/src/app/pages/home/home.component.ts b/client-v2/src/app/pages/home/home.component.ts index 93cf18e7..e5646ae2 100644 --- a/client-v2/src/app/pages/home/home.component.ts +++ b/client-v2/src/app/pages/home/home.component.ts @@ -4,9 +4,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' import { Store } from '@ngrx/store' import { Action } from '@ngrx/store/src/models' -import { combineLatestWith, delay, map, tap } from 'rxjs' -import { MenuItem } from 'src/app/dropdown/drop-down/drop-down.component' +import { combineLatestWith, map, tap } from 'rxjs' import { MenuService } from 'src/app/components/templates/sidebar-layout/menu.service' +import { MenuItem } from 'src/app/dropdown/drop-down/drop-down.component' import { EntityPreviewFlattend, EntityType } from 'src/app/fullstack-shared-models/entities.model' import { TaskPreview } from 'src/app/fullstack-shared-models/task.model' import { DeviceService } from 'src/app/services/device.service' @@ -64,15 +64,15 @@ export class HomeComponent { // @TODO: lets have a look at this again when socket integration is ready shouldFetchSubcription = this.deviceService.shouldFetch$.pipe(untilDestroyed(this)).subscribe(index => { - if (index == 0) { - this.store.dispatch(entitiesActions.loadPreviews()) - this.store.dispatch(taskActions.loadTaskPreviews()) - return - } + if (index == 0) { + this.store.dispatch(entitiesActions.loadPreviews()) + this.store.dispatch(taskActions.loadTaskPreviews()) + return + } - this.store.dispatch(entitiesActions.reloadPreviews()) - this.store.dispatch(taskActions.reloadTaskPreviews()) - }) + this.store.dispatch(entitiesActions.reloadPreviews()) + this.store.dispatch(taskActions.reloadTaskPreviews()) + }) isMobileScreen$ = this.deviceService.isMobileScreen$ @@ -99,6 +99,7 @@ export class HomeComponent { return true } + // changes are automatically reflected here, since it always stays the same object identity entityExpandedMap = this.uiStateService.sidebarUiState.entityExpandedMap toggleExpansion(node: EntityTreeNode) { node.isExpanded = !node.isExpanded @@ -154,7 +155,7 @@ export class HomeComponent { entitiesActions.loadPreviewsError, ]) - isHovered: Record = {} + isSelected: Record = {} nodeLoadingMap$ = this.loadingService.getEntitiesLoadingStateMap() dataSource = new ArrayDataSource(this.entityPreviewsTransformed$) From bed7fd98a1f37d020074e037129e44eec86a51e9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Mon, 25 Sep 2023 23:51:27 +0200 Subject: [PATCH 19/50] feat: update entity-pagen (no idea what this is) --- .../home/entity-page/entity-page.component.ts | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/client-v2/src/app/pages/home/entity-page/entity-page.component.ts b/client-v2/src/app/pages/home/entity-page/entity-page.component.ts index 010d3219..a07d5875 100644 --- a/client-v2/src/app/pages/home/entity-page/entity-page.component.ts +++ b/client-v2/src/app/pages/home/entity-page/entity-page.component.ts @@ -6,10 +6,10 @@ import { Store } from '@ngrx/store' import { combineLatestWith, map, of, shareReplay, switchMap } from 'rxjs' import { IconKey } from 'src/app/components/atoms/icons/icon/icons' import { Breadcrumb } from 'src/app/components/molecules/breadcrumbs/breadcrumbs.component' -import { EntityPreviewRecursive } from 'src/app/fullstack-shared-models/entities.model' +import { EntityPreviewRecursive, EntityType } from 'src/app/fullstack-shared-models/entities.model' import { TaskPreview } from 'src/app/fullstack-shared-models/task.model' import { LoadingStateService } from 'src/app/services/loading-state.service' -import { getEntityMenuItemsMap } from 'src/app/shared/entity-menu-items' +import { TaskMenuItemData, getEntityMenuItemsMap } from 'src/app/shared/entity-menu-items' import { AppState } from 'src/app/store' import { traceEntity, traceEntityIncludingTasks } from 'src/app/store/entities/utils' import { useDataForAction, useParamsForRoute, useTaskForActiveItems } from 'src/app/utils/menu-item.helpers' @@ -67,21 +67,35 @@ export class EntityPageComponent { ) ), map(([trace, loadingMap]) => - trace?.map((entity /* , index, { length } */) => { - // @TODO: we should check if the primary loading spinner is visible, and only if not, enable spinner in last breadcrumb - // const isLast = index == length - 1 - const loadingIcon: false | IconKey = /* !isLast && */ loadingMap[entity.id] && 'Loading' + trace?.map(({ entityType, ...entity }) => { + const loadingIcon: false | IconKey = loadingMap[entity.id] && 'Loading' const statusIcon = (entity as EntityPreviewRecursive & Pick).status + let contextMenuItems: Breadcrumb['contextMenuItems'] + + if (entityType == EntityType.TASK) { + const taskEntity: TaskMenuItemData = { + ...(entity as EntityPreviewRecursive & TaskPreview), + entityType, + } + + // @TODO: we must add the data for the tasks in the dropdown + contextMenuItems = this.entityOptionsMap[entityType].applyOperators( + useDataForAction(taskEntity), + useTaskForActiveItems(taskEntity as EntityPreviewRecursive & TaskPreview), + useParamsForRoute({ id: entity.id }) + ) + } else + contextMenuItems = this.entityOptionsMap[entityType].applyOperators( + useDataForAction({ id: entity.id, entityType: entityType as EntityType }), + useParamsForRoute({ id: entity.id }) + ) + return { title: entity.title, - icon: loadingIcon || statusIcon || entity.entityType, + icon: loadingIcon || statusIcon || entityType, route: `/home/${entity.id}`, - contextMenuItems: this.entityOptionsMap[entity.entityType].applyOperators( - useDataForAction({ id: entity.id, entityType: entity.entityType }), - useTaskForActiveItems(entity as EntityPreviewRecursive & TaskPreview), - useParamsForRoute({ id: entity.id }) - ), + contextMenuItems: contextMenuItems, } }) ) From fa4c0c502d4230f35f17dbe40bd87250c0e9e9b6 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Tue, 26 Sep 2023 00:19:04 +0200 Subject: [PATCH 20/50] fix: toolbar default hidden --- .../src/app/components/molecules/toolbar/toolbar.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-v2/src/app/components/molecules/toolbar/toolbar.component.ts b/client-v2/src/app/components/molecules/toolbar/toolbar.component.ts index c728dcfe..9d7d96e6 100644 --- a/client-v2/src/app/components/molecules/toolbar/toolbar.component.ts +++ b/client-v2/src/app/components/molecules/toolbar/toolbar.component.ts @@ -24,6 +24,6 @@ export class ToolbarComponent { if (hidden) return timer(150).pipe(map(() => false)) return of(true) }), - startWith(!this.hideToolbar) + startWith(false) ) } From 3a99de273c825a11d8c19bfa1190b08261a8d819 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Tue, 26 Sep 2023 00:21:34 +0200 Subject: [PATCH 21/50] feat: update BACKLOG icon --- client-v2/src/app/components/atoms/icons/icon/icons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-v2/src/app/components/atoms/icons/icon/icons.ts b/client-v2/src/app/components/atoms/icons/icon/icons.ts index 5ac152a1..66303cb5 100644 --- a/client-v2/src/app/components/atoms/icons/icon/icons.ts +++ b/client-v2/src/app/components/atoms/icons/icon/icons.ts @@ -74,7 +74,7 @@ export const taskStatusIconMap: Record = concatMatchingKeys( { [TaskStatus.OPEN]: 'far fa-circle', [TaskStatus.IN_PROGRESS]: 'far fa-clock', - [TaskStatus.BACKLOG]: 'fas fa-spinner rotate-[-45deg]', + [TaskStatus.BACKLOG]: 'fal fa-stroopwafel', [TaskStatus.COMPLETED]: 'fas fa-check-circle', [TaskStatus.NOT_PLANNED]: 'fas fa-times-circle', } as const, From 9790e1dd3c908801e45421481c25a5a5e5aaf6de Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Tue, 26 Sep 2023 00:59:05 +0200 Subject: [PATCH 22/50] build: bump componentStyle budget --- client-v2/angular.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-v2/angular.json b/client-v2/angular.json index a305e2ad..128d22fe 100644 --- a/client-v2/angular.json +++ b/client-v2/angular.json @@ -45,7 +45,7 @@ { "type": "anyComponentStyle", "maximumWarning": "2kb", - "maximumError": "5kb" + "maximumError": "6kb" } ], "fileReplacements": [ From ade1e9d37654a7fd9baad4eb1efd49c68559bc1c Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Tue, 26 Sep 2023 11:15:24 +0200 Subject: [PATCH 23/50] fix: fix unit tests --- .../organisms/entity-view/entity-view.component.spec.ts | 3 ++- .../organisms/entity-view/entity-view.component.test.ts | 3 ++- .../tip-tap-editor-toolbar.component.spec.ts | 7 +++++++ .../tip-tap-editor/tip-tap-editor.component.spec.ts | 2 ++ client-v2/tsconfig.spec.json | 4 +--- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.spec.ts b/client-v2/src/app/components/organisms/entity-view/entity-view.component.spec.ts index 3bbc2cf8..c9ec6d27 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.spec.ts +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.spec.ts @@ -6,6 +6,7 @@ import { storeMock } from 'src/app/utils/unit-test.mocks' import { EntityViewComponent } from './entity-view.component' import { TasklistViewComponent } from './views/tasklist-view/tasklist-view.component' +import { RxModule } from 'src/app/rx/rx.module' describe('EntityViewComponent', () => { let component: EntityViewComponent @@ -15,7 +16,7 @@ describe('EntityViewComponent', () => { await TestBed.configureTestingModule({ declarations: [EntityViewComponent, TasklistViewComponent], providers: [Injector, storeMock], - imports: [CdkMenuModule, AsyncPipe], + imports: [CdkMenuModule, AsyncPipe, RxModule], }).compileComponents() fixture = TestBed.createComponent(EntityViewComponent) diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts index ba8c6d8e..24d97dfc 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.test.ts @@ -10,6 +10,7 @@ import { EntityViewComponent, entityViewComponentMap } from './entity-view.compo import { TaskViewComponent } from './views/task-view/task-view.component' import { TasklistViewComponent } from './views/tasklist-view/tasklist-view.component' import { DropdownModule } from 'src/app/dropdown/dropdown.module' +import { RxModule } from 'src/app/rx/rx.module' const defaultTemplate = ` @@ -25,7 +26,7 @@ const setupComponent = (template = defaultTemplate) => { activeEntity$: new BehaviorSubject(null), entityOptionsMap$: new BehaviorSubject(null), }, - imports: [CdkMenuModule, DropdownModule], + imports: [CdkMenuModule, DropdownModule, RxModule], declarations: [ EntityViewComponent, ...entityViewComponents, diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts index 8b59de0c..97ec61e9 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts @@ -1,18 +1,25 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { TipTapEditorToolbarComponent } from './tip-tap-editor-toolbar.component' +import { provideEditorFeatures, getDefaultEditorFeatures } from '../features' +import { CdkMenuModule } from '@angular/cdk/menu' +import { TipTapEditorComponent } from '../tip-tap-editor/tip-tap-editor.component' describe('TipTapEditorToolbarComponent', () => { let component: TipTapEditorToolbarComponent let fixture: ComponentFixture beforeEach(async () => { + const editorFeaturesProvider = provideEditorFeatures(getDefaultEditorFeatures()) await TestBed.configureTestingModule({ + imports: [CdkMenuModule], declarations: [TipTapEditorToolbarComponent], + providers: [editorFeaturesProvider], }).compileComponents() fixture = TestBed.createComponent(TipTapEditorToolbarComponent) component = fixture.componentInstance + component.ttEditor = new TipTapEditorComponent(editorFeaturesProvider.useValue) fixture.detectChanges() }) diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts index 519dae04..7fe46eb6 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { TipTapEditorComponent } from './tip-tap-editor.component' +import { getDefaultEditorFeatures, provideEditorFeatures } from '../features' describe('TipTapEditorComponent', () => { let component: TipTapEditorComponent @@ -9,6 +10,7 @@ describe('TipTapEditorComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [TipTapEditorComponent], + providers: [provideEditorFeatures(getDefaultEditorFeatures())], }).compileComponents() fixture = TestBed.createComponent(TipTapEditorComponent) diff --git a/client-v2/tsconfig.spec.json b/client-v2/tsconfig.spec.json index 092345b0..e495ccd4 100644 --- a/client-v2/tsconfig.spec.json +++ b/client-v2/tsconfig.spec.json @@ -3,9 +3,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/spec", - "types": [ - "jasmine" - ] + "types": ["jasmine", "@total-typescript/ts-reset"] }, "files": [ "src/test.ts", From afc10a4f246926996458ba652470528c1766b731 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Tue, 26 Sep 2023 12:10:25 +0200 Subject: [PATCH 24/50] fix: fix component tests --- client-v2/cypress/support/helpers.ts | 4 ++-- .../organisms/task-tree/task-tree.component.test.ts | 8 +++++--- .../app/components/organisms/task/task.component.test.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client-v2/cypress/support/helpers.ts b/client-v2/cypress/support/helpers.ts index 8ba2e1d8..739dad6e 100644 --- a/client-v2/cypress/support/helpers.ts +++ b/client-v2/cypress/support/helpers.ts @@ -2,8 +2,8 @@ import { interceptItem } from '../../src/app/utils/menu-item.helpers' export const testName = (testName: string) => `[data-test-name="${testName}"]` -export const useStubsForActions = (stubMap?: Record>) => { - return interceptItem(({ action, title }) => { +export const useStubsForActions = (stubMap?: Record>) => { + return interceptItem(({ action, title }) => { if (!action) return {} // we cannot use optional chaining syntax here, because cypress->webpack complains diff --git a/client-v2/src/app/components/organisms/task-tree/task-tree.component.test.ts b/client-v2/src/app/components/organisms/task-tree/task-tree.component.test.ts index 6b952ba8..e01527c2 100644 --- a/client-v2/src/app/components/organisms/task-tree/task-tree.component.test.ts +++ b/client-v2/src/app/components/organisms/task-tree/task-tree.component.test.ts @@ -3,18 +3,20 @@ import { testName } from 'cypress/support/helpers' import { FocusableDirective } from 'src/app/directives/focusable.directive' import { MutationDirective } from 'src/app/directives/mutation.directive' import { TaskPreviewRecursive, TaskPriority, TaskStatus } from 'src/app/fullstack-shared-models/task.model' +import { HighlightPipe } from 'src/app/pipes/highlight.pipe' +import { RichTextEditorModule } from 'src/app/rich-text-editor/rich-text-editor.module' +import { RxModule } from 'src/app/rx/rx.module' import { actionsMock, storeMock } from 'src/app/utils/unit-test.mocks' import { IconsModule } from '../../atoms/icons/icons.module' import { InlineEditorComponent } from '../../atoms/inline-editor/inline-editor.component' import { TaskComponent } from '../task/task.component' import { TaskTreeComponent } from './task-tree.component' -import { PushModule } from '@rx-angular/template/push' -import { HighlightPipe } from 'src/app/pipes/highlight.pipe' +import { DropdownModule } from 'src/app/dropdown/dropdown.module' const setupComponent = (taskTree: TaskPreviewRecursive[]) => { cy.mount(``, { componentProperties: { taskTree }, - imports: [IconsModule, CdkMenuModule, PushModule], + imports: [IconsModule, CdkMenuModule, RxModule, RichTextEditorModule, DropdownModule], declarations: [ TaskTreeComponent, TaskComponent, diff --git a/client-v2/src/app/components/organisms/task/task.component.test.ts b/client-v2/src/app/components/organisms/task/task.component.test.ts index eabba2a7..30caff18 100644 --- a/client-v2/src/app/components/organisms/task/task.component.test.ts +++ b/client-v2/src/app/components/organisms/task/task.component.test.ts @@ -49,7 +49,7 @@ const setupComponent = ( const taskFixture: TaskPreviewFlattend = { title: 'The title', - childrenCount: 0, + children: [], description: '', id: '', listId: '', From ee6cb69a494fdfda98cb7d8033beb60520686422 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 27 Oct 2023 20:40:26 +0200 Subject: [PATCH 25/50] fix: fix search extension shared storage issue --- .../search-and-replace.extension.ts | 52 ++++++++++++------- .../tip-tap-editor-toolbar.component.html | 1 + 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts b/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts index 4fd78860..33af978c 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts @@ -1,6 +1,6 @@ // Stolen from https://github.com/ueberdosis/tiptap/pull/2075 -import { Extension } from '@tiptap/core' +import { Editor, Extension } from '@tiptap/core' import { Decoration, DecorationSet } from 'prosemirror-view' import { EditorState, Plugin, PluginKey } from 'prosemirror-state' import { Node as ProsemirrorNode } from 'prosemirror-model' @@ -34,14 +34,20 @@ interface Result { } interface SearchOptions { - searchTerm: string - replaceTerm: string - results: Result[] + // searchTerm: string + // replaceTerm: string + // results: Result[] searchResultClass: string caseSensitive: boolean disableRegex: boolean } +interface SearchStorage { + searchTerm: string + replaceTerm: string + results: Result[] +} + interface TextNodesWithPosition { text: string pos: number @@ -176,28 +182,36 @@ const replaceAll = (replaceTerm: string, results: Result[], { tr, dispatch }: an dispatch(tr) } +// need to use editor storage because the extension storage is shared among all instances +const getSearchStorage = (editor: Editor): SearchStorage => editor.storage['search'] as SearchStorage + // eslint-disable-next-line @typescript-eslint/ban-types -export const SearchAndReplace = Extension.create({ +export const SearchAndReplace = Extension.create({ name: 'search', addOptions() { return { - searchTerm: '', - replaceTerm: '', - results: [], searchResultClass: 'search-result', caseSensitive: false, disableRegex: false, } }, + onBeforeCreate() { + this.editor.storage['search'] = { + searchTerm: '', + replaceTerm: '', + results: [], + } + }, + addCommands() { return { setSearchTerm: (searchTerm: string) => ({ state, dispatch }) => { - this.options.searchTerm = searchTerm - this.options.results = [] + getSearchStorage(this.editor).searchTerm = searchTerm + getSearchStorage(this.editor).results = [] updateView(state, dispatch) @@ -206,8 +220,8 @@ export const SearchAndReplace = Extension.create({ setReplaceTerm: (replaceTerm: string) => ({ state, dispatch }) => { - this.options.replaceTerm = replaceTerm - this.options.results = [] + getSearchStorage(this.editor).replaceTerm = replaceTerm + getSearchStorage(this.editor).results = [] updateView(state, dispatch) @@ -216,10 +230,10 @@ export const SearchAndReplace = Extension.create({ replace: () => ({ state, dispatch }) => { - const { replaceTerm, results } = this.options + const { replaceTerm, results } = getSearchStorage(this.editor) replace(replaceTerm, results, { state, dispatch }) - this.options.results.shift() + getSearchStorage(this.editor).results.shift() updateView(state, dispatch) @@ -228,10 +242,10 @@ export const SearchAndReplace = Extension.create({ replaceAll: () => ({ state, tr, dispatch }) => { - const { replaceTerm, results } = this.options + const { replaceTerm, results } = getSearchStorage(this.editor) replaceAll(replaceTerm, results, { tr, dispatch }) - this.options.results = [] + getSearchStorage(this.editor).results = [] updateView(state, dispatch) @@ -252,7 +266,8 @@ export const SearchAndReplace = Extension.create({ return DecorationSet.empty }, apply({ doc, docChanged }) { - const { searchTerm, searchResultClass, disableRegex, caseSensitive } = extensionThis.options + const { searchResultClass, disableRegex, caseSensitive } = extensionThis.options + const { searchTerm } = getSearchStorage(extensionThis.editor) if (docChanged || searchTerm) { const { decorationsToReturn, results } = processSearches( @@ -260,8 +275,7 @@ export const SearchAndReplace = Extension.create({ regex(searchTerm, disableRegex, caseSensitive), searchResultClass ) - - extensionThis.options.results = results + getSearchStorage(extensionThis.editor).results = results return decorationsToReturn } diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html index 543d8745..9b7fef2e 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html @@ -83,6 +83,7 @@
    diff --git a/client-v2/src/app/rich-text-editor/app-editor.ts b/client-v2/src/app/rich-text-editor/app-editor.ts index e42055ed..078c0db1 100644 --- a/client-v2/src/app/rich-text-editor/app-editor.ts +++ b/client-v2/src/app/rich-text-editor/app-editor.ts @@ -5,14 +5,13 @@ import { Observable, Subject, delay, - distinctUntilKeyChanged, - filter, first, fromEvent, fromEventPattern, map, merge, share, + startWith, takeUntil, withLatestFrom, } from 'rxjs' @@ -31,13 +30,6 @@ export class AppEditor extends Editor { handler => this.off('destroy', handler) ).pipe(first(), share({ resetOnRefCountZero: true })) - private unbindTrigger$ = new Subject() - unbind$ = merge(this.unbindTrigger$, this.destroy$).pipe(debugObserver('unbind$ and destroy$')) - /** Cancel input binding */ - unbindInput() { - this.unbindTrigger$.next() - } - override get storage() { return super.storage as EditorStorage } @@ -49,13 +41,24 @@ export class AppEditor extends Editor { ) } - update$ = this.getEventStream('update') + updateRaw$ = this.getEventStream('update') selectionUpdate$ = this.getEventStream('selectionUpdate') transaction$ = this.getEventStream('transaction') focus$ = this.getEventStream('focus') blur$ = this.getEventStream('blur') + update$ = this.updateRaw$.pipe( + map(({ editor }) => { + const isEmpty = editor.isEmpty + return { + plainText: isEmpty ? '' : editor.getText().trim(), + html: isEmpty ? '' : editor.getHTML(), + } + }), + share({ resetOnRefCountZero: true }) + ) + resetState(content: Content, parseOptions?: ParseOptions) { const newState = EditorState.create({ doc: createDocument(content, this.schema, parseOptions), @@ -66,105 +69,53 @@ export class AppEditor extends Editor { this.view.updateState(newState) } - bindInput(input$: Observable) { + bindEditor(input$: Observable, searchTerm$?: Observable) { // cancel the previous binding - this.unbindInput() - - // input$ = from([ - // '

    This is content one and

    ', - // '

    This is content one and each

    ', - // '

    This is content one and each

    ', - // '

    This is content one and each time

    ', - // '

    This is content one and each time a little

    ', - // '

    This is content one and each time a little more

    ', - // '

    This is content one. But suddenly

    ', - // '

    Gone

    ', - // ]).pipe(concatMap(content => of(content).pipe(delay(4000)))) - - const updateOnBlur$ = merge(this.blur$, this.unbind$).pipe( - map(() => (this.isEmpty ? '' : this.getHTML())), - withLatestFrom(input$), - filter(([currentState, lastInput]) => currentState != lastInput), - distinctUntilKeyChanged(0), - map(([currentState]) => currentState), - takeUntil(this.unbind$), - debugObserver('updateOnBlur$'), - share({ resetOnRefCountZero: true }) - ) + this.unbind() + this.commands.blur() input$ .pipe( + debugObserver('input$'), takeUntil(this.unbind$), - map((input, index) => ({ + withLatestFrom(this.update$.pipe(startWith(null))), + map(([input, currentState], index) => ({ input, - currentState: this.isEmpty ? '' : this.getHTML(), + currentState, isFirst: index == 0, })) ) .subscribe(({ input, currentState, isFirst }) => { - if (input == currentState) return + if (input == currentState?.html || input == currentState?.plainText) return - if (isFirst) this.resetState(input) - else this.chain().setContent(input, false).setMeta('addToHistory', false).run() + if (isFirst) { + this.resetState(input) + } else { + this.chain().setContent(input, false).setMeta('addToHistory', false).run() + } }) + searchTerm$?.pipe(takeUntil(this.unbind$)).subscribe({ + next: searchTerm => this.commands.setSearchTerm(searchTerm), + complete: () => this.commands.setSearchTerm(''), + }) + return { - unbindInput: this.unbindInput.bind(this), + unbind: () => this.unbind(), unbind$: this.unbind$.pipe(takeUntil(this.unbind$.pipe(delay(0)))), - update$: this.update$.pipe(takeUntil(this.unbind$)), - updateOnBlur$, + updateRaw$: this.updateRaw$, selectionUpdate$: this.selectionUpdate$.pipe(takeUntil(this.unbind$)), focus$: this.focus$.pipe(takeUntil(this.unbind$)), blur$: this.blur$.pipe(takeUntil(this.unbind$)), } } - // bindInput_(input$: Observable) { - // // cancel the previous binding - // this.unbind$.next(null) - - // // @TODO: test this - // const unbind$ = merge(this.unbind$, this.destroy$).pipe(debugObserver('unbind$ and destroy$')) - - // // @TODO: lets add a bit of throttling here and add an isEmpty - // const update$ = this.update$.pipe( - // takeUntil(this.destroy$), - // takeUntil(unbind$), - // // map(({ editor }) => (editor.isEmpty ? '' : editor.getHTML())), - // map(({ editor }) => ''), - // debugObserver('update$'), - // share({ resetOnRefCountZero: true }) - // ) - - // input$ - // .pipe( - // takeUntil(unbind$), - // withLatestFrom(update$.pipe(startWith(null))), - // map(([input, lastOutput], index) => ({ - // input, - // lastOutput, - // isFirst: index == 0, - // })) - // ) - // .subscribe(({ input, lastOutput, isFirst }) => { - // if (input === lastOutput) return - - // if (isFirst) this.resetState(input) - // else { - // this.chain().setContent(input, false).setMeta('addToHistory', false).run() - // } - // }) - - // return { - // editor: this, - // unbind: () => this.unbind$.next(null), - // onUnbind$: unbind$.pipe(takeUntil(unbind$.pipe(delay(0)))), - // onUpdate$: update$, - // onSelectionUpdate$: this.selectionUpdate$.pipe(takeUntil(unbind$)), - // // onFocusChange$: this.isFocused$.pipe(takeUntil(unbind$)), - // onFocus$: this.focus$.pipe(takeUntil(unbind$)), - // onBlur$: this.blur$.pipe(takeUntil(unbind$)), - // } - // } + + private unbindTrigger$ = new Subject() + unbind$ = merge(this.unbindTrigger$, this.destroy$).pipe(debugObserver('unbind$ and destroy$')) + /** Cancel input binding */ + unbind() { + this.unbindTrigger$.next() + } deselect = () => window.getSelection()?.removeAllRanges() } diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts index 2f11305f..3e48c456 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts @@ -3,7 +3,17 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, Inject, Input, ViewC import { Router } from '@angular/router' import { HotToastService } from '@ngneat/hot-toast' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' -import { BehaviorSubject, distinctUntilChanged, map, merge, skip, startWith, switchMap, throttleTime } from 'rxjs' +import { + BehaviorSubject, + delay, + distinctUntilChanged, + map, + merge, + skip, + startWith, + switchMap, + throttleTime, +} from 'rxjs' import { IconKey } from 'src/app/components/atoms/icons/icon/icons' import { MenuItem } from 'src/app/dropdown/drop-down/drop-down.component' import { DeviceService } from 'src/app/services/device.service' @@ -87,6 +97,7 @@ export class TipTapEditorToolbarComponent implements AfterViewInit { startWith(false), distinctUntilChanged(), skip(1), + delay(0), // delay to make sure the editor has time to update its focus state before we check it untilDestroyed(this) ).subscribe(toolbarHasFocus => { if (!toolbarHasFocus && !this.ttEditor.editor.view.hasFocus()) { @@ -103,7 +114,8 @@ export class TipTapEditorToolbarComponent implements AfterViewInit { this.controlsMenuBar?.focusFirstItem() } - @Input() ttEditor!: TipTapEditorComponent + // eslint-disable-next-line @typescript-eslint/no-explicit-any + @Input() ttEditor!: TipTapEditorComponent @Input() openAsPageRoute?: string diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts index 3739cd17..5b1849af 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts @@ -1,17 +1,9 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Inject, - Input, - OnDestroy, - Output, - ViewEncapsulation, -} from '@angular/core' +import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, Output, ViewEncapsulation } from '@angular/core' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' import { coalesceWith } from '@rx-angular/cdk/coalescing' import { Observable, + ReplaySubject, Subject, delay, distinctUntilKeyChanged, @@ -21,7 +13,6 @@ import { mergeWith, share, shareReplay, - skip, startWith, switchMap, takeUntil, @@ -29,7 +20,6 @@ import { timer, withLatestFrom, } from 'rxjs' -import { debugObserver } from 'src/app/utils/observable.helpers' import { AppEditor } from '../app-editor' import { EditorFeature } from '../editor.types' import { EDITOR_FEATURES_TOKEN } from '../features' @@ -46,7 +36,7 @@ import { isChecklistItem } from '../helpers' class: 'block', }, }) -export class TipTapEditorComponent implements OnDestroy { +export class TipTapEditorComponent implements OnDestroy { constructor(@Inject(EDITOR_FEATURES_TOKEN) public features: EditorFeature[]) {} ngOnDestroy(): void { @@ -60,17 +50,14 @@ export class TipTapEditorComponent implements OnDestroy { return this.editor?.isEditable ?? true } - private searchTerm: string | null = null - @Input('searchTerm') set searchTerm_(searchTerm: string | null) { - this.searchTerm = searchTerm - if (searchTerm) this.editor?.commands.setSearchTerm(searchTerm) + private searchTerm$ = new ReplaySubject(1) + @Input() set searchTerm(searchTerm: string | null) { + if (searchTerm !== null) this.searchTerm$.next(searchTerm) } editor = new AppEditor({ editable: this.editable, extensions: this.features.flatMap(feature => feature.extensions), - - onDestroy: () => this.unbind$.next(null), }) private focusStateInput$ = new Subject() @@ -95,7 +82,7 @@ export class TipTapEditorComponent implements OnDestroy { // check if a task item was clicked (only inside the current editor) if (isChecklistItem(clickedElem) && this.editor.view.dom.contains(clickedElem as Node)) return false - // -> Its good to explicitly allow elems as instead of blindly ignoring everything from inside the editor + // -> Its good to explicitly allow elems instead of blindly ignoring everything from inside the editor return true }), @@ -105,27 +92,19 @@ export class TipTapEditorComponent implements OnDestroy { share({ resetOnRefCountZero: true }) ) - @Output() unbind$ = new EventEmitter() - - @Output('isActive') isActive$ = merge(this.focus$, this.blur$, this.unbind$).pipe( + @Output('isActive') isActive$ = merge(this.focus$, this.blur$).pipe( map(() => this.editor.isFocused), coalesceWith(timer(70)), - mergeWith(this.unbind$.pipe(map(() => false))), // because we blur the editor when (un)binding + mergeWith(this.editor.unbind$.pipe(map(() => false))), // because we blur the editor when (un)binding startWith(false), untilDestroyed(this), shareReplay({ bufferSize: 1, refCount: true }) ) - @Output('update') update$ = this.editor.update$.pipe( - untilDestroyed(this), - skip(1), // emits first state when editor is empty for some reason - // @TODO: here would go the throttler - map(({ editor }) => (editor.isEmpty ? '' : editor.getHTML())), - share({ resetOnRefCountZero: true }) - ) + @Output('update') update$ = this.editor.update$.pipe(untilDestroyed(this)) - private bindConfig$ = new Subject<{ input$: Observable; context: unknown }>() - @Input() set bind(bound: { input$: Observable; context: unknown }) { + private bindConfig$ = new Subject<{ input$: Observable; context: TContext }>() + @Input() set bind(bound: { input$: Observable; context: TContext }) { this.bindConfig$.next(bound) } private bound$ = this.bindConfig$.pipe( @@ -138,56 +117,22 @@ export class TipTapEditorComponent implements OnDestroy { share({ resetOnRefCountZero: true }) ) - bindEditor(input$: Observable, context: TContext) { - // cancel the previous binding - this.unbind$.next(null) - this.editor.commands.blur() - - input$ - .pipe( - debugObserver('input$'), - untilDestroyed(this), - takeUntil(this.unbind$), - withLatestFrom(this.update$.pipe(startWith(null))), - map(([input, currentState], index) => ({ - input, - currentState, - isFirst: index == 0, - })) - ) - .subscribe(({ input, currentState, isFirst }) => { - if (input == currentState) return - - if (isFirst) { - this.editor.resetState(input) - if (this.searchTerm) { - console.log('setting search term', this.searchTerm) - this.editor.commands.setSearchTerm(this.searchTerm) - } - } else { - this.editor.chain().setContent(input, false).setMeta('addToHistory', false).run() - } - }) - - const updateOnBlur$ = merge(this.blur$, this.unbind$).pipe( - withLatestFrom(this.update$, input$.pipe(startWith(null))), - map(([, content, lastInput]) => ({ content, lastInput, context })), - filter(({ content, lastInput }) => content != lastInput), - distinctUntilKeyChanged('content'), - untilDestroyed(this), - takeUntil(this.unbind$.pipe(delay(0))), - share({ resetOnRefCountZero: true }) - ) - + bindEditor(input$: Observable, context: TContext, searchTerm$?: Observable) { + const searchTerm$_ = searchTerm$ ? searchTerm$.pipe(mergeWith(this.searchTerm$)) : this.searchTerm$ return { - unbind: () => this.unbind$.next(null), - unbind$: this.unbind$.pipe(untilDestroyed(this), takeUntil(this.unbind$.pipe(delay(0)))), - update$: this.update$, - updateOnBlur$, - selectionUpdate$: this.editor.selectionUpdate$.pipe(untilDestroyed(this), takeUntil(this.unbind$)), - isActive$: this.isActive$.pipe(untilDestroyed(this), takeUntil(this.unbind$)), - focus$: this.focus$.pipe(untilDestroyed(this), takeUntil(this.unbind$)), - blur$: this.blur$.pipe(untilDestroyed(this), takeUntil(this.unbind$)), + ...this.editor.bindEditor(input$, searchTerm$_), + isActive$: this.isActive$, + updateOnBlur$: merge(this.blur$, this.editor.unbind$).pipe( + withLatestFrom(this.update$, input$.pipe(startWith(null))), + map(([, { html, plainText }, lastInput]) => ({ html, plainText, lastInput, context })), + filter(({ html, plainText, lastInput }) => html != lastInput || plainText != lastInput), + distinctUntilKeyChanged('html'), + untilDestroyed(this), + // Must be delayed so that `unbind$` triggers a last update before the bound is destroyed, + // to make sure all transactions are dispatched. + takeUntil(this.editor.unbind$.pipe(delay(0))), + share({ resetOnRefCountZero: true }) + ), } } } From 837e06751c4723a984dfb7628720cde8d625f303 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Wed, 15 Nov 2023 19:43:11 +0100 Subject: [PATCH 28/50] refactor: rename icons from kebab to camel case --- .../double-ellipsis-icon.component.ts | 2 +- client-v2/src/app/components/atoms/icons/icon/icons.ts | 8 ++++---- .../organisms/entity-view/entity-view.component.html | 2 +- client-v2/src/app/rich-text-editor/features.ts | 2 +- .../tip-tap-editor-toolbar.component.html | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client-v2/src/app/components/atoms/icons/double-ellipsis-icon/double-ellipsis-icon.component.ts b/client-v2/src/app/components/atoms/icons/double-ellipsis-icon/double-ellipsis-icon.component.ts index d8af7d2f..2e6f5a72 100644 --- a/client-v2/src/app/components/atoms/icons/double-ellipsis-icon/double-ellipsis-icon.component.ts +++ b/client-v2/src/app/components/atoms/icons/double-ellipsis-icon/double-ellipsis-icon.component.ts @@ -3,7 +3,7 @@ import { Component } from '@angular/core' @Component({ selector: 'double-ellipsis-icon', template: ` - + `, styleUrls: [], }) diff --git a/client-v2/src/app/components/atoms/icons/icon/icons.ts b/client-v2/src/app/components/atoms/icons/icon/icons.ts index 66303cb5..e8274569 100644 --- a/client-v2/src/app/components/atoms/icons/icon/icons.ts +++ b/client-v2/src/app/components/atoms/icons/icon/icons.ts @@ -33,10 +33,10 @@ const extraIcons = { chevronRight: 'fas fa-chevron-right', chevronLeft: 'fas fa-chevron-left', - 'caret-down': 'fas fa-caret-down', - 'caret-up': 'fas fa-caret-up', - 'ellipsis-h': 'fas fa-ellipsis-h', - 'ellipsis-v': 'fas fa-ellipsis-v', + caretDown: 'fas fa-caret-down', + caretUp: 'fas fa-caret-up', + ellipsisHorizontal: 'fas fa-ellipsis-h', + ellipsisVertical: 'fas fa-ellipsis-v', editor: { undo: 'far fa-undo-alt', diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.html b/client-v2/src/app/components/organisms/entity-view/entity-view.component.html index 725e23ee..a1ada75f 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.html +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.html @@ -18,7 +18,7 @@ [cdkMenuTriggerFor]="options" #trigger="cdkMenuTriggerFor" > - + diff --git a/client-v2/src/app/rich-text-editor/features.ts b/client-v2/src/app/rich-text-editor/features.ts index 77a54678..88ad2687 100644 --- a/client-v2/src/app/rich-text-editor/features.ts +++ b/client-v2/src/app/rich-text-editor/features.ts @@ -22,7 +22,7 @@ const moreMenuFeature = createEditorFeature({ { controlId: EditorControlId.More, title: 'More', - icon: 'ellipsis-h', + icon: 'ellipsisHorizontal', fixedWith: '2.75rem', }, ], diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html index 9b7fef2e..6fe90cd0 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.html @@ -51,7 +51,7 @@ [style.width]="controlItem.fixedWith" > - + Date: Thu, 16 Nov 2023 00:02:29 +0100 Subject: [PATCH 29/50] refactor: rename files to `editor.*` and remove old `types.ts` --- .../entity-description.component.ts | 2 +- .../organisms/task/task.component.ts | 4 +- .../editor-features/blocks.feature.ts | 2 +- .../editor-features/blur.feature.ts | 2 +- .../editor-features/custom-events.feature.ts | 2 +- .../extensions/checklist-counter.extension.ts | 2 +- .../extensions/indentation.extension.ts | 2 +- .../editor-features/inline.feature.ts | 2 +- .../search-and-replace.feature.ts | 2 +- .../{features.ts => editor.features.ts} | 2 +- .../{helpers.ts => editor.helpers.ts} | 0 .../tip-tap-editor-toolbar.component.spec.ts | 2 +- .../tip-tap-editor-toolbar.component.ts | 4 +- .../tip-tap-editor.component.spec.ts | 2 +- .../tip-tap-editor.component.ts | 4 +- client-v2/src/app/rich-text-editor/types.ts | 46 ------------------- 16 files changed, 17 insertions(+), 63 deletions(-) rename client-v2/src/app/rich-text-editor/{features.ts => editor.features.ts} (98%) rename client-v2/src/app/rich-text-editor/{helpers.ts => editor.helpers.ts} (100%) delete mode 100644 client-v2/src/app/rich-text-editor/types.ts diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts index 0c7d9412..7342e832 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts @@ -16,7 +16,7 @@ import { getDefaultEditorFeatures, getDefaultEditorLayout, provideEditorFeatures, -} from 'src/app/rich-text-editor/features' +} from 'src/app/rich-text-editor/editor.features' import { TipTapEditorComponent } from 'src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component' import { DeviceService } from 'src/app/services/device.service' diff --git a/client-v2/src/app/components/organisms/task/task.component.ts b/client-v2/src/app/components/organisms/task/task.component.ts index 2a5bad3a..cc9c01ba 100644 --- a/client-v2/src/app/components/organisms/task/task.component.ts +++ b/client-v2/src/app/components/organisms/task/task.component.ts @@ -35,8 +35,8 @@ import { getDefaultEditorFeatures, getDefaultEditorLayout, provideEditorFeatures, -} from 'src/app/rich-text-editor/features' -import { ChecklistCount, countChecklistItems } from 'src/app/rich-text-editor/helpers' +} from 'src/app/rich-text-editor/editor.features' +import { ChecklistCount, countChecklistItems } from 'src/app/rich-text-editor/editor.helpers' import { TipTapEditorComponent } from 'src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component' import { DeviceService } from 'src/app/services/device.service' import { colors, taskStatusColorMap } from 'src/app/shared/colors' diff --git a/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts index 6055ce28..9d882135 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts @@ -11,7 +11,7 @@ import { TaskItem } from '@tiptap/extension-task-item' import { TaskList } from '@tiptap/extension-task-list' import { IconKey } from 'src/app/components/atoms/icons/icon/icons' import { EditorControl, EditorControlId, EditorFeatureId, separator } from '../editor.types' -import { createEditorFeature, getActiveListType } from '../helpers' +import { createEditorFeature, getActiveListType } from '../editor.helpers' const extensionDisplayName = { bulletList: 'Bullet list', diff --git a/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts index d5302818..8b5d0860 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts @@ -1,6 +1,6 @@ import { Extension } from '@tiptap/core' import { EditorControlId, EditorFeatureId } from '../editor.types' -import { createEditorFeature } from '../helpers' +import { createEditorFeature } from '../editor.helpers' export const blurFeature = createEditorFeature({ featureId: EditorFeatureId.Blur, diff --git a/client-v2/src/app/rich-text-editor/editor-features/custom-events.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/custom-events.feature.ts index 8eb96fdc..054c305c 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/custom-events.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/custom-events.feature.ts @@ -1,5 +1,5 @@ import { Extension } from '@tiptap/core' -import { createEditorFeature } from '../helpers' +import { createEditorFeature } from '../editor.helpers' import { EditorFeatureId } from '../editor.types' import { Observable, Subject } from 'rxjs' diff --git a/client-v2/src/app/rich-text-editor/editor-features/extensions/checklist-counter.extension.ts b/client-v2/src/app/rich-text-editor/editor-features/extensions/checklist-counter.extension.ts index 10577f5e..56d980da 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/extensions/checklist-counter.extension.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/extensions/checklist-counter.extension.ts @@ -3,7 +3,7 @@ import { Plugin, PluginKey } from '@tiptap/pm/state' import { Decoration, DecorationSet } from '@tiptap/pm/view' import { mediaQueries } from 'src/app/services/device.service' import { colors } from 'src/app/shared/colors' -import { ChecklistCount, countChecklistItems } from '../../helpers' +import { ChecklistCount, countChecklistItems } from '../../editor.helpers' import { EditorState } from 'prosemirror-state' const getCircumferenceOffset = (radius: number, progress: number) => { diff --git a/client-v2/src/app/rich-text-editor/editor-features/extensions/indentation.extension.ts b/client-v2/src/app/rich-text-editor/editor-features/extensions/indentation.extension.ts index 7650b0d2..e6cd3d13 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/extensions/indentation.extension.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/extensions/indentation.extension.ts @@ -1,6 +1,6 @@ import { Editor, Extension, Range } from '@tiptap/core' import CodeBlock from '@tiptap/extension-code-block' -import { getActiveListType } from '../../helpers' +import { getActiveListType } from '../../editor.helpers' interface IndentationOptions { /** Wether indentation logic should only be ran for code blocks. diff --git a/client-v2/src/app/rich-text-editor/editor-features/inline.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/inline.feature.ts index eda7980f..021ca728 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/inline.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/inline.feature.ts @@ -3,7 +3,7 @@ import { Code } from '@tiptap/extension-code' import { Italic } from '@tiptap/extension-italic' import { Strike } from '@tiptap/extension-strike' import { EditorControlId, EditorFeatureId } from '../editor.types' -import { createEditorFeature } from '../helpers' +import { createEditorFeature } from '../editor.helpers' export const inlineFeature = createEditorFeature({ featureId: EditorFeatureId.Inline, diff --git a/client-v2/src/app/rich-text-editor/editor-features/search-and-replace.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/search-and-replace.feature.ts index 43c4003c..5216c31e 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/search-and-replace.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/search-and-replace.feature.ts @@ -1,6 +1,6 @@ import { SearchAndReplace } from './extensions/search-and-replace.extension' import { EditorFeatureId } from '../editor.types' -import { createEditorFeature } from '../helpers' +import { createEditorFeature } from '../editor.helpers' export const searchAndReplaceFeature = createEditorFeature({ featureId: EditorFeatureId.SearchAndReplace, diff --git a/client-v2/src/app/rich-text-editor/features.ts b/client-v2/src/app/rich-text-editor/editor.features.ts similarity index 98% rename from client-v2/src/app/rich-text-editor/features.ts rename to client-v2/src/app/rich-text-editor/editor.features.ts index 88ad2687..51feb3d6 100644 --- a/client-v2/src/app/rich-text-editor/features.ts +++ b/client-v2/src/app/rich-text-editor/editor.features.ts @@ -12,7 +12,7 @@ import { markdownFeature } from './editor-features/markdown.feature' import { placeholderFeature } from './editor-features/placeholder.feature' import { typographyFeature } from './editor-features/typography.feature' import { EditorControlId, EditorFeature, EditorFeatureId, EditorLayoutItem, separator } from './editor.types' -import { createEditorFeature } from './helpers' +import { createEditorFeature } from './editor.helpers' import { searchAndReplaceFeature } from './editor-features/search-and-replace.feature' const moreMenuFeature = createEditorFeature({ diff --git a/client-v2/src/app/rich-text-editor/helpers.ts b/client-v2/src/app/rich-text-editor/editor.helpers.ts similarity index 100% rename from client-v2/src/app/rich-text-editor/helpers.ts rename to client-v2/src/app/rich-text-editor/editor.helpers.ts diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts index 97ec61e9..313e80a8 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { TipTapEditorToolbarComponent } from './tip-tap-editor-toolbar.component' -import { provideEditorFeatures, getDefaultEditorFeatures } from '../features' +import { provideEditorFeatures, getDefaultEditorFeatures } from '../editor.features' import { CdkMenuModule } from '@angular/cdk/menu' import { TipTapEditorComponent } from '../tip-tap-editor/tip-tap-editor.component' diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts index 3e48c456..cb477ace 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts @@ -28,7 +28,7 @@ import { ResolvedEditorControlItem, isSeparator, } from '../editor.types' -import { EDITOR_FEATURES_TOKEN } from '../features' +import { EDITOR_FEATURES_TOKEN } from '../editor.features' import { TipTapEditorComponent } from '../tip-tap-editor/tip-tap-editor.component' const resolveControlsLayout = ( @@ -44,7 +44,7 @@ const resolveControlsLayout = ( const controlId = typeof featureLayout == 'string' ? featureLayout : featureLayout.controlId const control = controls.find(control => control.controlId == controlId) - if (!control) throw new Error(`Control '${featureLayout}' not found`) + if (!control) throw new Error(`Control '${control}' not found`) if (typeof featureLayout != 'string' && 'dropdown' in featureLayout) { const resolvedControl = control as ResolvedEditorControl diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts index 7fe46eb6..03548f6b 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing' import { TipTapEditorComponent } from './tip-tap-editor.component' -import { getDefaultEditorFeatures, provideEditorFeatures } from '../features' +import { getDefaultEditorFeatures, provideEditorFeatures } from '../editor.features' describe('TipTapEditorComponent', () => { let component: TipTapEditorComponent diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts index 5b1849af..bac1873f 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts @@ -22,8 +22,8 @@ import { } from 'rxjs' import { AppEditor } from '../app-editor' import { EditorFeature } from '../editor.types' -import { EDITOR_FEATURES_TOKEN } from '../features' -import { isChecklistItem } from '../helpers' +import { EDITOR_FEATURES_TOKEN } from '../editor.features' +import { isChecklistItem } from '../editor.helpers' @UntilDestroy() @Component({ diff --git a/client-v2/src/app/rich-text-editor/types.ts b/client-v2/src/app/rich-text-editor/types.ts deleted file mode 100644 index 05177009..00000000 --- a/client-v2/src/app/rich-text-editor/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { ChainedCommands, Editor } from '@tiptap/core' -import { IconKey } from '../components/atoms/icons/icon/icons' -import { MenuItem } from '../dropdown/drop-down/drop-down.component' - -export interface EditorConfig { - basic?: boolean - bold?: boolean - italic?: boolean - strike?: boolean - link?: boolean - blocks?: boolean - headings?: boolean - lists?: boolean - taskLists?: boolean - code?: boolean - codeBlock?: boolean - rule?: boolean - undoRedo?: boolean -} - -export type ConfigKey = keyof EditorConfig | (keyof EditorConfig)[] - -export interface FormattingControlGroup { - configKey: ConfigKey - controls: FormattingControl[] -} -export type FormattingControlMenuItem = MenuItem<{ - chain: (autoFocus?: boolean) => ChainedCommands - editor: Editor - data: TDropdownData -}> - -export interface FormattingControl { - configKey: ConfigKey - title: string | ((editor: Editor) => string) - icon: IconKey | ((editor: Editor) => IconKey) - displayKeybinding?: string - registerKeybinding?: string - dropdown?: { - items: FormattingControlMenuItem[] - data?: TDropdownData - } - isActive?: (editor: Editor) => boolean - action?: (chain: ChainedCommands, editor: Editor) => ChainedCommands - testName: string -} From 7f359a78db489a95c5a09f495bc3538fd15cdc8f Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Thu, 16 Nov 2023 00:17:04 +0100 Subject: [PATCH 30/50] fix: fix task description expansion states --- .../components/organisms/task/task.component.ts | 16 +++++++--------- .../src/app/rich-text-editor/app-editor.ts | 17 +++++++++++++---- .../tip-tap-editor-toolbar.component.ts | 15 ++------------- .../tip-tap-editor/tip-tap-editor.component.ts | 12 ++++++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/client-v2/src/app/components/organisms/task/task.component.ts b/client-v2/src/app/components/organisms/task/task.component.ts index cc9c01ba..3cf2fd59 100644 --- a/client-v2/src/app/components/organisms/task/task.component.ts +++ b/client-v2/src/app/components/organisms/task/task.component.ts @@ -171,24 +171,25 @@ export class TaskComponent { @Output('descriptionChange') descriptionUpdateOnBlur$ = new EventEmitter() + // this is where the editor output lands descriptionUpdates$ = new Subject() descriptionState$ = merge( this.descriptionUpdates$, this.task$.pipe( map(task => task?.description), - filter(description => description !== undefined && description !== null), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - map(description => description!) + filter((description): description is string => description !== undefined && description !== null) ) ).pipe(share({ resetOnRefCountZero: true })) @Output() isDescriptionActive$ = new BehaviorSubject(false) descriptionBlur$ = new Subject() + // this is where explicit toggles land isDescriptionExpandedInput$ = new Subject<{ emit: boolean isExpanded: boolean }>() + // this is what controls the flow -> combines explicit and implicit toggles (blur events) isDescriptionExpandedBus$ = this.nodeData$.pipe( map(nodeData => ({ emit: false, isExpanded: nodeData?.isDescriptionExpanded ?? false })), mergeWith(this.isDescriptionExpandedInput$), @@ -201,13 +202,14 @@ export class TaskComponent { startWith({ emit: false, isExpanded: false }), shareReplay({ bufferSize: 1, refCount: true }) ) + // this drives the view isDescriptionExpanded$ = this.isDescriptionExpandedBus$.pipe( map(({ isExpanded }) => isExpanded), distinctUntilChanged(), shareReplay({ bufferSize: 1, refCount: true }) ) - - @Output('descriptionExpansionChange') isDescriptionExpandedOutput = this.isDescriptionExpandedBus$.pipe( + // this is what the parent component listens to + @Output('descriptionExpansionChange') isDescriptionExpandedOutput$ = this.isDescriptionExpandedBus$.pipe( filter(({ emit }) => emit), map(({ isExpanded }) => isExpanded), distinctUntilChanged() @@ -337,10 +339,6 @@ export class TaskComponent { this.descriptionEditor?.editor.commands.focus() }) } - resetDescription() { - // @TODO: this needs be updated - this.descriptionEditor?.editor.commands.setContent(this.task$.value?.description || '') - } toggleDescription() { this.isDescriptionExpanded$.pipe(first()).subscribe(isExpanded => { this.isDescriptionExpandedInput$.next({ emit: true, isExpanded: !isExpanded }) diff --git a/client-v2/src/app/rich-text-editor/app-editor.ts b/client-v2/src/app/rich-text-editor/app-editor.ts index 078c0db1..63c5da25 100644 --- a/client-v2/src/app/rich-text-editor/app-editor.ts +++ b/client-v2/src/app/rich-text-editor/app-editor.ts @@ -11,6 +11,7 @@ import { map, merge, share, + skipUntil, startWith, takeUntil, withLatestFrom, @@ -41,14 +42,16 @@ export class AppEditor extends Editor { ) } - updateRaw$ = this.getEventStream('update') + // @TODO: investigate why this is emitting upon editor initialisation only in the + // task description, and not in the full page description editors + private updateRaw$ = this.getEventStream('update') selectionUpdate$ = this.getEventStream('selectionUpdate') transaction$ = this.getEventStream('transaction') focus$ = this.getEventStream('focus') blur$ = this.getEventStream('blur') - update$ = this.updateRaw$.pipe( + private update$ = this.updateRaw$.pipe( map(({ editor }) => { const isEmpty = editor.isEmpty return { @@ -103,7 +106,13 @@ export class AppEditor extends Editor { return { unbind: () => this.unbind(), unbind$: this.unbind$.pipe(takeUntil(this.unbind$.pipe(delay(0)))), - updateRaw$: this.updateRaw$, + update$: this.update$.pipe( + // Skip all updates until we have populated the editor with the initial input. + // This is needed because the editor will emit an update event when it is created + // in some cases. (only happend in the task description editor so far, not in the full page description editor) + skipUntil(input$), + takeUntil(this.unbind$) + ), selectionUpdate$: this.selectionUpdate$.pipe(takeUntil(this.unbind$)), focus$: this.focus$.pipe(takeUntil(this.unbind$)), blur$: this.blur$.pipe(takeUntil(this.unbind$)), @@ -111,7 +120,7 @@ export class AppEditor extends Editor { } private unbindTrigger$ = new Subject() - unbind$ = merge(this.unbindTrigger$, this.destroy$).pipe(debugObserver('unbind$ and destroy$')) + unbind$ = merge(this.unbindTrigger$, this.destroy$) /** Cancel input binding */ unbind() { this.unbindTrigger$.next() diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts index cb477ace..d8ac32ba 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor-toolbar/tip-tap-editor-toolbar.component.ts @@ -3,21 +3,12 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, Inject, Input, ViewC import { Router } from '@angular/router' import { HotToastService } from '@ngneat/hot-toast' import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy' -import { - BehaviorSubject, - delay, - distinctUntilChanged, - map, - merge, - skip, - startWith, - switchMap, - throttleTime, -} from 'rxjs' +import { BehaviorSubject, distinctUntilChanged, map, merge, skip, startWith, switchMap, throttleTime } from 'rxjs' import { IconKey } from 'src/app/components/atoms/icons/icon/icons' import { MenuItem } from 'src/app/dropdown/drop-down/drop-down.component' import { DeviceService } from 'src/app/services/device.service' import '../editor-features/custom-events.feature' +import { EDITOR_FEATURES_TOKEN } from '../editor.features' import { EditorControl, EditorControlArgs, @@ -28,7 +19,6 @@ import { ResolvedEditorControlItem, isSeparator, } from '../editor.types' -import { EDITOR_FEATURES_TOKEN } from '../editor.features' import { TipTapEditorComponent } from '../tip-tap-editor/tip-tap-editor.component' const resolveControlsLayout = ( @@ -97,7 +87,6 @@ export class TipTapEditorToolbarComponent implements AfterViewInit { startWith(false), distinctUntilChanged(), skip(1), - delay(0), // delay to make sure the editor has time to update its focus state before we check it untilDestroyed(this) ).subscribe(toolbarHasFocus => { if (!toolbarHasFocus && !this.ttEditor.editor.view.hasFocus()) { diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts index bac1873f..3523cfa6 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts @@ -101,8 +101,6 @@ export class TipTapEditorComponent implements OnDestroy { shareReplay({ bufferSize: 1, refCount: true }) ) - @Output('update') update$ = this.editor.update$.pipe(untilDestroyed(this)) - private bindConfig$ = new Subject<{ input$: Observable; context: TContext }>() @Input() set bind(bound: { input$: Observable; context: TContext }) { this.bindConfig$.next(bound) @@ -112,6 +110,11 @@ export class TipTapEditorComponent implements OnDestroy { map(({ input$, context }) => this.bindEditor(input$, context)), share({ resetOnRefCountZero: true }) ) + + @Output('update') update$ = this.bound$.pipe( + switchMap(({ update$ }) => update$), + share({ resetOnRefCountZero: true }) + ) @Output('updateOnBlur') updateOnBlur$ = this.bound$.pipe( switchMap(({ updateOnBlur$ }) => updateOnBlur$), share({ resetOnRefCountZero: true }) @@ -119,11 +122,12 @@ export class TipTapEditorComponent implements OnDestroy { bindEditor(input$: Observable, context: TContext, searchTerm$?: Observable) { const searchTerm$_ = searchTerm$ ? searchTerm$.pipe(mergeWith(this.searchTerm$)) : this.searchTerm$ + const bound = this.editor.bindEditor(input$, searchTerm$_) return { - ...this.editor.bindEditor(input$, searchTerm$_), + ...bound, isActive$: this.isActive$, updateOnBlur$: merge(this.blur$, this.editor.unbind$).pipe( - withLatestFrom(this.update$, input$.pipe(startWith(null))), + withLatestFrom(bound.update$, input$.pipe(startWith(null))), map(([, { html, plainText }, lastInput]) => ({ html, plainText, lastInput, context })), filter(({ html, plainText, lastInput }) => html != lastInput || plainText != lastInput), distinctUntilKeyChanged('html'), From 83c1e9e5fb5df8be0b4e45bd760bbe41f9dce9c7 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Thu, 16 Nov 2023 22:44:36 +0100 Subject: [PATCH 31/50] fix: fix updating content in entity view editor with empty description --- .../entity-view/views/task-view/task-view.component.ts | 5 ++++- .../views/tasklist-view/tasklist-view.component.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts index a519fc76..f9339cc9 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts @@ -42,7 +42,10 @@ export class TaskViewComponent { options$ = this.viewData.options$ description$ = this.detail$.pipe( - map(detail => detail?.description), + map(detail => { + if (!detail) return null + return detail.description || '' + }), distinctUntilChanged() ) descriptionContext$ = this.taskEntity$.pipe( diff --git a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts index ecd3f623..ef3928be 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts @@ -44,7 +44,10 @@ export class TasklistViewComponent { options$ = this.viewData.options$ description$ = this.detail$.pipe( - map(detail => detail?.description), + map(detail => { + if (!detail) return null + return detail.description || '' + }), distinctUntilChanged() ) descriptionContext$ = this.listEntity$.pipe( From 2dacce4c9297797a6d42c7c98eee1d30616245f1 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Thu, 16 Nov 2023 23:14:36 +0100 Subject: [PATCH 32/50] ci: upload cy e2e videos --- .github/workflows/tests.yml | 7 +++++++ client-v2/cypress.config.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1459f4b5..44fc66f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -130,3 +130,10 @@ jobs: - run: npm run e2e:ci working-directory: ./client-v2 + + - name: Upload e2e test videos + uses: actions/upload-artifact@v3 + with: + name: e2e test videos + path: client-v2/cypress/videos + retention-days: 7 diff --git a/client-v2/cypress.config.ts b/client-v2/cypress.config.ts index a4e11c0d..df8af710 100644 --- a/client-v2/cypress.config.ts +++ b/client-v2/cypress.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ e2e: { baseUrl: 'http://localhost:4200', supportFile: './cypress/support/e2e.ts', + video: true, }, component: { @@ -12,5 +13,6 @@ export default defineConfig({ bundler: 'webpack', }, specPattern: '**/*.test.ts', + video: true, }, }) From 0a36cf22524d13ccc6d0c58af54b0959a399e2a0 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Thu, 16 Nov 2023 23:22:45 +0100 Subject: [PATCH 33/50] test: remove `.only()` from tests --- client-v2/cypress/e2e/workspace.cy.ts | 2 +- .../views/tasklist-view/tasklist-view.component.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 87857d1d..103ec4dd 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -63,7 +63,7 @@ describe('Workspace', () => { }) }) - describe.only('Entity page', () => { + describe('Entity page', () => { describe('Tasklist view', () => { it('can edit the entity name', () => { cy.get(testName('sidebar-create-new-list')).click() diff --git a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts index ac7a307d..3f80ed04 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts @@ -62,7 +62,7 @@ const entityFixture: EntityPreviewRecursive = { const entityDetailFixture: TasklistDetail = { description: null, createdAt: '', ownerId: '' } describe('TasklistViewComponent', () => { - it.only('renders the tasklist', () => { + it('renders the tasklist', () => { setupComponent({ entity$: new BehaviorSubject(entityFixture), detail$: new BehaviorSubject(entityDetailFixture), From cfbd4595abc8e88842fa2bd8b3cf427aee8b683c Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Thu, 16 Nov 2023 23:26:36 +0100 Subject: [PATCH 34/50] ci: always upload cy videos --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44fc66f4..07df0a0b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -133,6 +133,7 @@ jobs: - name: Upload e2e test videos uses: actions/upload-artifact@v3 + if: always() with: name: e2e test videos path: client-v2/cypress/videos From c24491d0c6d8b0ead48c42aafc1aa3bdce78b930 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 17 Nov 2023 10:18:10 +0100 Subject: [PATCH 35/50] ci: upload all cy artifacts --- .github/workflows/tests.yml | 20 +++++++++++++------- client-v2/cypress.config.ts | 8 ++++++++ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 07df0a0b..82d1e50e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,6 @@ jobs: cache: 'npm' cache-dependency-path: client-v2/package-lock.json - - run: cd client-v2 - run: npm ci working-directory: ./client-v2 @@ -35,6 +34,14 @@ jobs: - run: npm run comp:ci working-directory: ./client-v2 + - name: Upload component test artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: component test artifacts + path: client-v2/.cypress/component + retention-days: 7 + # - name: Upload coverage artifacts # uses: actions/upload-artifact@v3 # with: @@ -60,11 +67,10 @@ jobs: cache: 'npm' cache-dependency-path: server/package-lock.json - - run: cd server - run: npm ci working-directory: ./server - - run: npm run build --if-present + - run: npm run build working-directory: ./server - run: npm run lint @@ -73,7 +79,7 @@ jobs: - run: npm run typecheck-snapshot-scripts working-directory: ./server - - name: Setup Postgres + - name: Setup Postgres uses: Daniel-Marynicz/postgresql-action@master with: postgres_image_tag: 12-alpine @@ -131,10 +137,10 @@ jobs: - run: npm run e2e:ci working-directory: ./client-v2 - - name: Upload e2e test videos + - name: Upload e2e test artifacts uses: actions/upload-artifact@v3 if: always() with: - name: e2e test videos - path: client-v2/cypress/videos + name: e2e test artifacts + path: client-v2/.cypress/e2e retention-days: 7 diff --git a/client-v2/cypress.config.ts b/client-v2/cypress.config.ts index df8af710..5c0fb301 100644 --- a/client-v2/cypress.config.ts +++ b/client-v2/cypress.config.ts @@ -4,7 +4,11 @@ export default defineConfig({ e2e: { baseUrl: 'http://localhost:4200', supportFile: './cypress/support/e2e.ts', + video: true, + videosFolder: './.cypress/e2e/videos', + screenshotsFolder: './.cypress/e2e/screenshots', + downloadsFolder: './.cypress/e2e/downloads', }, component: { @@ -13,6 +17,10 @@ export default defineConfig({ bundler: 'webpack', }, specPattern: '**/*.test.ts', + video: true, + videosFolder: './.cypress/component/videos', + screenshotsFolder: './.cypress/component/screenshots', + downloadsFolder: './.cypress/component/downloads', }, }) From 2b2fe3e3decf180d874974556b56815b655a1404 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 17 Nov 2023 11:17:57 +0100 Subject: [PATCH 36/50] test: force click in options menu test --- client-v2/cypress/e2e/workspace.cy.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 103ec4dd..1958a056 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -53,9 +53,10 @@ describe('Workspace', () => { cy.get('[data-test-is-loading="false"]') // wait for loading to finish cy.get(testName('entity-tree-node')) .first() - .focus() + // .focus() .within(() => { - cy.get(testName('open-menu')).click() + // we need to force the click because the element might not be visible in ci (even after focusing the parent which should make it visible) + cy.get(testName('open-menu')).click({ force: true }) }) cy.get(testName('drop-down-menu')).should('exist') From 360126d24993f2eb05771bfdb05ff992ed88968f Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Fri, 17 Nov 2023 11:39:19 +0100 Subject: [PATCH 37/50] test: wait for loading to finish in enity description --- client-v2/cypress/e2e/workspace.cy.ts | 14 ++++ .../editable-entity-title.component.html | 72 ++++++++++--------- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 1958a056..822142be 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -80,7 +80,14 @@ describe('Workspace', () => { const description = 'The testing entity description' + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + cy.get(testName('add-description')).click() + cy.get(testName('add-description')).should('not.exist') + cy.get(testName('description-editor')).focused().type(description).blur() cy.wait('@updateList').its('response.statusCode').should('equal', 200) // we currently don't have any other way to verify if updating the description has succeeded // maybe it is not a bad idea to assert on the request, but we could take this a step further and verify that the db record was updated @@ -141,7 +148,14 @@ describe('Workspace', () => { it('can edit the description', () => { const description = 'The testing entity description' + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + cy.get(testName('add-description')).click() + cy.get(testName('add-description')).should('not.exist') + cy.get(testName('description-editor')).focused().type(description).blur() cy.wait('@updateTask').its('response.statusCode').should('equal', 200) }) diff --git a/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html b/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html index 5fa92a67..e5fdc304 100644 --- a/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html +++ b/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html @@ -1,36 +1,44 @@ -
    - - - - + + +
    + + + + + +
    + -
    - +
    - - - +
    - - -
    +
    From 302be7b914ecf5c1b933e90ed42712ea6ca49f80 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sat, 18 Nov 2023 18:41:39 +0100 Subject: [PATCH 38/50] refactor: use template binding for entity-description editor and fix tests + clean up stuff --- .../editable-entity-title.component.html | 79 +++++++++---------- .../entity-description.component.html | 9 ++- .../entity-description.component.ts | 50 +++++------- .../views/task-view/task-view.component.ts | 2 +- .../tasklist-view.component.test.ts | 15 +++- .../tasklist-view/tasklist-view.component.ts | 2 +- .../tip-tap-editor.component.ts | 3 +- 7 files changed, 81 insertions(+), 79 deletions(-) diff --git a/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html b/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html index e5fdc304..5fc881cd 100644 --- a/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html +++ b/client-v2/src/app/components/molecules/editable-entity-heading/editable-entity-title.component.html @@ -1,44 +1,43 @@ - - -
    - - - - - -
    - +
    + + + + + - - +
    +
    + - + + +
    - + + +
    diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html index 22b58cc4..32b1917d 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html @@ -10,4 +10,11 @@
    - + diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts index 7342e832..9caf47b4 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts @@ -1,16 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, Output, ViewChild } from '@angular/core' import { UntilDestroy } from '@ngneat/until-destroy' -import { - Observable, - Subject, - delay, - distinctUntilKeyChanged, - filter, - map, - mergeMap, - shareReplay, - switchMap, -} from 'rxjs' +import { Observable, ReplaySubject, Subject, delay, distinctUntilKeyChanged, filter, map } from 'rxjs' import { defaultDesktopEditorLayout, getDefaultEditorFeatures, @@ -36,36 +26,34 @@ export interface DescriptionContext { export class EntityDescriptionComponent { constructor(private deviceService: DeviceService) {} - @ViewChild(TipTapEditorComponent) ttEditor!: TipTapEditorComponent + @ViewChild(TipTapEditorComponent) private ttEditor!: TipTapEditorComponent + focus() { + this.ttEditor.editor.commands.focus() + } toolbarLayout = defaultDesktopEditorLayout toolbarLayout$ = this.deviceService.isTouchPrimary$.pipe(map(getDefaultEditorLayout)) - private context$ = new Subject() + private context$ = new ReplaySubject() @Input() set context(context: DescriptionContext | null) { if (context) this.context$.next(context) } - private editorBound$ = this.context$.pipe( + bindConfig$ = this.context$.pipe( distinctUntilKeyChanged('id'), - map(({ id, description$ }) => this.ttEditor.bindEditor(description$, id)), - shareReplay({ bufferSize: 1, refCount: true }) + map(context => ({ input$: context.description$, context: context.id })) ) - @Output() isActive$ = this.editorBound$.pipe(switchMap(({ isActive$ }) => isActive$)) - @Output('update') update$ = this.editorBound$.pipe( - mergeMap(({ updateOnBlur$ }) => - updateOnBlur$.pipe(map(({ html, context }) => ({ id: context, description: html }))) - ) - ) - @Output('blur') blur$ = this.editorBound$.pipe( - mergeMap(({ blur$ }) => - blur$.pipe( - delay(0), - map(() => this.ttEditor.editor.view.hasFocus()), - filter(hasFocus => !hasFocus), - map(() => null) - ) - ) + @Output('isActive') isActive$ = new ReplaySubject() + + updateInput$ = new Subject<{ html: string; context: string }>() + @Output('update') update$ = this.updateInput$.pipe(map(({ html, context }) => ({ id: context, description: html }))) + + blurInput$ = new Subject() + @Output('blur') blur$ = this.blurInput$.pipe( + delay(0), + map(() => this.ttEditor.editor.view.hasFocus()), + filter(hasFocus => !hasFocus), + map(() => null) ) } diff --git a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts index f9339cc9..f073c28b 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/task-view/task-view.component.ts @@ -69,7 +69,7 @@ export class TaskViewComponent { @ViewChild(EntityDescriptionComponent) entityDescription?: EntityDescriptionComponent focusDescription() { moveToMacroQueue(() => { - this.entityDescription?.ttEditor.editor.commands.focus() + this.entityDescription?.focus() }) } diff --git a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts index 3f80ed04..d3ae2bc2 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.test.ts @@ -18,6 +18,11 @@ import { RxModule } from 'src/app/rx/rx.module' import { DropdownModule } from 'src/app/dropdown/dropdown.module' import { RichTextEditorModule } from 'src/app/rich-text-editor/rich-text-editor.module' import { ToolbarComponent } from 'src/app/components/molecules/toolbar/toolbar.component' +import { IconsModule } from 'src/app/components/atoms/icons/icons.module' +import { PageProgressBarComponent } from 'src/app/components/molecules/page-progress-bar/page-progress-bar.component' +import { TooltipModule } from 'src/app/tooltip/tooltip.module' +import { IntersectionDirective } from 'src/app/directives/intersection.directive' +import { LoadingStateService } from 'src/app/services/loading-state.service' const setupComponent = (viewData: EntityViewData, taskTreeMap: TaskTreeMap = {}) => { const store = { @@ -31,7 +36,7 @@ const setupComponent = (viewData: EntityViewData, taskTreeMap: T } cy.mount(` `, { componentProperties: {}, - imports: [CdkMenuModule, RxModule, DropdownModule, RichTextEditorModule], + imports: [CdkMenuModule, IconsModule, RxModule, DropdownModule, RichTextEditorModule, TooltipModule], declarations: [ TasklistViewComponent, MutationDirective, @@ -42,12 +47,18 @@ const setupComponent = (viewData: EntityViewData, taskTreeMap: T EntityDescriptionComponent, HighlightPipe, ToolbarComponent, + PageProgressBarComponent, + IntersectionDirective, ], providers: [ { provide: ENTITY_VIEW_DATA, useValue: viewData }, store, { provide: EntityViewComponent, useValue: { progress$: new BehaviorSubject(null) } }, actionsMock, + { + provide: LoadingStateService, + useValue: { getEntityLoadingState: () => of(false) }, + }, ], }) } @@ -86,8 +97,6 @@ describe('TasklistViewComponent', () => { cy.get(testName('description-editor')).should('not.be.hidden') cy.get(testName('description-editor')).contains(description) }) - - it.skip('can update the description') }) describe('Children', () => { diff --git a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts index ef3928be..4ab407dd 100644 --- a/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/views/tasklist-view/tasklist-view.component.ts @@ -71,7 +71,7 @@ export class TasklistViewComponent { @ViewChild(EntityDescriptionComponent) entityDescription?: EntityDescriptionComponent focusDescription() { moveToMacroQueue(() => { - this.entityDescription?.ttEditor.editor.commands.focus() + this.entityDescription?.focus() }) } diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts index 3523cfa6..79062886 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts @@ -120,12 +120,11 @@ export class TipTapEditorComponent implements OnDestroy { share({ resetOnRefCountZero: true }) ) - bindEditor(input$: Observable, context: TContext, searchTerm$?: Observable) { + private bindEditor(input$: Observable, context: TContext, searchTerm$?: Observable) { const searchTerm$_ = searchTerm$ ? searchTerm$.pipe(mergeWith(this.searchTerm$)) : this.searchTerm$ const bound = this.editor.bindEditor(input$, searchTerm$_) return { ...bound, - isActive$: this.isActive$, updateOnBlur$: merge(this.blur$, this.editor.unbind$).pipe( withLatestFrom(bound.update$, input$.pipe(startWith(null))), map(([, { html, plainText }, lastInput]) => ({ html, plainText, lastInput, context })), From 8fbb779a1f14004bc84815526e63bc512d50a7bd Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sat, 18 Nov 2023 19:17:36 +0100 Subject: [PATCH 39/50] style: add todo comments --- client-v2/cypress/e2e/auth.cy.ts | 1 + .../components/organisms/entity-view/entity-view.component.ts | 3 ++- server/src/app.service.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client-v2/cypress/e2e/auth.cy.ts b/client-v2/cypress/e2e/auth.cy.ts index ddddd9c0..e4235607 100644 --- a/client-v2/cypress/e2e/auth.cy.ts +++ b/client-v2/cypress/e2e/auth.cy.ts @@ -25,6 +25,7 @@ describe('Authentication', () => { describe('Login', () => { it('can login', () => { + // @TODO: there's sth wrong here, fix it // this should not be necessary, but somehow a previous `signup` call from within `beforeEach` prevents the following signup cy.clearDb() diff --git a/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts b/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts index 47863020..86e42911 100644 --- a/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts +++ b/client-v2/src/app/components/organisms/entity-view/entity-view.component.ts @@ -95,7 +95,8 @@ export class EntityViewComponent { // eslint-disable-next-line @typescript-eslint/no-explicit-any entityViewData: EntityViewData = { - // @TODO: (high prio): get rid of the delay here + // @TODO: (high prio): get rid of the macro queue here as its causing + // a huge delay of ~170ms on rendering the view entity$: this.entity$.pipe(delay(0)), // move to macro queue detail$: this.entityDetail$, options$: this.entityOptionsItems$, diff --git a/server/src/app.service.ts b/server/src/app.service.ts index fd0ed1d8..c339c2d6 100644 --- a/server/src/app.service.ts +++ b/server/src/app.service.ts @@ -15,6 +15,7 @@ export class AppService { return 'Hello World!' } + // @TODO: this should not reside in application code, lets move it to a CLI/npm script async clearDb() { if (this.configService.get('TESTING_ENV') != 'true') throw new ForbiddenException() From 03189b94025e7bdfd99d308f2e57bb5f1bd24c17 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sat, 25 Nov 2023 14:32:30 +0100 Subject: [PATCH 40/50] fix: dont emit `undefined` as binding config --- .../entity-description/entity-description.component.html | 2 +- .../tip-tap-editor/tip-tap-editor.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html index 32b1917d..741ebc5f 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.html +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.html @@ -12,7 +12,7 @@ implements OnDestroy { ) private bindConfig$ = new Subject<{ input$: Observable; context: TContext }>() - @Input() set bind(bound: { input$: Observable; context: TContext }) { - this.bindConfig$.next(bound) + @Input() set bind(bindConfig: { input$: Observable; context: TContext } | undefined) { + if (bindConfig) this.bindConfig$.next(bindConfig) } private bound$ = this.bindConfig$.pipe( untilDestroyed(this), From 16baaa94432f711d5303787a40d10e17571fe015 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sat, 25 Nov 2023 15:14:05 +0100 Subject: [PATCH 41/50] refactor: clean up tt editor --- .../{app-editor.ts => tip-tap-editor.ts} | 2 +- .../tip-tap-editor.component.ts | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) rename client-v2/src/app/rich-text-editor/{app-editor.ts => tip-tap-editor.ts} (99%) diff --git a/client-v2/src/app/rich-text-editor/app-editor.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor.ts similarity index 99% rename from client-v2/src/app/rich-text-editor/app-editor.ts rename to client-v2/src/app/rich-text-editor/tip-tap-editor.ts index 63c5da25..01bd628d 100644 --- a/client-v2/src/app/rich-text-editor/app-editor.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor.ts @@ -25,7 +25,7 @@ interface EditorStorage { [key: string]: unknown } -export class AppEditor extends Editor { +export class TipTapEditor extends Editor { destroy$ = fromEventPattern( handler => this.on('destroy', handler), handler => this.off('destroy', handler) diff --git a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts index edbb351d..86001b57 100644 --- a/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts +++ b/client-v2/src/app/rich-text-editor/tip-tap-editor/tip-tap-editor.component.ts @@ -20,7 +20,7 @@ import { timer, withLatestFrom, } from 'rxjs' -import { AppEditor } from '../app-editor' +import { TipTapEditor } from '../tip-tap-editor' import { EditorFeature } from '../editor.types' import { EDITOR_FEATURES_TOKEN } from '../editor.features' import { isChecklistItem } from '../editor.helpers' @@ -55,7 +55,7 @@ export class TipTapEditorComponent implements OnDestroy { if (searchTerm !== null) this.searchTerm$.next(searchTerm) } - editor = new AppEditor({ + editor = new TipTapEditor({ editable: this.editable, extensions: this.features.flatMap(feature => feature.extensions), }) @@ -65,7 +65,10 @@ export class TipTapEditorComponent implements OnDestroy { this.focusStateInput$.next(isFocused) } - @Output() focus$ = this.editor.focus$.pipe(map(({ event }) => event)) + @Output('focus') focus$ = this.editor.focus$.pipe( + map(({ event }) => event), + mergeWith(this.focusStateInput$.pipe(filter((isFocused): isFocused is true => isFocused))) + ) @Output('blur') blur$ = this.editor.blur$.pipe( filter(({ event }) => { const clickedElem = event.relatedTarget as HTMLElement | undefined @@ -107,7 +110,10 @@ export class TipTapEditorComponent implements OnDestroy { } private bound$ = this.bindConfig$.pipe( untilDestroyed(this), - map(({ input$, context }) => this.bindEditor(input$, context)), + map(({ input$, context }) => { + const bound = this.editor.bindEditor(input$, this.searchTerm$) + return { input$, context, ...bound } + }), share({ resetOnRefCountZero: true }) ) @@ -115,18 +121,11 @@ export class TipTapEditorComponent implements OnDestroy { switchMap(({ update$ }) => update$), share({ resetOnRefCountZero: true }) ) - @Output('updateOnBlur') updateOnBlur$ = this.bound$.pipe( - switchMap(({ updateOnBlur$ }) => updateOnBlur$), - share({ resetOnRefCountZero: true }) - ) - private bindEditor(input$: Observable, context: TContext, searchTerm$?: Observable) { - const searchTerm$_ = searchTerm$ ? searchTerm$.pipe(mergeWith(this.searchTerm$)) : this.searchTerm$ - const bound = this.editor.bindEditor(input$, searchTerm$_) - return { - ...bound, - updateOnBlur$: merge(this.blur$, this.editor.unbind$).pipe( - withLatestFrom(bound.update$, input$.pipe(startWith(null))), + @Output('updateOnBlur') updateOnBlur$ = this.bound$.pipe( + switchMap(({ input$, unbind$, context }) => + merge(this.blur$, unbind$).pipe( + withLatestFrom(this.update$, input$.pipe(startWith(null))), map(([, { html, plainText }, lastInput]) => ({ html, plainText, lastInput, context })), filter(({ html, plainText, lastInput }) => html != lastInput || plainText != lastInput), distinctUntilKeyChanged('html'), @@ -135,7 +134,8 @@ export class TipTapEditorComponent implements OnDestroy { // to make sure all transactions are dispatched. takeUntil(this.editor.unbind$.pipe(delay(0))), share({ resetOnRefCountZero: true }) - ), - } - } + ) + ), + share({ resetOnRefCountZero: true }) + ) } From 2ab9bbfd021af5b4baafda90060dc79a925612a4 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 00:03:19 +0100 Subject: [PATCH 42/50] test: extend workspace e2e test suite --- client-v2/cypress/e2e/workspace.cy.ts | 92 +++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 822142be..72fd0003 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -74,7 +74,7 @@ describe('Workspace', () => { cy.get(testName('entity-tree-node')).should('contain.text', entityName) }) - it('can edit the description', () => { + it('can add a description', () => { cy.get(testName('sidebar-create-new-list')).click() cy.get(testName('editable-entity-name')) @@ -93,6 +93,35 @@ describe('Workspace', () => { // maybe it is not a bad idea to assert on the request, but we could take this a step further and verify that the db record was updated }) + it('can update the description', () => { + cy.get(testName('sidebar-create-new-list')).click() + cy.get(testName('editable-entity-name')) + + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + + cy.get(testName('add-description')).click() + cy.get(testName('add-description')).should('not.exist') + + const description = 'The testing entity description' + cy.get(testName('description-editor')).focused().type(description).blur() + cy.wait('@updateList').its('response.statusCode').should('equal', 200) + + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + + cy.get(testName('add-description')).should('not.exist') + + const descriptionUpdate = ' - With updates' + cy.get(testName('description-editor')).click() + cy.get(testName('description-editor')).focused().type(descriptionUpdate).blur() + cy.wait('@updateList').its('response.statusCode').should('equal', 200) + }) + it('can add children', () => { cy.get(testName('sidebar-create-new-list')).click() cy.get(testName('editable-entity-name')) @@ -145,7 +174,7 @@ describe('Workspace', () => { // @TODO: assert that task-tree has changed }) - it('can edit the description', () => { + it('can add a description', () => { const description = 'The testing entity description' // wait for loading to finish @@ -160,6 +189,35 @@ describe('Workspace', () => { cy.wait('@updateTask').its('response.statusCode').should('equal', 200) }) + it('can update the description', () => { + cy.get(testName('sidebar-create-new-list')).click() + cy.get(testName('editable-entity-name')) + + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + + cy.get(testName('add-description')).click() + cy.get(testName('add-description')).should('not.exist') + + const description = 'The testing entity description' + cy.get(testName('description-editor')).focused().type(description).blur() + cy.wait('@updateList').its('response.statusCode').should('equal', 200) + + // wait for loading to finish + cy.get(testName('entity-name-container')).within(() => { + cy.get('[data-test-is-loading="false"]') + }) + + cy.get(testName('add-description')).should('not.exist') + + const descriptionUpdate = ' - With updates' + cy.get(testName('description-editor')).click() + cy.get(testName('description-editor')).focused().type(descriptionUpdate).blur() + cy.wait('@updateList').its('response.statusCode').should('equal', 200) + }) + it('can add tasks', () => { cy.get(testName('create-subtask')).click() cy.get(testName('task-tree-node')).should('exist') @@ -215,9 +273,9 @@ describe('Workspace', () => { cy.get(testName('task-menu-button')).click() }) - // task menu + // task menu - create new subtask cy.get(testName('drop-down-menu')).within(() => { - cy.contains(/Subtask/) + cy.contains(/Subtask/i) .closest(testName('menu-item')) .click() }) @@ -225,6 +283,32 @@ describe('Workspace', () => { cy.get(testName('task-tree-node')).should('have.length', 2) cy.get(testName('task-tree-node')).last().should('have.attr', 'data-test-node-level', 1) }) + + describe('Task description', () => { + it('can add a description', () => { + cy.get(testName('task-tree-node')).within(() => { + cy.get(testName('task-menu-button')).click() + }) + + // task menu - Add description + cy.get(testName('drop-down-menu')).within(() => { + cy.contains(/Description/i) + .closest(testName('menu-item')) + .click() + }) + + const descriptionCy = cy.get(testName('task-tree-node')).focused() + descriptionCy.type('This is a description').blur() + cy.wait('@updateTask').its('response.statusCode').should('equal', 200) + }) + + it.skip('can update a description', () => { + // @TODO: + }) + it.skip('can open the task as page from the description toolbar', () => { + // @TODO: + }) + }) }) }) }) From 8303bac84aa5feba007cc225d93530172f64cdf9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 00:03:51 +0100 Subject: [PATCH 43/50] feat: update close button label, add todo comments --- .../src/app/rich-text-editor/editor-features/blur.feature.ts | 2 +- client-v2/src/app/rich-text-editor/editor.features.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts index 8b5d0860..8f4ff098 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/blur.feature.ts @@ -18,7 +18,7 @@ export const blurFeature = createEditorFeature({ { controlId: EditorControlId.Blur, icon: 'close', - title: 'Save & Close', + title: 'Done', keybinding: 'Mod-Enter', action: ({ editor }) => { editor.commands.focus() diff --git a/client-v2/src/app/rich-text-editor/editor.features.ts b/client-v2/src/app/rich-text-editor/editor.features.ts index 51feb3d6..e56e2e45 100644 --- a/client-v2/src/app/rich-text-editor/editor.features.ts +++ b/client-v2/src/app/rich-text-editor/editor.features.ts @@ -43,7 +43,7 @@ export const defaultFeatureMap = { customEventsFeature, checklistCounterFeature: checklistCounterFeature(), searchAndReplaceFeature, -} +} // @TODO: satisfies Record const defaultFeatureMask = Object.fromEntries(Object.keys(defaultFeatureMap).map(key => [key, true])) // @TODO: can be made more generic -> `getEditorFeatureGroup(featureMap)(featureMask) => EditorFeature[]` From f7ba36310b303482c7eab254b3a9817c41f42525 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 00:58:14 +0100 Subject: [PATCH 44/50] fix: fix mofile toolbar issue & remove debugLog --- .../app/components/organisms/task/task.component.html | 11 ++--------- client-v2/src/app/rich-text-editor/tip-tap-editor.ts | 2 -- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/client-v2/src/app/components/organisms/task/task.component.html b/client-v2/src/app/components/organisms/task/task.component.html index 29177678..c59dbe6e 100644 --- a/client-v2/src/app/components/organisms/task/task.component.html +++ b/client-v2/src/app/components/organisms/task/task.component.html @@ -179,16 +179,9 @@ - - -
    -
    - - - - +
    + ({ From 5175eedb2d8559e788311da480652e11c5dbaca9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 01:45:33 +0100 Subject: [PATCH 45/50] test: skip flaky tests --- client-v2/cypress/e2e/auth.cy.ts | 2 +- client-v2/cypress/e2e/workspace.cy.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client-v2/cypress/e2e/auth.cy.ts b/client-v2/cypress/e2e/auth.cy.ts index e4235607..3b6cc487 100644 --- a/client-v2/cypress/e2e/auth.cy.ts +++ b/client-v2/cypress/e2e/auth.cy.ts @@ -24,7 +24,7 @@ describe('Authentication', () => { }) describe('Login', () => { - it('can login', () => { + it.skip('can login', () => { // @TODO: there's sth wrong here, fix it // this should not be necessary, but somehow a previous `signup` call from within `beforeEach` prevents the following signup cy.clearDb() diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 72fd0003..640b50b2 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -189,7 +189,8 @@ describe('Workspace', () => { cy.wait('@updateTask').its('response.statusCode').should('equal', 200) }) - it('can update the description', () => { + // seems to be a flaky test + it.skip('can update the description', () => { cy.get(testName('sidebar-create-new-list')).click() cy.get(testName('editable-entity-name')) From 97965032f7179d25613333ddd9bcc7abc72daa70 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 02:59:45 +0100 Subject: [PATCH 46/50] style: apply suggestions from code review --- .github/workflows/tests.yml | 4 ++-- client-v2/cypress/e2e/workspace.cy.ts | 1 - .../entity-description/entity-description.component.ts | 1 + .../app/rich-text-editor/editor-features/blocks.feature.ts | 2 -- .../extensions/search-and-replace.extension.ts | 3 --- 5 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 82d1e50e..9e09b658 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,7 +36,7 @@ jobs: - name: Upload component test artifacts uses: actions/upload-artifact@v3 - if: always() + if: failure() with: name: component test artifacts path: client-v2/.cypress/component @@ -139,7 +139,7 @@ jobs: - name: Upload e2e test artifacts uses: actions/upload-artifact@v3 - if: always() + if: failure() with: name: e2e test artifacts path: client-v2/.cypress/e2e diff --git a/client-v2/cypress/e2e/workspace.cy.ts b/client-v2/cypress/e2e/workspace.cy.ts index 640b50b2..d44b3351 100644 --- a/client-v2/cypress/e2e/workspace.cy.ts +++ b/client-v2/cypress/e2e/workspace.cy.ts @@ -53,7 +53,6 @@ describe('Workspace', () => { cy.get('[data-test-is-loading="false"]') // wait for loading to finish cy.get(testName('entity-tree-node')) .first() - // .focus() .within(() => { // we need to force the click because the element might not be visible in ci (even after focusing the parent which should make it visible) cy.get(testName('open-menu')).click({ force: true }) diff --git a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts index 9caf47b4..ca75777a 100644 --- a/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts +++ b/client-v2/src/app/components/molecules/entity-description/entity-description.component.ts @@ -51,6 +51,7 @@ export class EntityDescriptionComponent { blurInput$ = new Subject() @Output('blur') blur$ = this.blurInput$.pipe( + // @TODO: get rid of this delay code smell delay(0), map(() => this.ttEditor.editor.view.hasFocus()), filter(hasFocus => !hasFocus), diff --git a/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts b/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts index 9d882135..4c797d93 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/blocks.feature.ts @@ -159,8 +159,6 @@ export const blocksFeature = createEditorFeature({ return 'editor.paragraph' }, - // isActive: ({ editor }) => - // editor.isActive('heading') || !!getActiveListType(editor) || editor.isActive('codeBlock'), fixedWith: '2.75rem', }, // @TODO: currently only limited to 4 levels by FontAwesome only having 4 heading icons diff --git a/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts b/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts index 33af978c..6c4add02 100644 --- a/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts +++ b/client-v2/src/app/rich-text-editor/editor-features/extensions/search-and-replace.extension.ts @@ -34,9 +34,6 @@ interface Result { } interface SearchOptions { - // searchTerm: string - // replaceTerm: string - // results: Result[] searchResultClass: string caseSensitive: boolean disableRegex: boolean From 7bf2ce55fd02d9912bc66a0f324d53ceff8ad9d4 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 12:43:03 +0100 Subject: [PATCH 47/50] test: wait for db to be cleared --- client-v2/cypress/e2e/auth.cy.ts | 6 ++++-- client-v2/cypress/support/commands.ts | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/client-v2/cypress/e2e/auth.cy.ts b/client-v2/cypress/e2e/auth.cy.ts index 3b6cc487..59a0db0e 100644 --- a/client-v2/cypress/e2e/auth.cy.ts +++ b/client-v2/cypress/e2e/auth.cy.ts @@ -24,10 +24,10 @@ describe('Authentication', () => { }) describe('Login', () => { - it.skip('can login', () => { + it('can login', () => { // @TODO: there's sth wrong here, fix it // this should not be necessary, but somehow a previous `signup` call from within `beforeEach` prevents the following signup - cy.clearDb() + // cy.clearDb() cy.signup() cy.clearLocalStorage() @@ -36,6 +36,8 @@ describe('Authentication', () => { cy.get(testName('user-menu-toggle')).invoke('attr', 'data-logged-in').should('eq', 'true') }) it('cannot login with the wrong email', () => { + // cy.clearDb() + cy.signup() cy.clearLocalStorage() diff --git a/client-v2/cypress/support/commands.ts b/client-v2/cypress/support/commands.ts index 19de9822..bb89f071 100644 --- a/client-v2/cypress/support/commands.ts +++ b/client-v2/cypress/support/commands.ts @@ -23,7 +23,11 @@ function setLocalStorage(itemName: string, itemValue: string) { Cypress.Commands.add('setLocalStorage', setLocalStorage) function clearDb() { - cy.request('http://localhost:3001/clear-db').as('clearDb') + cy.request('http://localhost:3001/clear-db') + .as('clearDb') + .then(res => { + expect(res.status).to.eq(200) + }) } Cypress.Commands.add('clearDb', clearDb) @@ -36,6 +40,8 @@ function signup() { cy.request({ method: 'POST', url: 'http://localhost:3001/auth/signup', body: signupDto }) .as('shadow-signup') .then(res => { + expect(res.status).to.eq(201) + // token needs to be JSON parsable cy.setLocalStorage('rockket-auth-token', `${JSON.stringify(res.body.user.authToken)}`) }) From 121f84c6fe0d7dddb230368f58c5909481b11d22 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 16:21:20 +0100 Subject: [PATCH 48/50] build: integrate cypress cloud --- .gitignore | 2 ++ client-v2/angular.json | 3 ++- client-v2/cypress.config.ts | 2 ++ client-v2/cypress/e2e/auth.cy.ts | 2 -- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index fd40aa3b..7e6eb829 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules .idea # Local Netlify folder .netlify +# Cypress artifacts folder +.cypress \ No newline at end of file diff --git a/client-v2/angular.json b/client-v2/angular.json index 128d22fe..538eb826 100644 --- a/client-v2/angular.json +++ b/client-v2/angular.json @@ -139,7 +139,8 @@ "e2e-ci": { "builder": "@cypress/schematic:cypress", "options": { - "devServerTarget": "client-v2:serve" + "devServerTarget": "client-v2:serve", + "record": true }, "configurations": { "production": { diff --git a/client-v2/cypress.config.ts b/client-v2/cypress.config.ts index 5c0fb301..a7cc6715 100644 --- a/client-v2/cypress.config.ts +++ b/client-v2/cypress.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from 'cypress' export default defineConfig({ + projectId: 'zwatkd', + retries: 2, e2e: { baseUrl: 'http://localhost:4200', supportFile: './cypress/support/e2e.ts', diff --git a/client-v2/cypress/e2e/auth.cy.ts b/client-v2/cypress/e2e/auth.cy.ts index 59a0db0e..82f0dda0 100644 --- a/client-v2/cypress/e2e/auth.cy.ts +++ b/client-v2/cypress/e2e/auth.cy.ts @@ -36,8 +36,6 @@ describe('Authentication', () => { cy.get(testName('user-menu-toggle')).invoke('attr', 'data-logged-in').should('eq', 'true') }) it('cannot login with the wrong email', () => { - // cy.clearDb() - cy.signup() cy.clearLocalStorage() From 159fed6fd5cb7b6a9863555afd84d4ae9e3dca41 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 16:22:34 +0100 Subject: [PATCH 49/50] build: update to cypress 13 --- client-v2/package-lock.json | 368 ++++++++++++++++++++++++++---------- client-v2/package.json | 2 +- 2 files changed, 274 insertions(+), 96 deletions(-) diff --git a/client-v2/package-lock.json b/client-v2/package-lock.json index f708fc23..97689283 100644 --- a/client-v2/package-lock.json +++ b/client-v2/package-lock.json @@ -53,7 +53,7 @@ "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.59.5", "autoprefixer": "^10.4.14", - "cypress": "latest", + "cypress": "^13.5.1", "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", @@ -2522,9 +2522,10 @@ } }, "node_modules/@cypress/request": { - "version": "2.88.10", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -2539,9 +2540,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -2550,11 +2551,18 @@ } }, "node_modules/@cypress/request/node_modules/qs": { - "version": "6.5.3", + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "dev": true, - "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/@cypress/schematic": { @@ -3991,10 +3999,13 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "node_modules/@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", - "dev": true + "version": "18.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", + "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/object.omit": { "version": "3.0.0", @@ -5021,16 +5032,18 @@ }, "node_modules/asn1": { "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, - "license": "MIT", "dependencies": { "safer-buffer": "~2.1.0" } }, "node_modules/assert-plus": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.8" } @@ -5091,16 +5104,18 @@ }, "node_modules/aws-sign2": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/aws4": { - "version": "1.11.0", - "dev": true, - "license": "MIT" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true }, "node_modules/axios": { "version": "0.27.2", @@ -5253,8 +5268,9 @@ }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "tweetnacl": "^0.14.3" } @@ -5594,8 +5610,9 @@ }, "node_modules/caseless": { "version": "0.12.0", - "dev": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true }, "node_modules/chalk": { "version": "2.4.2", @@ -6324,15 +6341,15 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.10.0.tgz", - "integrity": "sha512-Y0wPc221xKKW1/4iAFCphkrG2jNR4MjOne3iGn4mcuCaE7Y5EtXL83N8BzRsAht7GYfWVjJ/UeTqEdDKHz39HQ==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz", + "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -6365,9 +6382,10 @@ "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", @@ -6377,14 +6395,9 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^14.0.0 || ^16.0.0 || >=18.0.0" + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "14.18.28", - "dev": true, - "license": "MIT" - }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", "dev": true, @@ -6568,8 +6581,9 @@ }, "node_modules/dashdash": { "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, - "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" }, @@ -6853,8 +6867,9 @@ }, "node_modules/ecc-jsbn": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, - "license": "MIT", "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -7797,11 +7812,12 @@ }, "node_modules/extsprintf": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true, "engines": [ "node >=0.6.0" - ], - "license": "MIT" + ] }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -8004,16 +8020,18 @@ }, "node_modules/forever-agent": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "*" } }, "node_modules/form-data": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -8188,8 +8206,9 @@ }, "node_modules/getpass": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, - "license": "MIT", "dependencies": { "assert-plus": "^1.0.0" } @@ -8491,8 +8510,9 @@ }, "node_modules/http-signature": { "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, - "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", @@ -8957,8 +8977,9 @@ }, "node_modules/is-typedarray": { "version": "1.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", @@ -9016,8 +9037,9 @@ }, "node_modules/isstream": { "version": "0.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", @@ -9188,8 +9210,9 @@ }, "node_modules/jsbn": { "version": "0.1.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true }, "node_modules/jsesc": { "version": "2.5.2", @@ -9209,8 +9232,9 @@ }, "node_modules/json-schema": { "version": "0.4.0", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -9223,8 +9247,9 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "dev": true, - "license": "ISC" + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true }, "node_modules/json5": { "version": "2.2.2", @@ -9260,11 +9285,12 @@ }, "node_modules/jsprim": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT", "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -11155,8 +11181,9 @@ }, "node_modules/performance-now": { "version": "2.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true }, "node_modules/picocolors": { "version": "1.0.0", @@ -12033,6 +12060,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "dev": true, @@ -12307,8 +12343,9 @@ }, "node_modules/psl": { "version": "1.9.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true }, "node_modules/pump": { "version": "3.0.0", @@ -12348,6 +12385,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -12893,9 +12936,10 @@ } }, "node_modules/semver": { - "version": "7.3.8", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -13379,9 +13423,10 @@ "license": "BSD-3-Clause" }, "node_modules/sshpk": { - "version": "1.17.0", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, - "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -13900,15 +13945,27 @@ } }, "node_modules/tough-cookie": { - "version": "2.5.0", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=0.8" + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" } }, "node_modules/tree-kill": { @@ -14009,8 +14066,9 @@ }, "node_modules/tunnel-agent": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, - "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -14020,8 +14078,9 @@ }, "node_modules/tweetnacl": { "version": "0.14.5", - "dev": true, - "license": "Unlicense" + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true }, "node_modules/type-check": { "version": "0.4.0", @@ -14099,6 +14158,12 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "dev": true, @@ -14207,6 +14272,16 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" @@ -14262,11 +14337,12 @@ }, "node_modules/verror": { "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "engines": [ "node >=0.6.0" ], - "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -14275,8 +14351,9 @@ }, "node_modules/verror/node_modules/core-util-is": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "node_modules/void-elements": { "version": "2.0.1", @@ -16245,7 +16322,9 @@ "requires": {} }, "@cypress/request": { - "version": "2.88.10", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -16261,16 +16340,21 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.5.2", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, "dependencies": { "qs": { - "version": "6.5.3", - "dev": true + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } } } }, @@ -17263,10 +17347,13 @@ "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==" }, "@types/node": { - "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", - "dev": true + "version": "18.18.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", + "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } }, "@types/object.omit": { "version": "3.0.0", @@ -17947,6 +18034,8 @@ }, "asn1": { "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -17954,6 +18043,8 @@ }, "assert-plus": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true }, "astral-regex": { @@ -17984,10 +18075,14 @@ }, "aws-sign2": { "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true }, "aws4": { - "version": "1.11.0", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, "axios": { @@ -18091,6 +18186,8 @@ }, "bcrypt-pbkdf": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "requires": { "tweetnacl": "^0.14.3" @@ -18309,6 +18406,8 @@ }, "caseless": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, "chalk": { @@ -18762,14 +18861,14 @@ "dev": true }, "cypress": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.10.0.tgz", - "integrity": "sha512-Y0wPc221xKKW1/4iAFCphkrG2jNR4MjOne3iGn4mcuCaE7Y5EtXL83N8BzRsAht7GYfWVjJ/UeTqEdDKHz39HQ==", + "version": "13.5.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz", + "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==", "dev": true, "requires": { - "@cypress/request": "^2.88.10", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -18802,19 +18901,16 @@ "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.3.2", + "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.1", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "dependencies": { - "@types/node": { - "version": "14.18.28", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "dev": true, @@ -18932,6 +19028,8 @@ }, "dashdash": { "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -19110,6 +19208,8 @@ }, "ecc-jsbn": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "requires": { "jsbn": "~0.1.0", @@ -19727,6 +19827,8 @@ }, "extsprintf": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true }, "fast-deep-equal": { @@ -19862,10 +19964,14 @@ }, "forever-agent": { "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", "dev": true }, "form-data": { "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -19976,6 +20082,8 @@ }, "getpass": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -20190,6 +20298,8 @@ }, "http-signature": { "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -20465,6 +20575,8 @@ }, "is-typedarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true }, "is-unicode-supported": { @@ -20498,6 +20610,8 @@ }, "isstream": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "dev": true }, "istanbul-lib-coverage": { @@ -20621,6 +20735,8 @@ }, "jsbn": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", "dev": true }, "jsesc": { @@ -20633,6 +20749,8 @@ }, "json-schema": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, "json-schema-traverse": { @@ -20644,6 +20762,8 @@ }, "json-stringify-safe": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "json5": { @@ -20668,6 +20788,8 @@ }, "jsprim": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", "dev": true, "requires": { "assert-plus": "1.0.0", @@ -21898,6 +22020,8 @@ }, "performance-now": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", "dev": true }, "picocolors": { @@ -22288,6 +22412,12 @@ "version": "2.0.1", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "dev": true @@ -22532,6 +22662,8 @@ }, "psl": { "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", "dev": true }, "pump": { @@ -22556,6 +22688,12 @@ "side-channel": "^1.0.4" } }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3" }, @@ -22890,7 +23028,9 @@ } }, "semver": { - "version": "7.3.8", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -23233,7 +23373,9 @@ "dev": true }, "sshpk": { - "version": "1.17.0", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "requires": { "asn1": "~0.2.3", @@ -23552,11 +23694,23 @@ "dev": true }, "tough-cookie": { - "version": "2.5.0", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dev": true, "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } } }, "tree-kill": { @@ -23621,6 +23775,8 @@ }, "tunnel-agent": { "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -23628,6 +23784,8 @@ }, "tweetnacl": { "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "dev": true }, "type-check": { @@ -23670,6 +23828,12 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "dev": true @@ -23730,6 +23894,16 @@ "punycode": "^2.1.0" } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "util-deprecate": { "version": "1.0.2" }, @@ -23766,6 +23940,8 @@ }, "verror": { "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -23775,6 +23951,8 @@ "dependencies": { "core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", "dev": true } } diff --git a/client-v2/package.json b/client-v2/package.json index 1a81e702..9503abd9 100644 --- a/client-v2/package.json +++ b/client-v2/package.json @@ -69,7 +69,7 @@ "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.59.5", "autoprefixer": "^10.4.14", - "cypress": "latest", + "cypress": "^13.5.1", "eslint": "^8.41.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", From f97018d3c314e0ecae00d54139a8d7cbf77f75d9 Mon Sep 17 00:00:00 2001 From: Floyd Haremsa Date: Sun, 26 Nov 2023 16:23:58 +0100 Subject: [PATCH 50/50] build: add cypress record key to ci runs --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9e09b658..4f89a6ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,6 +33,8 @@ jobs: - run: npm run comp:ci working-directory: ./client-v2 + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - name: Upload component test artifacts uses: actions/upload-artifact@v3 @@ -136,6 +138,8 @@ jobs: - run: npm run e2e:ci working-directory: ./client-v2 + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - name: Upload e2e test artifacts uses: actions/upload-artifact@v3