Browse Source

Merge branch 'algoadv-contrib' of github.com:algoadv/squidex

pull/167/merge
Sebastian Stehle 8 years ago
parent
commit
1f813fe000
  1. 2
      src/Squidex/app/features/content/pages/content/content-field.component.html
  2. 8
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  3. 1
      src/Squidex/app/framework/angular/rich-editor.component.html
  4. 8
      src/Squidex/app/framework/angular/rich-editor.component.scss
  5. 105
      src/Squidex/app/framework/angular/rich-editor.component.ts
  6. 1
      src/Squidex/app/framework/declarations.ts
  7. 3
      src/Squidex/app/framework/module.ts
  8. 5
      src/Squidex/app/shared/components/asset.component.html
  9. 18
      src/Squidex/app/shared/components/asset.component.ts
  10. 6
      src/Squidex/app/shared/components/rich-editor.component.html
  11. 34
      src/Squidex/app/shared/components/rich-editor.component.scss
  12. 150
      src/Squidex/app/shared/components/rich-editor.component.ts
  13. 1
      src/Squidex/app/shared/declarations.ts
  14. 9
      src/Squidex/app/shared/module.ts
  15. 19
      src/Squidex/app/shared/services/assets.service.ts
  16. 13
      src/Squidex/app/shared/utils/messages.ts

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

@ -53,7 +53,7 @@
<textarea class="form-control" [formControlName]="partition" rows="5"></textarea> <textarea class="form-control" [formControlName]="partition" rows="5"></textarea>
</div> </div>
<div *ngSwitchCase="'RichText'"> <div *ngSwitchCase="'RichText'">
<sqx-rich-editor [formControlName]="partition"></sqx-rich-editor> <sqx-rich-editor [formControlName]="partition" (assetPluginClicked)="richTextEditorAssetPluginClicked($event)"></sqx-rich-editor>
</div> </div>
<div *ngSwitchCase="'Markdown'"> <div *ngSwitchCase="'Markdown'">
<sqx-markdown-editor [formControlName]="partition"></sqx-markdown-editor> <sqx-markdown-editor [formControlName]="partition"></sqx-markdown-editor>

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

@ -9,6 +9,7 @@ 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()
@ -39,7 +42,6 @@ export class ContentFieldComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.masterLanguageCode = this.languages.find(l => l.isMaster).iso2Code; this.masterLanguageCode = this.languages.find(l => l.isMaster).iso2Code;
if (this.field.isDisabled) { if (this.field.isDisabled) {
this.fieldForm.disable(); this.fieldForm.disable();
} }
@ -53,6 +55,10 @@ export class ContentFieldComponent implements OnInit {
} }
} }
public richTextEditorAssetPluginClicked(event: any) {
this.router.navigate(['assets'], { relativeTo: this.route });
}
public selectFieldLanguage(partition: string) { public selectFieldLanguage(partition: string) {
return partition === 'iv' ? this.masterLanguageCode : partition; return partition === 'iv' ? this.masterLanguageCode : partition;
} }

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

@ -37,7 +37,6 @@ export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive'; export * from './angular/parent-link.directive';
export * from './angular/popup-link.directive'; export * from './angular/popup-link.directive';
export * from './angular/progress-bar.component'; export * from './angular/progress-bar.component';
export * from './angular/rich-editor.component';
export * from './angular/root-view.directive'; export * from './angular/root-view.directive';
export * from './angular/router-utils'; export * from './angular/router-utils';
export * from './angular/scroll-active.directive'; export * from './angular/scroll-active.directive';

3
src/Squidex/app/framework/module.ts

