Browse Source

rich text editor

- insert external images
- insert assets
pull/159/head
sowobm 8 years ago
parent
commit
80769d6e6c
  1. 12
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  2. 2
      src/Squidex/app/features/content/pages/content/content-page.component.html
  3. 12
      src/Squidex/app/shared/components/asset-drop.handler.ts
  4. 5
      src/Squidex/app/shared/components/asset.component.html
  5. 15
      src/Squidex/app/shared/components/asset.component.ts
  6. 46
      src/Squidex/app/shared/components/rich-editor.component.html
  7. 86
      src/Squidex/app/shared/components/rich-editor.component.ts
  8. 13
      src/Squidex/app/shared/utils/messages.ts

12
src/Squidex/app/features/content/pages/content/content-field.component.ts

@ -5,10 +5,11 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Component, Input, OnInit, ViewChild, ElementRef } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { AppLanguageDto, FieldDto } from 'shared'; import { AppLanguageDto, FieldDto } from 'shared';
import { ActivatedRoute, Router } from '@angular/router';
@Component({ @Component({
selector: 'sqx-content-field', selector: 'sqx-content-field',
@ -16,6 +17,8 @@ import { AppLanguageDto, FieldDto } from 'shared';
templateUrl: './content-field.component.html' templateUrl: './content-field.component.html'
}) })
export class ContentFieldComponent implements OnInit { export class ContentFieldComponent implements OnInit {
constructor(private readonly router: Router, private readonly route: ActivatedRoute) {
}
private masterLanguageCode: string; private masterLanguageCode: string;
@Input() @Input()
@ -34,11 +37,8 @@ export class ContentFieldComponent implements OnInit {
public fieldPartition: string; public fieldPartition: string;
public richTextEditorOptions: any; public richTextEditorOptions: any;
@ViewChild('assets')
public assetLink: ElementRef;
private buildRichTextEditorOptions() { private buildRichTextEditorOptions() {
// const self = this; const self = this;
return { return {
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image assets', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image assets',
plugins: 'code,image', plugins: 'code,image',
@ -50,7 +50,7 @@ export class ContentFieldComponent implements OnInit {
icon: 'browse', icon: 'browse',
tooltip: 'Insert Assets', tooltip: 'Insert Assets',
onclick: () => { onclick: () => {
console.log(this.assetLink); self.router.navigate(['assets'], { relativeTo: self.route })
} }
}); });
} }

2
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -49,7 +49,7 @@
<a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory *ngIf="!isNewMode"> <a class="panel-link" routerLink="history" routerLinkActive="active" #linkHistory *ngIf="!isNewMode">
<i class="icon-time"></i> <i class="icon-time"></i>
</a> </a>
<a class="panel-link" routerLink="assets" routerLinkActive="active" #assets> <a class="panel-link" routerLink="assets" routerLinkActive="active">
<i class="icon-media"></i> <i class="icon-media"></i>
</a> </a>

12
src/Squidex/app/shared/components/asset-drop.handler.ts

@ -17,16 +17,10 @@ export class AssetDropHandler {
} }
public buildDroppedAssetData(asset: AssetDto, dragEvent: DragEvent) { public buildDroppedAssetData(asset: AssetDto, dragEvent: DragEvent) {
switch (asset.mimeType) { if (asset.isImage) {
return this.handleImageAsset(asset, dragEvent);
case 'image/jpeg':
case 'image/jpg':
case 'image/png':
case 'image/gif':
return this.handleImageAsset(asset, dragEvent);
default:
return '';
} }
return '';
} }
private handleImageAsset(asset: AssetDto, dragEvent: DragEvent) { private handleImageAsset(asset: AssetDto, dragEvent: DragEvent) {

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

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

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

@ -14,6 +14,7 @@ import {
AppsStoreService, AppsStoreService,
AssetDto, AssetDto,
AssetsService, AssetsService,
AssetDragged,
AuthService, AuthService,
DateTime, DateTime,
DialogService, DialogService,
@ -21,7 +22,8 @@ import {
ModalView, ModalView,
UpdateAssetDto, UpdateAssetDto,
Version, Version,
Versioned Versioned,
MessageBus
} from './../declarations-base'; } from './../declarations-base';
@Component({ @Component({
@ -76,7 +78,8 @@ export class AssetComponent extends AppComponentBase implements OnInit {
constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService, constructor(apps: AppsStoreService, dialogs: DialogService, authService: AuthService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
) { ) {
super(dialogs, apps, authService); super(dialogs, apps, authService);
} }
@ -181,4 +184,12 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.resetRenameForm(); this.resetRenameForm();
} }
public onAssetDragStart(event: any) {
this.messageBus.emit(new AssetDragged(event.dragData, AssetDragged.DRAG_START, this));
}
public onAssetDragEnd(event: any) {
this.messageBus.emit(new AssetDragged(event.dragData, AssetDragged.DRAG_END, this));
}
} }

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

@ -1,48 +1,6 @@
<div class="editor-container"> <div class="editor-container">
<div class="file-drop drag drop-zone" [class.active]="draggedOver" dnd-droppable <div class="file-drop drag drop-zone" [class.active]="draggedOver" dnd-droppable (onDropSuccess)="onItemDropped($event)">
(onDropSuccess)="onItemDropped($event)"
(onDragEnter)="draggedOver=true"
(onDragLeave)="draggedOver=false">
<h3>Drop asset in this zone to insert into content</h3> <h3>Drop asset in this zone to insert into content</h3>
</div> </div>
<div class="editor" #editor></div> <div class="editor" #editor></div>
</div> </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" [hideIcons]="true" (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>-->

86
src/Squidex/app/shared/components/rich-editor.component.ts

@ -9,8 +9,7 @@ import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild,
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms';
import { AppComponentBase } from './app.component-base'; import { AppComponentBase } from './app.component-base';
import { AssetUrlPipe } from './pipes'; import { ApiUrlConfig, ModalView, AppsStoreService, MessageBus, AssetDragged, DialogService, AuthService, Types, ResourceLoaderService } from './../declarations-base';
import { ApiUrlConfig, ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager, Types, ResourceLoaderService } from './../declarations-base';
import { AssetDropHandler } from './asset-drop.handler'; import { AssetDropHandler } from './asset-drop.handler';
declare var tinymce: any; declare var tinymce: any;
@ -32,13 +31,10 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu
private tinyInitTimer: any; private tinyInitTimer: any;
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
private assetSelectorClickHandler: any = null;
public assetsItems: ImmutableArray<AssetDto>;
public assetsPager = new Pager(0, 0, 12);
private assetUrlGenerator: AssetUrlPipe;
private assetsMimeTypes: Array<string> = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'];
private assetDropHandler: AssetDropHandler; private assetDropHandler: AssetDropHandler;
public draggedOver = false; public draggedOver = false;
private assetDraggedSubscription: any;
@ViewChild('editor') @ViewChild('editor')
public editor: ElementRef; public editor: ElementRef;
@ -53,23 +49,22 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu
constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService, constructor(dialogs: DialogService, apps: AppsStoreService, authService: AuthService,
private readonly resourceLoader: ResourceLoaderService, private readonly resourceLoader: ResourceLoaderService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService, private readonly apiUrlConfig: ApiUrlConfig,
private readonly apiUrlConfig: ApiUrlConfig private readonly messageBus: MessageBus
) { ) {
super(dialogs, apps, authService); super(dialogs, apps, authService);
this.assetUrlGenerator = new AssetUrlPipe(this.apiUrlConfig);
this.assetDropHandler = new AssetDropHandler(this.apiUrlConfig); this.assetDropHandler = new AssetDropHandler(this.apiUrlConfig);
}
private load() { this.assetDraggedSubscription = this.messageBus.of(AssetDragged).subscribe(message => {
this.appNameOnce() // only handle images for now
.switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, undefined, this.assetsMimeTypes)) if (message.assetDto.isImage) {
.subscribe(dtos => { if (message.dragEvent === AssetDragged.DRAG_START) {
this.assetsItems = ImmutableArray.of(dtos.items); this.draggedOver = true;
this.assetsPager = this.assetsPager.setCount(dtos.total); } else {
}, error => { this.draggedOver = false;
this.notifyError(error); }
}); }
});
} }
private editorDefaultOptions() { private editorDefaultOptions() {
@ -93,26 +88,6 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu
self.callTouched(); self.callTouched();
}); });
self.tinyEditor.on('init', () => {
let editorDoc = self.tinyEditor.iframeElement;
let dragTarget: any = null;
editorDoc.ondragenter = (event: any) => {
console.log('dragenter');
self.draggedOver = true;
dragTarget = event.target;
};
editorDoc.ondragleave = (event: any) => {
if (event.target === dragTarget) {
self.draggedOver = false;
console.log('dragleave');
} else {
self.draggedOver = true;
}
};
});
// TODO: expose an observable to which we can subscribe to // TODO: expose an observable to which we can subscribe to
if (Types.isFunction(self.editorOptions.onSetup)) { if (Types.isFunction(self.editorOptions.onSetup)) {
self.editorOptions.onSetup(editor); self.editorOptions.onSetup(editor);
@ -127,22 +102,11 @@ 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() { public ngOnDestroy() {
clearTimeout(this.tinyInitTimer); clearTimeout(this.tinyInitTimer);
tinymce.remove(this.editor); tinymce.remove(this.editor);
this.assetDraggedSubscription.unsubscribe();
} }
public ngAfterViewInit() { public ngAfterViewInit() {
@ -150,7 +114,6 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu
this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => {
let editorOptions = { ...self.editorDefaultOptions(), ...self.editorOptions }; let editorOptions = { ...self.editorDefaultOptions(), ...self.editorOptions };
console.log(editorOptions);
tinymce.init(editorOptions); tinymce.init(editorOptions);
}); });
} }
@ -179,24 +142,7 @@ export class RichEditorComponent extends AppComponentBase implements ControlValu
this.callTouched = fn; 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();
}
}
public onItemDropped(event: any) { public onItemDropped(event: any) {
this.draggedOver = false;
let content = this.assetDropHandler.buildDroppedAssetData(event.dragData, event.mouseEvent); let content = this.assetDropHandler.buildDroppedAssetData(event.dragData, event.mouseEvent);
if (content) { if (content) {
this.tinyEditor.execCommand('mceInsertContent', false, content); this.tinyEditor.execCommand('mceInsertContent', false, content);

13
src/Squidex/app/shared/utils/messages.ts

@ -15,4 +15,17 @@ export class AssetUpdated {
public readonly sender: any public readonly sender: any
) { ) {
} }
}
export class AssetDragged {
public static readonly DRAG_START = 'Start';
public static readonly DRAG_END = 'End';
constructor(
public readonly assetDto: AssetDto,
public readonly dragEvent: string,
public readonly sender: any
) {
}
} }
Loading…
Cancel
Save