From 8469d48c207b9b00e83ac9d7a2f0e539ac126814 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Fri, 15 Mar 2024 13:06:07 -0400 Subject: [PATCH 01/26] feat: setup custom plugin --- .../dot-wysiwyg-field.component.html | 8 ++++++- .../dot-wysiwyg-field.component.ts | 23 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html index a12bdf282cbf..164285071a03 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html @@ -1 +1,7 @@ - + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 7d01bc07171d..277028de619d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -1,10 +1,17 @@ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; +import { Editor, TinyMCE } from 'tinymce'; import { ChangeDetectionStrategy, Component, Input, inject, signal } from '@angular/core'; import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +declare global { + interface Window { + tinymce: TinyMCE; + } +} + @Component({ selector: 'dot-wysiwyg-field', standalone: true, @@ -24,9 +31,21 @@ export class DotWYSIWYGFieldComponent { @Input() field!: DotCMSContentTypeField; protected readonly plugins = signal( - 'advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' + 'dotAddImage advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' ); + protected readonly toolbar = signal( - 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor dotimageclipboard backcolor emoticons' + 'buttondotAddImage paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor backcolor emoticons' ); + + setup(_editor: Editor) { + window.tinymce.PluginManager.add('dotAddImage', function (editor: Editor) { + editor.ui.registry.addButton('buttondotAddImage', { + text: 'Add Image', + onAction: function () { + /* */ + } + }); + }); + } } From 8b892445502c9106fe5efb5e0d1036a981a9329e Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Mon, 18 Mar 2024 15:40:24 -0400 Subject: [PATCH 02/26] chore: move DotAssetSearchComponent to ui lib --- .../dot-block-editor.component.stories.ts | 5 +- .../asset-form/asset-form.module.ts | 21 +- .../store/dot-asset-search.store.spec.ts | 268 ---------- .../bubble-link-form.component.ts | 5 +- .../suggestions/suggestions.component.ts | 3 +- .../src/lib/shared/services/index.ts | 2 - .../dot-wysiwyg-field.component.html | 4 +- .../dot-wysiwyg-field.component.ts | 36 +- core-web/libs/ui/src/index.ts | 3 + .../dot-asset-card-list.component.html | 9 +- .../dot-asset-card-list.component.scss | 0 .../dot-asset-card-list.component.spec.ts | 0 .../dot-asset-card-list.component.ts | 22 +- .../dot-asset-card-skeleton.component.html | 0 .../dot-asset-card-skeleton.component.scss | 0 .../dot-asset-card-skeleton.component.spec.ts | 0 .../dot-asset-card-skeleton.component.ts | 5 + .../dot-asset-card.component.html | 3 +- .../dot-asset-card.component.scss | 0 .../dot-asset-card.component.spec.ts | 0 .../dot-asset-card.component.ts | 20 +- .../dot-asset-search.component.html | 3 +- .../dot-asset-search.component.scss | 0 .../dot-asset-search.component.spec.ts | 0 .../dot-asset-search.component.ts | 15 + .../store/dot-asset-search.store.spec.ts | 489 ++++++++++++++++++ .../store/dot-asset-search.store.ts | 15 +- .../dot-content-search.service.ts} | 2 +- .../dot-language/dot-language.service.spec.ts | 16 + .../dot-language/dot-language.service.ts | 74 +++ 30 files changed, 687 insertions(+), 333 deletions(-) delete mode 100644 core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.spec.ts rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html (83%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.scss (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts (57%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.scss (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts (68%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html (86%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.scss (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts (59%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/dot-asset-search.component.html (87%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/dot-asset-search.component.scss (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/dot-asset-search.component.spec.ts (100%) rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/dot-asset-search.component.ts (74%) create mode 100644 core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts rename core-web/libs/{block-editor/src/lib/extensions/asset-form => ui/src/lib}/components/dot-asset-search/store/dot-asset-search.store.ts (93%) rename core-web/libs/{block-editor/src/lib/shared/services/search/search.service.ts => ui/src/lib/services/dot-content-search/dot-content-search.service.ts} (96%) create mode 100644 core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts create mode 100644 core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts diff --git a/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts b/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts index 07597f122cb1..0625cc332d3b 100644 --- a/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts +++ b/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts @@ -11,6 +11,7 @@ import { OrderListModule } from 'primeng/orderlist'; import { debounceTime, delay, tap } from 'rxjs/operators'; import { DotMessageService, DotPropertiesService } from '@dotcms/data-access'; +import { DotContentSearchService, DotLanguageService } from '@dotcms/ui'; import { DotBlockEditorComponent } from './dot-block-editor.component'; @@ -26,10 +27,8 @@ import { ASSET_MOCK, CONTENTLETS_MOCK, DotAiService, - DotLanguageService, DotUploadFileService, FileStatus, - SearchService, SuggestionsComponent, SuggestionsService } from '../../shared'; @@ -170,7 +169,7 @@ export const Primary = () => ({ } }, { - provide: SearchService, + provide: DotContentSearchService, useValue: { get(params) { const query = params.query.match(new RegExp(/(?<=:)(.*?)(?=\*)/))[0]; diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts index b9e250980478..c759a7c9bb88 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts +++ b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts @@ -2,13 +2,9 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { DotSpinnerModule } from '@dotcms/ui'; +import { DotAssetSearchComponent, DotSpinnerModule } from '@dotcms/ui'; import { AssetFormComponent } from './asset-form.component'; -import { DotAssetCardComponent } from './components/dot-asset-search/components/dot-asset-card/dot-asset-card.component'; -import { DotAssetCardListComponent } from './components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component'; -import { DotAssetCardSkeletonComponent } from './components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component'; -import { DotAssetSearchComponent } from './components/dot-asset-search/dot-asset-search.component'; import { DotExternalAssetComponent } from './components/dot-external-asset/dot-external-asset.component'; import { DotAssetPreviewComponent } from './components/dot-upload-asset/components/dot-asset-preview/dot-asset-preview.component'; import { DotUploadAssetComponent } from './components/dot-upload-asset/dot-upload-asset.component'; @@ -17,18 +13,21 @@ import { DotUploadFileService } from '../../shared'; import { PrimengModule } from '../../shared/primeng.module'; @NgModule({ - imports: [CommonModule, FormsModule, ReactiveFormsModule, DotSpinnerModule, PrimengModule], + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + DotSpinnerModule, + PrimengModule, + DotAssetSearchComponent + ], declarations: [ AssetFormComponent, - DotAssetCardListComponent, - DotAssetCardComponent, - DotAssetCardSkeletonComponent, DotExternalAssetComponent, - DotAssetSearchComponent, DotUploadAssetComponent, DotAssetPreviewComponent ], providers: [DotUploadFileService], - exports: [AssetFormComponent, DotAssetSearchComponent] + exports: [AssetFormComponent] }) export class AssetFormModule {} diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.spec.ts b/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.spec.ts deleted file mode 100644 index 51acaf7e8f24..000000000000 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.spec.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { of } from 'rxjs'; - -import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; - -import { DotAssetSearchStore } from './dot-asset-search.store'; - -import { - DotLanguageService, - ESOrderDirection, - IMAGE_CONTENTLETS_MOCK, - SearchService -} from '../../../../../shared'; - -const INITIAL_STATE = { contentlets: [], loading: true, preventScroll: false }; -const LanguageMock = { - 1: { - country: 'United States', - countryCode: 'US', - defaultLanguage: true, - id: 1, - language: 'English', - languageCode: 'en' - }, - 2: { - country: 'Espana', - countryCode: 'ES', - defaultLanguage: false, - id: 2, - language: 'Espanol', - languageCode: 'es' - } -}; - -const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 4).map((contentlet) => ({ - ...contentlet, - language: 'en-US' -})); - -describe('DotAssetSearchStore', () => { - let service: DotAssetSearchStore; - let searchService: SearchService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - DotAssetSearchStore, - { - provide: SearchService, - useValue: { - get: () => of() - } - }, - { - provide: DotLanguageService, - useValue: { - getLanguages: jest.fn().mockReturnValue(of(LanguageMock)) - } - } - ] - }); - - service = TestBed.inject(DotAssetSearchStore); - }); - - test('should have inital state', (done) => { - service.vm$.subscribe((res) => { - expect(res).toEqual(INITIAL_STATE); - done(); - }); - }); - - describe('Updaters', () => { - test('should update contentlets', (done) => { - const contentlet = [IMAGE_CONTENTLETS_MOCK[0], IMAGE_CONTENTLETS_MOCK[1]]; - service.updateContentlets(contentlet); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - ...INITIAL_STATE, - contentlets: contentlet - }); - done(); - }); - }); - - test('should update LanguageId', (done) => { - service.updatelanguageId(2); - - service.state$.subscribe((res) => { - expect(res.languageId).toEqual(2); - done(); - }); - }); - - test('should update Loading', (done) => { - service.updateLoading(false); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - ...INITIAL_STATE, - loading: false - }); - done(); - }); - }); - - test('should prevent Scroll', (done) => { - service.updatePreventScroll(true); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - ...INITIAL_STATE, - preventScroll: true - }); - done(); - }); - }); - - test('should Search', (done) => { - const search = 'Image'; - service.updateSearch(search); - - service.state$.subscribe((res) => { - expect(res.search).toEqual(search); - done(); - }); - }); - }); - - describe('Effects', () => { - beforeEach(() => { - searchService = TestBed.inject(SearchService); - }); - - test('should search contentlets based on a search query', (done) => { - const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); - // Spies - const loadingMock = jest.spyOn(service, 'updateLoading'); - jest.spyOn(service, 'updateSearch'); - jest.spyOn(service, 'updateContentlets'); - jest.spyOn(searchService, 'get').mockReturnValue( - of({ jsonObjectView: { contentlets } }) - ); - - const query = 'image'; - - service.searchContentlet(query); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets - }); - - done(); - }); - - expect(searchService.get).toHaveBeenCalledWith({ - query: `+catchall:${query}* +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset: 0 - }); - - // First -> the value is true because we start the search - // Second -> the value is false because we finish the search - expect(loadingMock.mock.calls).toEqual([[true], [false]]); - expect(service.updateContentlets).toHaveBeenCalledWith(contentlets); - expect(service.updateSearch).toHaveBeenCalledWith(query); - }); - - test('should not add "*" when the search has a "-" ', (done) => { - const contentlets = CONTENTLETS_MOCK_WITH_LANG; - jest.spyOn(searchService, 'get').mockReturnValue( - of({ jsonObjectView: { contentlets } }) - ); - - const query = 'hola-'; - - service.searchContentlet(query); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets - }); - - done(); - }); - - expect(searchService.get).toHaveBeenCalledWith({ - query: `+catchall:${query} +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset: 0 - }); - }); - - test('should load the next batch of contentlets based on the offset', (done) => { - const contentlets_p1 = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); - const contentlets_p2 = [...CONTENTLETS_MOCK_WITH_LANG].splice(2, 2); - - // Spies - jest.spyOn(service, 'updateSearch'); - jest.spyOn(service, 'updateContentlets'); - jest.spyOn(searchService, 'get').mockReturnValue( - of({ jsonObjectView: { contentlets: contentlets_p2 } }) - ); - - const query = 'image'; - const offset = 2; - - service.updateContentlets(contentlets_p1); - service.updateSearch(query); - service.nextBatch(offset); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets: [...contentlets_p1, ...contentlets_p2] - }); - - done(); - }); - - expect(searchService.get).toHaveBeenCalledWith({ - query: ` +catchall:${query}* title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset - }); - expect(service.updateContentlets).toHaveBeenCalledWith(contentlets_p1); - }); - - test('should set preventScroll to true when backend date is coming empty', (done) => { - const contentlets = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); - - // Spies - jest.spyOn(service, 'updateSearch'); - jest.spyOn(service, 'updateContentlets'); - jest.spyOn(searchService, 'get').mockReturnValue( - of({ jsonObjectView: { contentlets: [] } }) - ); - - const query = 'image'; - const offset = 2; - - service.updateContentlets(contentlets); - service.updateSearch(query); - service.nextBatch(offset); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: true, - loading: false, - contentlets - }); - - done(); - }); - }); - }); -}); diff --git a/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts b/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts index 665b4db9dc42..5fb47590f3ce 100644 --- a/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts +++ b/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts @@ -13,11 +13,12 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { debounceTime, take } from 'rxjs/operators'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { DotLanguageService, Languages } from '@dotcms/ui'; import { SuggestionPageComponent } from './components/suggestion-page/suggestion-page.component'; -import { Languages, SuggestionsCommandProps } from '../../shared'; -import { DotLanguageService, SuggestionsService } from '../../shared/services'; +import { SuggestionsCommandProps } from '../../shared'; +import { SuggestionsService } from '../../shared/services'; import { DEFAULT_LANG_ID } from '../bubble-menu/models'; import { isValidURL } from '../bubble-menu/utils'; diff --git a/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts b/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts index 101286b359da..e0a134326c34 100644 --- a/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts +++ b/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts @@ -14,9 +14,10 @@ import { MenuItem } from 'primeng/api'; import { map, take } from 'rxjs/operators'; import { DotCMSContentlet, DotCMSContentType } from '@dotcms/dotcms-models'; +import { DotLanguageService, Languages } from '@dotcms/ui'; import { DEFAULT_LANG_ID } from '../../../extensions'; -import { DotLanguageService, Languages, SuggestionsService } from '../../services'; +import { SuggestionsService } from '../../services'; import { SuggestionListComponent } from '../suggestion-list/suggestion-list.component'; export interface SuggestionsCommandProps { payload?: DotCMSContentlet; diff --git a/core-web/libs/block-editor/src/lib/shared/services/index.ts b/core-web/libs/block-editor/src/lib/shared/services/index.ts index 57be502f8713..1ef572d95954 100644 --- a/core-web/libs/block-editor/src/lib/shared/services/index.ts +++ b/core-web/libs/block-editor/src/lib/shared/services/index.ts @@ -1,6 +1,4 @@ -export * from './dot-language/dot-language.service'; export * from './dot-marketing-config/dot-marketing-config.service'; export * from './dot-upload-file/dot-upload-file.service'; -export * from './search/search.service'; export * from './suggestions/suggestions.service'; export * from './dot-ai/dot-ai.service'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html index 164285071a03..cae5d952ede0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html @@ -2,6 +2,4 @@ [formControlName]="field.variable" [plugins]="plugins()" [toolbar]="toolbar()" - [init]="{ - setup: setup - }" /> + [init]="init" /> diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 277028de619d..374579ff5bc2 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -1,10 +1,21 @@ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { Editor, TinyMCE } from 'tinymce'; -import { ChangeDetectionStrategy, Component, Input, inject, signal } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + inject, + signal, + NgZone +} from '@angular/core'; import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { DialogService } from 'primeng/dynamicdialog'; + import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; +import { DotAssetSearchComponent } from '@dotcms/ui'; declare global { interface Window { @@ -19,7 +30,7 @@ declare global { templateUrl: './dot-wysiwyg-field.component.html', styleUrl: './dot-wysiwyg-field.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [{ provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }], + providers: [DialogService, { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }], viewProviders: [ { provide: ControlContainer, @@ -29,6 +40,14 @@ declare global { }) export class DotWYSIWYGFieldComponent { @Input() field!: DotCMSContentTypeField; + public init = { + setup: (editor) => { + this.setup(editor); + } + }; + private readonly dialogService: DialogService = inject(DialogService); + private readonly ngZone: NgZone = inject(NgZone); + private readonly cd: ChangeDetectorRef = inject(ChangeDetectorRef); protected readonly plugins = signal( 'dotAddImage advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' @@ -39,11 +58,18 @@ export class DotWYSIWYGFieldComponent { ); setup(_editor: Editor) { - window.tinymce.PluginManager.add('dotAddImage', function (editor: Editor) { + window.tinymce.PluginManager.add('dotAddImage', (editor: Editor) => { editor.ui.registry.addButton('buttondotAddImage', { text: 'Add Image', - onAction: function () { - /* */ + onAction: () => { + this.ngZone.run(() => { + this.dialogService.open(DotAssetSearchComponent, { + header: 'Add Image', + width: '70%', + height: '70%' + }); + this.cd.detectChanges(); + }); } }); }); diff --git a/core-web/libs/ui/src/index.ts b/core-web/libs/ui/src/index.ts index 6a5db733fa09..b915a526e593 100644 --- a/core-web/libs/ui/src/index.ts +++ b/core-web/libs/ui/src/index.ts @@ -2,6 +2,7 @@ export * from './lib/dot-icon/dot-icon.module'; export * from './lib/dot-spinner/dot-spinner.module'; // Components +export * from './lib/components/dot-asset-search/dot-asset-search.component'; export * from './lib/components/dot-binary-option-selector/dot-binary-option-selector.component'; export * from './lib/dot-spinner/dot-spinner.component'; export * from './lib/components/dot-drop-zone/dot-drop-zone.component'; @@ -33,6 +34,8 @@ export * from './lib/directives/dot-avatar/dot-avatar.directive'; // Services export * from './lib/services/clipboard/ClipboardUtil'; export * from './lib/services/dot-copy-content-modal/dot-copy-content-modal.service'; +export * from './lib/services/dot-content-search/dot-content-search.service'; +export * from './lib/services/dot-language/dot-language.service'; // Pipes export * from './lib/pipes/dot-relative-date/dot-relative-date.pipe'; diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html similarity index 83% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html index 7bccac6cce94..df5314dbf7f2 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.html @@ -4,20 +4,17 @@ [items]="rows" [lazy]="true" (onScrollIndexChange)="onScrollIndexChange($event)" - scrollHeight="20rem" -> + scrollHeight="20rem">
+ (click)="selectedItem.emit(contentlet[0])"> + (click)="selectedItem.emit(contentlet[1])">
diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.scss b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.scss similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.scss rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.scss diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts similarity index 57% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts index c7acbea8e64f..ca585b5a8579 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts @@ -1,13 +1,28 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + Output, + inject +} from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; -import { sanitizeUrl, squarePlus } from '../../../../../../shared'; +import { DotAssetCardComponent } from '../dot-asset-card/dot-asset-card.component'; +import { DotAssetCardSkeletonComponent } from '../dot-asset-card-skeleton/dot-asset-card-skeleton.component'; + +const squarePlus = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDEiIHZpZXdCb3g9IjAgMCA0MCA0MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuMDc2MTcgOC41NTcxMkgwLjA5NTIxNDhWMzYuNDIzOEMwLjA5NTIxNDggMzguNjEzMyAxLjg4NjY0IDQwLjQwNDcgNC4wNzYxNyA0MC40MDQ3SDMxLjk0MjhWMzYuNDIzOEg0LjA3NjE3VjguNTU3MTJaTTM1LjkyMzggMC41OTUyMTVIMTIuMDM4MUM5Ljg0ODU1IDAuNTk1MjE1IDguMDU3MTIgMi4zODY2NCA4LjA1NzEyIDQuNTc2MTdWMjguNDYxOUM4LjA1NzEyIDMwLjY1MTQgOS44NDg1NSAzMi40NDI4IDEyLjAzODEgMzIuNDQyOEgzNS45MjM4QzM4LjExMzMgMzIuNDQyOCAzOS45MDQ3IDMwLjY1MTQgMzkuOTA0NyAyOC40NjE5VjQuNTc2MTdDMzkuOTA0NyAyLjM4NjY0IDM4LjExMzMgMC41OTUyMTUgMzUuOTIzOCAwLjU5NTIxNVpNMzUuOTIzOCAyOC40NjE5SDEyLjAzODFWNC41NzYxN0gzNS45MjM4VjI4LjQ2MTlaTTIxLjk5MDUgMjQuNDgwOUgyNS45NzE0VjE4LjUwOTVIMzEuOTQyOFYxNC41Mjg1SDI1Ljk3MTRWOC41NTcxMkgyMS45OTA1VjE0LjUyODVIMTYuMDE5VjE4LjUwOTVIMjEuOTkwNVYyNC40ODA5WiIgZmlsbD0iIzU3NkJFOCIvPgo8L3N2Zz4K'; @Component({ selector: 'dot-asset-card-list', templateUrl: './dot-asset-card-list.component.html', styleUrls: ['./dot-asset-card-list.component.scss'], + standalone: true, + imports: [CommonModule, DotAssetCardComponent, DotAssetCardSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush }) export class DotAssetCardListComponent { @@ -21,8 +36,9 @@ export class DotAssetCardListComponent { this._itemRows = this.createRowItem(value); } + private domSanitizer: DomSanitizer = inject(DomSanitizer); public loadingItems = [null, null, null]; - public icon = sanitizeUrl(squarePlus); + public icon = this.domSanitizer.bypassSecurityTrustHtml(squarePlus); private _itemRows: DotCMSContentlet[][] = []; private _offset = 0; diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.scss b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.scss similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.scss rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.scss diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts similarity index 68% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts index 051c5047cd6a..3799d0dbdf28 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.ts @@ -1,9 +1,14 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { CardModule } from 'primeng/card'; +import { SkeletonModule } from 'primeng/skeleton'; + @Component({ selector: 'dot-asset-card-skeleton', templateUrl: './dot-asset-card-skeleton.component.html', styleUrls: ['./dot-asset-card-skeleton.component.scss'], + standalone: true, + imports: [CardModule, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush }) export class DotAssetCardSkeletonComponent {} diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html similarity index 86% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html index ec6862087dc9..3ddf0af24e64 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html @@ -5,8 +5,7 @@ [contentlet]="contentlet" [cover]="false" [iconSize]="'72px'" - [showVideoThumbnail]="showVideoThumbnail" - > + [showVideoThumbnail]="true">

{{ contentlet?.fileName || contentlet?.title }}

diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.scss b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.scss similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.scss rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.scss diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts similarity index 59% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts index 5aa775502d23..b20353fd7b2b 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts @@ -1,28 +1,20 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { DotCMSContentlet, EDITOR_MARKETING_KEYS } from '@dotcms/dotcms-models'; +import { CardModule } from 'primeng/card'; -import { DotMarketingConfigService } from '../../../../../../shared'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; @Component({ selector: 'dot-asset-card', templateUrl: './dot-asset-card.component.html', styleUrls: ['./dot-asset-card.component.scss'], + standalone: true, + imports: [CardModule], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotAssetCardComponent implements OnInit { - showVideoThumbnail = true; - +export class DotAssetCardComponent { @Input() contentlet: DotCMSContentlet; - constructor(private dotMarketingConfigService: DotMarketingConfigService) {} - - ngOnInit() { - this.showVideoThumbnail = this.dotMarketingConfigService.getProperty( - EDITOR_MARKETING_KEYS.SHOW_VIDEO_THUMBNAIL - ); - } - /** * Return the contentlet Thumbanil based in the inode * diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html similarity index 87% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.html rename to core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html index 0d9951168c89..c05c6885023c 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html @@ -12,6 +12,5 @@ [done]="vm.preventScroll" [loading]="vm.loading" (selectedItem)="addAsset.emit($event)" - (nextBatch)="offset$.next($event)" - > + (nextBatch)="offset$.next($event)"> diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.scss b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.scss similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.scss rename to core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.scss diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts similarity index 100% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.spec.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts similarity index 74% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts index 0b57629d4d1f..2252f39562d3 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/dot-asset-search.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts @@ -1,5 +1,6 @@ import { BehaviorSubject, fromEvent, Subject } from 'rxjs'; +import { CommonModule } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, @@ -13,11 +14,16 @@ import { ViewChild } from '@angular/core'; +import { InputTextModule } from 'primeng/inputtext'; + import { debounceTime, skip, takeUntil, throttleTime } from 'rxjs/operators'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; // services +import { DotAssetCardComponent } from './components/dot-asset-card/dot-asset-card.component'; +import { DotAssetCardListComponent } from './components/dot-asset-card-list/dot-asset-card-list.component'; +import { DotAssetCardSkeletonComponent } from './components/dot-asset-card-skeleton/dot-asset-card-skeleton.component'; import { DotAssetSearchStore } from './store/dot-asset-search.store'; @Component({ @@ -25,6 +31,15 @@ import { DotAssetSearchStore } from './store/dot-asset-search.store'; templateUrl: './dot-asset-search.component.html', styleUrls: ['./dot-asset-search.component.scss'], providers: [DotAssetSearchStore], + standalone: true, + imports: [ + DotAssetCardComponent, + DotAssetCardListComponent, + DotAssetCardSkeletonComponent, + DotAssetCardListComponent, + InputTextModule, + CommonModule + ], changeDetection: ChangeDetectionStrategy.OnPush }) export class DotAssetSearchComponent implements OnInit, OnDestroy, AfterViewInit { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts new file mode 100644 index 000000000000..f9a9e7e16619 --- /dev/null +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts @@ -0,0 +1,489 @@ +import { of } from 'rxjs'; + +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { ESOrderDirection } from '@dotcms/data-access'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; + +import { DotAssetSearchStore } from './dot-asset-search.store'; + +import { DotLanguageService } from '../../../services/dot-language/dot-language.service'; +import { DotContentSearchService } from '../../../services/search/dot-content-search.service'; + +const EMPTY_CONTENTLET: DotCMSContentlet = { + inode: '14dd5ad9-55ae-42a8-a5a7-e259b6d0901a', + variantId: 'DEFAULT', + locked: false, + stInode: 'd5ea385d-32ee-4f35-8172-d37f58d9cd7a', + contentType: 'Image', + height: 4000, + identifier: '93ca45e0-06d2-4eef-be1d-79bd6bf0fc99', + hasTitleImage: true, + sortOrder: 0, + hostName: 'demo.dotcms.com', + extension: 'jpg', + isContent: true, + baseType: 'FILEASSETS', + archived: false, + working: true, + live: true, + isContentlet: true, + languageId: 1, + titleImage: 'fileAsset', + hasLiveVersion: true, + deleted: false, + folder: '', + host: '', + modDate: '', + modUser: '', + modUserName: '', + owner: '', + title: '', + url: '', + contentTypeIcon: 'assessment', + __icon__: 'Icon' +}; + +const EMPTY_IMAGE_CONTENTLET = { + mimeType: 'image/jpeg', + type: 'file_asset', + fileAssetVersion: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + fileAsset: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + ...EMPTY_CONTENTLET +}; + +export const IMAGE_CONTENTLETS_MOCK = [ + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '1 rain-forest-view.jpg', + name: 'rain-forest-view.jpg', + description: 'rain-forest-view', + title: 'Rain-forest-view.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileAsset: + 'https://previews.123rf.com/images/rglinsky/rglinsky1201/rglinsky120100188/12336990-vertical-de-la-imagen-orientada-a-la-famosa-torre-eiffel-en-par%C3%ADs-francia-.jpg', + fileName: '2 Foto8.jpg', + name: 'Foto8.jpg', + description: 'Foto8', + title: 'Foto8.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileAsset: + 'https://www.freesvgdownload.com/wp-content/uploads/2021/12/It-Takes-a-Big-Heart-To-Help.jpg', + fileName: '3 first-chair.jpg', + name: 'first-chair.jpg', + description: 'Stay at one of our resorts and get early hours with our first chair program.', + title: 'First to the Top' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileAsset: 'https://interactive-examples.mdn.mozilla.net/media/examples/plumeria.jpg', + fileName: '4 adult-antioxidant.jpg', + name: 'adult-antioxidant.jpg', + description: 'adult-antioxidant', + title: 'Adult-antioxidant.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '5 services-2.jpg', + name: 'services-2.jpg', + description: 'Backcountry Skiing Services', + title: 'services-2.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '6 template-breadcrumbs.png', + name: 'template-breadcrumbs.png', + description: 'Thumbnail image for template with breadcrumbs', + title: 'template-breadcrumbs.png' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '7 downloading.jpg', + name: 'downloading.jpg', + description: + 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', + title: 'Going Up / Going Down' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '8 resort-cottage.jpg', + name: 'resort-cottage.jpg', + description: 'resort-cottage', + title: 'Resort-cottage.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '9 first-chair.jpg', + name: 'first-chair.jpg', + description: 'Stay at one of our resorts and get early hours with our first chair program.', + title: 'First to the Top' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '10 adult-antioxidant.jpg', + name: 'adult-antioxidant.jpg', + description: 'adult-antioxidant', + title: 'Adult-antioxidant.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '11 downloading.jpg', + name: 'downloading.jpg', + description: + 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', + title: 'Going Up / Going Down' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '12 rain-forest-view.jpg', + name: 'rain-forest-view.jpg', + description: 'rain-forest-view', + title: 'Rain-forest-view.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '13 rain-forest-view.jpg', + name: 'rain-forest-view.jpg', + description: 'rain-forest-view', + title: 'Rain-forest-view.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '14 Foto8.jpg', + name: 'Foto8.jpg', + description: 'Foto8', + title: 'Foto8.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '15 first-chair.jpg', + name: 'first-chair.jpg', + description: 'Stay at one of our resorts and get early hours with our first chair program.', + title: 'First to the Top' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '16 adult-antioxidant.jpg', + name: 'adult-antioxidant.jpg', + description: 'adult-antioxidant', + title: 'Adult-antioxidant.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '17 services-2.jpg', + name: 'services-2.jpg', + description: 'Backcountry Skiing Services', + title: 'services-2.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '18 template-breadcrumbs.png', + name: 'template-breadcrumbs.png', + description: 'Thumbnail image for template with breadcrumbs', + title: 'template-breadcrumbs.png' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '19 downloading.jpg', + name: 'downloading.jpg', + description: + 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', + title: 'Going Up / Going Down' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '20 resort-cottage.jpg', + name: 'resort-cottage.jpg', + description: 'resort-cottage', + title: 'Resort-cottage.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '21 first-chair.jpg', + name: 'first-chair.jpg', + description: 'Stay at one of our resorts and get early hours with our first chair program.', + title: 'First to the Top' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '22 adult-antioxidant.jpg', + name: 'adult-antioxidant.jpg', + description: 'adult-antioxidant', + title: 'Adult-antioxidant.jpg' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '23 downloading.jpg', + name: 'downloading.jpg', + description: + 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', + title: 'Going Up / Going Down' + }, + { + ...EMPTY_IMAGE_CONTENTLET, + fileName: '24 rain-forest-view.jpg', + name: 'rain-forest-view.jpg', + description: 'rain-forest-view', + title: 'Rain-forest-view.jpg' + } +]; + +const INITIAL_STATE = { contentlets: [], loading: true, preventScroll: false }; +const LanguageMock = { + 1: { + country: 'United States', + countryCode: 'US', + defaultLanguage: true, + id: 1, + language: 'English', + languageCode: 'en' + }, + 2: { + country: 'Espana', + countryCode: 'ES', + defaultLanguage: false, + id: 2, + language: 'Espanol', + languageCode: 'es' + } +}; + +const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 4).map((contentlet) => ({ + ...contentlet, + language: 'en-US' +})); + +describe('DotAssetSearchStore', () => { + let service: DotAssetSearchStore; + let DotContentSearchService: DotContentSearchService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + DotAssetSearchStore, + { + provide: DotContentSearchService, + useValue: { + get: () => of() + } + }, + { + provide: DotLanguageService, + useValue: { + getLanguages: () => of(LanguageMock) + } + } + ] + }); + + service = TestBed.inject(DotAssetSearchStore); + }); + + it('should have inital state', (done) => { + service.vm$.subscribe((res) => { + expect(res).toEqual(INITIAL_STATE); + done(); + }); + }); + + describe('Updaters', () => { + it('should update contentlets', (done) => { + const contentlet = [IMAGE_CONTENTLETS_MOCK[0], IMAGE_CONTENTLETS_MOCK[1]]; + service.updateContentlets(contentlet); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + ...INITIAL_STATE, + contentlets: contentlet + }); + done(); + }); + }); + + it('should update LanguageId', (done) => { + service.updatelanguageId(2); + + service.state$.subscribe((res) => { + expect(res.languageId).toEqual(2); + done(); + }); + }); + + it('should update Loading', (done) => { + service.updateLoading(false); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + ...INITIAL_STATE, + loading: false + }); + done(); + }); + }); + + it('should prevent Scroll', (done) => { + service.updatePreventScroll(true); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + ...INITIAL_STATE, + preventScroll: true + }); + done(); + }); + }); + + it('should Search', (done) => { + const search = 'Image'; + service.updateSearch(search); + + service.state$.subscribe((res) => { + expect(res.search).toEqual(search); + done(); + }); + }); + }); + + describe('Effects', () => { + beforeEach(() => { + DotContentSearchService = TestBed.inject(DotContentSearchService); + }); + + it('should search contentlets based on a search query', (done) => { + const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); + // Spies + // const loadingMock = spyOn(service, 'updateLoading'); + spyOn(service, 'updateSearch'); + spyOn(service, 'updateContentlets'); + spyOn(DotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets } }) + ); + + const query = 'image'; + + service.searchContentlet(query); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + preventScroll: false, + loading: false, + contentlets + }); + + done(); + }); + + expect(DotContentSearchService.get).toHaveBeenCalledWith({ + query: `+catchall:${query}* +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + sortOrder: ESOrderDirection.ASC, + limit: 20, + offset: 0 + }); + + // First -> the value is true because we start the search + // Second -> the value is false because we finish the search + // expect(loadingMock.calls).toEqual([[true], [false]]); + expect(service.updateContentlets).toHaveBeenCalledWith(contentlets); + expect(service.updateSearch).toHaveBeenCalledWith(query); + }); + + it('should not add "*" when the search has a "-" ', (done) => { + const contentlets = CONTENTLETS_MOCK_WITH_LANG; + spyOn(DotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets } }) + ); + + const query = 'hola-'; + + service.searchContentlet(query); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + preventScroll: false, + loading: false, + contentlets + }); + + done(); + }); + + expect(DotContentSearchService.get).toHaveBeenCalledWith({ + query: `+catchall:${query} +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + sortOrder: ESOrderDirection.ASC, + limit: 20, + offset: 0 + }); + }); + + it('should load the next batch of contentlets based on the offset', (done) => { + const contentlets_p1 = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); + const contentlets_p2 = [...CONTENTLETS_MOCK_WITH_LANG].splice(2, 2); + + // Spies + spyOn(service, 'updateSearch'); + spyOn(service, 'updateContentlets'); + spyOn(DotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets: contentlets_p2 } }) + ); + + const query = 'image'; + const offset = 2; + + service.updateContentlets(contentlets_p1); + service.updateSearch(query); + service.nextBatch(offset); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + preventScroll: false, + loading: false, + contentlets: [...contentlets_p1, ...contentlets_p2] + }); + + done(); + }); + + expect(DotContentSearchService.get).toHaveBeenCalledWith({ + query: ` +catchall:${query}* title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + sortOrder: ESOrderDirection.ASC, + limit: 20, + offset + }); + expect(service.updateContentlets).toHaveBeenCalledWith(contentlets_p1); + }); + + it('should set preventScroll to true when backend date is coming empty', (done) => { + const contentlets = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); + + // Spies + spyOn(service, 'updateSearch'); + spyOn(service, 'updateContentlets'); + spyOn(DotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets: [] } }) + ); + + const query = 'image'; + const offset = 2; + + service.updateContentlets(contentlets); + service.updateSearch(query); + service.nextBatch(offset); + + service.vm$.subscribe((res) => { + expect(res).toEqual({ + preventScroll: true, + loading: false, + contentlets + }); + + done(); + }); + }); + }); +}); diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts similarity index 93% rename from core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index 7474f26e9a4a..39beb06d0614 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -6,14 +6,9 @@ import { Observable } from 'rxjs/internal/Observable'; import { map, mergeMap, tap, withLatestFrom } from 'rxjs/operators'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; +import { ESOrderDirection, EsQueryParams, DotContentSearchService } from '@dotcms/ui'; -import { - SearchService, - Languages, - DotLanguageService, - EsQueryParams, - ESOrderDirection -} from '../../../../../shared'; +import { DotLanguageService, Languages } from '../../../services/dot-language/dot-language.service'; const DEFAULT_LANG_ID = 1; @@ -32,7 +27,7 @@ const defaultState: DotImageSearchState = { contentlets: [], languageId: DEFAULT_LANG_ID, search: '', - assetType: null + assetType: 'image' }; @Injectable() @@ -113,7 +108,7 @@ export class DotAssetSearchStore extends ComponentStore { private languages: Languages; constructor( - private searchService: SearchService, + private DotContentSearchService: DotContentSearchService, private dotLanguageService: DotLanguageService ) { super(defaultState); @@ -124,7 +119,7 @@ export class DotAssetSearchStore extends ComponentStore { } private searchContentletsRequest(params, prev: DotCMSContentlet[]) { - return this.searchService.get(params).pipe( + return this.DotContentSearchService.get(params).pipe( map(({ jsonObjectView: { contentlets } }) => { const items = this.setContentletLanguage(contentlets); this.updateLoading(false); diff --git a/core-web/libs/block-editor/src/lib/shared/services/search/search.service.ts b/core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts similarity index 96% rename from core-web/libs/block-editor/src/lib/shared/services/search/search.service.ts rename to core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts index 4ddece725ba4..5fe39f6b32a6 100644 --- a/core-web/libs/block-editor/src/lib/shared/services/search/search.service.ts +++ b/core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts @@ -24,7 +24,7 @@ export interface EsQueryParams { @Injectable({ providedIn: 'root' }) -export class SearchService { +export class DotContentSearchService { constructor(private http: HttpClient) {} /** diff --git a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts new file mode 100644 index 000000000000..e585858c5689 --- /dev/null +++ b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DotLanguageService } from './dot-language.service'; + +describe('DotLanguageService', () => { + let service: DotLanguageService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DotLanguageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts new file mode 100644 index 000000000000..c4653637072a --- /dev/null +++ b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts @@ -0,0 +1,74 @@ +import { Observable, of } from 'rxjs'; + +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { map, pluck } from 'rxjs/operators'; + +interface DotLanguage { + country: string; + countryCode: string; + defaultLanguage: boolean; + id: number; + language: string; + languageCode: string; +} + +export interface Languages { + [key: string]: DotLanguage; +} + +@Injectable({ + providedIn: 'root' +}) +export class DotLanguageService { + private languages: Languages; + + constructor(private http: HttpClient) {} + + get defaultHeaders() { + const headers = new HttpHeaders(); + headers.set('Accept', '*/*').set('Content-Type', 'application/json'); + + return headers; + } + + /** + * Get an object of DotLanguage whose keys are the languageId. + * + * @return {*} {Observable} + * @memberof DotLanguageService + */ + getLanguages(): Observable { + if (this.languages) { + return of(this.languages); + } + + return this.http + .get(`/api/v2/languages`, { + headers: this.defaultHeaders + }) + .pipe( + pluck('entity'), + map((lang: DotLanguage[]) => { + const dotLang: Languages = this.getDotLanguageObject(lang); + + this.languages = dotLang; + + return dotLang; + }) + ); + } + + /** + * Transform an array of languages into an object + * using the language id as an object key. + * @private + * @param {DotLanguage[]} lang + * @return {*} {Languages} + * @memberof DotLanguageService + */ + private getDotLanguageObject(lang: DotLanguage[]): Languages { + return lang.reduce((obj, lang) => Object.assign(obj, { [lang.id]: lang }), {}); + } +} From 13e67360151e03f86ea1891f4ccbdb1a263c10dd Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Mon, 18 Mar 2024 19:19:04 -0400 Subject: [PATCH 03/26] feat: let user add images from dotCMS --- .../dot-asset-search-dialog.component.html | 4 ++ .../dot-asset-search-dialog.component.scss | 0 .../dot-asset-search-dialog.component.spec.ts | 21 +++++++++++ .../dot-asset-search-dialog.component.ts | 24 ++++++++++++ .../dot-wysiwyg-field.component.ts | 37 +++++++++++-------- .../dot-asset-card-list.component.ts | 4 +- .../store/dot-asset-search.store.ts | 10 +++-- 7 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.scss create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html new file mode 100644 index 000000000000..5181ede2aa40 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html @@ -0,0 +1,4 @@ + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts new file mode 100644 index 000000000000..a50e323ef216 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog.component'; + +describe('DotAssetSearchDialogComponent', () => { + let component: DotAssetSearchDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DotAssetSearchDialogComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(DotAssetSearchDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts new file mode 100644 index 000000000000..c518f97c9bea --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { DotAssetSearchComponent } from '@dotcms/ui'; + +@Component({ + selector: 'dot-asset-search-dialog', + standalone: true, + imports: [CommonModule, DotAssetSearchComponent], + templateUrl: './dot-asset-search-dialog.component.html', + styleUrl: './dot-asset-search-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class DotAssetSearchDialogComponent { + private readonly config = inject(DynamicDialogConfig); + private readonly ref = inject(DynamicDialogRef); + + onSelectAsset(asset: DotCMSContentlet): void { + this.ref.close(asset); + } +} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 374579ff5bc2..4b3927fba454 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -1,21 +1,16 @@ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; import { Editor, TinyMCE } from 'tinymce'; -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Input, - inject, - signal, - NgZone -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject, signal, NgZone } from '@angular/core'; import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DialogService } from 'primeng/dynamicdialog'; +import { filter, take } from 'rxjs/operators'; + import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; -import { DotAssetSearchComponent } from '@dotcms/ui'; + +import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog/dot-asset-search-dialog.component'; declare global { interface Window { @@ -47,7 +42,6 @@ export class DotWYSIWYGFieldComponent { }; private readonly dialogService: DialogService = inject(DialogService); private readonly ngZone: NgZone = inject(NgZone); - private readonly cd: ChangeDetectorRef = inject(ChangeDetectorRef); protected readonly plugins = signal( 'dotAddImage advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' @@ -63,12 +57,25 @@ export class DotWYSIWYGFieldComponent { text: 'Add Image', onAction: () => { this.ngZone.run(() => { - this.dialogService.open(DotAssetSearchComponent, { + const ref = this.dialogService.open(DotAssetSearchDialogComponent, { header: 'Add Image', - width: '70%', - height: '70%' + width: '800px', + height: '500px', + contentStyle: { padding: 0 } }); - this.cd.detectChanges(); + + ref.onClose + .pipe( + take(1), + filter((asset) => !!asset) + ) + .subscribe((asset) => + editor.insertContent( + `${
+                                        asset.title
+                                    }` + ) + ); }); } }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts index ca585b5a8579..f432e573452d 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts @@ -9,6 +9,8 @@ import { } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; +import { ScrollerModule } from 'primeng/scroller'; + import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotAssetCardComponent } from '../dot-asset-card/dot-asset-card.component'; @@ -22,7 +24,7 @@ const squarePlus = templateUrl: './dot-asset-card-list.component.html', styleUrls: ['./dot-asset-card-list.component.scss'], standalone: true, - imports: [CommonModule, DotAssetCardComponent, DotAssetCardSkeletonComponent], + imports: [CommonModule, ScrollerModule, DotAssetCardComponent, DotAssetCardSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush }) export class DotAssetCardListComponent { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index 39beb06d0614..aaaaf7736abe 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -6,9 +6,13 @@ import { Observable } from 'rxjs/internal/Observable'; import { map, mergeMap, tap, withLatestFrom } from 'rxjs/operators'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; -import { ESOrderDirection, EsQueryParams, DotContentSearchService } from '@dotcms/ui'; - -import { DotLanguageService, Languages } from '../../../services/dot-language/dot-language.service'; +import { + ESOrderDirection, + EsQueryParams, + DotContentSearchService, + DotLanguageService, + Languages +} from '@dotcms/ui'; const DEFAULT_LANG_ID = 1; From b83a7c00283695cf4ea40993a07853a257c46ff7 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 19 Mar 2024 11:13:56 -0400 Subject: [PATCH 04/26] chore: create WYSIWYS Plugins service --- .../dot-language/dot-language.service.spec.ts | 16 ---- .../dot-language/dot-language.service.ts | 74 ------------------- .../dot-wysiwyg-field.component.ts | 58 ++++----------- .../dot-wysiwyg-plugin.service.spec.ts | 16 ++++ .../dot-wysiwyg-plugin.service.ts | 49 ++++++++++++ core-web/libs/ui/src/index.ts | 1 + .../dot-asset-search-dialog.component.html | 0 .../dot-asset-search-dialog.component.scss | 0 .../dot-asset-search-dialog.component.spec.ts | 0 .../dot-asset-search-dialog.component.ts | 0 .../dot-asset-search.component.ts | 4 +- .../store/dot-asset-search.store.ts | 38 +++++----- 12 files changed, 105 insertions(+), 151 deletions(-) delete mode 100644 core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.spec.ts delete mode 100644 core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts rename core-web/libs/{edit-content/src/lib/fields/dot-wysiwyg-field => ui/src/lib/components/dot-asset-search/components}/dot-asset-search-dialog/dot-asset-search-dialog.component.html (100%) rename core-web/libs/{edit-content/src/lib/fields/dot-wysiwyg-field => ui/src/lib/components/dot-asset-search/components}/dot-asset-search-dialog/dot-asset-search-dialog.component.scss (100%) rename core-web/libs/{edit-content/src/lib/fields/dot-wysiwyg-field => ui/src/lib/components/dot-asset-search/components}/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts (100%) rename core-web/libs/{edit-content/src/lib/fields/dot-wysiwyg-field => ui/src/lib/components/dot-asset-search/components}/dot-asset-search-dialog/dot-asset-search-dialog.component.ts (100%) diff --git a/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.spec.ts b/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.spec.ts deleted file mode 100644 index e585858c5689..000000000000 --- a/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DotLanguageService } from './dot-language.service'; - -describe('DotLanguageService', () => { - let service: DotLanguageService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(DotLanguageService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.ts b/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.ts deleted file mode 100644 index c4653637072a..000000000000 --- a/core-web/libs/block-editor/src/lib/shared/services/dot-language/dot-language.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Observable, of } from 'rxjs'; - -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Injectable } from '@angular/core'; - -import { map, pluck } from 'rxjs/operators'; - -interface DotLanguage { - country: string; - countryCode: string; - defaultLanguage: boolean; - id: number; - language: string; - languageCode: string; -} - -export interface Languages { - [key: string]: DotLanguage; -} - -@Injectable({ - providedIn: 'root' -}) -export class DotLanguageService { - private languages: Languages; - - constructor(private http: HttpClient) {} - - get defaultHeaders() { - const headers = new HttpHeaders(); - headers.set('Accept', '*/*').set('Content-Type', 'application/json'); - - return headers; - } - - /** - * Get an object of DotLanguage whose keys are the languageId. - * - * @return {*} {Observable} - * @memberof DotLanguageService - */ - getLanguages(): Observable { - if (this.languages) { - return of(this.languages); - } - - return this.http - .get(`/api/v2/languages`, { - headers: this.defaultHeaders - }) - .pipe( - pluck('entity'), - map((lang: DotLanguage[]) => { - const dotLang: Languages = this.getDotLanguageObject(lang); - - this.languages = dotLang; - - return dotLang; - }) - ); - } - - /** - * Transform an array of languages into an object - * using the language id as an object key. - * @private - * @param {DotLanguage[]} lang - * @return {*} {Languages} - * @memberof DotLanguageService - */ - private getDotLanguageObject(lang: DotLanguage[]): Languages { - return lang.reduce((obj, lang) => Object.assign(obj, { [lang.id]: lang }), {}); - } -} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 4b3927fba454..7c27f3d1a63d 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -1,16 +1,14 @@ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; -import { Editor, TinyMCE } from 'tinymce'; +import { TinyMCE } from 'tinymce'; -import { ChangeDetectionStrategy, Component, Input, inject, signal, NgZone } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, inject, signal } from '@angular/core'; import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DialogService } from 'primeng/dynamicdialog'; -import { filter, take } from 'rxjs/operators'; - import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; -import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog/dot-asset-search-dialog.component'; +import { DotWysiwygPluginService } from './dot-wysiwyg-plugin/dot-wysiwyg-plugin.service'; declare global { interface Window { @@ -25,7 +23,11 @@ declare global { templateUrl: './dot-wysiwyg-field.component.html', styleUrl: './dot-wysiwyg-field.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, - providers: [DialogService, { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }], + providers: [ + DialogService, + { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }, + DotWysiwygPluginService + ], viewProviders: [ { provide: ControlContainer, @@ -35,50 +37,20 @@ declare global { }) export class DotWYSIWYGFieldComponent { @Input() field!: DotCMSContentTypeField; - public init = { + + private readonly dotWysiwygPluginService = inject(DotWysiwygPluginService); + + protected readonly init = { setup: (editor) => { - this.setup(editor); + this.dotWysiwygPluginService.initializePlugins(editor); } }; - private readonly dialogService: DialogService = inject(DialogService); - private readonly ngZone: NgZone = inject(NgZone); protected readonly plugins = signal( - 'dotAddImage advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' + 'advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template' ); protected readonly toolbar = signal( - 'buttondotAddImage paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor backcolor emoticons' + 'dotAddImage paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor backcolor emoticons' ); - - setup(_editor: Editor) { - window.tinymce.PluginManager.add('dotAddImage', (editor: Editor) => { - editor.ui.registry.addButton('buttondotAddImage', { - text: 'Add Image', - onAction: () => { - this.ngZone.run(() => { - const ref = this.dialogService.open(DotAssetSearchDialogComponent, { - header: 'Add Image', - width: '800px', - height: '500px', - contentStyle: { padding: 0 } - }); - - ref.onClose - .pipe( - take(1), - filter((asset) => !!asset) - ) - .subscribe((asset) => - editor.insertContent( - `${
-                                        asset.title
-                                    }` - ) - ); - }); - } - }); - }); - } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts new file mode 100644 index 000000000000..1d286c98d20a --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { DotWysiwygPluginService } from './dot-wysiwyg-plugin.service'; + +describe('DotWysiwygPluginService', () => { + let service: DotWysiwygPluginService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(DotWysiwygPluginService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts new file mode 100644 index 000000000000..a17c2140c470 --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -0,0 +1,49 @@ +import { Editor } from 'tinymce'; + +import { Injectable, NgZone, inject } from '@angular/core'; + +import { DialogService } from 'primeng/dynamicdialog'; + +import { filter, take } from 'rxjs/operators'; + +import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { DotAssetSearchDialogComponent } from '@dotcms/ui'; + +@Injectable() +export class DotWysiwygPluginService { + private readonly dialogService: DialogService = inject(DialogService); + private readonly ngZone: NgZone = inject(NgZone); + + initializePlugins(editor: Editor): void { + this.dotImagePlugin(editor); + } + + private dotImagePlugin(editor: Editor): void { + editor.ui.registry.addButton('dotAddImage', { + text: 'Add Image', + onAction: () => { + this.ngZone.run(() => { + const ref = this.dialogService.open(DotAssetSearchDialogComponent, { + header: 'Add Image', + width: '800px', + height: '500px', + contentStyle: { padding: 0 } + }); + + ref.onClose + .pipe( + take(1), + filter((asset) => !!asset) + ) + .subscribe((asset: DotCMSContentlet) => + editor.insertContent( + `${
+                                    asset.title
+                                }` + ) + ); + }); + } + }); + } +} diff --git a/core-web/libs/ui/src/index.ts b/core-web/libs/ui/src/index.ts index b915a526e593..78f7a356558c 100644 --- a/core-web/libs/ui/src/index.ts +++ b/core-web/libs/ui/src/index.ts @@ -3,6 +3,7 @@ export * from './lib/dot-spinner/dot-spinner.module'; // Components export * from './lib/components/dot-asset-search/dot-asset-search.component'; +export * from './lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component'; export * from './lib/components/dot-binary-option-selector/dot-binary-option-selector.component'; export * from './lib/dot-spinner/dot-spinner.component'; export * from './lib/components/dot-drop-zone/dot-drop-zone.component'; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.html rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.scss b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.scss similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.scss rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.scss diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-asset-search-dialog/dot-asset-search-dialog.component.ts rename to core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts index 2252f39562d3..87944b7d8a9b 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts @@ -50,7 +50,9 @@ export class DotAssetSearchComponent implements OnInit, OnDestroy, AfterViewInit this.store.updatelanguageId(id); } + private _assetType: EditorAssetTypes; @Input() set type(type: EditorAssetTypes) { + this._assetType = type; this.store.updateAssetType(type); } @@ -62,7 +64,7 @@ export class DotAssetSearchComponent implements OnInit, OnDestroy, AfterViewInit constructor(private store: DotAssetSearchStore) {} ngOnInit(): void { - this.store.searchContentlet(''); + this.store.init(this._assetType); this.offset$ .pipe(takeUntil(this.destroy$), skip(1), throttleTime(450)) diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index aaaaf7736abe..bf69aeca4964 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -3,7 +3,7 @@ import { ComponentStore } from '@ngrx/component-store'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; -import { map, mergeMap, tap, withLatestFrom } from 'rxjs/operators'; +import { map, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; import { @@ -14,13 +14,11 @@ import { Languages } from '@dotcms/ui'; -const DEFAULT_LANG_ID = 1; - export interface DotImageSearchState { loading: boolean; preventScroll: boolean; contentlets: DotCMSContentlet[]; - languageId: number; + languageId?: number; search: string; assetType: EditorAssetTypes; } @@ -29,7 +27,6 @@ const defaultState: DotImageSearchState = { loading: true, preventScroll: false, contentlets: [], - languageId: DEFAULT_LANG_ID, search: '', assetType: 'image' }; @@ -82,20 +79,29 @@ export class DotAssetSearchStore extends ComponentStore { readonly updateSearch = this.updater((state, search) => { return { ...state, + loading: true, search }; }); // Effects + readonly init = this.effect((origin$: Observable) => { + return origin$.pipe( + tap((assetType) => this.updateAssetType(assetType)), + switchMap(() => this.dotLanguageService.getLanguages()), + tap((languages) => (this.languages = languages)), + withLatestFrom(this.state$), + switchMap(([_, state]) => this.searchContentletsRequest(this.params(state), [])) + ); + }); + readonly searchContentlet = this.effect((origin$: Observable) => { return origin$.pipe( - tap((search) => { - this.updateLoading(true); - this.updateSearch(search); - }), + tap((search) => this.updateSearch(search)), withLatestFrom(this.state$), - map(([search, state]) => ({ ...state, search })), - mergeMap((data) => this.searchContentletsRequest(this.params({ ...data }), [])) + mergeMap(([search, state]) => + this.searchContentletsRequest(this.params({ ...state, search }), []) + ) ); }); @@ -116,10 +122,6 @@ export class DotAssetSearchStore extends ComponentStore { private dotLanguageService: DotLanguageService ) { super(defaultState); - - this.dotLanguageService.getLanguages().subscribe((languages) => { - this.languages = languages; - }); } private searchContentletsRequest(params, prev: DotCMSContentlet[]) { @@ -134,11 +136,13 @@ export class DotAssetSearchStore extends ComponentStore { ); } - private params({ search, assetType, offset = 0, languageId }): EsQueryParams { + private params(data): EsQueryParams { + const { search, assetType, offset = 0, languageId = '' } = data; const filter = search.includes('-') ? search : `${search}*`; + const languageQuery = languageId ? `+languageId:${languageId}` : ''; return { - query: `+catchall:${filter} title:'${search}'^15 +languageId:${languageId} +baseType:(4 OR 9) +metadata.contenttype:${ + query: `+catchall:${filter} title:'${search}'^15 ${languageQuery} +baseType:(4 OR 9) +metadata.contenttype:${ assetType || '' }/* +deleted:false +working:true`, sortOrder: ESOrderDirection.ASC, From fcccf321bc86ee148b4561e773f7835218834614 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 19 Mar 2024 11:16:39 -0400 Subject: [PATCH 05/26] chore: clean up --- .../src/lib/fields/dot-edit-content-fields.module.ts | 7 +++++-- .../dot-wysiwyg-field/dot-wysiwyg-field.component.ts | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts index d764475ea7f7..4d4a85de7d74 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts @@ -11,6 +11,7 @@ import { DotEditContentSelectFieldComponent } from './dot-edit-content-select-fi import { DotEditContentTagFieldComponent } from './dot-edit-content-tag-field/dot-edit-content-tag-field.component'; import { DotEditContentTextAreaComponent } from './dot-edit-content-text-area/dot-edit-content-text-area.component'; import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/dot-edit-content-text-field.component'; +import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field/dot-wysiwyg-field.component'; @NgModule({ declarations: [], @@ -25,7 +26,8 @@ import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/ DotEditContentMultiSelectFieldComponent, DotEditContentBinaryFieldComponent, DotEditContentJsonFieldComponent, - DotEditContentCustomFieldComponent + DotEditContentCustomFieldComponent, + DotWYSIWYGFieldComponent ], exports: [ DotEditContentTextAreaComponent, @@ -38,7 +40,8 @@ import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/ DotEditContentMultiSelectFieldComponent, DotEditContentBinaryFieldComponent, DotEditContentJsonFieldComponent, - DotEditContentCustomFieldComponent + DotEditContentCustomFieldComponent, + DotWYSIWYGFieldComponent ] }) export class DotEditContentFieldsModule {} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 7c27f3d1a63d..fe62f75d093b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -25,8 +25,8 @@ declare global { changeDetection: ChangeDetectionStrategy.OnPush, providers: [ DialogService, - { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }, - DotWysiwygPluginService + DotWysiwygPluginService, + { provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' } ], viewProviders: [ { From 3c44f5277a64c40618006548e2dd15850a4601fd Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 19 Mar 2024 11:19:09 -0400 Subject: [PATCH 06/26] chore: add button icon --- .../fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts | 2 +- .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index fe62f75d093b..ed278258bcfd 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -51,6 +51,6 @@ export class DotWYSIWYGFieldComponent { ); protected readonly toolbar = signal( - 'dotAddImage paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor backcolor emoticons' + 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link dotAddImage hr | preview | validation media | forecolor backcolor emoticons' ); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index a17c2140c470..ae87896f271f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -20,7 +20,8 @@ export class DotWysiwygPluginService { private dotImagePlugin(editor: Editor): void { editor.ui.registry.addButton('dotAddImage', { - text: 'Add Image', + text: 'Dot Image', + icon: 'image', onAction: () => { this.ngZone.run(() => { const ref = this.dialogService.open(DotAssetSearchDialogComponent, { From 8420b28414cf9a9aefec6b9858434bcccb0f409c Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 19 Mar 2024 13:28:08 -0400 Subject: [PATCH 07/26] chore: allow user to drop images --- .../src/lib/block-editor.module.ts | 4 +-- .../dot-block-editor.component.stories.ts | 3 +- .../asset-form/asset-form.module.ts | 2 +- .../dot-upload-asset.component.ts | 3 +- .../asset-uploader.extension.ts | 8 ++--- .../floating-button.component.ts | 2 +- .../floating-button.extension.ts | 4 +-- .../plugin/floating-button.plugin.ts | 2 +- .../src/lib/shared/services/index.ts | 1 - core-web/libs/data-access/src/index.ts | 1 + .../dot-upload-file.service.spec.ts | 0 .../dot-upload-file.service.ts | 2 +- .../dot-wysiwyg-plugin.service.ts | 35 +++++++++++++++++++ 13 files changed, 48 insertions(+), 19 deletions(-) rename core-web/libs/{block-editor/src/lib/shared/services => data-access/src/lib}/dot-upload-file/dot-upload-file.service.spec.ts (100%) rename core-web/libs/{block-editor/src/lib/shared/services => data-access/src/lib}/dot-upload-file/dot-upload-file.service.ts (97%) diff --git a/core-web/libs/block-editor/src/lib/block-editor.module.ts b/core-web/libs/block-editor/src/lib/block-editor.module.ts index 35449c869303..9ba44b0d4545 100644 --- a/core-web/libs/block-editor/src/lib/block-editor.module.ts +++ b/core-web/libs/block-editor/src/lib/block-editor.module.ts @@ -6,7 +6,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ConfirmationService } from 'primeng/api'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; -import { DotMessageService } from '@dotcms/data-access'; +import { DotMessageService, DotUploadFileService } from '@dotcms/data-access'; import { LoggerService, StringUtils } from '@dotcms/dotcms-js'; import { DotFieldRequiredDirective, DotMessagePipe } from '@dotcms/ui'; @@ -29,7 +29,7 @@ import { } from './extensions'; import { AssetFormModule } from './extensions/asset-form/asset-form.module'; import { ContentletBlockComponent } from './nodes'; -import { DotAiService, DotUploadFileService, EditorDirective } from './shared'; +import { DotAiService, EditorDirective } from './shared'; import { PrimengModule } from './shared/primeng.module'; import { SharedModule } from './shared/shared.module'; diff --git a/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts b/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts index 0625cc332d3b..6d11145aee9a 100644 --- a/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts +++ b/core-web/libs/block-editor/src/lib/components/dot-block-editor/dot-block-editor.component.stories.ts @@ -10,7 +10,7 @@ import { OrderListModule } from 'primeng/orderlist'; import { debounceTime, delay, tap } from 'rxjs/operators'; -import { DotMessageService, DotPropertiesService } from '@dotcms/data-access'; +import { DotMessageService, DotPropertiesService, DotUploadFileService } from '@dotcms/data-access'; import { DotContentSearchService, DotLanguageService } from '@dotcms/ui'; import { DotBlockEditorComponent } from './dot-block-editor.component'; @@ -27,7 +27,6 @@ import { ASSET_MOCK, CONTENTLETS_MOCK, DotAiService, - DotUploadFileService, FileStatus, SuggestionsComponent, SuggestionsService diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts index c759a7c9bb88..2b345fa035e3 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts +++ b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.module.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { DotUploadFileService } from '@dotcms/data-access'; import { DotAssetSearchComponent, DotSpinnerModule } from '@dotcms/ui'; import { AssetFormComponent } from './asset-form.component'; @@ -9,7 +10,6 @@ import { DotExternalAssetComponent } from './components/dot-external-asset/dot-e import { DotAssetPreviewComponent } from './components/dot-upload-asset/components/dot-asset-preview/dot-asset-preview.component'; import { DotUploadAssetComponent } from './components/dot-upload-asset/dot-upload-asset.component'; -import { DotUploadFileService } from '../../shared'; import { PrimengModule } from '../../shared/primeng.module'; @NgModule({ diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-upload-asset/dot-upload-asset.component.ts b/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-upload-asset/dot-upload-asset.component.ts index 4a1ed57d79f1..799edec6be2f 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-upload-asset/dot-upload-asset.component.ts +++ b/core-web/libs/block-editor/src/lib/extensions/asset-form/components/dot-upload-asset/dot-upload-asset.component.ts @@ -16,12 +16,11 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { catchError, take } from 'rxjs/operators'; +import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; import { shakeAnimation } from './animations'; -import { DotUploadFileService } from '../../../../shared'; - export enum STATUS { SELECT = 'SELECT', PREVIEW = 'PREVIEW', diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-uploader/asset-uploader.extension.ts b/core-web/libs/block-editor/src/lib/extensions/asset-uploader/asset-uploader.extension.ts index dd899a9a2e1f..8dc9ace59fc8 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-uploader/asset-uploader.extension.ts +++ b/core-web/libs/block-editor/src/lib/extensions/asset-uploader/asset-uploader.extension.ts @@ -8,18 +8,14 @@ import { take } from 'rxjs/operators'; import { Extension } from '@tiptap/core'; +import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; import { UploadPlaceholderComponent } from './components/upload-placeholder/upload-placeholder.component'; import { PlaceholderPlugin } from './plugins/placeholder.plugin'; import { ImageNode } from '../../nodes'; -import { - deselectCurrentNode, - DotUploadFileService, - getCursorPosition, - isImageURL -} from '../../shared'; +import { deselectCurrentNode, getCursorPosition, isImageURL } from '../../shared'; interface UploadNode { view: EditorView; diff --git a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts index 1fef00a9dc37..00e7f8fda025 100644 --- a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts +++ b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { FileStatus } from '../../shared/services/dot-upload-file/dot-upload-file.service'; +import { FileStatus } from '../../../../../data-access/src/lib/dot-upload-file/dot-upload-file.service'; @Component({ selector: 'dot-floating-button', diff --git a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.extension.ts b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.extension.ts index 140791a1e400..9a0fca748d32 100644 --- a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.extension.ts +++ b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.extension.ts @@ -4,11 +4,11 @@ import { Injector, ViewContainerRef } from '@angular/core'; import { Extension } from '@tiptap/core'; +import { DotUploadFileService } from '@dotcms/data-access'; + import { FloatingButtonComponent } from './floating-button.component'; import { DotFloatingButtonPlugin } from './plugin/floating-button.plugin'; -import { DotUploadFileService } from '../../shared'; - export const FLOATING_BUTTON_PLUGIN_KEY = new PluginKey('floating-button'); export function DotFloatingButton(injector: Injector, viewContainerRef: ViewContainerRef) { diff --git a/core-web/libs/block-editor/src/lib/extensions/floating-button/plugin/floating-button.plugin.ts b/core-web/libs/block-editor/src/lib/extensions/floating-button/plugin/floating-button.plugin.ts index 20f1b08f2ade..f847f62cea7d 100644 --- a/core-web/libs/block-editor/src/lib/extensions/floating-button/plugin/floating-button.plugin.ts +++ b/core-web/libs/block-editor/src/lib/extensions/floating-button/plugin/floating-button.plugin.ts @@ -9,10 +9,10 @@ import { take, takeUntil, tap } from 'rxjs/operators'; import { Editor } from '@tiptap/core'; +import { DotUploadFileService, FileStatus } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { ImageNode } from '../../../nodes'; -import { DotUploadFileService, FileStatus } from '../../../shared'; import { getNodeCoords } from '../../bubble-menu/utils'; import { FloatingButtonComponent } from '../floating-button.component'; diff --git a/core-web/libs/block-editor/src/lib/shared/services/index.ts b/core-web/libs/block-editor/src/lib/shared/services/index.ts index 1ef572d95954..13bcfd4afc9a 100644 --- a/core-web/libs/block-editor/src/lib/shared/services/index.ts +++ b/core-web/libs/block-editor/src/lib/shared/services/index.ts @@ -1,4 +1,3 @@ export * from './dot-marketing-config/dot-marketing-config.service'; -export * from './dot-upload-file/dot-upload-file.service'; export * from './suggestions/suggestions.service'; export * from './dot-ai/dot-ai.service'; diff --git a/core-web/libs/data-access/src/index.ts b/core-web/libs/data-access/src/index.ts index 122444b29880..3f5f38f9179d 100644 --- a/core-web/libs/data-access/src/index.ts +++ b/core-web/libs/data-access/src/index.ts @@ -34,6 +34,7 @@ export * from './lib/dot-site-browser/dot-site-browser.service'; export * from './lib/dot-tags/dot-tags.service'; export * from './lib/dot-themes/dot-themes.service'; export * from './lib/dot-upload/dot-upload.service'; +export * from './lib/dot-upload-file/dot-upload-file.service'; export * from './lib/dot-versionable/dot-versionable.service'; export * from './lib/dot-wizard/dot-wizard.service'; export * from './lib/dot-workflow-actions-fire/dot-workflow-actions-fire.service'; diff --git a/core-web/libs/block-editor/src/lib/shared/services/dot-upload-file/dot-upload-file.service.spec.ts b/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts similarity index 100% rename from core-web/libs/block-editor/src/lib/shared/services/dot-upload-file/dot-upload-file.service.spec.ts rename to core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts diff --git a/core-web/libs/block-editor/src/lib/shared/services/dot-upload-file/dot-upload-file.service.ts b/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.ts similarity index 97% rename from core-web/libs/block-editor/src/lib/shared/services/dot-upload-file/dot-upload-file.service.ts rename to core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.ts index cc9a8c5904c2..856438adf157 100644 --- a/core-web/libs/block-editor/src/lib/shared/services/dot-upload-file/dot-upload-file.service.ts +++ b/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.ts @@ -44,7 +44,7 @@ export class DotUploadFileService { return this.setTempResource({ data, maxSize, signal }).pipe( switchMap((response: DotCMSTempFile | DotCMSTempFile[]) => { const files = Array.isArray(response) ? response : [response]; - const contentlets = []; + const contentlets: Record[] = []; files.forEach((file: DotCMSTempFile) => { contentlets.push({ baseType: 'dotAsset', diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index ae87896f271f..56885333cae9 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -6,12 +6,14 @@ import { DialogService } from 'primeng/dynamicdialog'; import { filter, take } from 'rxjs/operators'; +import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotAssetSearchDialogComponent } from '@dotcms/ui'; @Injectable() export class DotWysiwygPluginService { private readonly dialogService: DialogService = inject(DialogService); + private readonly dotUploadFileService: DotUploadFileService = inject(DotUploadFileService); private readonly ngZone: NgZone = inject(NgZone); initializePlugins(editor: Editor): void { @@ -46,5 +48,38 @@ export class DotWysiwygPluginService { }); } }); + + this.dotFilePlugin(editor); + } + + private dotFilePlugin(editor: Editor) { + editor.on('drop', async (event) => { + // get image + const file = event.dataTransfer.files[0]; + + // Check if the file is an image + if (!file.type.includes('image')) { + return; + } + + event.preventDefault(); + event.stopImmediatePropagation(); + event.stopPropagation(); + + this.dotUploadFileService + .publishContent({ + data: file + }) + .subscribe((contentlets: DotCMSContentlet[]) => { + const data = contentlets[0]; + const contentlet = data[Object.keys(data)[0]]; + + editor.insertContent( + `${
+                            contentlet.title
+                        }` + ); + }); + }); } } From 984bd87c272a4c69e4c5118f842b62b60175645d Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Tue, 19 Mar 2024 14:42:49 -0400 Subject: [PATCH 08/26] feat: add dot Image data attributes --- .../dot-wysiwyg-plugin.service.ts | 22 ++++++++----------- .../dot-wysiwyg-plugin/utils/editor.utils.ts | 10 +++++++++ .../dot-asset-search-dialog.component.html | 5 +---- .../dot-asset-search-dialog.component.spec.ts | 1 + .../dot-asset-search-dialog.component.ts | 9 ++++++-- 5 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index 56885333cae9..ec345c5f6d77 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -10,6 +10,8 @@ import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotAssetSearchDialogComponent } from '@dotcms/ui'; +import { formatFotImageNode } from './utils/editor.utils'; + @Injectable() export class DotWysiwygPluginService { private readonly dialogService: DialogService = inject(DialogService); @@ -30,7 +32,10 @@ export class DotWysiwygPluginService { header: 'Add Image', width: '800px', height: '500px', - contentStyle: { padding: 0 } + contentStyle: { padding: 0 }, + data: { + assetType: 'image' + } }); ref.onClose @@ -39,11 +44,7 @@ export class DotWysiwygPluginService { filter((asset) => !!asset) ) .subscribe((asset: DotCMSContentlet) => - editor.insertContent( - `${
-                                    asset.title
-                                }` - ) + editor.insertContent(formatFotImageNode(asset)) ); }); } @@ -72,13 +73,8 @@ export class DotWysiwygPluginService { }) .subscribe((contentlets: DotCMSContentlet[]) => { const data = contentlets[0]; - const contentlet = data[Object.keys(data)[0]]; - - editor.insertContent( - `${
-                            contentlet.title
-                        }` - ); + const asset = data[Object.keys(data)[0]]; + editor.insertContent(formatFotImageNode(asset)); }); }); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts new file mode 100644 index 000000000000..a34018ebf89f --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts @@ -0,0 +1,10 @@ +import { DotCMSContentlet } from '@dotcms/dotcms-models'; + +export const formatFotImageNode = (asset: DotCMSContentlet) => { + return `${asset.title}`; +}; diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html index 5181ede2aa40..3bce9c247e7f 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.html @@ -1,4 +1 @@ - + diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts index a50e323ef216..5d440cff4251 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; + import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog.component'; describe('DotAssetSearchDialogComponent', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts index c518f97c9bea..ddd50c066295 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts @@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; -import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; import { DotAssetSearchComponent } from '@dotcms/ui'; @Component({ @@ -15,9 +15,14 @@ import { DotAssetSearchComponent } from '@dotcms/ui'; changeDetection: ChangeDetectionStrategy.OnPush }) export class DotAssetSearchDialogComponent { - private readonly config = inject(DynamicDialogConfig); private readonly ref = inject(DynamicDialogRef); + protected editorAssetType: EditorAssetTypes; + + constructor(private readonly config: DynamicDialogConfig) { + this.editorAssetType = this.config.data?.assetType; + } + onSelectAsset(asset: DotCMSContentlet): void { this.ref.close(asset); } From 36845e94951c9b8c50a5488ce9622093edb27782 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 14:24:29 -0400 Subject: [PATCH 09/26] chore: fix tests --- .../dotcms-block-editor/src/app/app.module.ts | 12 +- .../bubble-link-form.component.ts | 16 +- .../suggestions/suggestions.component.ts | 17 +- core-web/libs/data-access/src/index.ts | 1 + .../dot-content-search.service.ts | 8 +- core-web/libs/ui/src/index.ts | 2 - .../dot-asset-card-list.component.spec.ts | 30 +- .../dot-asset-card-skeleton.component.spec.ts | 27 +- .../dot-asset-card.component.html | 2 +- .../dot-asset-card.component.spec.ts | 20 +- .../dot-asset-search-dialog.component.spec.ts | 24 +- .../dot-asset-search-dialog.component.ts | 1 + .../dot-asset-search.component.spec.ts | 36 +- .../dot-asset-search.component.ts | 3 +- .../store/dot-asset-search.store.spec.ts | 495 +++++------------- .../store/dot-asset-search.store.ts | 35 +- .../dot-language/dot-language.service.spec.ts | 16 - .../dot-language/dot-language.service.ts | 74 --- .../src/lib/dot-contentlet.mock.ts | 42 ++ 19 files changed, 334 insertions(+), 527 deletions(-) rename core-web/libs/{ui/src/lib/services => data-access/src/lib}/dot-content-search/dot-content-search.service.ts (87%) delete mode 100644 core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts delete mode 100644 core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts diff --git a/core-web/apps/dotcms-block-editor/src/app/app.module.ts b/core-web/apps/dotcms-block-editor/src/app/app.module.ts index 3fff454dea1f..6a95d4e8b407 100644 --- a/core-web/apps/dotcms-block-editor/src/app/app.module.ts +++ b/core-web/apps/dotcms-block-editor/src/app/app.module.ts @@ -10,7 +10,12 @@ import { ListboxModule } from 'primeng/listbox'; import { OrderListModule } from 'primeng/orderlist'; import { BlockEditorModule, DotBlockEditorComponent } from '@dotcms/block-editor'; -import { DotPropertiesService } from '@dotcms/data-access'; +import { + DotPropertiesService, + DotContentSearchService, + DotLanguagesService +} from '@dotcms/data-access'; +import { DotAssetSearchComponent } from '@dotcms/ui'; import { AppComponent } from './app.component'; @@ -24,9 +29,10 @@ import { AppComponent } from './app.component'; BlockEditorModule, OrderListModule, ListboxModule, - HttpClientModule + HttpClientModule, + DotAssetSearchComponent ], - providers: [DotPropertiesService] + providers: [DotPropertiesService, DotContentSearchService, DotLanguagesService] }) export class AppModule implements DoBootstrap { constructor(private injector: Injector) {} diff --git a/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts b/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts index 5fb47590f3ce..4f6c1ffdd714 100644 --- a/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts +++ b/core-web/libs/block-editor/src/lib/extensions/bubble-link-form/bubble-link-form.component.ts @@ -12,8 +12,8 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { debounceTime, take } from 'rxjs/operators'; -import { DotCMSContentlet } from '@dotcms/dotcms-models'; -import { DotLanguageService, Languages } from '@dotcms/ui'; +import { DotLanguagesService } from '@dotcms/data-access'; +import { DotCMSContentlet, DotLanguage } from '@dotcms/dotcms-models'; import { SuggestionPageComponent } from './components/suggestion-page/suggestion-page.component'; @@ -49,7 +49,7 @@ export class BubbleLinkFormComponent implements OnInit { }; private minChars = 3; - private dotLangs: Languages; + private dotLangs: { [key: string]: DotLanguage } = {}; loading = false; form: FormGroup; @@ -88,7 +88,7 @@ export class BubbleLinkFormComponent implements OnInit { constructor( private fb: FormBuilder, private suggestionsService: SuggestionsService, - private dotLanguageService: DotLanguageService + private dotLanguagesService: DotLanguagesService ) {} ngOnInit() { @@ -111,10 +111,12 @@ export class BubbleLinkFormComponent implements OnInit { this.setNodeProps.emit({ link: this.currentLink, blank }) ); - this.dotLanguageService - .getLanguages() + this.dotLanguagesService + .get() .pipe(take(1)) - .subscribe((dotLang) => (this.dotLangs = dotLang)); + .subscribe((dotLang) => { + dotLang.forEach((lang) => (this.dotLangs[lang.id] = lang)); + }); } /** diff --git a/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts b/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts index e0a134326c34..6b05defa793a 100644 --- a/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts +++ b/core-web/libs/block-editor/src/lib/shared/components/suggestions/suggestions.component.ts @@ -13,12 +13,13 @@ import { MenuItem } from 'primeng/api'; import { map, take } from 'rxjs/operators'; -import { DotCMSContentlet, DotCMSContentType } from '@dotcms/dotcms-models'; -import { DotLanguageService, Languages } from '@dotcms/ui'; +import { DotLanguagesService } from '@dotcms/data-access'; +import { DotCMSContentlet, DotCMSContentType, DotLanguage } from '@dotcms/dotcms-models'; import { DEFAULT_LANG_ID } from '../../../extensions'; import { SuggestionsService } from '../../services'; import { SuggestionListComponent } from '../suggestion-list/suggestion-list.component'; + export interface SuggestionsCommandProps { payload?: DotCMSContentlet; type: { name: string; level?: number }; @@ -57,7 +58,7 @@ export class SuggestionsComponent implements OnInit { private itemsLoaded: ItemsType; private selectedContentType: DotCMSContentType; - private dotLangs: Languages; + private dotLangs: { [key: string]: DotLanguage } = {}; private initialItems: DotMenuItem[]; isFilterActive = false; @@ -75,17 +76,19 @@ export class SuggestionsComponent implements OnInit { constructor( private suggestionsService: SuggestionsService, - private dotLanguageService: DotLanguageService, + private dotLanguagesService: DotLanguagesService, private cd: ChangeDetectorRef ) {} ngOnInit(): void { this.initialItems = this.items; this.itemsLoaded = ItemsType.BLOCK; - this.dotLanguageService - .getLanguages() + this.dotLanguagesService + .get() .pipe(take(1)) - .subscribe((dotLang) => (this.dotLangs = dotLang)); + .subscribe((dotLang) => { + dotLang.forEach((lang) => (this.dotLangs[lang.id] = lang)); + }); } /** diff --git a/core-web/libs/data-access/src/index.ts b/core-web/libs/data-access/src/index.ts index 3f5f38f9179d..ca9ed24ea4dc 100644 --- a/core-web/libs/data-access/src/index.ts +++ b/core-web/libs/data-access/src/index.ts @@ -11,6 +11,7 @@ export * from './lib/dot-current-user/dot-current-user.service'; export * from './lib/dot-devices/dot-devices.service'; export * from './lib/dot-edit-page/dot-edit-page.service'; export * from './lib/dot-es-content/dot-es-content.service'; +export * from './lib/dot-content-search/dot-content-search.service'; export * from './lib/dot-events/dot-events.service'; export * from './lib/dot-format-date/dot-format-date.service'; export * from './lib/dot-generate-secure-password/dot-generate-secure-password.service'; diff --git a/core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts b/core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.ts similarity index 87% rename from core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts rename to core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.ts index 5fe39f6b32a6..cf9fb8c05846 100644 --- a/core-web/libs/ui/src/lib/services/dot-content-search/dot-content-search.service.ts +++ b/core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.ts @@ -5,12 +5,12 @@ import { Injectable } from '@angular/core'; import { pluck } from 'rxjs/operators'; -export enum ESOrderDirection { +export enum ESOrderDirectionSearch { ASC = 'ASC', DESC = 'DESC' } -export interface EsQueryParams { +export interface EsQueryParamsSearch { itemsPerPage?: number; filter?: string; lang?: string; @@ -18,7 +18,7 @@ export interface EsQueryParams { query: string; sortField?: string; limit?: number; - sortOrder?: ESOrderDirection; + sortOrder?: ESOrderDirectionSearch; } @Injectable({ @@ -33,7 +33,7 @@ export class DotContentSearchService { * @returns Observable * @memberof DotESContentService */ - public get({ query, limit = 0, offset = 0 }: EsQueryParams): Observable { + public get({ query, limit = 0, offset = 0 }: EsQueryParamsSearch): Observable { return this.http .post('/api/content/_search', { query, diff --git a/core-web/libs/ui/src/index.ts b/core-web/libs/ui/src/index.ts index c0a93d027270..37b3c4f808ec 100644 --- a/core-web/libs/ui/src/index.ts +++ b/core-web/libs/ui/src/index.ts @@ -44,8 +44,6 @@ export * from './lib/directives/dot-string-template-outlet.directive'; // Services export * from './lib/services/clipboard/ClipboardUtil'; export * from './lib/services/dot-copy-content-modal/dot-copy-content-modal.service'; -export * from './lib/services/dot-content-search/dot-content-search.service'; -export * from './lib/services/dot-language/dot-language.service'; // Pipes export * from './lib/pipes/dot-relative-date/dot-relative-date.pipe'; diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts index e93e60c36066..329d9eaa9697 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.spec.ts @@ -1,19 +1,31 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; + +import { CommonModule } from '@angular/common'; + +import { ScrollerModule } from 'primeng/scroller'; import { DotAssetCardListComponent } from './dot-asset-card-list.component'; +import { DotAssetCardComponent } from '../dot-asset-card/dot-asset-card.component'; +import { DotAssetCardSkeletonComponent } from '../dot-asset-card-skeleton/dot-asset-card-skeleton.component'; + describe('DotAssetCardListComponent', () => { + let spectator: Spectator; let component: DotAssetCardListComponent; - let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DotAssetCardListComponent] - }).compileComponents(); + const createComponent = createComponentFactory({ + component: DotAssetCardListComponent, + imports: [ + CommonModule, + ScrollerModule, + DotAssetCardComponent, + DotAssetCardSkeletonComponent + ] + }); - fixture = TestBed.createComponent(DotAssetCardListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + spectator = createComponent(); + component = spectator.component; }); it('should create', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts index 2901addcc777..3f7c2fb1ce13 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts @@ -1,19 +1,22 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; -import { DotAssetSkeletonComponent } from './dot-asset-card-skeleton.component'; +import { CardModule } from 'primeng/card'; +import { SkeletonModule } from 'primeng/skeleton'; -describe('DotAssetSkeletonComponent', () => { - let component: DotAssetSkeletonComponent; - let fixture: ComponentFixture; +import { DotAssetCardSkeletonComponent } from './dot-asset-card-skeleton.component'; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DotAssetSkeletonComponent] - }).compileComponents(); +describe('DotAssetCardSkeletonComponent', () => { + let spectator: Spectator; + let component: DotAssetCardSkeletonComponent; - fixture = TestBed.createComponent(DotAssetSkeletonComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + const createComponent = createComponentFactory({ + component: DotAssetCardSkeletonComponent, + imports: [CardModule, SkeletonModule] + }); + + beforeEach(() => { + spectator = createComponent(); + component = spectator.component; }); it('should create', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html index 3ddf0af24e64..8eb90a5d0620 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html @@ -11,7 +11,7 @@

{{ contentlet?.fileName || contentlet?.title }}

- {{ contentlet.language }} + {{ contentlet?.language }}
diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts index 8fb82514f617..2e0cceadfaf9 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts @@ -1,19 +1,21 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; + +import { CardModule } from 'primeng/card'; import { DotAssetCardComponent } from './dot-asset-card.component'; describe('DotAssetCardComponent', () => { + let spectator: Spectator; let component: DotAssetCardComponent; - let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DotAssetCardComponent] - }).compileComponents(); + const createComponent = createComponentFactory({ + component: DotAssetCardComponent, + imports: [CardModule] + }); - fixture = TestBed.createComponent(DotAssetCardComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + spectator = createComponent(); + component = spectator.component; }); it('should create', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts index 5d440cff4251..6e355eeec589 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts @@ -1,19 +1,25 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; +import { MockComponent } from 'ng-mocks'; + +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog.component'; +import { DotAssetSearchComponent } from '../../dot-asset-search.component'; + describe('DotAssetSearchDialogComponent', () => { + let spectator: Spectator; let component: DotAssetSearchDialogComponent; - let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [DotAssetSearchDialogComponent] - }).compileComponents(); + const createComponent = createComponentFactory({ + component: DotAssetSearchDialogComponent, + declarations: [MockComponent(DotAssetSearchComponent)], + providers: [DynamicDialogRef, DynamicDialogConfig] + }); - fixture = TestBed.createComponent(DotAssetSearchDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + spectator = createComponent(); + component = spectator.component; }); it('should create', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts index ddd50c066295..c66efcdddd1b 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts @@ -10,6 +10,7 @@ import { DotAssetSearchComponent } from '@dotcms/ui'; selector: 'dot-asset-search-dialog', standalone: true, imports: [CommonModule, DotAssetSearchComponent], + providers: [DynamicDialogRef, DynamicDialogConfig], templateUrl: './dot-asset-search-dialog.component.html', styleUrl: './dot-asset-search-dialog.component.scss', changeDetection: ChangeDetectionStrategy.OnPush diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts index 1a185cd7d31a..a3f4f3117369 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts @@ -1,19 +1,37 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createComponentFactory, Spectator } from '@ngneat/spectator'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +import { InputTextModule } from 'primeng/inputtext'; + +import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; + +import { DotAssetCardComponent } from './components/dot-asset-card/dot-asset-card.component'; +import { DotAssetCardListComponent } from './components/dot-asset-card-list/dot-asset-card-list.component'; +import { DotAssetCardSkeletonComponent } from './components/dot-asset-card-skeleton/dot-asset-card-skeleton.component'; import { DotAssetSearchComponent } from './dot-asset-search.component'; +import { DotAssetSearchStore } from './store/dot-asset-search.store'; describe('DotAssetSearchComponent', () => { + let spectator: Spectator; let component: DotAssetSearchComponent; - let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [DotAssetSearchComponent] - }).compileComponents(); + const createComponent = createComponentFactory({ + component: DotAssetSearchComponent, + mocks: [DotContentSearchService, DotLanguagesService], + providers: [DotAssetSearchStore, DotContentSearchService, DotLanguagesService], + imports: [ + HttpClientTestingModule, + InputTextModule, + DotAssetCardComponent, + DotAssetCardListComponent, + DotAssetCardSkeletonComponent + ] + }); - fixture = TestBed.createComponent(DotAssetSearchComponent); - component = fixture.componentInstance; - fixture.detectChanges(); + beforeEach(() => { + spectator = createComponent(); + component = spectator.component; }); it('should create', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts index 87944b7d8a9b..f3d03c37652b 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts @@ -18,6 +18,7 @@ import { InputTextModule } from 'primeng/inputtext'; import { debounceTime, skip, takeUntil, throttleTime } from 'rxjs/operators'; +import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; // services @@ -30,7 +31,7 @@ import { DotAssetSearchStore } from './store/dot-asset-search.store'; selector: 'dot-asset-search', templateUrl: './dot-asset-search.component.html', styleUrls: ['./dot-asset-search.component.scss'], - providers: [DotAssetSearchStore], + providers: [DotAssetSearchStore, DotContentSearchService, DotLanguagesService], standalone: true, imports: [ DotAssetCardComponent, diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts index f9a9e7e16619..c229c24ed11a 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts @@ -1,59 +1,15 @@ +import { SpectatorService, createServiceFactory } from '@ngneat/spectator'; import { of } from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { TestBed } from '@angular/core/testing'; -import { ESOrderDirection } from '@dotcms/data-access'; +import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { EMPTY_IMAGE_CONTENTLET } from '@dotcms/utils-testing'; import { DotAssetSearchStore } from './dot-asset-search.store'; -import { DotLanguageService } from '../../../services/dot-language/dot-language.service'; -import { DotContentSearchService } from '../../../services/search/dot-content-search.service'; - -const EMPTY_CONTENTLET: DotCMSContentlet = { - inode: '14dd5ad9-55ae-42a8-a5a7-e259b6d0901a', - variantId: 'DEFAULT', - locked: false, - stInode: 'd5ea385d-32ee-4f35-8172-d37f58d9cd7a', - contentType: 'Image', - height: 4000, - identifier: '93ca45e0-06d2-4eef-be1d-79bd6bf0fc99', - hasTitleImage: true, - sortOrder: 0, - hostName: 'demo.dotcms.com', - extension: 'jpg', - isContent: true, - baseType: 'FILEASSETS', - archived: false, - working: true, - live: true, - isContentlet: true, - languageId: 1, - titleImage: 'fileAsset', - hasLiveVersion: true, - deleted: false, - folder: '', - host: '', - modDate: '', - modUser: '', - modUserName: '', - owner: '', - title: '', - url: '', - contentTypeIcon: 'assessment', - __icon__: 'Icon' -}; - -const EMPTY_IMAGE_CONTENTLET = { - mimeType: 'image/jpeg', - type: 'file_asset', - fileAssetVersion: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - fileAsset: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', - ...EMPTY_CONTENTLET -}; - -export const IMAGE_CONTENTLETS_MOCK = [ +export const IMAGE_CONTENTLETS_MOCK: DotCMSContentlet[] = [ { ...EMPTY_IMAGE_CONTENTLET, fileName: '1 rain-forest-view.jpg', @@ -63,8 +19,7 @@ export const IMAGE_CONTENTLETS_MOCK = [ }, { ...EMPTY_IMAGE_CONTENTLET, - fileAsset: - 'https://previews.123rf.com/images/rglinsky/rglinsky1201/rglinsky120100188/12336990-vertical-de-la-imagen-orientada-a-la-famosa-torre-eiffel-en-par%C3%ADs-francia-.jpg', + fileAsset: 'test2.jpg', fileName: '2 Foto8.jpg', name: 'Foto8.jpg', description: 'Foto8', @@ -72,8 +27,7 @@ export const IMAGE_CONTENTLETS_MOCK = [ }, { ...EMPTY_IMAGE_CONTENTLET, - fileAsset: - 'https://www.freesvgdownload.com/wp-content/uploads/2021/12/It-Takes-a-Big-Heart-To-Help.jpg', + fileAsset: 'test3.jpg', fileName: '3 first-chair.jpg', name: 'first-chair.jpg', description: 'Stay at one of our resorts and get early hours with our first chair program.', @@ -81,7 +35,7 @@ export const IMAGE_CONTENTLETS_MOCK = [ }, { ...EMPTY_IMAGE_CONTENTLET, - fileAsset: 'https://interactive-examples.mdn.mozilla.net/media/examples/plumeria.jpg', + fileAsset: 'test4.jpg', fileName: '4 adult-antioxidant.jpg', name: 'adult-antioxidant.jpg', description: 'adult-antioxidant', @@ -89,153 +43,25 @@ export const IMAGE_CONTENTLETS_MOCK = [ }, { ...EMPTY_IMAGE_CONTENTLET, - fileName: '5 services-2.jpg', - name: 'services-2.jpg', - description: 'Backcountry Skiing Services', - title: 'services-2.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '6 template-breadcrumbs.png', - name: 'template-breadcrumbs.png', - description: 'Thumbnail image for template with breadcrumbs', - title: 'template-breadcrumbs.png' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '7 downloading.jpg', - name: 'downloading.jpg', - description: - 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', - title: 'Going Up / Going Down' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '8 resort-cottage.jpg', - name: 'resort-cottage.jpg', - description: 'resort-cottage', - title: 'Resort-cottage.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '9 first-chair.jpg', + fileAsset: 'test5.jpg', + fileName: '5 first-chair.jpg', name: 'first-chair.jpg', description: 'Stay at one of our resorts and get early hours with our first chair program.', title: 'First to the Top' }, { ...EMPTY_IMAGE_CONTENTLET, - fileName: '10 adult-antioxidant.jpg', + fileAsset: 'test6.jpg', + fileName: '6 adult-antioxidant.jpg', name: 'adult-antioxidant.jpg', description: 'adult-antioxidant', title: 'Adult-antioxidant.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '11 downloading.jpg', - name: 'downloading.jpg', - description: - 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', - title: 'Going Up / Going Down' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '12 rain-forest-view.jpg', - name: 'rain-forest-view.jpg', - description: 'rain-forest-view', - title: 'Rain-forest-view.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '13 rain-forest-view.jpg', - name: 'rain-forest-view.jpg', - description: 'rain-forest-view', - title: 'Rain-forest-view.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '14 Foto8.jpg', - name: 'Foto8.jpg', - description: 'Foto8', - title: 'Foto8.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '15 first-chair.jpg', - name: 'first-chair.jpg', - description: 'Stay at one of our resorts and get early hours with our first chair program.', - title: 'First to the Top' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '16 adult-antioxidant.jpg', - name: 'adult-antioxidant.jpg', - description: 'adult-antioxidant', - title: 'Adult-antioxidant.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '17 services-2.jpg', - name: 'services-2.jpg', - description: 'Backcountry Skiing Services', - title: 'services-2.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '18 template-breadcrumbs.png', - name: 'template-breadcrumbs.png', - description: 'Thumbnail image for template with breadcrumbs', - title: 'template-breadcrumbs.png' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '19 downloading.jpg', - name: 'downloading.jpg', - description: - 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', - title: 'Going Up / Going Down' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '20 resort-cottage.jpg', - name: 'resort-cottage.jpg', - description: 'resort-cottage', - title: 'Resort-cottage.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '21 first-chair.jpg', - name: 'first-chair.jpg', - description: 'Stay at one of our resorts and get early hours with our first chair program.', - title: 'First to the Top' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '22 adult-antioxidant.jpg', - name: 'adult-antioxidant.jpg', - description: 'adult-antioxidant', - title: 'Adult-antioxidant.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '23 downloading.jpg', - name: 'downloading.jpg', - description: - 'With the opening our our new Peak Bar this year our Top Expressive lift has improve access for downloading', - title: 'Going Up / Going Down' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileName: '24 rain-forest-view.jpg', - name: 'rain-forest-view.jpg', - description: 'rain-forest-view', - title: 'Rain-forest-view.jpg' } ]; const INITIAL_STATE = { contentlets: [], loading: true, preventScroll: false }; -const LanguageMock = { - 1: { +const LanguageMock = [ + { country: 'United States', countryCode: 'US', defaultLanguage: true, @@ -243,7 +69,7 @@ const LanguageMock = { language: 'English', languageCode: 'en' }, - 2: { + { country: 'Espana', countryCode: 'ES', defaultLanguage: false, @@ -251,38 +77,42 @@ const LanguageMock = { language: 'Espanol', languageCode: 'es' } -}; +]; -const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 4).map((contentlet) => ({ - ...contentlet, - language: 'en-US' -})); +// const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 6).map((contentlet) => ({ +// ...contentlet, +// language: 'en-US' +// })); describe('DotAssetSearchStore', () => { + let spectator: SpectatorService; let service: DotAssetSearchStore; - let DotContentSearchService: DotContentSearchService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - providers: [ - DotAssetSearchStore, - { - provide: DotContentSearchService, - useValue: { - get: () => of() - } - }, - { - provide: DotLanguageService, - useValue: { - getLanguages: () => of(LanguageMock) - } + // let dotContentSearchService: DotContentSearchService; + + const createService = createServiceFactory({ + service: DotAssetSearchStore, + imports: [HttpClientTestingModule], + providers: [ + DotAssetSearchStore, + { + provide: DotContentSearchService, + useValue: { + get: () => of() } - ] - }); + }, + { + provide: DotLanguagesService, + useValue: { + get: () => of(LanguageMock) + } + } + ] + }); - service = TestBed.inject(DotAssetSearchStore); + beforeEach(() => { + spectator = createService(); + service = spectator.service; + // dotContentSearchService = spectator.inject(DotContentSearchService); }); it('should have inital state', (done) => { @@ -350,140 +180,105 @@ describe('DotAssetSearchStore', () => { }); }); - describe('Effects', () => { - beforeEach(() => { - DotContentSearchService = TestBed.inject(DotContentSearchService); - }); - - it('should search contentlets based on a search query', (done) => { - const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); - // Spies - // const loadingMock = spyOn(service, 'updateLoading'); - spyOn(service, 'updateSearch'); - spyOn(service, 'updateContentlets'); - spyOn(DotContentSearchService, 'get').and.returnValue( - of({ jsonObjectView: { contentlets } }) - ); - - const query = 'image'; - - service.searchContentlet(query); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets - }); - - done(); - }); - - expect(DotContentSearchService.get).toHaveBeenCalledWith({ - query: `+catchall:${query}* +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset: 0 - }); - - // First -> the value is true because we start the search - // Second -> the value is false because we finish the search - // expect(loadingMock.calls).toEqual([[true], [false]]); - expect(service.updateContentlets).toHaveBeenCalledWith(contentlets); - expect(service.updateSearch).toHaveBeenCalledWith(query); - }); - - it('should not add "*" when the search has a "-" ', (done) => { - const contentlets = CONTENTLETS_MOCK_WITH_LANG; - spyOn(DotContentSearchService, 'get').and.returnValue( - of({ jsonObjectView: { contentlets } }) - ); - - const query = 'hola-'; - - service.searchContentlet(query); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets - }); - - done(); - }); - - expect(DotContentSearchService.get).toHaveBeenCalledWith({ - query: `+catchall:${query} +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset: 0 - }); - }); - - it('should load the next batch of contentlets based on the offset', (done) => { - const contentlets_p1 = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); - const contentlets_p2 = [...CONTENTLETS_MOCK_WITH_LANG].splice(2, 2); - - // Spies - spyOn(service, 'updateSearch'); - spyOn(service, 'updateContentlets'); - spyOn(DotContentSearchService, 'get').and.returnValue( - of({ jsonObjectView: { contentlets: contentlets_p2 } }) - ); - - const query = 'image'; - const offset = 2; - - service.updateContentlets(contentlets_p1); - service.updateSearch(query); - service.nextBatch(offset); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: false, - loading: false, - contentlets: [...contentlets_p1, ...contentlets_p2] - }); - - done(); - }); - - expect(DotContentSearchService.get).toHaveBeenCalledWith({ - query: ` +catchall:${query}* title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, - limit: 20, - offset - }); - expect(service.updateContentlets).toHaveBeenCalledWith(contentlets_p1); - }); - - it('should set preventScroll to true when backend date is coming empty', (done) => { - const contentlets = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); - - // Spies - spyOn(service, 'updateSearch'); - spyOn(service, 'updateContentlets'); - spyOn(DotContentSearchService, 'get').and.returnValue( - of({ jsonObjectView: { contentlets: [] } }) - ); - - const query = 'image'; - const offset = 2; - - service.updateContentlets(contentlets); - service.updateSearch(query); - service.nextBatch(offset); - - service.vm$.subscribe((res) => { - expect(res).toEqual({ - preventScroll: true, - loading: false, - contentlets - }); - - done(); - }); - }); - }); + // describe('Effects', () => { + // beforeEach(() => { + // DotContentSearchService = TestBed.inject(DotContentSearchService); + // }); + + // it('should search contentlets based on a search query', (done) => { + // const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); + // // Spies + // // const loadingMock = spyOn(service, 'updateLoading'); + // spyOn(service, 'updateSearch'); + // spyOn(service, 'updateContentlets'); + // spyOn(DotContentSearchService, 'get').and.returnValue( + // of({ jsonObjectView: { contentlets } }) + // ); + + // const query = 'image'; + + // service.searchContentlet(query); + + // service.vm$.subscribe((res) => { + // expect(res).toEqual({ + // preventScroll: false, + // loading: false, + // contentlets + // }); + + // done(); + // }); + + // expect(DotContentSearchService.get).toHaveBeenCalledWith({ + // query: `+catchall:${query}* +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + // sortOrder: ESOrderDirection.ASC, + // limit: 20, + // offset: 0 + // }); + + // // First -> the value is true because we start the search + // // Second -> the value is false because we finish the search + // // expect(loadingMock.calls).toEqual([[true], [false]]); + // expect(service.updateContentlets).toHaveBeenCalledWith(contentlets); + // expect(service.updateSearch).toHaveBeenCalledWith(query); + // }); + + // it('should not add "*" when the search has a "-" ', (done) => { + // const contentlets = CONTENTLETS_MOCK_WITH_LANG; + // spyOn(DotContentSearchService, 'get').and.returnValue( + // of({ jsonObjectView: { contentlets } }) + // ); + + // const query = 'hola-'; + + // service.searchContentlet(query); + + // service.vm$.subscribe((res) => { + // expect(res).toEqual({ + // preventScroll: false, + // loading: false, + // contentlets + // }); + + // done(); + // }); + + // expect(DotContentSearchService.get).toHaveBeenCalledWith({ + // query: `+catchall:${query} +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + // sortOrder: ESOrderDirection.ASC, + // limit: 20, + // offset: 0 + // }); + // }); + + // it('should load the next batch of contentlets based on the offset', (done) => { + // const contentlets_p1 = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); + // const contentlets_p2 = [...CONTENTLETS_MOCK_WITH_LANG].splice(2, 2); + + // // Spies + // spyOn(service, 'updateSearch'); + // spyOn(service, 'updateContentlets'); + // spyOn(DotContentSearchService, 'get').and.returnValue( + // of({ jsonObjectView: { contentlets: contentlets_p2 } }) + // ); + + // const query = 'image'; + // const offset = 2; + + // service.updateContentlets(contentlets_p1); + // service.updateSearch(query); + // service.nextBatch(offset); + + // service.vm$.subscribe((res) => { + // expect(res).toEqual({ + // preventScroll: false, + // loading: false, + // contentlets: [...contentlets_p1, ...contentlets_p2] + // }); + + // done(); + // }); + // }); + + // }); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index bf69aeca4964..4b4d49ffc66a 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -5,20 +5,19 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { map, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; import { - ESOrderDirection, - EsQueryParams, DotContentSearchService, - DotLanguageService, - Languages -} from '@dotcms/ui'; + ESOrderDirectionSearch, + EsQueryParamsSearch, + DotLanguagesService +} from '@dotcms/data-access'; +import { DotCMSContentlet, DotLanguage, EditorAssetTypes } from '@dotcms/dotcms-models'; export interface DotImageSearchState { loading: boolean; preventScroll: boolean; contentlets: DotCMSContentlet[]; - languageId?: number; + languageId: number | string; search: string; assetType: EditorAssetTypes; } @@ -27,6 +26,7 @@ const defaultState: DotImageSearchState = { loading: true, preventScroll: false, contentlets: [], + languageId: '*', search: '', assetType: 'image' }; @@ -88,8 +88,15 @@ export class DotAssetSearchStore extends ComponentStore { readonly init = this.effect((origin$: Observable) => { return origin$.pipe( tap((assetType) => this.updateAssetType(assetType)), - switchMap(() => this.dotLanguageService.getLanguages()), - tap((languages) => (this.languages = languages)), + switchMap(() => + this.dotLanguagesService.get().pipe( + tap((languages) => { + languages.forEach((lang) => { + this.languages[lang.id] = lang; + }); + }) + ) + ), withLatestFrom(this.state$), switchMap(([_, state]) => this.searchContentletsRequest(this.params(state), [])) ); @@ -115,11 +122,11 @@ export class DotAssetSearchStore extends ComponentStore { ); }); - private languages: Languages; + private languages: { [key: string]: DotLanguage } = {}; constructor( private DotContentSearchService: DotContentSearchService, - private dotLanguageService: DotLanguageService + private dotLanguagesService: DotLanguagesService ) { super(defaultState); } @@ -136,16 +143,16 @@ export class DotAssetSearchStore extends ComponentStore { ); } - private params(data): EsQueryParams { + private params(data): EsQueryParamsSearch { const { search, assetType, offset = 0, languageId = '' } = data; const filter = search.includes('-') ? search : `${search}*`; const languageQuery = languageId ? `+languageId:${languageId}` : ''; return { - query: `+catchall:${filter} title:'${search}'^15 ${languageQuery} +baseType:(4 OR 9) +metadata.contenttype:${ + query: `+catchall:${filter} +title:'${search}'^15 ${languageQuery} +baseType:(4 OR 9) +metadata.contenttype:${ assetType || '' }/* +deleted:false +working:true`, - sortOrder: ESOrderDirection.ASC, + sortOrder: ESOrderDirectionSearch.ASC, limit: 20, offset }; diff --git a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts deleted file mode 100644 index e585858c5689..000000000000 --- a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { DotLanguageService } from './dot-language.service'; - -describe('DotLanguageService', () => { - let service: DotLanguageService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(DotLanguageService); - }); - - it('should be created', () => { - expect(service).toBeTruthy(); - }); -}); diff --git a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts b/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts deleted file mode 100644 index c4653637072a..000000000000 --- a/core-web/libs/ui/src/lib/services/dot-language/dot-language.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Observable, of } from 'rxjs'; - -import { HttpClient, HttpHeaders } from '@angular/common/http'; -import { Injectable } from '@angular/core'; - -import { map, pluck } from 'rxjs/operators'; - -interface DotLanguage { - country: string; - countryCode: string; - defaultLanguage: boolean; - id: number; - language: string; - languageCode: string; -} - -export interface Languages { - [key: string]: DotLanguage; -} - -@Injectable({ - providedIn: 'root' -}) -export class DotLanguageService { - private languages: Languages; - - constructor(private http: HttpClient) {} - - get defaultHeaders() { - const headers = new HttpHeaders(); - headers.set('Accept', '*/*').set('Content-Type', 'application/json'); - - return headers; - } - - /** - * Get an object of DotLanguage whose keys are the languageId. - * - * @return {*} {Observable} - * @memberof DotLanguageService - */ - getLanguages(): Observable { - if (this.languages) { - return of(this.languages); - } - - return this.http - .get(`/api/v2/languages`, { - headers: this.defaultHeaders - }) - .pipe( - pluck('entity'), - map((lang: DotLanguage[]) => { - const dotLang: Languages = this.getDotLanguageObject(lang); - - this.languages = dotLang; - - return dotLang; - }) - ); - } - - /** - * Transform an array of languages into an object - * using the language id as an object key. - * @private - * @param {DotLanguage[]} lang - * @return {*} {Languages} - * @memberof DotLanguageService - */ - private getDotLanguageObject(lang: DotLanguage[]): Languages { - return lang.reduce((obj, lang) => Object.assign(obj, { [lang.id]: lang }), {}); - } -} diff --git a/core-web/libs/utils-testing/src/lib/dot-contentlet.mock.ts b/core-web/libs/utils-testing/src/lib/dot-contentlet.mock.ts index 270fa42ffd29..38d795ce88e4 100644 --- a/core-web/libs/utils-testing/src/lib/dot-contentlet.mock.ts +++ b/core-web/libs/utils-testing/src/lib/dot-contentlet.mock.ts @@ -49,6 +49,40 @@ export const mockDotContentlet: StructureTypeView[] = [ } ]; +export const EMPTY_CONTENTLET: DotCMSContentlet = { + inode: '14dd5ad9-55ae-42a8-a5a7-e259b6d0901a', + variantId: 'DEFAULT', + locked: false, + stInode: 'd5ea385d-32ee-4f35-8172-d37f58d9cd7a', + contentType: 'Image', + height: 4000, + identifier: '93ca45e0-06d2-4eef-be1d-79bd6bf0fc99', + hasTitleImage: true, + sortOrder: 0, + hostName: 'demo.dotcms.com', + extension: 'jpg', + isContent: true, + baseType: 'FILEASSETS', + archived: false, + working: true, + live: true, + isContentlet: true, + languageId: 1, + titleImage: 'fileAsset', + hasLiveVersion: true, + deleted: false, + folder: '', + host: '', + modDate: '', + modUser: '', + modUserName: '', + owner: '', + title: '', + url: '', + contentTypeIcon: 'assessment', + __icon__: 'Icon' +}; + export const URL_MAP_CONTENTLET: DotCMSContentlet = { URL_MAP_FOR_CONTENT: '/blog/post/french-polynesia-everything-you-need-to-know', archived: false, @@ -112,3 +146,11 @@ export const URL_MAP_CONTENTLET: DotCMSContentlet = { urlTitle: 'french-polynesia-everything-you-need-to-know', working: true }; + +export const EMPTY_IMAGE_CONTENTLET: DotCMSContentlet = { + mimeType: 'image/jpeg', + type: 'file_asset', + fileAssetVersion: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + fileAsset: 'https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg', + ...EMPTY_CONTENTLET +}; From 46f68062b3980dc8cd182586c6729e521982527e Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 14:48:19 -0400 Subject: [PATCH 10/26] chore: cover DotWYSIWYGField Component tests --- .../dot-wysiwyg-field.component.spec.ts | 41 +++++++++++++++++-- .../dot-wysiwyg-field.component.ts | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts index 4e30beac1231..cdae0f9a61e9 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts @@ -1,6 +1,8 @@ +import { expect } from '@jest/globals'; import { Spectator, createComponentFactory } from '@ngneat/spectator'; import { EditorComponent, EditorModule } from '@tinymce/tinymce-angular'; -import { MockComponent } from 'ng-mocks'; +import { MockComponent, MockService } from 'ng-mocks'; +import { Editor } from 'tinymce'; import { ControlContainer, @@ -9,28 +11,48 @@ import { ReactiveFormsModule } from '@angular/forms'; +import { DialogService } from 'primeng/dynamicdialog'; + +import { DotUploadFileService } from '@dotcms/data-access'; + import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field.component'; +import { DotWysiwygPluginService } from './dot-wysiwyg-plugin/dot-wysiwyg-plugin.service'; import { WYSIWYG_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; const ALL_PLUGINS = 'advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template'; const ALL_TOOLBAR_ITEMS = - 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image hr | preview | validation media | forecolor dotimageclipboard backcolor emoticons'; + 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | dotAddImage | link hr | preview | validation media | forecolor backcolor emoticons'; describe('DotWYSIWYGFieldComponent', () => { let spectator: Spectator; + let dotWysiwygPluginService: DotWysiwygPluginService; + const createComponent = createComponentFactory({ component: DotWYSIWYGFieldComponent, imports: [EditorModule, FormsModule, ReactiveFormsModule], declarations: [MockComponent(EditorComponent)], componentViewProviders: [ + { + provide: DotWysiwygPluginService, + useValue: { + initializePlugins: jest.fn() + } + }, { provide: ControlContainer, useValue: createFormGroupDirectiveMock() } ], - providers: [FormGroupDirective] + providers: [ + FormGroupDirective, + DialogService, + { + provide: DotUploadFileService, + useValue: MockService(DotUploadFileService) + } + ] }); beforeEach(() => { @@ -39,6 +61,8 @@ describe('DotWYSIWYGFieldComponent', () => { field: WYSIWYG_MOCK } }); + + dotWysiwygPluginService = spectator.inject(DotWysiwygPluginService, true); }); it('should instance WYSIWYG editor and set the correct plugins and toolbar items', () => { @@ -46,5 +70,16 @@ describe('DotWYSIWYGFieldComponent', () => { expect(editor).toBeTruthy(); expect(editor.plugins).toEqual(ALL_PLUGINS); expect(editor.toolbar).toEqual(ALL_TOOLBAR_ITEMS); + expect(editor.init).toEqual({ + setup: expect.any(Function) + }); + }); + + it('should initialize Plugins when the setup method is called', () => { + const spy = jest.spyOn(dotWysiwygPluginService, 'initializePlugins'); + const editor = spectator.query(EditorComponent); + const mockEditor = {} as Editor; + editor.init.setup(mockEditor); + expect(spy).toHaveBeenCalledWith(mockEditor); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index ed278258bcfd..67afffa42799 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -51,6 +51,6 @@ export class DotWYSIWYGFieldComponent { ); protected readonly toolbar = signal( - 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link dotAddImage hr | preview | validation media | forecolor backcolor emoticons' + 'paste print textpattern | insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | dotAddImage | link hr | preview | validation media | forecolor backcolor emoticons' ); } From ed28bc7365376ed316dc406bcbe560089fedc9be Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 16:41:02 -0400 Subject: [PATCH 11/26] chore: cover DotWysiwygPlugin Service tests --- .../dot-wysiwyg-plugin.service.spec.ts | 191 +++++++++++++++++- .../dot-wysiwyg-plugin.service.ts | 2 +- 2 files changed, 186 insertions(+), 7 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts index 1d286c98d20a..0c39f5d2a329 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts @@ -1,16 +1,195 @@ -import { TestBed } from '@angular/core/testing'; +import { expect } from '@jest/globals'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; + +import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { DotUploadFileService } from '@dotcms/data-access'; +import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { DotAssetSearchDialogComponent } from '@dotcms/ui'; +import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; import { DotWysiwygPluginService } from './dot-wysiwyg-plugin.service'; +import { formatFotImageNode } from './utils/editor.utils'; + +/** + * This Mock is used to check we are sending the correct configuration to the editor + * No need to mock all the methods and properties of the Editor + * Some methods are customized to check the configuration + */ +class MockEditor { + private customButtons = {}; + private events = {}; + + ui = { + registry: { + getAll: () => { + return { + buttons: this.customButtons + }; + }, + addButton: (name, config) => { + this.customButtons[name] = config; + } + } + }; + + on = (name, fn) => { + if (!this.events[name]) { + this.events[name] = [fn]; + + return; + } + + this.events[name].push(fn); + }; + + fakeOnCall = (name, event) => { + this.events[name].forEach((fn) => fn(event)); + }; + + insertContent = jest.fn(); +} describe('DotWysiwygPluginService', () => { - let service: DotWysiwygPluginService; + let spectator: SpectatorService; + let dialogService: DialogService; + let dotUploadFileService: DotUploadFileService; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let editor: any; + + const createService = createServiceFactory({ + service: DotWysiwygPluginService, + declarations: [MockComponent(DotAssetSearchDialogComponent)], + providers: [ + DialogService, + { + provide: DotUploadFileService, + useValue: { + publishContent: jest.fn() + } + } + ] + }); beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(DotWysiwygPluginService); + spectator = createService(); + dialogService = spectator.inject(DialogService); + dotUploadFileService = spectator.inject(DotUploadFileService); + editor = new MockEditor(); }); - it('should be created', () => { - expect(service).toBeTruthy(); + describe('dotImagePlugin', () => { + it('should configure the dotAddImage button', () => { + const spyButton = jest.spyOn(editor.ui.registry, 'addButton'); + const spyOn = jest.spyOn(editor, 'on'); + + spectator.service.initializePlugins(editor); + + expect(spyOn).toHaveBeenCalledWith('drop', expect.any(Function)); + expect(spyButton).toHaveBeenCalledWith('dotAddImage', { + text: 'Dot Image', + icon: 'image', + onAction: expect.any(Function) + }); + }); + + it('should open the dialog when the button is clicked', () => { + const spyDialog = jest.spyOn(dialogService, 'open').mockReturnValue({ + onClose: of(EMPTY_CONTENTLET) + } as DynamicDialogRef); + + const spyEditorInserContent = jest.spyOn(editor, 'insertContent'); + + spectator.service.initializePlugins(editor); + + const button = editor.ui.registry.getAll().buttons['dotAddImage']; + const dialogConfig = { + header: 'Add Image', + width: '800px', + height: '500px', + contentStyle: { padding: 0 }, + data: { + assetType: 'image' + } + }; + + // Simulate the button click + button.onAction(); + + expect(spyDialog).toHaveBeenCalledWith(DotAssetSearchDialogComponent, dialogConfig); + expect(spyEditorInserContent).toHaveBeenCalledWith( + formatFotImageNode(EMPTY_CONTENTLET) + ); + }); + + it('should upload the image when dropped', () => { + const uploadRespMock: unknown = [{ '1234': EMPTY_CONTENTLET }]; + const spyUpload = jest + .spyOn(dotUploadFileService, 'publishContent') + .mockReturnValue(of(uploadRespMock as DotCMSContentlet[])); + const spyEditorInserContent = jest.spyOn(editor, 'insertContent'); + + spectator.service.initializePlugins(editor); + + const dropEvent = { + dataTransfer: { + files: [ + { + type: 'image/png' + } + ] + }, + preventDefault: jest.fn(), + stopImmediatePropagation: jest.fn(), + stopPropagation: jest.fn() + }; + + editor.fakeOnCall('drop', dropEvent); + + expect(spyUpload).toHaveBeenCalledWith({ + data: dropEvent.dataTransfer.files[0] + }); + expect(spyEditorInserContent).toHaveBeenCalledWith( + formatFotImageNode(EMPTY_CONTENTLET) + ); + + expect(dropEvent.preventDefault).toHaveBeenCalled(); + expect(dropEvent.stopImmediatePropagation).toHaveBeenCalled(); + expect(dropEvent.stopPropagation).toHaveBeenCalled(); + }); + + it('should not upload the image when dropped', () => { + const uploadRespMock: unknown = [{ '1234': EMPTY_CONTENTLET }]; + const spyUpload = jest + .spyOn(dotUploadFileService, 'publishContent') + .mockReturnValue(of(uploadRespMock as DotCMSContentlet[])); + const spyEditorInserContent = jest.spyOn(editor, 'insertContent'); + + spectator.service.initializePlugins(editor); + + const dropEvent = { + dataTransfer: { + files: [ + { + type: 'video/mp4' + } + ] + }, + preventDefault: jest.fn(), + stopImmediatePropagation: jest.fn(), + stopPropagation: jest.fn() + }; + + editor.fakeOnCall('drop', dropEvent); + + expect(spyUpload).not.toHaveBeenCalledWith({ + data: dropEvent.dataTransfer.files[0] + }); + expect(spyEditorInserContent).not.toHaveBeenCalledWith( + formatFotImageNode(EMPTY_CONTENTLET) + ); + }); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index ec345c5f6d77..9b16e4a4da2e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -71,7 +71,7 @@ export class DotWysiwygPluginService { .publishContent({ data: file }) - .subscribe((contentlets: DotCMSContentlet[]) => { + .subscribe((contentlets) => { const data = contentlets[0]; const asset = data[Object.keys(data)[0]]; editor.insertContent(formatFotImageNode(asset)); From 1d628e53200858d650706a93e126fa2258e78817 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 16:53:40 -0400 Subject: [PATCH 12/26] chore: cover formatDotImageNode Util tests --- .../dot-wysiwyg-plugin.service.spec.ts | 8 ++--- .../dot-wysiwyg-plugin.service.ts | 7 ++--- .../utils/editor.utils.spec.ts | 29 +++++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts index 0c39f5d2a329..e0a933bd625b 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts @@ -11,7 +11,7 @@ import { DotAssetSearchDialogComponent } from '@dotcms/ui'; import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; import { DotWysiwygPluginService } from './dot-wysiwyg-plugin.service'; -import { formatFotImageNode } from './utils/editor.utils'; +import { formatDotImageNode } from './utils/editor.utils'; /** * This Mock is used to check we are sending the correct configuration to the editor @@ -120,7 +120,7 @@ describe('DotWysiwygPluginService', () => { expect(spyDialog).toHaveBeenCalledWith(DotAssetSearchDialogComponent, dialogConfig); expect(spyEditorInserContent).toHaveBeenCalledWith( - formatFotImageNode(EMPTY_CONTENTLET) + formatDotImageNode(EMPTY_CONTENTLET) ); }); @@ -152,7 +152,7 @@ describe('DotWysiwygPluginService', () => { data: dropEvent.dataTransfer.files[0] }); expect(spyEditorInserContent).toHaveBeenCalledWith( - formatFotImageNode(EMPTY_CONTENTLET) + formatDotImageNode(EMPTY_CONTENTLET) ); expect(dropEvent.preventDefault).toHaveBeenCalled(); @@ -188,7 +188,7 @@ describe('DotWysiwygPluginService', () => { data: dropEvent.dataTransfer.files[0] }); expect(spyEditorInserContent).not.toHaveBeenCalledWith( - formatFotImageNode(EMPTY_CONTENTLET) + formatDotImageNode(EMPTY_CONTENTLET) ); }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index 9b16e4a4da2e..8e8e9ce6abd4 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -10,7 +10,7 @@ import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { DotAssetSearchDialogComponent } from '@dotcms/ui'; -import { formatFotImageNode } from './utils/editor.utils'; +import { formatDotImageNode } from './utils/editor.utils'; @Injectable() export class DotWysiwygPluginService { @@ -44,7 +44,7 @@ export class DotWysiwygPluginService { filter((asset) => !!asset) ) .subscribe((asset: DotCMSContentlet) => - editor.insertContent(formatFotImageNode(asset)) + editor.insertContent(formatDotImageNode(asset)) ); }); } @@ -55,7 +55,6 @@ export class DotWysiwygPluginService { private dotFilePlugin(editor: Editor) { editor.on('drop', async (event) => { - // get image const file = event.dataTransfer.files[0]; // Check if the file is an image @@ -74,7 +73,7 @@ export class DotWysiwygPluginService { .subscribe((contentlets) => { const data = contentlets[0]; const asset = data[Object.keys(data)[0]]; - editor.insertContent(formatFotImageNode(asset)); + editor.insertContent(formatDotImageNode(asset)); }); }); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts new file mode 100644 index 000000000000..96c102f2227d --- /dev/null +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts @@ -0,0 +1,29 @@ +import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; + +import { formatDotImageNode } from './editor.utils'; + +describe('formatDotImageNode', () => { + it('should return formatted image node', () => { + const asset: DotCMSContentlet = { + ...EMPTY_CONTENTLET, + assetVersion: 'version', + asset: 'asset', + title: 'title', + titleImage: 'titleImage', + inode: 'inode', + identifier: 'identifier' + }; + + const result = formatDotImageNode(asset); + + expect(result).toBe( + `${asset.title}` + ); + }); +}); From f89704f602a478d6e852e187b55d841e211c50da Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 17:03:58 -0400 Subject: [PATCH 13/26] chore: clean up --- .../dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts index a34018ebf89f..7945c8c26b82 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts @@ -1,6 +1,6 @@ import { DotCMSContentlet } from '@dotcms/dotcms-models'; -export const formatFotImageNode = (asset: DotCMSContentlet) => { +export const formatDotImageNode = (asset: DotCMSContentlet) => { return `${asset.title} Date: Wed, 20 Mar 2024 21:54:14 -0400 Subject: [PATCH 14/26] chore: cover DotContentSearch Service tests --- .../dot-content-search.service.spec.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.spec.ts diff --git a/core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.spec.ts b/core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.spec.ts new file mode 100644 index 000000000000..b6ffb41e7c96 --- /dev/null +++ b/core-web/libs/data-access/src/lib/dot-content-search/dot-content-search.service.spec.ts @@ -0,0 +1,36 @@ +import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; + +import { DotContentSearchService, EsQueryParamsSearch } from './dot-content-search.service'; + +describe('DotContentSearchService', () => { + let spectator: SpectatorHttp; + const createHttp = createHttpFactory(DotContentSearchService); + + beforeEach(() => (spectator = createHttp())); + + it('should call the search method with the right EsQueryParamsSearch', (done) => { + const params: EsQueryParamsSearch = { + query: 'test', + limit: 10, + offset: 0 + }; + + spectator.service.get(params).subscribe((resp) => { + expect(resp).toEqual({ contentlets: [] }); + done(); + }); + + const req = spectator.expectOne('/api/content/_search', HttpMethod.POST); + expect(req.request.body).toEqual({ + query: 'test', + sort: 'score,modDate desc', + limit: 10, + offset: 0 + }); + req.flush({ + entity: { + contentlets: [] + } + }); + }); +}); From 726df382770337aac49e3ff13211433c71807bfa Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Wed, 20 Mar 2024 22:01:31 -0400 Subject: [PATCH 15/26] fix: lint --- .../lib/extensions/floating-button/floating-button.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts index 00e7f8fda025..5cf86ffa8336 100644 --- a/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts +++ b/core-web/libs/block-editor/src/lib/extensions/floating-button/floating-button.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { FileStatus } from '../../../../../data-access/src/lib/dot-upload-file/dot-upload-file.service'; +import { FileStatus } from '@dotcms/data-access'; @Component({ selector: 'dot-floating-button', From 3b4ff4139b4c1381bdccfdeea377b30d023f4485 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 12:12:43 -0400 Subject: [PATCH 16/26] chore: fix import in DotBlockEditor Component --- .../block-editor/src/lib/block-editor.module.ts | 16 +++++++++++++--- .../asset-form/asset-form.component.scss | 4 ++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core-web/libs/block-editor/src/lib/block-editor.module.ts b/core-web/libs/block-editor/src/lib/block-editor.module.ts index 9ba44b0d4545..39be1706b76d 100644 --- a/core-web/libs/block-editor/src/lib/block-editor.module.ts +++ b/core-web/libs/block-editor/src/lib/block-editor.module.ts @@ -6,9 +6,15 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ConfirmationService } from 'primeng/api'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; -import { DotMessageService, DotUploadFileService } from '@dotcms/data-access'; +import { + DotContentSearchService, + DotLanguagesService, + DotMessageService, + DotPropertiesService, + DotUploadFileService +} from '@dotcms/data-access'; import { LoggerService, StringUtils } from '@dotcms/dotcms-js'; -import { DotFieldRequiredDirective, DotMessagePipe } from '@dotcms/ui'; +import { DotAssetSearchComponent, DotFieldRequiredDirective, DotMessagePipe } from '@dotcms/ui'; //Editor import { DotBlockEditorComponent } from './components/dot-block-editor/dot-block-editor.component'; @@ -49,7 +55,8 @@ const initTranslations = (dotMessageService: DotMessageService) => { UploadPlaceholderComponent, DotMessagePipe, ConfirmDialogModule, - AIImagePromptComponent + AIImagePromptComponent, + DotAssetSearchComponent ], declarations: [ EditorDirective, @@ -73,6 +80,9 @@ const initTranslations = (dotMessageService: DotMessageService) => { StringUtils, DotAiService, ConfirmationService, + DotPropertiesService, + DotContentSearchService, + DotLanguagesService, { provide: APP_INITIALIZER, useFactory: initTranslations, diff --git a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.component.scss b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.component.scss index d26ff251441e..90d774beef7e 100644 --- a/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.component.scss +++ b/core-web/libs/block-editor/src/lib/extensions/asset-form/asset-form.component.scss @@ -26,4 +26,8 @@ .p-tabview-panel { height: 25rem; } + + .p-tabview-panels { + padding: 0; + } } From fab4272e1edb40466909ad0888a6e42755e8b7a6 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 13:39:23 -0400 Subject: [PATCH 17/26] chore: add context menu for images --- .../dot-wysiwyg-field.component.spec.ts | 5 ++++- .../dot-wysiwyg-field/dot-wysiwyg-field.component.ts | 9 +++++---- .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts | 3 +-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts index b2388b32a4c8..318cc2215519 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts @@ -23,7 +23,7 @@ import { WYSIWYG_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; const ALL_PLUGINS = 'advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template'; const ALL_TOOLBAR_ITEMS = - 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage'; + 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage'; describe('DotWYSIWYGFieldComponent', () => { let spectator: Spectator; @@ -72,6 +72,9 @@ describe('DotWYSIWYGFieldComponent', () => { expect(editor.toolbar).toEqual(ALL_TOOLBAR_ITEMS); expect(editor.init).toEqual({ menubar: false, + image_caption: true, + image_advtab: true, + contextmenu: 'align link image', setup: expect.any(Function) }); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts index 5ef90fe9eee4..d6dbdc5c57c1 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts @@ -42,9 +42,10 @@ export class DotWYSIWYGFieldComponent { protected readonly init: RawEditorOptions = { menubar: false, - setup: (editor) => { - this.dotWysiwygPluginService.initializePlugins(editor); - } + image_caption: true, + image_advtab: true, + contextmenu: 'align link image', + setup: (editor) => this.dotWysiwygPluginService.initializePlugins(editor) }; protected readonly plugins = signal( @@ -52,6 +53,6 @@ export class DotWYSIWYGFieldComponent { ); protected readonly toolbar = signal( - 'insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage' + 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage' ); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index 8e8e9ce6abd4..e715903b2a4c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -24,12 +24,11 @@ export class DotWysiwygPluginService { private dotImagePlugin(editor: Editor): void { editor.ui.registry.addButton('dotAddImage', { - text: 'Dot Image', icon: 'image', onAction: () => { this.ngZone.run(() => { const ref = this.dialogService.open(DotAssetSearchDialogComponent, { - header: 'Add Image', + header: 'Insert Image', width: '800px', height: '500px', contentStyle: { padding: 0 }, From 99b5c48939267cb52ee1155ccba4a53cc3477448 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 13:48:50 -0400 Subject: [PATCH 18/26] chore: rename component to DotEditContentWYSIWYGField --- .../dot-edit-content-field.component.spec.ts | 2 +- .../dot-edit-content-field.component.ts | 4 ++-- .../src/lib/fields/dot-edit-content-fields.module.ts | 6 +++--- .../dot-edit-content-wysiwyg-field.component.html} | 0 .../dot-edit-content-wysiwyg-field.component.scss} | 0 .../dot-edit-content-wysiwyg-field.component.spec.ts} | 10 +++++----- .../dot-edit-content-wysiwyg-field.component.ts} | 8 ++++---- .../dot-wysiwyg-plugin.service.spec.ts | 0 .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts | 0 .../dot-wysiwyg-plugin/utils/editor.utils.spec.ts | 0 .../dot-wysiwyg-plugin/utils/editor.utils.ts | 0 11 files changed, 15 insertions(+), 15 deletions(-) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field/dot-wysiwyg-field.component.html => dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.html} (100%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field/dot-wysiwyg-field.component.scss => dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.scss} (100%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts => dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts} (89%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field/dot-wysiwyg-field.component.ts => dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts} (88%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field => dot-edit-content-wysiwyg-field}/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts (100%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field => dot-edit-content-wysiwyg-field}/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts (100%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field => dot-edit-content-wysiwyg-field}/dot-wysiwyg-plugin/utils/editor.utils.spec.ts (100%) rename core-web/libs/edit-content/src/lib/fields/{dot-wysiwyg-field => dot-edit-content-wysiwyg-field}/dot-wysiwyg-plugin/utils/editor.utils.ts (100%) diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts index 333f08b83358..5b572297f2fc 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts @@ -31,7 +31,7 @@ import { DotEditContentSelectFieldComponent } from '../../fields/dot-edit-conten import { DotEditContentTagFieldComponent } from '../../fields/dot-edit-content-tag-field/dot-edit-content-tag-field.component'; import { DotEditContentTextAreaComponent } from '../../fields/dot-edit-content-text-area/dot-edit-content-text-area.component'; import { DotEditContentTextFieldComponent } from '../../fields/dot-edit-content-text-field/dot-edit-content-text-field.component'; -import { DotWYSIWYGFieldComponent } from '../../fields/dot-wysiwyg-field/dot-wysiwyg-field.component'; +import { DotWYSIWYGFieldComponent } from '../../fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; import { DotEditContentService } from '../../services/dot-edit-content.service'; import { diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts index 34ebe3afe029..58cd844fc6cb 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.ts @@ -9,7 +9,7 @@ import { DotFieldRequiredDirective } from '@dotcms/ui'; import { DotEditContentBinaryFieldComponent } from '../../fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component'; import { DotEditContentFieldsModule } from '../../fields/dot-edit-content-fields.module'; import { DotEditContentKeyValueComponent } from '../../fields/dot-edit-content-key-value/dot-edit-content-key-value.component'; -import { DotWYSIWYGFieldComponent } from '../../fields/dot-wysiwyg-field/dot-wysiwyg-field.component'; +import { DotEditContentWYSIWYGFieldComponent } from '../../fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; import { CALENDAR_FIELD_TYPES } from '../../models/dot-edit-content-field.constant'; import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; @@ -35,7 +35,7 @@ import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; BlockEditorModule, DotEditContentBinaryFieldComponent, DotEditContentKeyValueComponent, - DotWYSIWYGFieldComponent + DotEditContentWYSIWYGFieldComponent ] }) export class DotEditContentFieldComponent { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts index 4d4a85de7d74..42f186a479d5 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-fields.module.ts @@ -11,7 +11,7 @@ import { DotEditContentSelectFieldComponent } from './dot-edit-content-select-fi import { DotEditContentTagFieldComponent } from './dot-edit-content-tag-field/dot-edit-content-tag-field.component'; import { DotEditContentTextAreaComponent } from './dot-edit-content-text-area/dot-edit-content-text-area.component'; import { DotEditContentTextFieldComponent } from './dot-edit-content-text-field/dot-edit-content-text-field.component'; -import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field/dot-wysiwyg-field.component'; +import { DotEditContentWYSIWYGFieldComponent } from './dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; @NgModule({ declarations: [], @@ -27,7 +27,7 @@ import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field/dot-wysiwyg-field. DotEditContentBinaryFieldComponent, DotEditContentJsonFieldComponent, DotEditContentCustomFieldComponent, - DotWYSIWYGFieldComponent + DotEditContentWYSIWYGFieldComponent ], exports: [ DotEditContentTextAreaComponent, @@ -41,7 +41,7 @@ import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field/dot-wysiwyg-field. DotEditContentBinaryFieldComponent, DotEditContentJsonFieldComponent, DotEditContentCustomFieldComponent, - DotWYSIWYGFieldComponent + DotEditContentWYSIWYGFieldComponent ] }) export class DotEditContentFieldsModule {} diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.html similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.html rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.html diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.scss similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.scss rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.scss diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts similarity index 89% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts index 318cc2215519..fdc7836247bc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.spec.ts @@ -15,7 +15,7 @@ import { DialogService } from 'primeng/dynamicdialog'; import { DotUploadFileService } from '@dotcms/data-access'; -import { DotWYSIWYGFieldComponent } from './dot-wysiwyg-field.component'; +import { DotEditContentWYSIWYGFieldComponent } from './dot-edit-content-wysiwyg-field.component'; import { DotWysiwygPluginService } from './dot-wysiwyg-plugin/dot-wysiwyg-plugin.service'; import { WYSIWYG_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; @@ -23,14 +23,14 @@ import { WYSIWYG_MOCK, createFormGroupDirectiveMock } from '../../utils/mocks'; const ALL_PLUGINS = 'advlist autolink lists link image charmap preview anchor pagebreak searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table directionality emoticons template'; const ALL_TOOLBAR_ITEMS = - 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage'; + 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent dotAddImage hr'; -describe('DotWYSIWYGFieldComponent', () => { - let spectator: Spectator; +describe('DotEditContentWYSIWYGFieldComponent', () => { + let spectator: Spectator; let dotWysiwygPluginService: DotWysiwygPluginService; const createComponent = createComponentFactory({ - component: DotWYSIWYGFieldComponent, + component: DotEditContentWYSIWYGFieldComponent, imports: [EditorModule, FormsModule, ReactiveFormsModule], declarations: [MockComponent(EditorComponent)], componentViewProviders: [ diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts similarity index 88% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts index d6dbdc5c57c1..3e465c420d0f 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts @@ -20,8 +20,8 @@ declare global { selector: 'dot-wysiwyg-field', standalone: true, imports: [EditorModule, FormsModule, ReactiveFormsModule], - templateUrl: './dot-wysiwyg-field.component.html', - styleUrl: './dot-wysiwyg-field.component.scss', + templateUrl: './dot-edit-content-wysiwyg-field.component.html', + styleUrl: './dot-edit-content-wysiwyg-field.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, providers: [ DialogService, @@ -35,7 +35,7 @@ declare global { } ] }) -export class DotWYSIWYGFieldComponent { +export class DotEditContentWYSIWYGFieldComponent { @Input() field!: DotCMSContentTypeField; private readonly dotWysiwygPluginService = inject(DotWysiwygPluginService); @@ -53,6 +53,6 @@ export class DotWYSIWYGFieldComponent { ); protected readonly toolbar = signal( - 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent hr | dotAddImage' + 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | dotAddImage bullist numlist outdent indent hr' ); } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.spec.ts diff --git a/core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts similarity index 100% rename from core-web/libs/edit-content/src/lib/fields/dot-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/utils/editor.utils.ts From c4e67762d30acf6a84af9e7d94df02ee0d22da5a Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 13:55:07 -0400 Subject: [PATCH 19/26] chore: clean up --- .../dot-edit-content-wysiwyg-field.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts index 3e465c420d0f..2e3a0c2d9d40 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts @@ -53,6 +53,6 @@ export class DotEditContentWYSIWYGFieldComponent { ); protected readonly toolbar = signal( - 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | dotAddImage bullist numlist outdent indent hr' + 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent dotAddImage hr' ); } From ba0646b6000af223f6390d8b9103ae8988363a0f Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 16:16:29 -0400 Subject: [PATCH 20/26] chore: clean up --- .../dot-upload-file.service.spec.ts | 17 ++++++++++++++--- .../dot-asset-card-list.component.ts | 2 +- .../dot-asset-search-dialog.component.ts | 1 - .../store/dot-asset-search.store.ts | 3 +-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts b/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts index 19f9a0fe3913..e49dbf3f3ae4 100644 --- a/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts +++ b/core-web/libs/data-access/src/lib/dot-upload-file/dot-upload-file.service.spec.ts @@ -1,13 +1,24 @@ -import { TestBed } from '@angular/core/testing'; +import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; + +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { DotUploadFileService } from './dot-upload-file.service'; +import { DotUploadService } from '../dot-upload/dot-upload.service'; + describe('DotUploadFileService', () => { + let spectator: SpectatorService; let service: DotUploadFileService; + const createService = createServiceFactory({ + service: DotUploadFileService, + imports: [HttpClientTestingModule], + providers: [DotUploadService] + }); + beforeEach(() => { - TestBed.configureTestingModule({ teardown: { destroyAfterEach: false } }); - service = TestBed.inject(DotUploadFileService); + spectator = createService(); + service = spectator.service; }); it('should be created', () => { diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts index f432e573452d..bc531d04b32a 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts @@ -40,7 +40,7 @@ export class DotAssetCardListComponent { private domSanitizer: DomSanitizer = inject(DomSanitizer); public loadingItems = [null, null, null]; - public icon = this.domSanitizer.bypassSecurityTrustHtml(squarePlus); + public icon = this.domSanitizer.bypassSecurityTrustResourceUrl(squarePlus); private _itemRows: DotCMSContentlet[][] = []; private _offset = 0; diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts index c66efcdddd1b..ddd50c066295 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.ts @@ -10,7 +10,6 @@ import { DotAssetSearchComponent } from '@dotcms/ui'; selector: 'dot-asset-search-dialog', standalone: true, imports: [CommonModule, DotAssetSearchComponent], - providers: [DynamicDialogRef, DynamicDialogConfig], templateUrl: './dot-asset-search-dialog.component.html', styleUrl: './dot-asset-search-dialog.component.scss', changeDetection: ChangeDetectionStrategy.OnPush diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index 4b4d49ffc66a..03243b3aa43d 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -146,10 +146,9 @@ export class DotAssetSearchStore extends ComponentStore { private params(data): EsQueryParamsSearch { const { search, assetType, offset = 0, languageId = '' } = data; const filter = search.includes('-') ? search : `${search}*`; - const languageQuery = languageId ? `+languageId:${languageId}` : ''; return { - query: `+catchall:${filter} +title:'${search}'^15 ${languageQuery} +baseType:(4 OR 9) +metadata.contenttype:${ + query: `+catchall:${filter} title:'${search}'^15 +languageId:${languageId} +baseType:(4 OR 9) +metadata.contenttype:${ assetType || '' }/* +deleted:false +working:true`, sortOrder: ESOrderDirectionSearch.ASC, From 9923791ca9d6b573d31c0eb158288cb84a1611f8 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 18:48:17 -0400 Subject: [PATCH 21/26] chore: cover DotAssetSearchComponent tests --- .../dot-asset-card-list.component.ts | 16 +++-- .../dot-asset-card-skeleton.component.html | 8 +-- .../dot-asset-card-skeleton.component.spec.ts | 29 ++++++-- .../dot-asset-card.component.html | 9 ++- .../dot-asset-card.component.spec.ts | 47 ++++++++++-- .../dot-asset-card.component.ts | 28 +------- .../dot-asset-search.component.html | 9 ++- .../dot-asset-search.component.spec.ts | 71 ++++++++++++++++--- .../dot-asset-search.component.ts | 2 +- .../store/dot-asset-search.store.ts | 11 +++ 10 files changed, 171 insertions(+), 59 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts index bc531d04b32a..4a51c189c4ed 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-list/dot-asset-card-list.component.ts @@ -4,7 +4,9 @@ import { Component, EventEmitter, Input, + OnChanges, Output, + SimpleChanges, inject } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; @@ -27,16 +29,13 @@ const squarePlus = imports: [CommonModule, ScrollerModule, DotAssetCardComponent, DotAssetCardSkeletonComponent], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotAssetCardListComponent { +export class DotAssetCardListComponent implements OnChanges { @Output() nextBatch: EventEmitter = new EventEmitter(); @Output() selectedItem: EventEmitter = new EventEmitter(); @Input() done = false; @Input() loading = true; - @Input() set contentlets(value: DotCMSContentlet[]) { - this._offset = value?.length || 0; - this._itemRows = this.createRowItem(value); - } + @Input() contentlets: DotCMSContentlet[] = []; private domSanitizer: DomSanitizer = inject(DomSanitizer); public loadingItems = [null, null, null]; @@ -49,6 +48,13 @@ export class DotAssetCardListComponent { return [...this._itemRows]; } + ngOnChanges(changes: SimpleChanges) { + if (changes.contentlets) { + this._offset = this.contentlets?.length || 0; + this._itemRows = this.createRowItem(this.contentlets); + } + } + onScrollIndexChange(e: { first: number; last: number }) { if (this.done) { return; diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html index 3e02dc251bbb..38ed289b7b79 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.html @@ -1,12 +1,12 @@ - + - +
- - + +
diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts index 3f7c2fb1ce13..126460fa44c2 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card-skeleton/dot-asset-card-skeleton.component.spec.ts @@ -1,25 +1,42 @@ import { createComponentFactory, Spectator } from '@ngneat/spectator'; import { CardModule } from 'primeng/card'; -import { SkeletonModule } from 'primeng/skeleton'; +import { Skeleton, SkeletonModule } from 'primeng/skeleton'; import { DotAssetCardSkeletonComponent } from './dot-asset-card-skeleton.component'; describe('DotAssetCardSkeletonComponent', () => { let spectator: Spectator; - let component: DotAssetCardSkeletonComponent; const createComponent = createComponentFactory({ component: DotAssetCardSkeletonComponent, - imports: [CardModule, SkeletonModule] + imports: [CardModule, SkeletonModule], + declarations: [Skeleton] }); beforeEach(() => { spectator = createComponent(); - component = spectator.component; }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should have the four skelestons components', () => { + const skeletons = spectator.queryAll(Skeleton); + expect(skeletons).toHaveLength(4); + }); + + it('should have the right inputs for each p-skeleton', () => { + const headerSkeleton = spectator.query('[data-testId="p-skeleton-header"]'); + expect(headerSkeleton.getAttribute('shape')).toEqual('square'); + expect(headerSkeleton.getAttribute('size')).toEqual('94px'); + + const bodySkeleton = spectator.query('[data-testId="p-skeleton-body"]'); + expect(bodySkeleton.getAttribute('height')).toEqual('1rem'); + + const state1Skeleton = spectator.query('[data-testId="p-skeleton-state-1"]'); + expect(state1Skeleton.getAttribute('width')).toEqual('2rem'); + expect(state1Skeleton.getAttribute('height')).toEqual('1rem'); + + const state2Skeleton = spectator.query('[data-testId="p-skeleton-state-2"]'); + expect(state2Skeleton.getAttribute('shape')).toEqual('circle'); + expect(state2Skeleton.getAttribute('size')).toEqual('16px'); }); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html index 8eb90a5d0620..800d716198de 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.html @@ -5,13 +5,16 @@ [contentlet]="contentlet" [cover]="false" [iconSize]="'72px'" - [showVideoThumbnail]="true"> + [showVideoThumbnail]="true" + data-testId="dot-contentlet-thumbnail"> -

{{ contentlet?.fileName || contentlet?.title }}

+

+ {{ contentlet?.fileName || contentlet?.title }} +

- {{ contentlet?.language }} + {{ contentlet?.language }}
diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts index 2e0cceadfaf9..55b2cd24ddd9 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.spec.ts @@ -1,12 +1,21 @@ import { createComponentFactory, Spectator } from '@ngneat/spectator'; -import { CardModule } from 'primeng/card'; +import { Card, CardModule } from 'primeng/card'; + +import { DotCMSContentlet } from '@dotcms/dotcms-models'; +import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; import { DotAssetCardComponent } from './dot-asset-card.component'; +const contentlet: DotCMSContentlet = { + ...EMPTY_CONTENTLET, + title: 'test title', + fileName: 'test fileName', + language: 'en' +}; + describe('DotAssetCardComponent', () => { let spectator: Spectator; - let component: DotAssetCardComponent; const createComponent = createComponentFactory({ component: DotAssetCardComponent, @@ -14,11 +23,37 @@ describe('DotAssetCardComponent', () => { }); beforeEach(() => { - spectator = createComponent(); - component = spectator.component; + spectator = createComponent({ + props: { + contentlet + } + }); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should use the dot-contentlet-thumbnail', () => { + const card = spectator.query(Card); + const thumbnail = spectator.query('dot-contentlet-thumbnail'); + expect(thumbnail).toBeDefined(); + expect(card).toBeDefined(); + }); + + it('should display the contentlet file name and language', () => { + const title = spectator.query('[data-testId="dot-card-title"]'); + const language = spectator.query('[data-testId="dot-card-language"]'); + + expect(title.innerHTML.trim()).toBe(contentlet.fileName); + expect(language.innerHTML.trim()).toBe(contentlet.language); + }); + + it('should display the contentlet title when the fileName property is empty', () => { + spectator.setInput('contentlet', { + ...contentlet, + fileName: '' + }); + + spectator.detectChanges(); + + const title = spectator.query('[data-testId="dot-card-title"]'); + expect(title.innerHTML.trim()).toBe(contentlet.title); }); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts index b20353fd7b2b..2a89325f7b25 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Input, NO_ERRORS_SCHEMA } from '@angular/core'; import { CardModule } from 'primeng/card'; @@ -10,31 +10,9 @@ import { DotCMSContentlet } from '@dotcms/dotcms-models'; styleUrls: ['./dot-asset-card.component.scss'], standalone: true, imports: [CardModule], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + schemas: [NO_ERRORS_SCHEMA] // WebComponent }) export class DotAssetCardComponent { @Input() contentlet: DotCMSContentlet; - - /** - * Return the contentlet Thumbanil based in the inode - * - * @param {string} inode - * @return {*} {string} - * @memberof DotAssetCardComponent - */ - getImage(inode: string): string { - return `/dA/${inode}/500w/50q`; - } - - /** - * Return the contentlet icon - * - * @return {*} {string} - * @memberof DotAssetCardComponent - */ - getContentletIcon(): string { - return this.contentlet?.baseType !== 'FILEASSET' - ? this.contentlet?.contentTypeIcon - : this.contentlet?.__icon__; - } } diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html index c05c6885023c..66eb323f0df2 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.html @@ -1,7 +1,14 @@ diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts index a3f4f3117369..27af9d1206fc 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts @@ -1,10 +1,13 @@ -import { createComponentFactory, Spectator } from '@ngneat/spectator'; +import { byTestId, createComponentFactory, Spectator } from '@ngneat/spectator'; +import { of } from 'rxjs'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { fakeAsync, tick } from '@angular/core/testing'; import { InputTextModule } from 'primeng/inputtext'; import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; +import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; import { DotAssetCardComponent } from './components/dot-asset-card/dot-asset-card.component'; import { DotAssetCardListComponent } from './components/dot-asset-card-list/dot-asset-card-list.component'; @@ -14,27 +17,79 @@ import { DotAssetSearchStore } from './store/dot-asset-search.store'; describe('DotAssetSearchComponent', () => { let spectator: Spectator; - let component: DotAssetSearchComponent; + + let store: DotAssetSearchStore; const createComponent = createComponentFactory({ component: DotAssetSearchComponent, - mocks: [DotContentSearchService, DotLanguagesService], - providers: [DotAssetSearchStore, DotContentSearchService, DotLanguagesService], + providers: [ + { + provide: DotContentSearchService, + useValue: { + get: () => of({ jsonObjectView: { contentlets: [] } }) + } + }, + { + provide: DotLanguagesService, + useValue: { + get: () => + of([ + { + id: '1', + languageCode: 'en', + countryCode: 'us', + language: 'English', + country: 'United States' + } + ]) + } + } + ], imports: [ HttpClientTestingModule, InputTextModule, DotAssetCardComponent, DotAssetCardListComponent, DotAssetCardSkeletonComponent - ] + ], + componentProviders: [DotAssetSearchStore] }); beforeEach(() => { spectator = createComponent(); - component = spectator.component; + spectator.detectChanges(); + store = spectator.inject(DotAssetSearchStore, true); + spectator.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should send the correct inputs to DotAssetCardListComponent', () => { + const dotAssetCardListComponent = spectator.query(DotAssetCardListComponent); + + // Default state + expect(dotAssetCardListComponent.contentlets).toEqual([]); + expect(dotAssetCardListComponent.done).toEqual(true); + expect(dotAssetCardListComponent.loading).toEqual(false); }); + + it('should call store nextBatch', fakeAsync(() => { + const spy = spyOn(store, 'nextBatch'); + spectator.triggerEventHandler(DotAssetCardListComponent, 'nextBatch', 10); + tick(1000); + expect(spy).toHaveBeenCalledWith(10); + })); + + it('should call addAsset Output', fakeAsync(() => { + const spy = spyOn(spectator.component.addAsset, 'emit'); + spectator.triggerEventHandler(DotAssetCardListComponent, 'selectedItem', EMPTY_CONTENTLET); + tick(1000); + expect(spy).toHaveBeenCalledWith(EMPTY_CONTENTLET); + })); + + it('should call store searchContentlet', fakeAsync(() => { + const spy = spyOn(store, 'searchContentlet'); + const inputElement = spectator.query(byTestId('input-search')) as HTMLInputElement; + spectator.typeInElement('search', inputElement); + tick(1000); + expect(spy).toHaveBeenCalledWith('search'); + })); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts index f3d03c37652b..341eb1944be1 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts @@ -69,7 +69,7 @@ export class DotAssetSearchComponent implements OnInit, OnDestroy, AfterViewInit this.offset$ .pipe(takeUntil(this.destroy$), skip(1), throttleTime(450)) - .subscribe(this.store.nextBatch); + .subscribe((offset) => this.store.nextBatch(offset)); requestAnimationFrame(() => this.input.nativeElement.focus()); } diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index 03243b3aa43d..e58a385baa05 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -129,6 +129,17 @@ export class DotAssetSearchStore extends ComponentStore { private dotLanguagesService: DotLanguagesService ) { super(defaultState); + + this.dotLanguagesService + .get() + .pipe( + tap((languages) => { + languages.forEach((lang) => { + this.languages[lang.id] = lang; + }); + }) + ) + .subscribe(); } private searchContentletsRequest(params, prev: DotCMSContentlet[]) { From 74d3e1de55d89cf5396845c1af22cc3d0858f1ea Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 22:19:09 -0400 Subject: [PATCH 22/26] chore: cover DotAssetSearchStore tests --- .../dot-asset-search.component.spec.ts | 21 +- .../dot-asset-search.component.ts | 65 +++-- .../store/dot-asset-search.store.spec.ts | 234 ++++++------------ .../store/dot-asset-search.store.ts | 186 ++++++-------- 4 files changed, 215 insertions(+), 291 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts index 27af9d1206fc..6b5a44f011da 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.spec.ts @@ -56,7 +56,12 @@ describe('DotAssetSearchComponent', () => { }); beforeEach(() => { - spectator = createComponent(); + spectator = createComponent({ + props: { + languageId: '1', + type: 'image' + } + }); spectator.detectChanges(); store = spectator.inject(DotAssetSearchStore, true); spectator.detectChanges(); @@ -75,7 +80,12 @@ describe('DotAssetSearchComponent', () => { const spy = spyOn(store, 'nextBatch'); spectator.triggerEventHandler(DotAssetCardListComponent, 'nextBatch', 10); tick(1000); - expect(spy).toHaveBeenCalledWith(10); + expect(spy).toHaveBeenCalledWith({ + languageId: '1', + assetType: 'image', + offset: 10, + search: '' + }); })); it('should call addAsset Output', fakeAsync(() => { @@ -90,6 +100,11 @@ describe('DotAssetSearchComponent', () => { const inputElement = spectator.query(byTestId('input-search')) as HTMLInputElement; spectator.typeInElement('search', inputElement); tick(1000); - expect(spy).toHaveBeenCalledWith('search'); + expect(spy).toHaveBeenCalledWith({ + languageId: '1', + assetType: 'image', + offset: 0, + search: 'search' + }); })); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts index 341eb1944be1..86c4400c6b3f 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/dot-asset-search.component.ts @@ -1,22 +1,24 @@ -import { BehaviorSubject, fromEvent, Subject } from 'rxjs'; +import { BehaviorSubject, fromEvent } from 'rxjs'; import { CommonModule } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, Component, + DestroyRef, ElementRef, EventEmitter, Input, - OnDestroy, OnInit, Output, - ViewChild + ViewChild, + inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { InputTextModule } from 'primeng/inputtext'; -import { debounceTime, skip, takeUntil, throttleTime } from 'rxjs/operators'; +import { debounceTime, skip, throttleTime } from 'rxjs/operators'; import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; import { DotCMSContentlet, EditorAssetTypes } from '@dotcms/dotcms-models'; @@ -43,46 +45,59 @@ import { DotAssetSearchStore } from './store/dot-asset-search.store'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotAssetSearchComponent implements OnInit, OnDestroy, AfterViewInit { +export class DotAssetSearchComponent implements OnInit, AfterViewInit { @ViewChild('input') input!: ElementRef; @Output() addAsset = new EventEmitter(); - @Input() set languageId(id) { - this.store.updatelanguageId(id); - } + @Input() languageId = '*'; + @Input() type: EditorAssetTypes; - private _assetType: EditorAssetTypes; - @Input() set type(type: EditorAssetTypes) { - this._assetType = type; - this.store.updateAssetType(type); - } + private currentSearch = ''; + private readonly store = inject(DotAssetSearchStore); + private readonly destroyRef = inject(DestroyRef); - vm$ = this.store.vm$; offset$ = new BehaviorSubject(0); - - private destroy$: Subject = new Subject(); - - constructor(private store: DotAssetSearchStore) {} + vm$ = this.store.vm$; ngOnInit(): void { - this.store.init(this._assetType); + // Initial load + this.store.searchContentlet({ + ...this.searchParams(), + search: '', + offset: 0 + }); this.offset$ - .pipe(takeUntil(this.destroy$), skip(1), throttleTime(450)) - .subscribe((offset) => this.store.nextBatch(offset)); + .pipe(takeUntilDestroyed(this.destroyRef), skip(1), throttleTime(450)) + .subscribe((offset) => + this.store.nextBatch({ + ...this.searchParams(), + offset + }) + ); requestAnimationFrame(() => this.input.nativeElement.focus()); } ngAfterViewInit() { fromEvent(this.input.nativeElement, 'input') - .pipe(takeUntil(this.destroy$), debounceTime(450)) + .pipe(takeUntilDestroyed(this.destroyRef), debounceTime(450)) .subscribe(({ target }) => { - this.store.searchContentlet(target.value); + const value = (target as HTMLInputElement).value; + this.currentSearch = value; + this.store.searchContentlet({ + ...this.searchParams(), + search: value + }); }); } - ngOnDestroy(): void { - this.destroy$.next(true); + private searchParams() { + return { + languageId: this.languageId || '', + search: this.currentSearch, + assetType: this.type, + offset: this.offset$.value || 0 + }; } } diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts index c229c24ed11a..e9a055fc3f82 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.spec.ts @@ -1,9 +1,11 @@ import { SpectatorService, createServiceFactory } from '@ngneat/spectator'; import { of } from 'rxjs'; -import { HttpClientTestingModule } from '@angular/common/http/testing'; - -import { DotContentSearchService, DotLanguagesService } from '@dotcms/data-access'; +import { + DotContentSearchService, + DotLanguagesService, + ESOrderDirectionSearch +} from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; import { EMPTY_IMAGE_CONTENTLET } from '@dotcms/utils-testing'; @@ -40,27 +42,10 @@ export const IMAGE_CONTENTLETS_MOCK: DotCMSContentlet[] = [ name: 'adult-antioxidant.jpg', description: 'adult-antioxidant', title: 'Adult-antioxidant.jpg' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileAsset: 'test5.jpg', - fileName: '5 first-chair.jpg', - name: 'first-chair.jpg', - description: 'Stay at one of our resorts and get early hours with our first chair program.', - title: 'First to the Top' - }, - { - ...EMPTY_IMAGE_CONTENTLET, - fileAsset: 'test6.jpg', - fileName: '6 adult-antioxidant.jpg', - name: 'adult-antioxidant.jpg', - description: 'adult-antioxidant', - title: 'Adult-antioxidant.jpg' } ]; -const INITIAL_STATE = { contentlets: [], loading: true, preventScroll: false }; -const LanguageMock = [ +const LANGUAGE_MOCK = [ { country: 'United States', countryCode: 'US', @@ -79,21 +64,21 @@ const LanguageMock = [ } ]; -// const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 6).map((contentlet) => ({ -// ...contentlet, -// language: 'en-US' -// })); +const CONTENTLETS_MOCK_WITH_LANG = IMAGE_CONTENTLETS_MOCK.splice(0, 4).map((contentlet) => ({ + ...contentlet, + language: 'en-US' +})); + +const INITIAL_STATE = { contentlets: [], loading: true, preventScroll: false }; describe('DotAssetSearchStore', () => { let spectator: SpectatorService; let service: DotAssetSearchStore; - // let dotContentSearchService: DotContentSearchService; + let dotContentSearchService: DotContentSearchService; const createService = createServiceFactory({ service: DotAssetSearchStore, - imports: [HttpClientTestingModule], providers: [ - DotAssetSearchStore, { provide: DotContentSearchService, useValue: { @@ -103,7 +88,7 @@ describe('DotAssetSearchStore', () => { { provide: DotLanguagesService, useValue: { - get: () => of(LanguageMock) + get: () => of(LANGUAGE_MOCK) } } ] @@ -112,7 +97,7 @@ describe('DotAssetSearchStore', () => { beforeEach(() => { spectator = createService(); service = spectator.service; - // dotContentSearchService = spectator.inject(DotContentSearchService); + dotContentSearchService = spectator.inject(DotContentSearchService); }); it('should have inital state', (done) => { @@ -129,156 +114,97 @@ describe('DotAssetSearchStore', () => { service.vm$.subscribe((res) => { expect(res).toEqual({ - ...INITIAL_STATE, + preventScroll: false, + loading: false, contentlets: contentlet }); done(); }); }); - it('should update LanguageId', (done) => { - service.updatelanguageId(2); - - service.state$.subscribe((res) => { - expect(res.languageId).toEqual(2); - done(); - }); - }); - - it('should update Loading', (done) => { - service.updateLoading(false); + it('should merge contentlets', (done) => { + const contentlet = [IMAGE_CONTENTLETS_MOCK[0], IMAGE_CONTENTLETS_MOCK[1]]; + service.mergeContentlets(contentlet); service.vm$.subscribe((res) => { expect(res).toEqual({ - ...INITIAL_STATE, - loading: false + preventScroll: false, + loading: false, + contentlets: [...INITIAL_STATE.contentlets, ...contentlet] }); done(); }); }); + }); - it('should prevent Scroll', (done) => { - service.updatePreventScroll(true); + describe('Effects', () => { + it('should search contentlets', (done) => { + const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); - service.vm$.subscribe((res) => { + const spyLoading = spyOn(service, 'updateLoading'); + const spySearch = spyOn(dotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets } }) + ); + + const params = { + search: 'image', + assetType: 'image', + languageId: 1, + offset: 0 + }; + + spectator.service.searchContentlet(params); + + spectator.service.vm$.subscribe((res) => { expect(res).toEqual({ - ...INITIAL_STATE, - preventScroll: true + preventScroll: false, + loading: false, + contentlets }); + done(); }); - }); - it('should Search', (done) => { - const search = 'Image'; - service.updateSearch(search); - - service.state$.subscribe((res) => { - expect(res.search).toEqual(search); - done(); + expect(spyLoading).toHaveBeenCalledWith(true); + expect(spySearch).toHaveBeenCalledWith({ + query: `+catchall:${params.search}* title:'${params.search}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + sortOrder: ESOrderDirectionSearch.ASC, + limit: 20, + offset: 0 }); }); - }); - - // describe('Effects', () => { - // beforeEach(() => { - // DotContentSearchService = TestBed.inject(DotContentSearchService); - // }); - - // it('should search contentlets based on a search query', (done) => { - // const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); - // // Spies - // // const loadingMock = spyOn(service, 'updateLoading'); - // spyOn(service, 'updateSearch'); - // spyOn(service, 'updateContentlets'); - // spyOn(DotContentSearchService, 'get').and.returnValue( - // of({ jsonObjectView: { contentlets } }) - // ); - - // const query = 'image'; - - // service.searchContentlet(query); - - // service.vm$.subscribe((res) => { - // expect(res).toEqual({ - // preventScroll: false, - // loading: false, - // contentlets - // }); - - // done(); - // }); - - // expect(DotContentSearchService.get).toHaveBeenCalledWith({ - // query: `+catchall:${query}* +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - // sortOrder: ESOrderDirection.ASC, - // limit: 20, - // offset: 0 - // }); - - // // First -> the value is true because we start the search - // // Second -> the value is false because we finish the search - // // expect(loadingMock.calls).toEqual([[true], [false]]); - // expect(service.updateContentlets).toHaveBeenCalledWith(contentlets); - // expect(service.updateSearch).toHaveBeenCalledWith(query); - // }); - - // it('should not add "*" when the search has a "-" ', (done) => { - // const contentlets = CONTENTLETS_MOCK_WITH_LANG; - // spyOn(DotContentSearchService, 'get').and.returnValue( - // of({ jsonObjectView: { contentlets } }) - // ); - // const query = 'hola-'; + it('should load next banch', (done) => { + const contentlets = CONTENTLETS_MOCK_WITH_LANG.splice(0, 2); + const spySearch = spyOn(dotContentSearchService, 'get').and.returnValue( + of({ jsonObjectView: { contentlets } }) + ); - // service.searchContentlet(query); + const params = { + search: 'image', + assetType: 'image', + languageId: 1, + offset: 10 + }; - // service.vm$.subscribe((res) => { - // expect(res).toEqual({ - // preventScroll: false, - // loading: false, - // contentlets - // }); + spectator.service.nextBatch(params); - // done(); - // }); - - // expect(DotContentSearchService.get).toHaveBeenCalledWith({ - // query: `+catchall:${query} +title:'${query}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, - // sortOrder: ESOrderDirection.ASC, - // limit: 20, - // offset: 0 - // }); - // }); - - // it('should load the next batch of contentlets based on the offset', (done) => { - // const contentlets_p1 = [...CONTENTLETS_MOCK_WITH_LANG].splice(0, 2); - // const contentlets_p2 = [...CONTENTLETS_MOCK_WITH_LANG].splice(2, 2); - - // // Spies - // spyOn(service, 'updateSearch'); - // spyOn(service, 'updateContentlets'); - // spyOn(DotContentSearchService, 'get').and.returnValue( - // of({ jsonObjectView: { contentlets: contentlets_p2 } }) - // ); - - // const query = 'image'; - // const offset = 2; - - // service.updateContentlets(contentlets_p1); - // service.updateSearch(query); - // service.nextBatch(offset); - - // service.vm$.subscribe((res) => { - // expect(res).toEqual({ - // preventScroll: false, - // loading: false, - // contentlets: [...contentlets_p1, ...contentlets_p2] - // }); + spectator.service.vm$.subscribe((res) => { + expect(res).toEqual({ + preventScroll: false, + loading: false, + contentlets + }); - // done(); - // }); - // }); + done(); + }); - // }); + expect(spySearch).toHaveBeenCalledWith({ + query: `+catchall:${params.search}* title:'${params.search}'^15 +languageId:1 +baseType:(4 OR 9) +metadata.contenttype:image/* +deleted:false +working:true`, + sortOrder: ESOrderDirectionSearch.ASC, + limit: 20, + offset: 10 + }); + }); + }); }); diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts index e58a385baa05..4c6cf60c368a 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/store/dot-asset-search.store.ts @@ -1,9 +1,9 @@ -import { ComponentStore } from '@ngrx/component-store'; +import { ComponentStore, tapResponse } from '@ngrx/component-store'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; -import { map, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators'; +import { map, switchMap, tap } from 'rxjs/operators'; import { DotContentSearchService, @@ -11,56 +11,43 @@ import { EsQueryParamsSearch, DotLanguagesService } from '@dotcms/data-access'; -import { DotCMSContentlet, DotLanguage, EditorAssetTypes } from '@dotcms/dotcms-models'; +import { DotCMSContentlet, DotLanguage } from '@dotcms/dotcms-models'; -export interface DotImageSearchState { +export interface DotAssetSearch { loading: boolean; preventScroll: boolean; contentlets: DotCMSContentlet[]; - languageId: number | string; +} + +interface DotAssetSeachQuery { search: string; - assetType: EditorAssetTypes; + assetType: string; + offset?: number; + languageId?: number | string; } -const defaultState: DotImageSearchState = { +const defaultState: DotAssetSearch = { loading: true, preventScroll: false, - contentlets: [], - languageId: '*', - search: '', - assetType: 'image' + contentlets: [] }; @Injectable() -export class DotAssetSearchStore extends ComponentStore { +export class DotAssetSearchStore extends ComponentStore { // Selectors - readonly vm$ = this.select(({ contentlets, loading, preventScroll }) => ({ + readonly vm$ = this.select((state) => state); + + readonly updateContentlets = this.updater((_state, contentlets) => ({ contentlets, - loading, - preventScroll + preventScroll: !contentlets?.length, + loading: false })); - // Setters - readonly updateContentlets = this.updater((state, contentlets) => { - return { - ...state, - contentlets - }; - }); - - readonly updateAssetType = this.updater((state, assetType) => { - return { - ...state, - assetType - }; - }); - - readonly updatelanguageId = this.updater((state, languageId) => { - return { - ...state, - languageId - }; - }); + readonly mergeContentlets = this.updater((state, contentlets) => ({ + contentlets: [...state.contentlets, ...contentlets], + preventScroll: !contentlets?.length, + loading: false + })); readonly updateLoading = this.updater((state, loading) => { return { @@ -69,92 +56,73 @@ export class DotAssetSearchStore extends ComponentStore { }; }); - readonly updatePreventScroll = this.updater((state, preventScroll) => { - return { - ...state, - preventScroll - }; - }); + private languages: { [key: string]: DotLanguage } = {}; - readonly updateSearch = this.updater((state, search) => { - return { - ...state, - loading: true, - search - }; - }); + constructor( + private dotContentSearchService: DotContentSearchService, + private dotLanguagesService: DotLanguagesService + ) { + super(defaultState); - // Effects - readonly init = this.effect((origin$: Observable) => { - return origin$.pipe( - tap((assetType) => this.updateAssetType(assetType)), - switchMap(() => - this.dotLanguagesService.get().pipe( - tap((languages) => { - languages.forEach((lang) => { - this.languages[lang.id] = lang; - }); - }) - ) - ), - withLatestFrom(this.state$), - switchMap(([_, state]) => this.searchContentletsRequest(this.params(state), [])) - ); - }); + this.dotLanguagesService.get().subscribe((languages) => { + languages.forEach((lang) => { + this.languages[lang.id] = lang; + }); + }); + } - readonly searchContentlet = this.effect((origin$: Observable) => { - return origin$.pipe( - tap((search) => this.updateSearch(search)), - withLatestFrom(this.state$), - mergeMap(([search, state]) => - this.searchContentletsRequest(this.params({ ...state, search }), []) - ) + /** + * Search for contentlets + * + * @memberof DotAssetSearchStore + */ + readonly searchContentlet = this.effect((params$: Observable) => { + return params$.pipe( + tap(() => this.updateLoading(true)), + switchMap((params) => { + return this.searchContentletsRequest(params).pipe( + tapResponse( + (contentlets) => this.updateContentlets(contentlets), + (_error) => { + /* */ + } + ) + ); + }) ); }); - readonly nextBatch = this.effect((origin$: Observable) => { - return origin$.pipe( - withLatestFrom(this.state$), - map(([offset, state]) => ({ ...state, offset })), - mergeMap(({ contentlets, ...data }) => - this.searchContentletsRequest(this.params(data), contentlets) + /** + * Load more contentlets + * + * @memberof DotAssetSearchStore + */ + readonly nextBatch = this.effect((params$: Observable) => { + return params$.pipe( + switchMap((params) => + this.searchContentletsRequest(params).pipe( + tapResponse( + (contentlets) => this.mergeContentlets(contentlets), + (_error) => { + /* */ + } + ) + ) ) ); }); - private languages: { [key: string]: DotLanguage } = {}; + private searchContentletsRequest(params): Observable { + const query = this.queryParams(params); - constructor( - private DotContentSearchService: DotContentSearchService, - private dotLanguagesService: DotLanguagesService - ) { - super(defaultState); - - this.dotLanguagesService - .get() - .pipe( - tap((languages) => { - languages.forEach((lang) => { - this.languages[lang.id] = lang; - }); - }) - ) - .subscribe(); - } - - private searchContentletsRequest(params, prev: DotCMSContentlet[]) { - return this.DotContentSearchService.get(params).pipe( + return this.dotContentSearchService.get(query).pipe( map(({ jsonObjectView: { contentlets } }) => { - const items = this.setContentletLanguage(contentlets); - this.updateLoading(false); - this.updatePreventScroll(!contentlets?.length); - - return this.updateContentlets([...prev, ...items]); + return this.setContentletLanguage(contentlets); }) ); } - private params(data): EsQueryParamsSearch { + private queryParams(data): EsQueryParamsSearch { const { search, assetType, offset = 0, languageId = '' } = data; const filter = search.includes('-') ? search : `${search}*`; @@ -180,13 +148,13 @@ export class DotAssetSearchStore extends ComponentStore { return contentlets.map((contentlet) => { return { ...contentlet, - language: this.getLanguage(contentlet.languageId) + language: this.getLanguageBadge(contentlet.languageId) }; }); } - private getLanguage(languageId: number): string { - const { languageCode, countryCode } = this.languages[languageId]; + private getLanguageBadge(languageId: number): string { + const { languageCode, countryCode } = this.languages[languageId] || {}; if (!languageCode || !countryCode) { return ''; From 0cef9708ace883b9652a2ce0e140fa3e6cccb69e Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Thu, 21 Mar 2024 22:27:54 -0400 Subject: [PATCH 23/26] chore: cover DotAssetSearchDialog Component tests --- .../dot-asset-search-dialog.component.spec.ts | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts index 6e355eeec589..9fef576437e6 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-search-dialog/dot-asset-search-dialog.component.spec.ts @@ -3,26 +3,51 @@ import { MockComponent } from 'ng-mocks'; import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { EMPTY_CONTENTLET } from '@dotcms/utils-testing'; + import { DotAssetSearchDialogComponent } from './dot-asset-search-dialog.component'; import { DotAssetSearchComponent } from '../../dot-asset-search.component'; describe('DotAssetSearchDialogComponent', () => { let spectator: Spectator; - let component: DotAssetSearchDialogComponent; - + let dynamicDialogRef: DynamicDialogRef; const createComponent = createComponentFactory({ component: DotAssetSearchDialogComponent, declarations: [MockComponent(DotAssetSearchComponent)], - providers: [DynamicDialogRef, DynamicDialogConfig] + providers: [ + { + provide: DynamicDialogRef, + useValue: { + close: (_) => { + /* */ + } + } + }, + { + provide: DynamicDialogConfig, + useValue: { + data: { + assetType: 'image' + } + } + } + ] }); beforeEach(() => { spectator = createComponent(); - component = spectator.component; + dynamicDialogRef = spectator.inject(DynamicDialogRef, true); + }); + + it('should set editorAssetType from config data', () => { + const dotAssetSearchComponent = spectator.query(DotAssetSearchComponent); + expect(dotAssetSearchComponent.type).toBe('image'); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should close dialog with selected asset on addAsset', () => { + const spy = spyOn(dynamicDialogRef, 'close'); + spectator.triggerEventHandler(DotAssetSearchComponent, 'addAsset', EMPTY_CONTENTLET); + expect(spy).toHaveBeenCalledWith(EMPTY_CONTENTLET); }); }); From da10072dee4833bbae9e90023231950d7f2a6b16 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Fri, 22 Mar 2024 10:48:15 -0400 Subject: [PATCH 24/26] chore: clean up --- .../dot-edit-content-field.component.spec.ts | 4 ++-- .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts index 5b572297f2fc..2b43a30a0cb3 100644 --- a/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/components/dot-edit-content-field/dot-edit-content-field.component.spec.ts @@ -31,7 +31,7 @@ import { DotEditContentSelectFieldComponent } from '../../fields/dot-edit-conten import { DotEditContentTagFieldComponent } from '../../fields/dot-edit-content-tag-field/dot-edit-content-tag-field.component'; import { DotEditContentTextAreaComponent } from '../../fields/dot-edit-content-text-area/dot-edit-content-text-area.component'; import { DotEditContentTextFieldComponent } from '../../fields/dot-edit-content-text-field/dot-edit-content-text-field.component'; -import { DotWYSIWYGFieldComponent } from '../../fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; +import { DotEditContentWYSIWYGFieldComponent } from '../../fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component'; import { FIELD_TYPES } from '../../models/dot-edit-content-field.enum'; import { DotEditContentService } from '../../services/dot-edit-content.service'; import { @@ -126,7 +126,7 @@ const FIELD_TYPES_COMPONENTS: Record | DotEditFieldTe providers: [mockProvider(DotMessageDisplayService)] }, [FIELD_TYPES.WYSIWYG]: { - component: DotWYSIWYGFieldComponent, + component: DotEditContentWYSIWYGFieldComponent, declarations: [MockComponent(EditorComponent)] } }; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts index e0a933bd625b..0eb5ecfeebd0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts @@ -89,7 +89,6 @@ describe('DotWysiwygPluginService', () => { expect(spyOn).toHaveBeenCalledWith('drop', expect.any(Function)); expect(spyButton).toHaveBeenCalledWith('dotAddImage', { - text: 'Dot Image', icon: 'image', onAction: expect.any(Function) }); @@ -106,7 +105,7 @@ describe('DotWysiwygPluginService', () => { const button = editor.ui.registry.getAll().buttons['dotAddImage']; const dialogConfig = { - header: 'Add Image', + header: 'Insert Image', width: '800px', height: '500px', contentStyle: { padding: 0 }, From f22f4499d050d422b5f61133b5bcfb65d1c28da3 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Fri, 22 Mar 2024 11:24:22 -0400 Subject: [PATCH 25/26] chore: feedback --- .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts | 5 +++++ .../components/dot-asset-card/dot-asset-card.component.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts index 0eb5ecfeebd0..fef6071ebfd0 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.spec.ts @@ -56,6 +56,11 @@ describe('DotWysiwygPluginService', () => { let spectator: SpectatorService; let dialogService: DialogService; let dotUploadFileService: DotUploadFileService; + /** + * `any` is used here because the Editor is a complex object that we don't need to mock all the methods and properties + * This mock also contains some custom methods to check the configuration + * We are using this mock to check the configuration of the editor + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any let editor: any; diff --git a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts index 2a89325f7b25..1de5aba3768c 100644 --- a/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts +++ b/core-web/libs/ui/src/lib/components/dot-asset-search/components/dot-asset-card/dot-asset-card.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { CardModule } from 'primeng/card'; @@ -11,7 +11,7 @@ import { DotCMSContentlet } from '@dotcms/dotcms-models'; standalone: true, imports: [CardModule], changeDetection: ChangeDetectionStrategy.OnPush, - schemas: [NO_ERRORS_SCHEMA] // WebComponent + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class DotAssetCardComponent { @Input() contentlet: DotCMSContentlet; From 5ed37ceb1366e48a499a5f6c889a15e047271d28 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Fri, 22 Mar 2024 16:24:14 -0400 Subject: [PATCH 26/26] chore: code cleanup --- .../dot-edit-content-wysiwyg-field.component.ts | 8 +------- .../dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts | 7 ++----- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts index 2e3a0c2d9d40..64de5360bde2 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-edit-content-wysiwyg-field.component.ts @@ -1,5 +1,5 @@ import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular'; -import { RawEditorOptions, TinyMCE } from 'tinymce'; +import { RawEditorOptions } from 'tinymce'; import { ChangeDetectionStrategy, Component, Input, inject, signal } from '@angular/core'; import { ControlContainer, FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -10,12 +10,6 @@ import { DotCMSContentTypeField } from '@dotcms/dotcms-models'; import { DotWysiwygPluginService } from './dot-wysiwyg-plugin/dot-wysiwyg-plugin.service'; -declare global { - interface Window { - tinymce: TinyMCE; - } -} - @Component({ selector: 'dot-wysiwyg-field', standalone: true, diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts index e715903b2a4c..78aaed215f4e 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-wysiwyg-field/dot-wysiwyg-plugin/dot-wysiwyg-plugin.service.ts @@ -4,7 +4,7 @@ import { Injectable, NgZone, inject } from '@angular/core'; import { DialogService } from 'primeng/dynamicdialog'; -import { filter, take } from 'rxjs/operators'; +import { filter } from 'rxjs/operators'; import { DotUploadFileService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; @@ -38,10 +38,7 @@ export class DotWysiwygPluginService { }); ref.onClose - .pipe( - take(1), - filter((asset) => !!asset) - ) + .pipe(filter((asset) => !!asset)) .subscribe((asset: DotCMSContentlet) => editor.insertContent(formatDotImageNode(asset)) );