From a7f0a8b33611c1b80cc64cd9d50df5b1680d4180 Mon Sep 17 00:00:00 2001 From: sowobm Date: Wed, 1 Nov 2017 18:51:17 +0200 Subject: [PATCH 01/11] add image selector on rich text editor --- src/Squidex/Squidex.csproj | 8 + .../angular/rich-editor.component.html | 38 ++++- .../angular/rich-editor.component.scss | 28 ++++ .../angular/rich-editor.component.ts | 48 +++++- src/Squidex/app/framework/declarations.ts | 2 +- src/Squidex/app/framework/module.ts | 6 +- .../shared/components/asset.component.html | 2 +- .../app/shared/components/asset.component.ts | 3 + .../app/shared/components/declarations.ts | 76 ++++++++++ .../components/rich-editor.component.html | 29 ++++ .../components/rich-editor.component.scss | 36 +++++ .../components/rich-editor.component.ts | 143 ++++++++++++++++++ src/Squidex/app/shared/declarations.ts | 1 + src/Squidex/app/shared/module.ts | 9 +- src/Squidex/appsettings.json | 20 +-- 15 files changed, 425 insertions(+), 24 deletions(-) create mode 100644 src/Squidex/app/shared/components/declarations.ts create mode 100644 src/Squidex/app/shared/components/rich-editor.component.html create mode 100644 src/Squidex/app/shared/components/rich-editor.component.scss create mode 100644 src/Squidex/app/shared/components/rich-editor.component.ts diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index a7ffd5aa4..bc47b99f5 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -22,6 +22,10 @@ + + + + PreserveNewest @@ -81,6 +85,10 @@ + + + + <_DocumentationFile Include="$(DocumentationFile)" /> diff --git a/src/Squidex/app/framework/angular/rich-editor.component.html b/src/Squidex/app/framework/angular/rich-editor.component.html index bb84a4048..009451124 100644 --- a/src/Squidex/app/framework/angular/rich-editor.component.html +++ b/src/Squidex/app/framework/angular/rich-editor.component.html @@ -1 +1,37 @@ -
\ No newline at end of file +
+ + \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/rich-editor.component.scss b/src/Squidex/app/framework/angular/rich-editor.component.scss index 07b2c640b..33c03a1e5 100644 --- a/src/Squidex/app/framework/angular/rich-editor.component.scss +++ b/src/Squidex/app/framework/angular/rich-editor.component.scss @@ -5,4 +5,32 @@ background: $color-dark-foreground; border: 1px solid $color-input; height: 30rem; +} +.asset-selector { + z-index: 65560; + + .modal-header { + background: transparent; + border-bottom: 1px solid #c5c5c5; + + .modal-title { + text-decoration: none; + color: #333; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + text-shadow: none; + } + } + + .modal-content { + width: 100%; + border-radius: 0; + -webkit-border-radius: 0; + } + .modal-dialog { + max-width: 900px; + } + + .btn { + border-radius: 0; + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/rich-editor.component.ts b/src/Squidex/app/framework/angular/rich-editor.component.ts index 4e95a8152..cb742dc84 100644 --- a/src/Squidex/app/framework/angular/rich-editor.component.ts +++ b/src/Squidex/app/framework/angular/rich-editor.component.ts @@ -5,13 +5,16 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; import { Types } from './../utils/types'; import { ResourceLoaderService } from './../services/resource-loader.service'; +import { AppComponentBase } from './../../shared/components/app.component-base'; +import { ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager } from './../../shared/declarations-base'; + declare var tinymce: any; export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { @@ -24,20 +27,42 @@ export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { templateUrl: './rich-editor.component.html', providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] }) -export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { +export class RichEditorComponent extends AppComponentBase implements ControlValueAccessor, AfterViewInit, OnDestroy, OnInit { private callChange = (v: any) => { /* NOOP */ }; private callTouched = () => { /* NOOP */ }; private tinyEditor: any; private tinyInitTimer: any; private value: string; private isDisabled = false; + public assetsItems: ImmutableArray; + public assetsPager = new Pager(0, 0, 12); @ViewChild('editor') public editor: ElementRef; - constructor( - private readonly resourceLoader: ResourceLoaderService + public assetsDialog = new ModalView(); + public assetsForm = this.formBuilder.group({ + name: [''] + }); + + constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService, + private readonly resourceLoader: ResourceLoaderService, + private readonly formBuilder: FormBuilder, + private readonly assetsService: AssetsService ) { + super(dialogs, apps, authService); + } + + public ngOnInit() { + + this.appNameOnce() + .switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip)) + .subscribe(dtos => { + this.assetsItems = ImmutableArray.of(dtos.items); + this.assetsPager = this.assetsPager.setCount(dtos.total); + }, error => { + this.notifyError(error); + }); } public ngOnDestroy() { @@ -74,7 +99,9 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, self.tinyEditor.setContent(this.value || ''); }, 500); }, - removed_menuitems: 'newdocument', plugins: 'code', target: this.editor.nativeElement + removed_menuitems: 'newdocument', plugins: 'code,image', target: this.editor.nativeElement, file_picker_types: 'image', file_picker_callback: (cb: any, value: any, meta: any) => { + self.assetsDialog.show(); + } }); }); } @@ -102,4 +129,13 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, public registerOnTouched(fn: any) { this.callTouched = fn; } + + public selecteAsset() { + console.log('Selecting asset ' + this.assetsForm.controls['name'].value); + } + + public cancelSelectAsset() { + console.log('asset selection canceled'); + this.assetsDialog.hide(); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 40c8b1d87..188559d86 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -36,7 +36,7 @@ export * from './angular/panel-container.directive'; export * from './angular/parent-link.directive'; export * from './angular/popup-link.directive'; export * from './angular/progress-bar.component'; -export * from './angular/rich-editor.component'; +// export * from './angular/rich-editor.component'; export * from './angular/root-view.directive'; export * from './angular/router-utils'; export * from './angular/scroll-active.directive'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 973635904..c23309878 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -54,7 +54,7 @@ import { PopupLinkDirective, ProgressBarComponent, ResourceLoaderService, - RichEditorComponent, + // RichEditorComponent, RootViewDirective, RootViewService, ScrollActiveDirective, @@ -115,7 +115,7 @@ import { ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, - RichEditorComponent, + // RichEditorComponent, RootViewDirective, ScrollActiveDirective, ShortcutComponent, @@ -164,7 +164,7 @@ import { ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, - RichEditorComponent, + // RichEditorComponent, RootViewDirective, ScrollActiveDirective, ShortcutComponent, diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 88fc97799..96e1f7633 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index 85540c6cf..d559efba6 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -56,6 +56,9 @@ export class AssetComponent extends AppComponentBase implements OnInit { @Output() public deleting = new EventEmitter(); + @Output() + public clicked = new EventEmitter(); + @Output() public failed = new EventEmitter(); diff --git a/src/Squidex/app/shared/components/declarations.ts b/src/Squidex/app/shared/components/declarations.ts new file mode 100644 index 000000000..40c8b1d87 --- /dev/null +++ b/src/Squidex/app/shared/components/declarations.ts @@ -0,0 +1,76 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +export * from './angular/animations'; +export * from './angular/autocomplete.component'; +export * from './angular/can-deactivate.guard'; +export * from './angular/confirm-click.directive'; +export * from './angular/control-errors.component'; +export * from './angular/copy.directive'; +export * from './angular/date-time-editor.component'; +export * from './angular/date-time.pipes'; +export * from './angular/dialog-renderer.component'; +export * from './angular/dropdown.component'; +export * from './angular/file-drop.directive'; +export * from './angular/focus-on-init.directive'; +export * from './angular/geolocation-editor.component'; +export * from './angular/http-extensions-impl'; +export * from './angular/image-source.directive'; +export * from './angular/indeterminate-value.directive'; +export * from './angular/jscript-editor.component'; +export * from './angular/json-editor.component'; +export * from './angular/lowercase-input.directive'; +export * from './angular/markdown-editor.component'; +export * from './angular/modal-target.directive'; +export * from './angular/modal-view.directive'; +export * from './angular/money.pipe'; +export * from './angular/name.pipe'; +export * from './angular/numbers.pipes'; +export * from './angular/onboarding-tooltip.component'; +export * from './angular/panel.component'; +export * from './angular/panel-container.directive'; +export * from './angular/parent-link.directive'; +export * from './angular/popup-link.directive'; +export * from './angular/progress-bar.component'; +export * from './angular/rich-editor.component'; +export * from './angular/root-view.directive'; +export * from './angular/router-utils'; +export * from './angular/scroll-active.directive'; +export * from './angular/shortcut.component'; +export * from './angular/slider.component'; +export * from './angular/sorted.directive'; +export * from './angular/stars.component'; +export * from './angular/tag-editor.component'; +export * from './angular/template-wrapper.directive'; +export * from './angular/title.component'; +export * from './angular/toggle.component'; +export * from './angular/user-report.component'; +export * from './angular/validators'; +export * from './configurations'; + +export * from './services/analytics.service'; +export * from './services/clipboard.service'; +export * from './services/dialog.service'; +export * from './services/local-store.service'; +export * from './services/local-cache.service'; +export * from './services/message-bus.service'; +export * from './services/onboarding.service'; +export * from './services/resource-loader.service'; +export * from './services/root-view.service'; +export * from './services/shortcut.service'; +export * from './services/title.service'; + +export * from './utils/date-helper'; +export * from './utils/date-time'; +export * from './utils/duration'; +export * from './utils/immutable-array'; +export * from './utils/math-helper'; +export * from './utils/modal-view'; +export * from './utils/pager'; +export * from './utils/string-helper'; +export * from './utils/types'; +export * from './utils/version'; \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html new file mode 100644 index 000000000..2a0f03b4b --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -0,0 +1,29 @@ +
+ + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.scss b/src/Squidex/app/shared/components/rich-editor.component.scss new file mode 100644 index 000000000..33c03a1e5 --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.scss @@ -0,0 +1,36 @@ +@import '_mixins'; +@import '_vars'; + +.editor { + background: $color-dark-foreground; + border: 1px solid $color-input; + height: 30rem; +} +.asset-selector { + z-index: 65560; + + .modal-header { + background: transparent; + border-bottom: 1px solid #c5c5c5; + + .modal-title { + text-decoration: none; + color: #333; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + text-shadow: none; + } + } + + .modal-content { + width: 100%; + border-radius: 0; + -webkit-border-radius: 0; + } + .modal-dialog { + max-width: 900px; + } + + .btn { + border-radius: 0; + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts new file mode 100644 index 000000000..ed8714654 --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.ts @@ -0,0 +1,143 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; + +import { Types, ResourceLoaderService } from 'framework'; +import { AppComponentBase } from './app.component-base'; +import { ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager } from './../declarations-base'; + +declare var tinymce: any; + +export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true +}; + +@Component({ + selector: 'sqx-rich-editor', + styleUrls: ['./rich-editor.component.scss'], + templateUrl: './rich-editor.component.html', + providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class RichEditorComponent extends AppComponentBase implements ControlValueAccessor, AfterViewInit, OnDestroy { + private callChange = (v: any) => { /* NOOP */ }; + private callTouched = () => { /* NOOP */ }; + private tinyEditor: any; + private tinyInitTimer: any; + private value: string; + private isDisabled = false; + public assetsItems: ImmutableArray; + public assetsPager = new Pager(0, 0, 12); + + @ViewChild('editor') + public editor: ElementRef; + + public assetsDialog = new ModalView(); + public assetsForm = this.formBuilder.group({ + name: [''] + }); + + constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService, + private readonly resourceLoader: ResourceLoaderService, + private readonly formBuilder: FormBuilder, + private readonly assetsService: AssetsService + ) { + super(dialogs, apps, authService); + } + + private load() { + this.appNameOnce() + .switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip)) + .subscribe(dtos => { + this.assetsItems = ImmutableArray.of(dtos.items); + this.assetsPager = this.assetsPager.setCount(dtos.total); + }, error => { + this.notifyError(error); + }); + } + + public ngOnDestroy() { + clearTimeout(this.tinyInitTimer); + + tinymce.remove(this.editor); + } + + public ngAfterViewInit() { + const self = this; + + this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { + tinymce.init({ + setup: (editor: any) => { + self.tinyEditor = editor; + self.tinyEditor.setMode(this.isDisabled ? 'readonly' : 'design'); + + self.tinyEditor.on('change', () => { + const value = editor.getContent(); + + if (this.value !== value) { + this.value = value; + + self.callChange(value); + } + }); + + self.tinyEditor.on('blur', () => { + self.callTouched(); + }); + + this.tinyInitTimer = + setTimeout(() => { + self.tinyEditor.setContent(this.value || ''); + }, 500); + }, + removed_menuitems: 'newdocument', plugins: 'code,image', target: this.editor.nativeElement, file_picker_types: 'image', file_picker_callback: (cb: any, value: any, meta: any) => { + self.load(); + self.assetsDialog.show(); + } + }); + }); + } + + public writeValue(value: string) { + this.value = Types.isString(value) ? value : ''; + + if (this.tinyEditor) { + this.tinyEditor.setContent(this.value); + } + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + + if (this.tinyEditor) { + this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design'); + } + } + + public registerOnChange(fn: any) { + this.callChange = fn; + } + + public registerOnTouched(fn: any) { + this.callTouched = fn; + } + + public selecteAsset() { + console.log('Selecting asset ' + this.assetsForm.controls['name'].value); + } + + public cancelSelectAsset() { + console.log('asset selection canceled'); + this.assetsDialog.hide(); + } + + public onAssetClicked(asset: AssetDto) { + console.log('Asset clicked on'); + console.log(asset); + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 3e4313916..5fc29bd25 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -13,5 +13,6 @@ export * from './components/help.component'; export * from './components/history.component'; export * from './components/language-selector.component'; export * from './components/pipes'; +export * from './components/rich-editor.component'; export * from './declarations-base'; \ No newline at end of file diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index f67368770..6f229df77 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -57,7 +57,8 @@ import { UserManagementService, UsersProviderService, UsersService, - WebhooksService + WebhooksService, + RichEditorComponent } from './declarations'; @NgModule({ @@ -81,7 +82,8 @@ import { UserNamePipe, UserNameRefPipe, UserPicturePipe, - UserPictureRefPipe + UserPictureRefPipe, + RichEditorComponent ], exports: [ AppFormComponent, @@ -99,7 +101,8 @@ import { UserNamePipe, UserNameRefPipe, UserPicturePipe, - UserPictureRefPipe + UserPictureRefPipe, + RichEditorComponent ] }) export class SqxSharedModule { diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index eec0956f2..5d3319bce 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -3,7 +3,7 @@ /* * Set the base url of your application, to generate correct urls in background process. */ - "baseUrl": "http://localhost:5000" + "baseUrl": "http://localhost:59777" }, "ui": { @@ -154,7 +154,7 @@ * * Read More: https://docs.mongodb.com/manual/reference/connection-string/ */ - "configuration": "mongodb://localhost", + "configuration": "mongodb://localhost:27017", /* * The database for all your content collections (one collection per app). */ @@ -171,21 +171,23 @@ * Enable password auth. */ "allowPasswordAuth": true, + "adminEmail": "sow@orderboxmedia.com", + "adminPassword": "Admin!@#123", /* - * Settings for Google auth (keep empty to disable). + * Settings for Google auth (keep empty to disable).1 */ - "googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", - "googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", + "googleClient": "", + "googleSecret": "", /* * Settings for Github auth (keep empty to disable). */ - "githubClient": "211ea00e726baf754c78", - "githubSecret": "d0a0d0fe2c26469ae20987ac265b3a339fd73132", + "githubClient": "", + "githubSecret": "", /* * Settings for Microsoft auth (keep empty to disable). */ - "microsoftClient": "b55da740-6648-4502-8746-b9003f29d5f1", - "microsoftSecret": "idWbANxNYEF4cB368WXJhjN", + "microsoftClient": "", + "microsoftSecret": "", /* * Lock new users automatically, the administrator must unlock them. */ From 02ad5bab0501f46431bef97d8315ac368ad7097d Mon Sep 17 00:00:00 2001 From: sowobm Date: Thu, 2 Nov 2017 12:49:55 +0200 Subject: [PATCH 02/11] add assets selector in tinyMCE rich text editor --- src/Squidex/Squidex.csproj | 8 -- .../app/shared/components/declarations.ts | 76 ------------------- .../components/rich-editor.component.html | 21 +++-- .../components/rich-editor.component.ts | 44 ++++++++--- 4 files changed, 49 insertions(+), 100 deletions(-) delete mode 100644 src/Squidex/app/shared/components/declarations.ts diff --git a/src/Squidex/Squidex.csproj b/src/Squidex/Squidex.csproj index bc47b99f5..a7ffd5aa4 100644 --- a/src/Squidex/Squidex.csproj +++ b/src/Squidex/Squidex.csproj @@ -22,10 +22,6 @@ - - - - PreserveNewest @@ -85,10 +81,6 @@ - - - - <_DocumentationFile Include="$(DocumentationFile)" /> diff --git a/src/Squidex/app/shared/components/declarations.ts b/src/Squidex/app/shared/components/declarations.ts deleted file mode 100644 index 40c8b1d87..000000000 --- a/src/Squidex/app/shared/components/declarations.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -export * from './angular/animations'; -export * from './angular/autocomplete.component'; -export * from './angular/can-deactivate.guard'; -export * from './angular/confirm-click.directive'; -export * from './angular/control-errors.component'; -export * from './angular/copy.directive'; -export * from './angular/date-time-editor.component'; -export * from './angular/date-time.pipes'; -export * from './angular/dialog-renderer.component'; -export * from './angular/dropdown.component'; -export * from './angular/file-drop.directive'; -export * from './angular/focus-on-init.directive'; -export * from './angular/geolocation-editor.component'; -export * from './angular/http-extensions-impl'; -export * from './angular/image-source.directive'; -export * from './angular/indeterminate-value.directive'; -export * from './angular/jscript-editor.component'; -export * from './angular/json-editor.component'; -export * from './angular/lowercase-input.directive'; -export * from './angular/markdown-editor.component'; -export * from './angular/modal-target.directive'; -export * from './angular/modal-view.directive'; -export * from './angular/money.pipe'; -export * from './angular/name.pipe'; -export * from './angular/numbers.pipes'; -export * from './angular/onboarding-tooltip.component'; -export * from './angular/panel.component'; -export * from './angular/panel-container.directive'; -export * from './angular/parent-link.directive'; -export * from './angular/popup-link.directive'; -export * from './angular/progress-bar.component'; -export * from './angular/rich-editor.component'; -export * from './angular/root-view.directive'; -export * from './angular/router-utils'; -export * from './angular/scroll-active.directive'; -export * from './angular/shortcut.component'; -export * from './angular/slider.component'; -export * from './angular/sorted.directive'; -export * from './angular/stars.component'; -export * from './angular/tag-editor.component'; -export * from './angular/template-wrapper.directive'; -export * from './angular/title.component'; -export * from './angular/toggle.component'; -export * from './angular/user-report.component'; -export * from './angular/validators'; -export * from './configurations'; - -export * from './services/analytics.service'; -export * from './services/clipboard.service'; -export * from './services/dialog.service'; -export * from './services/local-store.service'; -export * from './services/local-cache.service'; -export * from './services/message-bus.service'; -export * from './services/onboarding.service'; -export * from './services/resource-loader.service'; -export * from './services/root-view.service'; -export * from './services/shortcut.service'; -export * from './services/title.service'; - -export * from './utils/date-helper'; -export * from './utils/date-time'; -export * from './utils/duration'; -export * from './utils/immutable-array'; -export * from './utils/math-helper'; -export * from './utils/modal-view'; -export * from './utils/pager'; -export * from './utils/string-helper'; -export * from './utils/types'; -export * from './utils/version'; \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html index 2a0f03b4b..413c8e708 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.html +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -7,20 +7,31 @@ diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts index ed8714654..b88b56988 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.ts +++ b/src/Squidex/app/shared/components/rich-editor.component.ts @@ -8,9 +8,9 @@ import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; -import { Types, ResourceLoaderService } from 'framework'; import { AppComponentBase } from './app.component-base'; -import { ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager } from './../declarations-base'; +import { AssetUrlPipe } from './pipes'; +import { ApiUrlConfig, ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager, Types, ResourceLoaderService } from './../declarations-base'; declare var tinymce: any; @@ -31,8 +31,10 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu private tinyInitTimer: any; private value: string; private isDisabled = false; + private assetSelectorClickHandler: any = null; public assetsItems: ImmutableArray; public assetsPager = new Pager(0, 0, 12); + private assetUrlGenerator: AssetUrlPipe; @ViewChild('editor') public editor: ElementRef; @@ -45,9 +47,11 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService, private readonly resourceLoader: ResourceLoaderService, private readonly formBuilder: FormBuilder, - private readonly assetsService: AssetsService + private readonly assetsService: AssetsService, + private readonly apiUrlConfig: ApiUrlConfig ) { super(dialogs, apps, authService); + this.assetUrlGenerator = new AssetUrlPipe(this.apiUrlConfig); } private load() { @@ -61,6 +65,18 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu }); } + public goNext() { + this.assetsPager = this.assetsPager.goNext(); + + this.load(); + } + + public goPrev() { + this.assetsPager = this.assetsPager.goPrev(); + + this.load(); + } + public ngOnDestroy() { clearTimeout(this.tinyInitTimer); @@ -98,6 +114,10 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu removed_menuitems: 'newdocument', plugins: 'code,image', target: this.editor.nativeElement, file_picker_types: 'image', file_picker_callback: (cb: any, value: any, meta: any) => { self.load(); self.assetsDialog.show(); + self.assetSelectorClickHandler = { + cb: cb, + meta: meta + }; } }); }); @@ -127,17 +147,19 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu this.callTouched = fn; } - public selecteAsset() { - console.log('Selecting asset ' + this.assetsForm.controls['name'].value); - } - - public cancelSelectAsset() { - console.log('asset selection canceled'); + public closeAssetDialog() { this.assetsDialog.hide(); + this.assetSelectorClickHandler = null; } public onAssetClicked(asset: AssetDto) { - console.log('Asset clicked on'); - console.log(asset); + if (this.assetSelectorClickHandler != null) { + this.assetSelectorClickHandler.cb(this.assetUrlGenerator.transform(asset), { + description: asset.fileName, + width: asset.pixelWidth, + height: asset.pixelHeight + }); + this.closeAssetDialog(); + } } } \ No newline at end of file From a0052790a7e56807d3354116d08073cf67bd378f Mon Sep 17 00:00:00 2001 From: sowobm Date: Thu, 2 Nov 2017 18:26:03 +0200 Subject: [PATCH 03/11] added ability to insert images in rich text editor with assets --- .../angular/rich-editor.component.html | 1 - .../angular/rich-editor.component.scss | 8 - .../angular/rich-editor.component.ts | 105 ----------- src/Squidex/app/framework/declarations.ts | 1 - src/Squidex/app/framework/module.ts | 3 - .../shared/components/asset.component.html | 2 +- .../app/shared/components/asset.component.ts | 3 + .../components/rich-editor.component.html | 40 +++++ .../components/rich-editor.component.scss | 36 ++++ .../components/rich-editor.component.ts | 165 ++++++++++++++++++ src/Squidex/app/shared/declarations.ts | 1 + src/Squidex/app/shared/module.ts | 9 +- 12 files changed, 252 insertions(+), 122 deletions(-) delete mode 100644 src/Squidex/app/framework/angular/rich-editor.component.html delete mode 100644 src/Squidex/app/framework/angular/rich-editor.component.scss delete mode 100644 src/Squidex/app/framework/angular/rich-editor.component.ts create mode 100644 src/Squidex/app/shared/components/rich-editor.component.html create mode 100644 src/Squidex/app/shared/components/rich-editor.component.scss create mode 100644 src/Squidex/app/shared/components/rich-editor.component.ts diff --git a/src/Squidex/app/framework/angular/rich-editor.component.html b/src/Squidex/app/framework/angular/rich-editor.component.html deleted file mode 100644 index bb84a4048..000000000 --- a/src/Squidex/app/framework/angular/rich-editor.component.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/rich-editor.component.scss b/src/Squidex/app/framework/angular/rich-editor.component.scss deleted file mode 100644 index 07b2c640b..000000000 --- a/src/Squidex/app/framework/angular/rich-editor.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -@import '_mixins'; -@import '_vars'; - -.editor { - background: $color-dark-foreground; - border: 1px solid $color-input; - height: 30rem; -} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/rich-editor.component.ts b/src/Squidex/app/framework/angular/rich-editor.component.ts deleted file mode 100644 index 4e95a8152..000000000 --- a/src/Squidex/app/framework/angular/rich-editor.component.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; - -import { Types } from './../utils/types'; - -import { ResourceLoaderService } from './../services/resource-loader.service'; - -declare var tinymce: any; - -export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { - provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true -}; - -@Component({ - selector: 'sqx-rich-editor', - styleUrls: ['./rich-editor.component.scss'], - templateUrl: './rich-editor.component.html', - providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] -}) -export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { - private callChange = (v: any) => { /* NOOP */ }; - private callTouched = () => { /* NOOP */ }; - private tinyEditor: any; - private tinyInitTimer: any; - private value: string; - private isDisabled = false; - - @ViewChild('editor') - public editor: ElementRef; - - constructor( - private readonly resourceLoader: ResourceLoaderService - ) { - } - - public ngOnDestroy() { - clearTimeout(this.tinyInitTimer); - - tinymce.remove(this.editor); - } - - public ngAfterViewInit() { - const self = this; - - this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { - tinymce.init({ - setup: (editor: any) => { - self.tinyEditor = editor; - self.tinyEditor.setMode(this.isDisabled ? 'readonly' : 'design'); - - self.tinyEditor.on('change', () => { - const value = editor.getContent(); - - if (this.value !== value) { - this.value = value; - - self.callChange(value); - } - }); - - self.tinyEditor.on('blur', () => { - self.callTouched(); - }); - - this.tinyInitTimer = - setTimeout(() => { - self.tinyEditor.setContent(this.value || ''); - }, 500); - }, - removed_menuitems: 'newdocument', plugins: 'code', target: this.editor.nativeElement - }); - }); - } - - public writeValue(value: string) { - this.value = Types.isString(value) ? value : ''; - - if (this.tinyEditor) { - this.tinyEditor.setContent(this.value); - } - } - - public setDisabledState(isDisabled: boolean): void { - this.isDisabled = isDisabled; - - if (this.tinyEditor) { - this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design'); - } - } - - public registerOnChange(fn: any) { - this.callChange = fn; - } - - public registerOnTouched(fn: any) { - this.callTouched = fn; - } -} \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 40c8b1d87..7ba82733d 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -36,7 +36,6 @@ export * from './angular/panel-container.directive'; export * from './angular/parent-link.directive'; export * from './angular/popup-link.directive'; export * from './angular/progress-bar.component'; -export * from './angular/rich-editor.component'; export * from './angular/root-view.directive'; export * from './angular/router-utils'; export * from './angular/scroll-active.directive'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 973635904..7a99b4dbc 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -54,7 +54,6 @@ import { PopupLinkDirective, ProgressBarComponent, ResourceLoaderService, - RichEditorComponent, RootViewDirective, RootViewService, ScrollActiveDirective, @@ -115,7 +114,6 @@ import { ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, - RichEditorComponent, RootViewDirective, ScrollActiveDirective, ShortcutComponent, @@ -164,7 +162,6 @@ import { ParentLinkDirective, PopupLinkDirective, ProgressBarComponent, - RichEditorComponent, RootViewDirective, ScrollActiveDirective, ShortcutComponent, diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 88fc97799..96e1f7633 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index 85540c6cf..d559efba6 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -56,6 +56,9 @@ export class AssetComponent extends AppComponentBase implements OnInit { @Output() public deleting = new EventEmitter(); + @Output() + public clicked = new EventEmitter(); + @Output() public failed = new EventEmitter(); diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html new file mode 100644 index 000000000..413c8e708 --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -0,0 +1,40 @@ +
+ + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.scss b/src/Squidex/app/shared/components/rich-editor.component.scss new file mode 100644 index 000000000..33c03a1e5 --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.scss @@ -0,0 +1,36 @@ +@import '_mixins'; +@import '_vars'; + +.editor { + background: $color-dark-foreground; + border: 1px solid $color-input; + height: 30rem; +} +.asset-selector { + z-index: 65560; + + .modal-header { + background: transparent; + border-bottom: 1px solid #c5c5c5; + + .modal-title { + text-decoration: none; + color: #333; + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; + text-shadow: none; + } + } + + .modal-content { + width: 100%; + border-radius: 0; + -webkit-border-radius: 0; + } + .modal-dialog { + max-width: 900px; + } + + .btn { + border-radius: 0; + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts new file mode 100644 index 000000000..b88b56988 --- /dev/null +++ b/src/Squidex/app/shared/components/rich-editor.component.ts @@ -0,0 +1,165 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; + +import { AppComponentBase } from './app.component-base'; +import { AssetUrlPipe } from './pipes'; +import { ApiUrlConfig, ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager, Types, ResourceLoaderService } from './../declarations-base'; + +declare var tinymce: any; + +export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true +}; + +@Component({ + selector: 'sqx-rich-editor', + styleUrls: ['./rich-editor.component.scss'], + templateUrl: './rich-editor.component.html', + providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class RichEditorComponent extends AppComponentBase implements ControlValueAccessor, AfterViewInit, OnDestroy { + private callChange = (v: any) => { /* NOOP */ }; + private callTouched = () => { /* NOOP */ }; + private tinyEditor: any; + private tinyInitTimer: any; + private value: string; + private isDisabled = false; + private assetSelectorClickHandler: any = null; + public assetsItems: ImmutableArray; + public assetsPager = new Pager(0, 0, 12); + private assetUrlGenerator: AssetUrlPipe; + + @ViewChild('editor') + public editor: ElementRef; + + public assetsDialog = new ModalView(); + public assetsForm = this.formBuilder.group({ + name: [''] + }); + + constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService, + private readonly resourceLoader: ResourceLoaderService, + private readonly formBuilder: FormBuilder, + private readonly assetsService: AssetsService, + private readonly apiUrlConfig: ApiUrlConfig + ) { + super(dialogs, apps, authService); + this.assetUrlGenerator = new AssetUrlPipe(this.apiUrlConfig); + } + + private load() { + this.appNameOnce() + .switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip)) + .subscribe(dtos => { + this.assetsItems = ImmutableArray.of(dtos.items); + this.assetsPager = this.assetsPager.setCount(dtos.total); + }, error => { + this.notifyError(error); + }); + } + + public goNext() { + this.assetsPager = this.assetsPager.goNext(); + + this.load(); + } + + public goPrev() { + this.assetsPager = this.assetsPager.goPrev(); + + this.load(); + } + + public ngOnDestroy() { + clearTimeout(this.tinyInitTimer); + + tinymce.remove(this.editor); + } + + public ngAfterViewInit() { + const self = this; + + this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { + tinymce.init({ + setup: (editor: any) => { + self.tinyEditor = editor; + self.tinyEditor.setMode(this.isDisabled ? 'readonly' : 'design'); + + self.tinyEditor.on('change', () => { + const value = editor.getContent(); + + if (this.value !== value) { + this.value = value; + + self.callChange(value); + } + }); + + self.tinyEditor.on('blur', () => { + self.callTouched(); + }); + + this.tinyInitTimer = + setTimeout(() => { + self.tinyEditor.setContent(this.value || ''); + }, 500); + }, + removed_menuitems: 'newdocument', plugins: 'code,image', target: this.editor.nativeElement, file_picker_types: 'image', file_picker_callback: (cb: any, value: any, meta: any) => { + self.load(); + self.assetsDialog.show(); + self.assetSelectorClickHandler = { + cb: cb, + meta: meta + }; + } + }); + }); + } + + public writeValue(value: string) { + this.value = Types.isString(value) ? value : ''; + + if (this.tinyEditor) { + this.tinyEditor.setContent(this.value); + } + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + + if (this.tinyEditor) { + this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design'); + } + } + + public registerOnChange(fn: any) { + this.callChange = fn; + } + + public registerOnTouched(fn: any) { + this.callTouched = fn; + } + + public closeAssetDialog() { + this.assetsDialog.hide(); + this.assetSelectorClickHandler = null; + } + + public onAssetClicked(asset: AssetDto) { + if (this.assetSelectorClickHandler != null) { + this.assetSelectorClickHandler.cb(this.assetUrlGenerator.transform(asset), { + description: asset.fileName, + width: asset.pixelWidth, + height: asset.pixelHeight + }); + this.closeAssetDialog(); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 3e4313916..5fc29bd25 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -13,5 +13,6 @@ export * from './components/help.component'; export * from './components/history.component'; export * from './components/language-selector.component'; export * from './components/pipes'; +export * from './components/rich-editor.component'; export * from './declarations-base'; \ No newline at end of file diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index f67368770..6f229df77 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -57,7 +57,8 @@ import { UserManagementService, UsersProviderService, UsersService, - WebhooksService + WebhooksService, + RichEditorComponent } from './declarations'; @NgModule({ @@ -81,7 +82,8 @@ import { UserNamePipe, UserNameRefPipe, UserPicturePipe, - UserPictureRefPipe + UserPictureRefPipe, + RichEditorComponent ], exports: [ AppFormComponent, @@ -99,7 +101,8 @@ import { UserNamePipe, UserNameRefPipe, UserPicturePipe, - UserPictureRefPipe + UserPictureRefPipe, + RichEditorComponent ] }) export class SqxSharedModule { From 75fb4d5389775fdc281ded5bdd7592b7255e3d80 Mon Sep 17 00:00:00 2001 From: sowobm Date: Fri, 3 Nov 2017 15:14:43 +0200 Subject: [PATCH 04/11] rich text editor assets selector changes - show only image mime types (jpeg, png and gif) - hide edit, download and delete icons on asset listing - use absolute urls for inserted images --- src/Squidex/app/shared/components/asset.component.html | 8 ++++---- src/Squidex/app/shared/components/asset.component.ts | 3 +++ .../app/shared/components/rich-editor.component.html | 2 +- .../app/shared/components/rich-editor.component.ts | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index 96e1f7633..af8ff345b 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -15,17 +15,17 @@
- + - + - + - + diff --git a/src/Squidex/app/shared/components/asset.component.ts b/src/Squidex/app/shared/components/asset.component.ts index d559efba6..1a78c7dc4 100644 --- a/src/Squidex/app/shared/components/asset.component.ts +++ b/src/Squidex/app/shared/components/asset.component.ts @@ -44,6 +44,9 @@ export class AssetComponent extends AppComponentBase implements OnInit { @Input() public closeMode = false; + @Input() + public hideIcons = false; + @Output() public loaded = new EventEmitter(); diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html index 413c8e708..a1a7f69ba 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.html +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -15,7 +15,7 @@