Browse Source

add image selector on rich text editor

pull/159/head
sowobm 8 years ago
parent
commit
a7f0a8b336
  1. 8
      src/Squidex/Squidex.csproj
  2. 36
      src/Squidex/app/framework/angular/rich-editor.component.html
  3. 28
      src/Squidex/app/framework/angular/rich-editor.component.scss
  4. 48
      src/Squidex/app/framework/angular/rich-editor.component.ts
  5. 2
      src/Squidex/app/framework/declarations.ts
  6. 6
      src/Squidex/app/framework/module.ts
  7. 2
      src/Squidex/app/shared/components/asset.component.html
  8. 3
      src/Squidex/app/shared/components/asset.component.ts
  9. 76
      src/Squidex/app/shared/components/declarations.ts
  10. 29
      src/Squidex/app/shared/components/rich-editor.component.html
  11. 36
      src/Squidex/app/shared/components/rich-editor.component.scss
  12. 143
      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. 20
      src/Squidex/appsettings.json

8
src/Squidex/Squidex.csproj

@ -22,6 +22,10 @@
<None Remove="Assets\**" /> <None Remove="Assets\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="app\shell\pages\media\media-page.ts" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="dockerfile"> <None Update="dockerfile">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
@ -81,6 +85,10 @@
<PackageReference Include="System.ValueTuple" Version="4.4.0" /> <PackageReference Include="System.ValueTuple" Version="4.4.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="app\shell\pages\media\media-page.ts" />
</ItemGroup>
<Target Name="IncludeDocFile" BeforeTargets="PrepareForPublish"> <Target Name="IncludeDocFile" BeforeTargets="PrepareForPublish">
<ItemGroup Condition=" '$(DocumentationFile)' != '' "> <ItemGroup Condition=" '$(DocumentationFile)' != '' ">
<_DocumentationFile Include="$(DocumentationFile)" /> <_DocumentationFile Include="$(DocumentationFile)" />

36
src/Squidex/app/framework/angular/rich-editor.component.html

@ -1 +1,37 @@
<div class="editor" #editor></div> <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)="cancelSelectAsset()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="assetsForm" (ngSubmit)="selecteAsset()">
<div class="form-group">
<label for="asset-name">Name</label>
<sqx-control-errors for="name" [submitted]="selecteAsset"></sqx-control-errors>
<input type="text" class="form-control" id="asset-name" formControlName="name" autocomplete="off" sqxFocusOnInit />
</div>
<div class="row">
<sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset"></sqx-asset>
</div>
<div class="form-group clearfix text-right">
<button type="submit" class="btn btn-sm btn-success">Select</button>
<button type="reset" class="btn btn-sm btn-secondary" (click)="cancelSelectAsset()">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>

28
src/Squidex/app/framework/angular/rich-editor.component.scss

@ -6,3 +6,31 @@
border: 1px solid $color-input; border: 1px solid $color-input;
height: 30rem; 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;
}
}

48
src/Squidex/app/framework/angular/rich-editor.component.ts

@ -5,13 +5,16 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, ViewChild } from '@angular/core'; import { AfterViewInit, Component, forwardRef, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms';
import { Types } from './../utils/types'; import { Types } from './../utils/types';
import { ResourceLoaderService } from './../services/resource-loader.service'; import { ResourceLoaderService } from './../services/resource-loader.service';
import { AppComponentBase } from './../../shared/components/app.component-base';
import { ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager } from './../../shared/declarations-base';
declare var tinymce: any; declare var tinymce: any;
export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
@ -24,20 +27,42 @@ export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
templateUrl: './rich-editor.component.html', templateUrl: './rich-editor.component.html',
providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy { export class RichEditorComponent extends AppComponentBase implements ControlValueAccessor, AfterViewInit, OnDestroy, OnInit {
private callChange = (v: any) => { /* NOOP */ }; private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ }; private callTouched = () => { /* NOOP */ };
private tinyEditor: any; private tinyEditor: any;
private tinyInitTimer: any; private tinyInitTimer: any;
private value: string; private value: string;
private isDisabled = false; private isDisabled = false;
public assetsItems: ImmutableArray<AssetDto>;
public assetsPager = new Pager(0, 0, 12);
@ViewChild('editor') @ViewChild('editor')
public editor: ElementRef; public editor: ElementRef;
constructor( public assetsDialog = new ModalView();
private readonly resourceLoader: ResourceLoaderService 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
) { ) {
super(dialogs, apps, authService);
}
public ngOnInit() {
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 ngOnDestroy() { public ngOnDestroy() {
@ -74,7 +99,9 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
self.tinyEditor.setContent(this.value || ''); self.tinyEditor.setContent(this.value || '');
}, 500); }, 500);
}, },
removed_menuitems: 'newdocument', plugins: 'code', target: this.editor.nativeElement removed_menuitems: 'newdocument', plugins: 'code,image', target: this.editor.nativeElement, file_picker_types: 'image', file_picker_callback: (cb: any, value: any, meta: any) => {
self.assetsDialog.show();
}
}); });
}); });
} }
@ -102,4 +129,13 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.callTouched = fn; this.callTouched = fn;
} }
public selecteAsset() {
console.log('Selecting asset ' + this.assetsForm.controls['name'].value);
}
public cancelSelectAsset() {
console.log('asset selection canceled');
this.assetsDialog.hide();
}
} }