@ -55,7 +55,6 @@ import {
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
ResourceLoaderService, ResourceLoaderService,
RichEditorComponent,
RootViewDirective, RootViewDirective,
RootViewService, RootViewService,
ScrollActiveDirective, ScrollActiveDirective,
@ -117,7 +116,6 @@ import {
ParentLinkDirective, ParentLinkDirective,
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
RichEditorComponent,
RootViewDirective, RootViewDirective,
ScrollActiveDirective, ScrollActiveDirective,
ShortcutComponent, ShortcutComponent,
@ -167,7 +165,6 @@ import {
ParentLinkDirective, ParentLinkDirective,
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
RichEditorComponent,
RootViewDirective, RootViewDirective,
ScrollActiveDirective, ScrollActiveDirective,
ShortcutComponent, ShortcutComponent,

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">

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

@ -13,12 +13,14 @@ import { AppContext } from './app-context';
import { import {
AssetDto, AssetDto,
AssetsService, AssetsService,
AssetDragged,
DateTime, DateTime,
fadeAnimation, fadeAnimation,
ModalView, ModalView,
UpdateAssetDto, UpdateAssetDto,
Version, Version,
Versioned Versioned,
MessageBus
} from './../declarations-base'; } from './../declarations-base';
@Component({ @Component({
@ -56,6 +58,9 @@ export class AssetComponent implements OnInit {
@Output() @Output()
public deleting = new EventEmitter<AssetDto>(); public deleting = new EventEmitter<AssetDto>();
@Output()
public clicked = new EventEmitter<AssetDto>();
@Output() @Output()
public failed = new EventEmitter(); public failed = new EventEmitter();
@ -73,7 +78,8 @@ export class AssetComponent implements OnInit {
constructor(public readonly ctx: AppContext, constructor(public readonly ctx: AppContext,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService private readonly assetsService: AssetsService,
private readonly messageBus: MessageBus
) { ) {
} }
@ -177,4 +183,12 @@ export class AssetComponent 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));
}
} }

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

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

34
src/Squidex/app/shared/components/rich-editor.component.scss

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

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

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

1
src/Squidex/app/shared/declarations.ts

@ -12,5 +12,6 @@ export * from './components/help.component';
export * from './components/history.component'; export * from './components/history.component';
export * from './components/language-selector.component'; export * from './components/language-selector.component';
export * from './components/pipes'; export * from './components/pipes';
export * from './components/rich-editor.component';
export * from './declarations-base'; export * from './declarations-base';

9
src/Squidex/app/shared/module.ts

@ -58,7 +58,8 @@ import {
UserPictureRefPipe, UserPictureRefPipe,
UserManagementService, UserManagementService,
UsersProviderService, UsersProviderService,
UsersService UsersService,
RichEditorComponent
} from './declarations'; } from './declarations';
@NgModule({ @NgModule({
@ -82,7 +83,8 @@ import {
UserNamePipe, UserNamePipe,
UserNameRefPipe, UserNameRefPipe,
UserPicturePipe, UserPicturePipe,
UserPictureRefPipe UserPictureRefPipe,
RichEditorComponent
], ],
exports: [ exports: [
AppFormComponent, AppFormComponent,
@ -100,7 +102,8 @@ import {
UserNamePipe, UserNamePipe,
UserNameRefPipe, UserNameRefPipe,
UserPicturePipe, UserPicturePipe,
UserPictureRefPipe UserPictureRefPipe,
RichEditorComponent
] ]
}) })
export class SqxSharedModule { export class SqxSharedModule {

19
src/Squidex/app/shared/services/assets.service.ts

@ -19,6 +19,8 @@ import {
Versioned Versioned
} from 'framework'; } from 'framework';
import { AssetUrlPipe } from 'shared';
export class AssetsDto { export class AssetsDto {
constructor( constructor(
public readonly total: number, public readonly total: number,
@ -100,12 +102,16 @@ export class AssetReplacedDto {
@Injectable() @Injectable()
export class AssetsService { export class AssetsService {
private assetUrlGenerator: AssetUrlPipe;
constructor( constructor(
private readonly http: HttpClient, private readonly http: HttpClient,
private readonly apiUrl: ApiUrlConfig, private readonly apiUrl: ApiUrlConfig,
private readonly analytics: AnalyticsService, private readonly analytics: AnalyticsService,
private readonly localCache: LocalCacheService private readonly localCache: LocalCacheService
) { ) {
this.assetUrlGenerator = new AssetUrlPipe(this.apiUrl);
} }
public getAssets(appName: string, take: number, skip: number, query?: string, mimeTypes?: string[], ids?: string[]): Observable<AssetsDto> { public getAssets(appName: string, take: number, skip: number, query?: string, mimeTypes?: string[], ids?: string[]): Observable<AssetsDto> {
@ -302,6 +308,19 @@ export class AssetsService {
}) })
.pretifyError('Failed to delete asset. Please reload.'); .pretifyError('Failed to delete asset. Please reload.');
} }
public buildDroppedAssetData(asset: AssetDto, dragEvent: DragEvent) {
if (asset.isImage) {
return this.handleImageAsset(asset, dragEvent);
}
return '';
}
private handleImageAsset(asset: AssetDto, dragEvent: DragEvent) {
let res = '<img src="' + this.assetUrlGenerator.transform(asset) + '" ';
res += 'width="' + asset.pixelWidth + '" height="' + asset.pixelHeight + '">';
return res;
}
} }
function getFormData(file: File) { function getFormData(file: File) {

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

@ -16,3 +16,16 @@ export class AssetUpdated {
) { ) {
} }
} }
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