Browse Source

added ability to insert images in rich text editor with assets

pull/159/head
sowobm 8 years ago
parent
commit
a0052790a7
  1. 1
      src/Squidex/app/framework/angular/rich-editor.component.html
  2. 8
      src/Squidex/app/framework/angular/rich-editor.component.scss
  3. 105
      src/Squidex/app/framework/angular/rich-editor.component.ts
  4. 1
      src/Squidex/app/framework/declarations.ts
  5. 3
      src/Squidex/app/framework/module.ts
  6. 2
      src/Squidex/app/shared/components/asset.component.html
  7. 3
      src/Squidex/app/shared/components/asset.component.ts
  8. 40
      src/Squidex/app/shared/components/rich-editor.component.html
  9. 36
      src/Squidex/app/shared/components/rich-editor.component.scss
  10. 165
      src/Squidex/app/shared/components/rich-editor.component.ts
  11. 1
      src/Squidex/app/shared/declarations.ts
  12. 9
      src/Squidex/app/shared/module.ts

1
src/Squidex/app/framework/angular/rich-editor.component.html

@ -1 +0,0 @@
<div class="editor" #editor></div>

8
src/Squidex/app/framework/angular/rich-editor.component.scss

@ -1,8 +0,0 @@
@import '_mixins';
@import '_vars';
.editor {
background: $color-dark-foreground;
border: 1px solid $color-input;
height: 30rem;
}

105
src/Squidex/app/framework/angular/rich-editor.component.ts

@ -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;
}
}

1
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';

3
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,

2
src/Squidex/app/shared/components/asset.component.html

@ -1,4 +1,4 @@
<div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset">
<div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset" (click)="clicked.emit(asset)">
<div class="card-block">
<div class="file-preview" *ngIf="asset && progress == 0" @fade>
<span class="file-type" *ngIf="asset.fileType">

3
src/Squidex/app/shared/components/asset.component.ts

@ -56,6 +56,9 @@ export class AssetComponent extends AppComponentBase implements OnInit {
@Output()
public deleting = new EventEmitter<AssetDto>();
@Output()
public clicked = new EventEmitter<AssetDto>();
@Output()
public failed = new EventEmitter();

40
src/Squidex/app/shared/components/rich-editor.component.html

@ -0,0 +1,40 @@
<div class="editor" #editor></div>
<div class="modal asset-selector" *sqxModalView="assetsDialog;onRoot:true">
<div class="modal-backdrop"></div>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mce-title">Select an asset</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="closeAssetDialog()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="assetsForm" (ngSubmit)="closeAssetDialog()">
<div class="row">
<sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset" (clicked)="onAssetClicked($event)" ></sqx-asset>
</div>
<div class="clearfix" *ngIf="assetsPager.numberOfItems > 0">
<div class="float-right pagination">
<span class="pagination-text">{{assetsPager.itemFirst}}-{{assetsPager.itemLast}} of {{assetsPager.numberOfItems}}</span>
<button class="btn btn-link btn-decent pagination-button" [disabled]="!assetsPager.canGoPrev" (click)="goPrev()">
<i class="icon-angle-left"></i>
</button>
<button class="btn btn-link btn-decent pagination-button" [disabled]="!assetsPager.canGoNext" (click)="goNext()">
<i class="icon-angle-right"></i>
</button>
</div>
</div>
<div class="form-group clearfix text-right">
<button type="submit" class="btn btn-sm btn-secondary" (click)="closeAssetDialog()">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>

36
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;
}
}

165
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<AssetDto>;
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();
}
}
}

1
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';

9
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 {

Loading…
Cancel
Save