2
src/Squidex/app/framework/declarations.ts

@ -36,7 +36,7 @@ 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/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';

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

@ -54,7 +54,7 @@ import {
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
ResourceLoaderService, ResourceLoaderService,
RichEditorComponent, // RichEditorComponent,
RootViewDirective, RootViewDirective,
RootViewService, RootViewService,
ScrollActiveDirective, ScrollActiveDirective,
@ -115,7 +115,7 @@ import {
ParentLinkDirective, ParentLinkDirective,
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
RichEditorComponent, // RichEditorComponent,
RootViewDirective, RootViewDirective,
ScrollActiveDirective, ScrollActiveDirective,
ShortcutComponent, ShortcutComponent,
@ -164,7 +164,7 @@ import {
ParentLinkDirective, ParentLinkDirective,
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
RichEditorComponent, // RichEditorComponent,
RootViewDirective, RootViewDirective,
ScrollActiveDirective, ScrollActiveDirective,
ShortcutComponent, ShortcutComponent,

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

@ -1,4 +1,4 @@
<div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset"> <div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset" (click)="clicked.emit(asset)">
<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">

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

@ -56,6 +56,9 @@ export class AssetComponent extends AppComponentBase 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();

76
src/Squidex/app/shared/components/declarations.ts

@ -0,0 +1,76 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './angular/animations';
export * from './angular/autocomplete.component';
export * from './angular/can-deactivate.guard';
export * from './angular/confirm-click.directive';
export * from './angular/control-errors.component';
export * from './angular/copy.directive';
export * from './angular/date-time-editor.component';
export * from './angular/date-time.pipes';
export * from './angular/dialog-renderer.component';
export * from './angular/dropdown.component';
export * from './angular/file-drop.directive';
export * from './angular/focus-on-init.directive';
export * from './angular/geolocation-editor.component';
export * from './angular/http-extensions-impl';
export * from './angular/image-source.directive';
export * from './angular/indeterminate-value.directive';
export * from './angular/jscript-editor.component';
export * from './angular/json-editor.component';
export * from './angular/lowercase-input.directive';
export * from './angular/markdown-editor.component';
export * from './angular/modal-target.directive';
export * from './angular/modal-view.directive';
export * from './angular/money.pipe';
export * from './angular/name.pipe';
export * from './angular/numbers.pipes';
export * from './angular/onboarding-tooltip.component';
export * from './angular/panel.component';
export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive';
export * from './angular/popup-link.directive';
export * from './angular/progress-bar.component';
export * from './angular/rich-editor.component';
export * from './angular/root-view.directive';
export * from './angular/router-utils';
export * from './angular/scroll-active.directive';
export * from './angular/shortcut.component';
export * from './angular/slider.component';
export * from './angular/sorted.directive';
export * from './angular/stars.component';
export * from './angular/tag-editor.component';
export * from './angular/template-wrapper.directive';
export * from './angular/title.component';
export * from './angular/toggle.component';
export * from './angular/user-report.component';
export * from './angular/validators';
export * from './configurations';
export * from './services/analytics.service';
export * from './services/clipboard.service';
export * from './services/dialog.service';
export * from './services/local-store.service';
export * from './services/local-cache.service';
export * from './services/message-bus.service';
export * from './services/onboarding.service';
export * from './services/resource-loader.service';
export * from './services/root-view.service';
export * from './services/shortcut.service';
export * from './services/title.service';
export * from './utils/date-helper';
export * from './utils/date-time';
export * from './utils/duration';
export * from './utils/immutable-array';
export * from './utils/math-helper';
export * from './utils/modal-view';
export * from './utils/pager';
export * from './utils/string-helper';
export * from './utils/types';
export * from './utils/version';

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

@ -0,0 +1,29 @@
<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)="cancelSelectAsset()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form [formGroup]="assetsForm" (ngSubmit)="selecteAsset()">
<div class="row">
<sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset" (updated)="onAssetClicked($event)" ></sqx-asset>
</div>
<div class="form-group clearfix text-right">
<button type="submit" class="btn btn-sm btn-success">Select</button>
<button type="reset" class="btn btn-sm btn-secondary" (click)="cancelSelectAsset()">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>

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

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

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

@ -0,0 +1,143 @@
/*
* 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 { Types, ResourceLoaderService } from 'framework';
import { AppComponentBase } from './app.component-base';
import { ModalView, AppsStoreService, AssetDto, AssetsService, ImmutableArray, DialogService, AuthService, Pager } 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;
public assetsItems: ImmutableArray<AssetDto>;
public assetsPager = new Pager(0, 0, 12);
@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
) {
super(dialogs, apps, authService);
}
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 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();
}
});
});
}
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 selecteAsset() {
console.log('Selecting asset ' + this.assetsForm.controls['name'].value);
}
public cancelSelectAsset() {
console.log('asset selection canceled');
this.assetsDialog.hide();
}
public onAssetClicked(asset: AssetDto) {
console.log('Asset clicked on');
console.log(asset);
}
}

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

@ -13,5 +13,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

@ -57,7 +57,8 @@ import {
UserManagementService, UserManagementService,
UsersProviderService, UsersProviderService,
UsersService, UsersService,
WebhooksService WebhooksService,
RichEditorComponent
} from './declarations'; } from './declarations';
@NgModule({ @NgModule({
@ -81,7 +82,8 @@ import {
UserNamePipe, UserNamePipe,
UserNameRefPipe, UserNameRefPipe,
UserPicturePipe, UserPicturePipe,
UserPictureRefPipe UserPictureRefPipe,
RichEditorComponent
], ],
exports: [ exports: [
AppFormComponent, AppFormComponent,
@ -99,7 +101,8 @@ import {
UserNamePipe, UserNamePipe,
UserNameRefPipe, UserNameRefPipe,
UserPicturePipe, UserPicturePipe,
UserPictureRefPipe UserPictureRefPipe,
RichEditorComponent
] ]
}) })
export class SqxSharedModule { export class SqxSharedModule {

20
src/Squidex/appsettings.json

@ -3,7 +3,7 @@
/* /*
* Set the base url of your application, to generate correct urls in background process. * Set the base url of your application, to generate correct urls in background process.
*/ */
"baseUrl": "http://localhost:5000" "baseUrl": "http://localhost:59777"
}, },
"ui": { "ui": {
@ -154,7 +154,7 @@
* *
* Read More: https://docs.mongodb.com/manual/reference/connection-string/ * Read More: https://docs.mongodb.com/manual/reference/connection-string/
*/ */
"configuration": "mongodb://localhost", "configuration": "mongodb://localhost:27017",
/* /*
* The database for all your content collections (one collection per app). * The database for all your content collections (one collection per app).
*/ */
@ -171,21 +171,23 @@
* Enable password auth. * Enable password auth.
*/ */
"allowPasswordAuth": true, "allowPasswordAuth": true,
"adminEmail": "sow@orderboxmedia.com",
"adminPassword": "Admin!@#123",
/* /*
* Settings for Google auth (keep empty to disable). * Settings for Google auth (keep empty to disable).1
*/ */
"googleClient": "1006817248705-t3lb3ge808m9am4t7upqth79hulk456l.apps.googleusercontent.com", "googleClient": "",
"googleSecret": "QsEi-fHqkGw2_PjJmtNHf2wg", "googleSecret": "",
/* /*
* Settings for Github auth (keep empty to disable). * Settings for Github auth (keep empty to disable).
*/ */
"githubClient": "211ea00e726baf754c78", "githubClient": "",
"githubSecret": "d0a0d0fe2c26469ae20987ac265b3a339fd73132", "githubSecret": "",
/* /*
* Settings for Microsoft auth (keep empty to disable). * Settings for Microsoft auth (keep empty to disable).
*/ */
"microsoftClient": "b55da740-6648-4502-8746-b9003f29d5f1", "microsoftClient": "",
"microsoftSecret": "idWbANxNYEF4cB368WXJhjN", "microsoftSecret": "",
/* /*
* Lock new users automatically, the administrator must unlock them. * Lock new users automatically, the administrator must unlock them.
*/ */

Loading…
Cancel
Save