mirror of https://github.com/Squidex/squidex.git
12 changed files with 252 additions and 122 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,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">×</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> |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue