diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index 2a028b2ac2..0bad0ed130 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -111,7 +111,8 @@ "GROUPS_CLOUD": "Groups Cloud Component", "CONFIRM-DIALOG": "Confirmation Dialog", "COMMUNITY": "Community", - "SERVICE_TASK_LIST": "Service Task List" + "SERVICE_TASK_LIST": "Service Task List", + "RICH_TEXT_EDITOR": "Rich Text Editor" }, "TRASHCAN": { "ACTIONS": { diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index 2145690894..938eaf8711 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -321,6 +321,10 @@ export const appRoutes: Routes = [ path: 'datatable/dnd', loadChildren: () => import('./components/datatable/drag-and-drop/datatable-dnd.module').then(m => m.AppDataTableDndModule) }, + { + path: 'rich-text-editor', + loadChildren: () => import('./components/rich-text-editor/rich-text-editor.module').then(m => m.AppRichTextEditorModule) + }, { path: 'search', component: SearchResultComponent, diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 6a029f246f..80fa8820f2 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -74,6 +74,7 @@ export class AppLayoutComponent implements OnInit, OnDestroy { { href: '/datatable/dnd', icon: 'view_module', title: 'Drag and Drop' }, { href: '/copy-content', icon: 'view_module', title: 'Copy Content' } ]}, + { href: '/rich-text-editor', icon: 'list_alt', title: 'APP_LAYOUT.RICH_TEXT_EDITOR' }, { href: '/template-list', icon: 'list_alt', title: 'APP_LAYOUT.TEMPLATE' }, { href: '/webscript', icon: 'extension', title: 'APP_LAYOUT.WEBSCRIPT' }, { href: '/tag', icon: 'local_offer', title: 'APP_LAYOUT.TAG' }, diff --git a/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.html b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.html new file mode 100644 index 0000000000..0a6c77e64a --- /dev/null +++ b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.html @@ -0,0 +1,12 @@ +
+ +
+

Rich Text Editor

+ +
+
+

Output Data

+
{{editorOutputData | json}}
+
+ +
diff --git a/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.scss b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.scss new file mode 100644 index 0000000000..a4e4d2df94 --- /dev/null +++ b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.scss @@ -0,0 +1,19 @@ +.app-rich-text-editor-container { + .app-rich-text-editor-col { + padding: 20px; + + &-rx { + border-left: 1px dashed var(--theme-primary-color); + border-spacing: 5px; + } + + &-title { + text-align: center; + } + } + + .app-rich-text-editor-output { + max-width: 30vw; + overflow-x: scroll; + } +} diff --git a/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.ts b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.ts new file mode 100644 index 0000000000..8f9757dfe3 --- /dev/null +++ b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.component.ts @@ -0,0 +1,112 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { OutputData } from '@editorjs/editorjs'; +import { RichTextEditorComponent as AdfRichTextEditorComponent } from '@alfresco/adf-core'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'app-rich-text-editor', + templateUrl: './rich-text-editor.component.html', + styleUrls: ['./rich-text-editor.component.scss'] +}) +export class RichTextEditorComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild('textEditor') + textEditor: AdfRichTextEditorComponent; + + onDestroy$ = new Subject(); + + editorOutputData: OutputData; + + sampleData = { + time: 1656674370891, + blocks: [ + { + id: '99jwc03ETP', + type: 'header', + data: { + text: 'Header', + level: 2 + } + }, + { + id: 'ffdulIdU1E', + type: 'paragraph', + data: { + text: `is simply dummy text of the printing and typesetting industry. + Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, + when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, + but also the leap into electronic typesetting, remaining essentially unchanged. + It was popularised in the 1960s with the release of sheets containing + Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem`, + alignment: 'left' + } + }, + { + id: 'rTcF4u0pr3', + type: 'list', + data: { + style: 'unordered', + items: [ + 'Unordered list example', + 'Unordered list example' + ] + } + }, + { + id: 'Kg2e_K1nHU', + type: 'list', + data: { + style: 'ordered', + items: [ + 'Ordered list example', + 'Ordered list example' + ] + } + }, + { + id: 'xqqk0DEmqh', + type: 'code', + data: { + code: '// Amazing code example\n\ncatch(Exception ex){\n // Houston, we have a problem\n}' + } + } + ] + }; + + constructor() { } + + ngOnInit(): void { + } + + ngAfterViewInit(): void { + this.textEditor.outputData$.pipe( + takeUntil(this.onDestroy$) + ).subscribe(outputData => { + this.editorOutputData = outputData; + }); + } + + ngOnDestroy(): void { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + +} diff --git a/demo-shell/src/app/components/rich-text-editor/rich-text-editor.module.ts b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.module.ts new file mode 100644 index 0000000000..b3020aad9f --- /dev/null +++ b/demo-shell/src/app/components/rich-text-editor/rich-text-editor.module.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RichTextEditorComponent } from './rich-text-editor.component'; +import { ContentModule } from '@alfresco/adf-content-services'; +import { CoreModule } from '@alfresco/adf-core'; +import { RouterModule, Routes } from '@angular/router'; +import { FlexLayoutModule } from '@angular/flex-layout'; + +const routes: Routes = [ + { + path: '', + component: RichTextEditorComponent + } +]; + +@NgModule({ + declarations: [RichTextEditorComponent], + imports: [ + CommonModule, + CoreModule, + RouterModule.forChild(routes), + ContentModule.forChild(), + FlexLayoutModule + ] +}) +export class AppRichTextEditorModule { } diff --git a/docs/README.md b/docs/README.md index e61a6275c7..44d61beb46 100644 --- a/docs/README.md +++ b/docs/README.md @@ -111,6 +111,7 @@ for more information about installing and using the source code. | [Notification History component](core/components/notification-history.component.md) ![Experimental](docassets/images/ExperimentalIcon.png) | This component is in the current status just an experimental component. | | | The main purpose of the Notification history component is list all the notification received in the current session. They will disappear from the list after the refresh. | [Source](../lib/core/notifications/components/notification-history.component.ts) | | | [Pagination Component](core/components/pagination.component.md) | Adds pagination to the component it is used with. | [Source](../lib/core/pagination/pagination.component.ts) | +| [Rich Text Editor Component](core/components/rich-text-editor.md) | Displays a rich text editor that allows users to add formatted text | [Source](../lib/core/rich-text-editor/rich-text-editor.component.ts) | | [Search Text Input Component](core/components/search-text-input.component.md) | Displays a input text that supports autocompletion | [Source](../lib/core/search-text/search-text-input.component.ts) | | [Sidebar action menu component](core/components/sidebar-action-menu.component.md) | Displays a sidebar-action menu information panel. | [Source](../lib/core/layout/components/sidebar-action/sidebar-action-menu.component.ts) | | [Sidenav Layout component](core/components/sidenav-layout.component.md) | Displays the standard three-region ADF application layout. | [Source](../lib/core/layout/components/sidenav-layout/sidenav-layout.component.ts) | diff --git a/docs/core/components/form-field.component.md b/docs/core/components/form-field.component.md index bab9485563..dd57fc864d 100644 --- a/docs/core/components/form-field.component.md +++ b/docs/core/components/form-field.component.md @@ -57,5 +57,6 @@ Forms defined in APS have the following default mappings for the form fields: | Attach File | upload | AttachWidgetComponent or [`UploadWidgetComponent`](../../../lib/core/form/components/widgets/upload/upload.widget.ts) (based on metadata) | | Display value | readonly | [`TextWidgetComponent`](../../../lib/core/form/components/widgets/text/text.widget.ts) | | Display text | readonly-text | [`DisplayTextWidgetComponent`](../../../lib/core/form/components/widgets/display-text/display-text.widget.ts) | +| Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](../../../lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts) | | N/A | container | [`ContainerWidgetComponent`](../../../lib/core/form/components/widgets/container/container.widget.ts) (layout component) | | N/A | N/A | [`UnknownWidgetComponent`](../../../lib/core/form/components/widgets/unknown/unknown.widget.ts) | diff --git a/docs/core/components/rich-text-editor.md b/docs/core/components/rich-text-editor.md new file mode 100644 index 0000000000..a64b585b01 --- /dev/null +++ b/docs/core/components/rich-text-editor.md @@ -0,0 +1,78 @@ +--- +Title: Rich Text Editor component +Added: v1.0.0 +Status: Active +Last reviewed: 2020-07-20 +--- + +# [Rich Text Editor component](../../../lib/core/rich-text-editor/rich-text-editor.component.ts "Defined in rich-text-editor.component.ts") + +Wrap [Editor.js](https://editorjs.io/) element to show a Rich Text editor allows to add formatted text. + +## Contents + +- [Basic usage](#basic-usage) +- [Class members](#class-members) + - [Properties](#properties) + +## Basic usage + +**app.component.html** + +```html + + +``` + +**app.component.ts** + +```ts +@Component({...}) +export class RichTextEditorDemo { + + @ViewChild('editor') + editorRef; + + data = { + time: 1658154611110, + blocks: [ + { + id: '99jwc03ETP', + type: 'header', + data: { + text: 'Header', + level: 2 + } + }, + { + id: 'ffdulIdU1E', + type: 'paragraph', + data: { + text: 'Sample paragraph', + alignment: 'left' + } + }, + ], + version: 1 + }; + + async saveContent(){ + try { + const editorContent = await this.editorRef.getEditorContent(); + // do some stuff with editor content + } catch (error) { + // catch error + } + } + +} +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| -------- | ------------ | ------------- | -------------------------------------------------------------------------------------------- | +| data | `OutputData` | null | EditorJs data format (follow the [official documentation](https://editorjs.io/saving-data) ) | +| readOnly | `boolean` | false | If true users won't have the ability to change the document content | diff --git a/docs/core/services/form-rendering.service.md b/docs/core/services/form-rendering.service.md index fbfb99aa2f..9560ecb14c 100644 --- a/docs/core/services/form-rendering.service.md +++ b/docs/core/services/form-rendering.service.md @@ -73,6 +73,7 @@ The [`Form`](../../../lib/process-services/src/lib/task-list/models/form.model.t | Checkbox | "boolean" | [`CheckboxWidgetComponent`](../../../lib/core/form/components/widgets/checkbox/checkbox.widget.ts) | | Date | "date" | [`DateWidgetComponent`](../../../lib/core/form/components/widgets/date/date.widget.ts) | | Display text | "readonly-text" | [`DisplayTextWidgetComponentComponent`](../../../lib/core/form/components/widgets/display-text/display-text.widget.ts) | +| Display Rich text | "display-rich-text" | [`DisplayRichTextWidgetComponent`](../../../lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts) | | Display value | "readonly" | DisplayValueWidgetComponent | | Dropdown | "dropdown" | [`DropdownWidgetComponent`](../../../lib/core/form/components/widgets/dropdown/dropdown.widget.ts) | | Dynamic table | "dynamic-table" | [`DynamicTableWidgetComponent`](../../../lib/core/form/components/widgets/dynamic-table/dynamic-table.widget.ts) | diff --git a/docs/user-guide/extensibility.md b/docs/user-guide/extensibility.md index 3309f9860f..8d57aa0eee 100644 --- a/docs/user-guide/extensibility.md +++ b/docs/user-guide/extensibility.md @@ -90,6 +90,7 @@ formRenderingService.setComponentTypeResolver('text', customResolver, true); | Hyperlink | hyperlink | [`HyperlinkWidgetComponent`](../../lib/core/form/components/widgets/hyperlink/hyperlink.widget.ts) | | Display value | readonly | DisplayValueWidgetComponent | | Display text | readonly-text | [`DisplayTextWidgetComponentComponent`](../../lib/core/form/components/widgets/display-text/display-text.widget.ts) | +| Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](../../lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts) | | Typeahead | typeahead | [`TypeaheadWidgetComponent`](../../lib/core/form/components/widgets/typeahead/typeahead.widget.ts) | | People | people | [`PeopleWidgetComponent`](../../lib/core/form/components/widgets/people/people.widget.ts) | | Group of people | functional-group | [`FunctionalGroupWidgetComponent`](../../lib/core/form/components/widgets/functional-group/functional-group.widget.ts) | diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts index 19ba08c712..2b0367e5fe 100644 --- a/lib/core/core.module.ts +++ b/lib/core/core.module.ts @@ -62,6 +62,7 @@ import { versionCompatibilityFactory } from './services/version-compatibility-fa import { VersionCompatibilityService } from './services/version-compatibility.service'; import { AlfrescoJsClientsModule } from '@alfresco/adf-core/api'; import { LegacyApiClientModule } from './api-factories/legacy-api-client.module'; +import { RichTextEditorModule } from './rich-text-editor/rich-text-editor.module'; @NgModule({ imports: [ @@ -99,7 +100,8 @@ import { LegacyApiClientModule } from './api-factories/legacy-api-client.module' SearchTextModule, BlankPageModule, LegacyApiClientModule, - AlfrescoJsClientsModule + AlfrescoJsClientsModule, + RichTextEditorModule ], exports: [ AboutModule, @@ -134,7 +136,8 @@ import { LegacyApiClientModule } from './api-factories/legacy-api-client.module' IconModule, NotificationHistoryModule, SearchTextModule, - BlankPageModule + BlankPageModule, + RichTextEditorModule ] }) export class CoreModule { diff --git a/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.html b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.html new file mode 100644 index 0000000000..7ccb286968 --- /dev/null +++ b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.scss b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.scss new file mode 100644 index 0000000000..1880db3e82 --- /dev/null +++ b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.scss @@ -0,0 +1,22 @@ +.adf-display-rich-text-widget { + padding: 10px; + + pre { + min-height: 100px; + font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; + color: #41314e; + line-height: 1.6em; + font-size: 12px; + background: #f8f7fa; + border: 1px solid #f1f1f4; + box-shadow: none; + white-space: pre; + word-wrap: normal; + overflow-x: auto; + resize: vertical; + border-radius: 3px; + padding: 10px 12px; + outline: none; + width: 100%; + } +} diff --git a/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts new file mode 100644 index 0000000000..77e507e549 --- /dev/null +++ b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.spec.ts @@ -0,0 +1,73 @@ +import { DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { CoreTestingModule, setupTestBed } from 'core/testing'; + +import { DisplayRichTextWidgetComponent } from './display-rich-text.widget'; + +describe('DisplayRichTextWidgetComponent', () => { + let widget: DisplayRichTextWidgetComponent; + let fixture: ComponentFixture; + let debugEl: DebugElement; + + const cssSelector = { + parsedHTML: '.adf-display-rich-text-widget-parsed-html' + }; + + const fakeFormField: any = { + id: 'fake-form-field', + name: 'fake-label', + value: { + time: 1658154611110, + blocks: [ + { + id: '1', + type: 'header', + data: { + text: 'Editor.js', + level: 1 + } + } + ], + version: 1 + }, + required: false, + readOnly: false, + overrideId: false, + colspan: 1, + placeholder: null, + minLength: 0, + maxLength: 0, + params: { + existingColspan: 1, + maxColspan: 1 + } + }; + + setupTestBed({ + imports: [ + CoreTestingModule + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DisplayRichTextWidgetComponent); + widget = fixture.componentInstance; + debugEl = fixture.debugElement; + widget.field = fakeFormField; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(widget).toBeTruthy(); + }); + + it('should parse editorjs data to html', async () => { + const expectedHtml = '

Editor.js

'; + fixture.detectChanges(); + await fixture.whenStable(); + const parsedHtmlEl = debugEl.query(By.css(cssSelector.parsedHTML)); + expect(parsedHtmlEl.nativeElement.innerHTML).toEqual(expectedHtml); + }); + +}); diff --git a/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts new file mode 100644 index 0000000000..8ea78ebc12 --- /dev/null +++ b/lib/core/form/components/widgets/display-rich-text/display-rich-text.widget.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @angular-eslint/component-selector */ + +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { FormService } from '../../../services/form.service'; +import { WidgetComponent } from '../widget.component'; +/* cspell:disable-next-line */ +import * as edjsHTML from 'editorjs-html'; +@Component({ + selector: 'display-rich-text', + templateUrl: './display-rich-text.widget.html', + styleUrls: ['./display-rich-text.widget.scss'], + host: { + '(click)': 'event($event)', + '(blur)': 'event($event)', + '(change)': 'event($event)', + '(focus)': 'event($event)', + '(focusin)': 'event($event)', + '(focusout)': 'event($event)', + '(input)': 'event($event)', + '(invalid)': 'event($event)', + '(select)': 'event($event)' + }, + encapsulation: ViewEncapsulation.None +}) +export class DisplayRichTextWidgetComponent extends WidgetComponent implements OnInit { + + parsedHTML: any; + + constructor(public formService: FormService) { + super(formService); + } + + ngOnInit(): void { + /* cspell:disable-next-line */ + this.parsedHTML = edjsHTML().parseStrict(this.field.value).join('\n'); + } + +} diff --git a/lib/core/form/components/widgets/index.ts b/lib/core/form/components/widgets/index.ts index 451a20d161..c8a1ff4647 100644 --- a/lib/core/form/components/widgets/index.ts +++ b/lib/core/form/components/widgets/index.ts @@ -48,6 +48,7 @@ import { DateTimeWidgetComponent } from './date-time/date-time.widget'; import { JsonWidgetComponent } from './json/json.widget'; import { UploadFolderWidgetComponent } from './upload-folder/upload-folder.widget'; import { FileViewerWidgetComponent } from './file-viewer/file-viewer.widget'; +import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich-text.widget'; // core export * from './widget.component'; @@ -80,6 +81,7 @@ export * from './date-time/date-time.widget'; export * from './json/json.widget'; export * from './upload-folder/upload-folder.widget'; export * from './file-viewer/file-viewer.widget'; +export * from './display-rich-text/display-rich-text.widget'; // editors (dynamic table) export * from './dynamic-table/dynamic-table.widget.model'; @@ -123,7 +125,8 @@ export const WIDGET_DIRECTIVES: any[] = [ JsonWidgetComponent, AmountEditorComponent, UploadFolderWidgetComponent, - FileViewerWidgetComponent + FileViewerWidgetComponent, + DisplayRichTextWidgetComponent ]; export const MASK_DIRECTIVE: any[] = [ diff --git a/lib/core/form/services/form-rendering.service.spec.ts b/lib/core/form/services/form-rendering.service.spec.ts index 1dd2c64c49..5fcbffc7ec 100644 --- a/lib/core/form/services/form-rendering.service.spec.ts +++ b/lib/core/form/services/form-rendering.service.spec.ts @@ -22,7 +22,8 @@ import { UnknownWidgetComponent, UploadWidgetComponent, TextWidgetComponent, - JsonWidgetComponent + JsonWidgetComponent, + DisplayRichTextWidgetComponent } from './../components/widgets/index'; import { FormRenderingService } from './form-rendering.service'; @@ -128,4 +129,10 @@ describe('FormRenderingService', () => { const type = resolver(null); expect(type).toBe(JsonWidgetComponent); }); + + it('should resolve Display Rich Text widget for display-rich-text field type', () => { + const resolver = service.getComponentTypeResolver('display-rich-text'); + const type = resolver(null); + expect(type).toBe(DisplayRichTextWidgetComponent); + }); }); diff --git a/lib/core/form/services/form-rendering.service.ts b/lib/core/form/services/form-rendering.service.ts index 31b2e294e4..557d214ee7 100644 --- a/lib/core/form/services/form-rendering.service.ts +++ b/lib/core/form/services/form-rendering.service.ts @@ -49,6 +49,7 @@ export class FormRenderingService extends DynamicComponentMapper { document: DynamicComponentResolver.fromType(widgets.DocumentWidgetComponent), upload: DynamicComponentResolver.fromType(widgets.UploadWidgetComponent), datetime: DynamicComponentResolver.fromType(widgets.DateTimeWidgetComponent), - 'file-viewer': DynamicComponentResolver.fromType(widgets.FileViewerWidgetComponent) + 'file-viewer': DynamicComponentResolver.fromType(widgets.FileViewerWidgetComponent), + 'display-rich-text': DynamicComponentResolver.fromType(widgets.DisplayRichTextWidgetComponent) }; } diff --git a/lib/core/index.ts b/lib/core/index.ts index 679146759a..32207a0065 100644 --- a/lib/core/index.ts +++ b/lib/core/index.ts @@ -26,6 +26,7 @@ export * from './language-menu/index'; export * from './info-drawer/index'; export * from './data-column/index'; export * from './datatable/index'; +export * from './rich-text-editor/index'; export * from './context-menu/index'; export * from './card-view/index'; export * from './app-config/index'; diff --git a/lib/core/mock/form/demo-form.mock.ts b/lib/core/mock/form/demo-form.mock.ts index 870686f394..a117bbc6cf 100644 --- a/lib/core/mock/form/demo-form.mock.ts +++ b/lib/core/mock/form/demo-form.mock.ts @@ -1806,27 +1806,115 @@ export class DemoForm { } } ], - 2: [{ - fieldType: 'AttachFileFieldRepresentation', - id: 'attachfiletest', - name: 'attachfiletest', - type: 'upload', - required: true, - colspan: 2, - placeholder: 'attachfile', - params: { - existingColspan: 2, - maxColspan: 2, - fileSource: { - serviceId: 'local-file', - name: 'Local File' + 2: [ + { + fieldType: 'AttachFileFieldRepresentation', + id: 'attachfiletest', + name: 'attachfiletest', + type: 'upload', + required: true, + colspan: 2, + placeholder: 'attachfile', + params: { + existingColspan: 2, + maxColspan: 2, + fileSource: { + serviceId: 'local-file', + name: 'Local File' + }, + multiple: true, + link: false, + displayableCMProperties: [] }, - multiple: true, - link: false - }, - visibilityCondition: { + visibilityCondition: {} } - }] + ] + } + }, + { + id: 'c23bf7e8-d43c-48b6-86b9-56d174faec4f', + name: 'Label', + type: 'container', + tab: null, + numberOfColumns: 2, + fields: { + 1: [ + { + id: 'DisplayRichtext06jsjb', + name: 'Display Rich text', + type: 'display-rich-text', + readOnly: false, + value: { + time: 1658423394276, + blocks: [ + { + id: 'dry4RE17v_', + type: 'header', + data: { + text: 'Display Rich Text widget example', + level: 1 + } + }, + { + id: 'my1r7YmMOs', + type: 'paragraph', + data: { + text: `Is simply a redonly + dummy text + of the printing and typesetting industry.\n + Lorem Ipsum has been the industry\'s standard du + mmy text ever since the 1500s,\n when an unknown printer took a galley of type + and scrambled it to make a type specimen book. + It has survived not only five centuries,\n + but also the leap into electronic typesetting, + remaining essentially unchanged.\n + It was underline  + in the 1960s with the release of sheets containing\n + Lorem Ipsum passages, and more recently with desktop + publishing software like Aldus PageMaker including versions of Lorem, + example of inline code` + } + }, + { + id: 'vgpY3obXS7', + type: 'list', + data: { + style: 'unordered', + items: [ + 'Unordered list example', + 'Unordered list example' + ] + } + }, + { + id: 'lWR9x3OF1M', + type: 'list', + data: { + style: 'ordered', + items: ['Ordered list example', 'Ordered list example'] + } + }, + { + id: 'aeZV7M8sU1', + type: 'code', + data: { + code: '// Code Block Example\ncatch(Exception ex){\n // Houston, we have a problem\n}' + } + } + ], + version: '2.25.0' + }, + isCustomType: false, + colspan: 1, + rowspan: 1, + visibilityCondition: null, + params: { + existingColspan: 1, + maxColspan: 2 + } + } + ], + 2: [] } } ], diff --git a/lib/core/rich-text-editor/editorjs-config.ts b/lib/core/rich-text-editor/editorjs-config.ts new file mode 100644 index 0000000000..b73c04afe7 --- /dev/null +++ b/lib/core/rich-text-editor/editorjs-config.ts @@ -0,0 +1,72 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +/** Plugin import */ +import CodeTool from '@editorjs/code'; +import Header from '@editorjs/header'; +import InlineCode from '@editorjs/inline-code'; +import List from '@editorjs/list'; +import Marker from '@editorjs/marker'; +import Underline from '@editorjs/underline'; +import * as ChangeFontSize from '@quanzo/change-font-size'; +import * as ColorPlugin from 'editorjs-text-color-plugin'; + +export const editorJsConfig = { + autofocus: true, + logLevel: 'ERROR', + tools: { + underline: { + class: Underline, + shortcut: 'CMD+U' + }, + header: { + class: Header, + inlineToolbar: true + }, + list: { + class: List, + inlineToolbar: true, + config: { + defaultStyle: 'unordered' + } + }, + Color: { + class: ColorPlugin, + config: { + customPicker: true, + colorCollections: ['#FF1300', '#ffa500', '#9C27B0', '#673AB7', '#3F51B5', '#0070FF', '#03A9F4', '#00BCD4', '#5f9ea0', '#4CAF50', '#8BC34A', '#CDDC39', '#FFF', '#000', '#c0c0c0', '#808080', '#800000'], + defaultColor: '#FF1300', + type: 'text' + } + }, + Marker: { + class: Marker, + shortcut: 'CMD+M' + }, + 'Increase/Decrease font size': { + class: ChangeFontSize, + config: { + cssClass: 'plus20pc' + } + }, + inlineCode: { + class: InlineCode, + shortcut: 'CMD+SHIFT+M' + }, + code: CodeTool + } +}; diff --git a/lib/core/rich-text-editor/index.ts b/lib/core/rich-text-editor/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/core/rich-text-editor/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './public-api'; diff --git a/lib/core/rich-text-editor/public-api.ts b/lib/core/rich-text-editor/public-api.ts new file mode 100644 index 0000000000..e5066128c5 --- /dev/null +++ b/lib/core/rich-text-editor/public-api.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +export * from './rich-text-editor.component'; + +export * from './rich-text-editor.module'; diff --git a/lib/core/rich-text-editor/rich-text-editor.component.html b/lib/core/rich-text-editor/rich-text-editor.component.html new file mode 100644 index 0000000000..b6e940d312 --- /dev/null +++ b/lib/core/rich-text-editor/rich-text-editor.component.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/lib/core/rich-text-editor/rich-text-editor.component.scss b/lib/core/rich-text-editor/rich-text-editor.component.scss new file mode 100644 index 0000000000..2d88f3bc5b --- /dev/null +++ b/lib/core/rich-text-editor/rich-text-editor.component.scss @@ -0,0 +1,10 @@ +/* stylelint-disable selector-class-pattern */ +.adf-rich-text-editor-container { + .editorjs { + &.readonly { + .codex-editor__redactor { + padding-bottom: 0 !important; + } + } + } +} diff --git a/lib/core/rich-text-editor/rich-text-editor.component.spec.ts b/lib/core/rich-text-editor/rich-text-editor.component.spec.ts new file mode 100644 index 0000000000..f9b7db4b63 --- /dev/null +++ b/lib/core/rich-text-editor/rich-text-editor.component.spec.ts @@ -0,0 +1,130 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { RichTextEditorComponent } from './rich-text-editor.component'; + +describe('RichTextEditorComponent', () => { + let component: RichTextEditorComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + + const cssSelectors = { + editorContent: '.codex-editor', + editorJsElement: '.editorjs' + }; + + const mockEditorData = { + time: 1658154611110, + blocks: [ + { + id: '1', + type: 'header', + data: { + text: 'Editor.js', + level: 2 + } + } + ], + version: 1 + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RichTextEditorComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RichTextEditorComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it('should render rich text editor', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const editor = debugElement.query(By.css(cssSelectors.editorContent)); + expect(editor).toBeTruthy(); + }); + + it('should generate dynamic id', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + expect(component.dynamicId).toContain('editorjs'); + }); + + it('should set dynamic id to editor js element', async () => { + spyOn(window.crypto, 'getRandomValues').and.returnValue('randomId'); + fixture.detectChanges(); + await fixture.whenStable(); + const editor = debugElement.query(By.css(cssSelectors.editorJsElement)); + expect(editor.nativeElement.id).toEqual('editorjs-randomId'); + }); + + it('should get editorjs data by calling getEditorContent', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + spyOn(component.editorInstance, 'save').and.returnValue(Promise.resolve(mockEditorData)); + const savedEditorData = await component.getEditorContent(); + expect(savedEditorData).toEqual(mockEditorData); + }); + + it('should destroy editor instance on ngOnDestroy', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const destroyEditorSpy = spyOn(component.editorInstance, 'destroy'); + component.ngOnDestroy(); + expect(destroyEditorSpy).toHaveBeenCalledTimes(1); + expect(destroyEditorSpy).toHaveBeenCalled(); + }); + + it('should not destroy editor instance on ngOnDestroy if editor is not ready', async () => { + fixture.detectChanges(); + await fixture.whenStable(); + const destroyEditorSpy = spyOn(component.editorInstance, 'destroy'); + component.isReady = false; + component.ngOnDestroy(); + expect(destroyEditorSpy).not.toHaveBeenCalled(); + }); + + it('should add readonly class if readOnly is set to true', async () => { + component.readOnly = true; + fixture.detectChanges(); + await fixture.whenStable(); + const editorEl = debugElement.query(By.css(cssSelectors.editorJsElement)); + expect(editorEl.nativeElement.classList).toContain('readonly'); + }); + + it('should not add readonly class if readOnly is set to false', async () => { + component.readOnly = false; + fixture.detectChanges(); + await fixture.whenStable(); + const editorEl = debugElement.query(By.css(cssSelectors.editorJsElement)); + expect(editorEl.nativeElement.classList).not.toContain('readonly'); + }); + +}); diff --git a/lib/core/rich-text-editor/rich-text-editor.component.ts b/lib/core/rich-text-editor/rich-text-editor.component.ts new file mode 100644 index 0000000000..ef25b12dfd --- /dev/null +++ b/lib/core/rich-text-editor/rich-text-editor.component.ts @@ -0,0 +1,91 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; + +import EditorJS, { OutputData } from '@editorjs/editorjs'; +import { Subject } from 'rxjs'; +import { editorJsConfig } from './editorjs-config'; + +@Component({ + selector: 'adf-rich-text-editor', + templateUrl: './rich-text-editor.component.html', + styleUrls: ['./rich-text-editor.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class RichTextEditorComponent implements OnInit, OnDestroy, AfterViewInit { + + @Input() + data: OutputData; + + @Input() + readOnly = false; + + private _outputData = new Subject(); + + outputData$ = this._outputData.asObservable(); + + editorInstance: EditorJS; + dynamicId: string; + isReady = false; + + constructor() { + } + + ngOnInit(): void { + this.dynamicId = `editorjs-${crypto.getRandomValues(new Uint32Array(1))}`; + } + + ngAfterViewInit(): void { + this.editorInstance = new EditorJS({ + holder: this.dynamicId, + ...editorJsConfig, + data: this.data, + readOnly: this.readOnly, + onChange: () => { + if (!this.readOnly) { + this.sendEditorOutputData(); + } + }, + onReady: () => { + this.isReady = true; + if (!this.readOnly) { + this.sendEditorOutputData(); + } + } + } as any); + } + + private sendEditorOutputData() { + this.editorInstance.save().then((outputData) => { + this._outputData.next(outputData); + }).catch((error) => { + console.log('Saving failed: ', error); + }); + } + + getEditorContent() { + return this.editorInstance.save(); + } + + ngOnDestroy(): void { + if (this.isReady) { + this.editorInstance.destroy(); + } + } + +} diff --git a/lib/core/rich-text-editor/rich-text-editor.module.ts b/lib/core/rich-text-editor/rich-text-editor.module.ts new file mode 100644 index 0000000000..814902cbe0 --- /dev/null +++ b/lib/core/rich-text-editor/rich-text-editor.module.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RichTextEditorComponent } from './rich-text-editor.component'; + + +@NgModule({ + declarations: [RichTextEditorComponent], + imports: [ + CommonModule + ], + exports: [RichTextEditorComponent] +}) +export class RichTextEditorModule { } diff --git a/package-lock.json b/package-lock.json index b3597644c9..6c75e93c2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4508,6 +4508,49 @@ "integrity": "sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==", "dev": true }, + "@editorjs/code": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@editorjs/code/-/code-2.7.0.tgz", + "integrity": "sha512-gXtTce915fHp3H9i4IqhTxEDbbkT2heFfYiW/bhFHsCmZDpyGzfZxi94kmrEqDmbxXjV49ZZ6GZbR26If13KJw==", + "dev": true + }, + "@editorjs/editorjs": { + "version": "2.25.0", + "resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.25.0.tgz", + "integrity": "sha512-TEu7h7EPh6Lx2VGIM1jVn2dky23TbpVJBd3AGvXcam1lyHuJwZ1iydbsQGF9pFkHSCiaQptTfIjZ8Y24v47Wag==", + "requires": { + "codex-notifier": "^1.1.2", + "codex-tooltip": "^1.0.5", + "nanoid": "^3.1.22" + } + }, + "@editorjs/header": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@editorjs/header/-/header-2.6.2.tgz", + "integrity": "sha512-U1dnT+KGjwFmpWneEEyR2Nqp42hn9iKwQDgRHWQM+y6qx82pg+eAyuIf0QWt2Mluu9uPD2CzNfvJ+pxIuwX8Lw==" + }, + "@editorjs/inline-code": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@editorjs/inline-code/-/inline-code-1.3.1.tgz", + "integrity": "sha512-iY6DeRmJo2Jl6sB2S9QEA9OoSp+KCHBztoY2fjPeiBcKCOKX8we5H3JQJTLxT0L/N8uJqCUiiPKgG6xvqaCn+g==", + "dev": true + }, + "@editorjs/list": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@editorjs/list/-/list-1.7.0.tgz", + "integrity": "sha512-0k0RKbQqfV32u24UYHHz5mrmSu4wr246qqXBT7xQiS533Bfd4hzki6UGzvy4f275ULzi+egbjI3BXLkpoTh9iQ==" + }, + "@editorjs/marker": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@editorjs/marker/-/marker-1.2.2.tgz", + "integrity": "sha512-BN/IHbVKKahGnMAiBOV5U7XUjfF6JkIkTZ6qNLxtTr7PUPM8UsJV8G8pyll9CX+XRUYYZyokA2kEBHTS4vUyyw==", + "dev": true + }, + "@editorjs/underline": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@editorjs/underline/-/underline-1.0.0.tgz", + "integrity": "sha512-2qbmr6/Y5ZIsfmJeAxLujCndc5wpIEQ2uxoPIdR3uHIUQhA6Q28U5HdoGpEZwsP1d7e/A/vBhN18Ugro0iA0MQ==" + }, "@emotion/cache": { "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", @@ -9494,6 +9537,12 @@ "integrity": "sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==", "dev": true }, + "@quanzo/change-font-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quanzo/change-font-size/-/change-font-size-1.0.0.tgz", + "integrity": "sha512-Gwp68CHT45mjszaELuTCOLsakWrUVmcrIEUJGOaGgPTr6VWqoqdFiyuHqtyGmO5/69eoo/HIzlYJ7z3NYgJ3Fg==", + "dev": true + }, "@reach/router": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@reach/router/-/router-1.3.4.tgz", @@ -27732,6 +27781,16 @@ } } }, + "codex-notifier": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/codex-notifier/-/codex-notifier-1.1.2.tgz", + "integrity": "sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg==" + }, + "codex-tooltip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/codex-tooltip/-/codex-tooltip-1.0.5.tgz", + "integrity": "sha512-IuA8LeyLU5p1B+HyhOsqR6oxyFQ11k3i9e9aXw40CrHFTRO2Y1npNBVU3W1SvhKAbUU7R/YikUBdcYFP0RcJag==" + }, "coffeescript": { "version": "1.12.7", "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", @@ -30273,6 +30332,28 @@ "safer-buffer": "^2.1.0" } }, + "editorjs-html": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/editorjs-html/-/editorjs-html-3.4.2.tgz", + "integrity": "sha512-Gy8FV4oHMvnPDXLsdpZYW0Dgjaagff9bPd28/WW9aFdy87FvktJxQz8H+NKpxiGcGoNY27MmjFffurK2yhXjEg==" + }, + "editorjs-inline-font-size-tool": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/editorjs-inline-font-size-tool/-/editorjs-inline-font-size-tool-1.0.1.tgz", + "integrity": "sha512-ZuwRGaLrPBwPqW9KawMGjWuvyvPGO/tPUReWYBdyqAmFuOB5H5nw8TK1gAVIMFp7Q+vT0pQdscgHwnktj8pd5w==", + "dev": true + }, + "editorjs-paragraph-with-alignment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/editorjs-paragraph-with-alignment/-/editorjs-paragraph-with-alignment-3.0.0.tgz", + "integrity": "sha512-vSjjB/KUECEBxYqbj9yn1L799L14n0uoKrvk5qfaAokIHB8mYg5hZ8mOTtoS2cJu+xE3QZ/jmHY/Fh0EJeM8ZQ==" + }, + "editorjs-text-color-plugin": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/editorjs-text-color-plugin/-/editorjs-text-color-plugin-1.13.1.tgz", + "integrity": "sha512-iCK274omDhoU7/zIFwV1fP5ydgfObYXSGV+q2zlFq3D4JDnvtn0KP+6NPM1A4dDlxTczpywPByRKIwmf7z4CIg==", + "dev": true + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -40289,8 +40370,7 @@ "nanoid": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", - "dev": true + "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" }, "nanomatch": { "version": "1.2.13", diff --git a/package.json b/package.json index cae20b9f1e..e7a2260f41 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,10 @@ "@angular/platform-browser-dynamic": "^10.0.4", "@angular/router": "^10.0.4", "@apollo/client": "^3.5.6", + "@editorjs/editorjs": "2.25.0", + "@editorjs/header": "2.6.2", + "@editorjs/list": "1.7.0", + "@editorjs/underline": "1.0.0", "@mat-datetimepicker/core": "^5.1.1", "@mat-datetimepicker/moment": "^5.1.1", "@ngx-translate/core": "^13.0.0", @@ -94,6 +98,8 @@ "cropperjs": "1.5.12", "custom-event-polyfill": "^1.0.7", "dotenv-expand": "^5.1.0", + "editorjs-html": "3.4.2", + "editorjs-paragraph-with-alignment": "3.0.0", "minimatch": "^3.0.4", "minimatch-browser": "1.0.0", "moment-es6": "^1.0.0", @@ -116,11 +122,15 @@ "@angular-eslint/template-parser": "1.2.0", "@angular/cli": "^10.2.2", "@angular/compiler-cli": "^10.0.12", + "@editorjs/code": "2.7.0", + "@editorjs/inline-code": "1.3.1", + "@editorjs/marker": "1.2.2", "@nrwl/schematics": "8.12.11", "@nrwl/storybook": "^12.9.0", "@nrwl/workspace": "^11.2.11", "@paperist/types-remark": "0.1.3", "@playwright/test": "^1.19.2", + "@quanzo/change-font-size": "1.0.0", "@storybook/addon-essentials": "~6.3.0", "@storybook/angular": "~6.3.0", "@storybook/builder-webpack5": "~6.3.0", @@ -141,6 +151,7 @@ "cspell": "^5.5.1", "css-loader": "^5.2.6", "dotenv": "^8.2.0", + "editorjs-text-color-plugin": "1.13.1", "eslint": "^7.6.0", "eslint-plugin-ban": "^1.6.0", "eslint-plugin-import": "2.25.4",