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