mirror of https://github.com/Squidex/squidex.git
17 changed files with 258 additions and 127 deletions
@ -1 +0,0 @@ |
|||||
<div class="editor" #editor></div> |
|
||||
@ -1,8 +0,0 @@ |
|||||
@import '_mixins'; |
|
||||
@import '_vars'; |
|
||||
|
|
||||
.editor { |
|
||||
background: $color-dark-foreground; |
|
||||
border: 1px solid $color-input; |
|
||||
height: 30rem; |
|
||||
} |
|
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,6 @@ |
|||||
|
<div class="editor-container"> |
||||
|
<div class="file-drop drag drop-zone" [class.active]="draggedOver" dnd-droppable (onDropSuccess)="onItemDropped($event)"> |
||||
|
<h3>Drop asset in this zone to insert into content</h3> |
||||
|
</div> |
||||
|
<div class="editor" #editor></div> |
||||
|
</div> |
||||
@ -0,0 +1,34 @@ |
|||||
|
@import '_mixins'; |
||||
|
@import '_vars'; |
||||
|
|
||||
|
.editor { |
||||
|
background: $color-dark-foreground; |
||||
|
border: 1px solid $color-input; |
||||
|
height: 30rem; |
||||
|
} |
||||
|
.editor-container { |
||||
|
position: relative; |
||||
|
|
||||
|
.drop-zone { |
||||
|
background: rgba(238, 241, 244, 0.89); |
||||
|
z-index: 5000; |
||||
|
position: absolute; |
||||
|
top: 92px; |
||||
|
left: 20px; |
||||
|
right: 30px; |
||||
|
border-color: #c8d2db; |
||||
|
border-style: dashed; |
||||
|
opacity: 0; |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
text-align: center; |
||||
|
padding-top: 35%; |
||||
|
} |
||||
|
|
||||
|
.drop-zone.active { |
||||
|
opacity: 1; |
||||
|
display:block; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,150 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { MessageBus, AssetDragged, AssetsService, 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 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 draggedOver = false; |
||||
|
private assetDraggedSubscription: any; |
||||
|
|
||||
|
@ViewChild('editor') |
||||
|
public editor: ElementRef; |
||||
|
|
||||
|
@Output() |
||||
|
public assetPluginClicked = new EventEmitter<object>(); |
||||
|
|
||||
|
public assetsForm = this.formBuilder.group({ |
||||
|
name: [''] |
||||
|
}); |
||||
|
|
||||
|
constructor(private readonly resourceLoader: ResourceLoaderService, |
||||
|
private readonly formBuilder: FormBuilder, |
||||
|
private readonly messageBus: MessageBus, |
||||
|
private readonly assetsService: AssetsService |
||||
|
) { |
||||
|
this.assetDraggedSubscription = this.messageBus.of(AssetDragged).subscribe(message => { |
||||
|
// only handle images for now
|
||||
|
if (message.assetDto.isImage) { |
||||
|
if (message.dragEvent === AssetDragged.DRAG_START) { |
||||
|
this.draggedOver = true; |
||||
|
} else { |
||||
|
this.draggedOver = false; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private getEditorOptions() { |
||||
|
const self = this; |
||||
|
return { |
||||
|
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image media assets', |
||||
|
plugins: 'code,image,media', |
||||
|
file_picker_types: 'image', |
||||
|
convert_urls: false, |
||||
|
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(); |
||||
|
}); |
||||
|
|
||||
|
editor.addButton('assets', { |
||||
|
text: '', |
||||
|
icon: 'browse', |
||||
|
tooltip: 'Insert Assets', |
||||
|
onclick: (event: any) => { |
||||
|
self.assetPluginClicked.emit(event); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
self.tinyInitTimer = |
||||
|
setTimeout(() => { |
||||
|
self.tinyEditor.setContent(this.value || ''); |
||||
|
}, 500); |
||||
|
}, |
||||
|
removed_menuitems: 'newdocument', target: this.editor.nativeElement |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public ngOnDestroy() { |
||||
|
clearTimeout(this.tinyInitTimer); |
||||
|
|
||||
|
tinymce.remove(this.editor); |
||||
|
this.assetDraggedSubscription.unsubscribe(); |
||||
|
} |
||||
|
|
||||
|
public ngAfterViewInit() { |
||||
|
const self = this; |
||||
|
this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { |
||||
|
tinymce.init(self.getEditorOptions()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
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 onItemDropped(event: any) { |
||||
|
let content = this.assetsService.buildDroppedAssetData(event.dragData, event.mouseEvent); |
||||
|
if (content) { |
||||
|
this.tinyEditor.execCommand('mceInsertContent', false, content); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue