Browse Source

Drag and drop assets to markdown editor.

pull/235/head
Sebastian Stehle 8 years ago
parent
commit
6665ddb2ed
  1. 4
      src/Squidex/app/features/content/pages/content/content-field.component.html
  2. 2
      src/Squidex/app/features/content/pages/content/content-field.component.ts
  3. 5
      src/Squidex/app/framework/angular/markdown-editor.component.html
  4. 15
      src/Squidex/app/framework/angular/markdown-editor.component.scss
  5. 106
      src/Squidex/app/framework/angular/markdown-editor.component.ts
  6. 1
      src/Squidex/app/framework/declarations.ts
  7. 3
      src/Squidex/app/framework/module.ts
  8. 9
      src/Squidex/app/shared/components/markdown-editor.component.html
  9. 41
      src/Squidex/app/shared/components/markdown-editor.component.scss
  10. 211
      src/Squidex/app/shared/components/markdown-editor.component.ts
  11. 1
      src/Squidex/app/shared/components/rich-editor.component.scss
  12. 12
      src/Squidex/app/shared/components/rich-editor.component.ts
  13. 1
      src/Squidex/app/shared/declarations.ts
  14. 3
      src/Squidex/app/shared/module.ts
  15. 15
      src/Squidex/app/theme/_common.scss

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

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

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

@ -56,7 +56,7 @@ export class ContentFieldComponent implements OnInit {
}
}
public richTextEditorAssetPluginClicked(event: any) {
public assetPluginClicked() {
this.router.navigate(['assets'], { relativeTo: this.route });
}

5
src/Squidex/app/framework/angular/markdown-editor.component.html

@ -1,5 +0,0 @@
<div #container>
<div #inner [class.fullscreen]="isFullscreen">
<textarea class="form-control" #editor></textarea>
</div>
</div>

15
src/Squidex/app/framework/angular/markdown-editor.component.scss

@ -1,15 +0,0 @@
@import '_mixins';
@import '_vars';
$background: #fff;
.editor {
height: 30rem;
}
.fullscreen {
@include fixed(0, 0, 0, 0);
border: 0;
background: $background;
z-index: 100000;
}

106
src/Squidex/app/framework/angular/markdown-editor.component.ts

@ -1,106 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Component, forwardRef, ElementRef, 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 SimpleMDE: any;
export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MarkdownEditorComponent), multi: true
};
@Component({
selector: 'sqx-markdown-editor',
styleUrls: ['./markdown-editor.component.scss'],
templateUrl: './markdown-editor.component.html',
providers: [SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR]
})
export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewInit {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
private simplemde: any;
private value: string;
private isDisabled = false;
@ViewChild('editor')
public editor: ElementRef;
@ViewChild('container')
public container: ElementRef;
@ViewChild('inner')
public inner: ElementRef;
public isFullscreen = false;
constructor(
private readonly resourceLoader: ResourceLoaderService
) {
this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css');
}
public writeValue(value: string) {
this.value = Types.isString(value) ? value : '';
if (this.simplemde) {
this.simplemde.value(this.value);
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.simplemde) {
this.simplemde.codemirror.setOption('readOnly', isDisabled);
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
public ngAfterViewInit() {
this.resourceLoader.loadScript('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js').then(() => {
this.simplemde = new SimpleMDE({ element: this.editor.nativeElement });
this.simplemde.value(this.value || '');
this.simplemde.codemirror.setOption('readOnly', this.isDisabled);
this.simplemde.codemirror.on('change', () => {
const value = this.simplemde.value();
if (this.value !== value) {
this.value = value;
this.callChange(value);
}
});
this.simplemde.codemirror.on('blur', () => {
this.callTouched();
});
this.simplemde.codemirror.on('refresh', () => {
this.isFullscreen = this.simplemde.isFullscreenActive();
if (this.isFullscreen) {
document.body.appendChild(this.inner.nativeElement);
} else {
this.container.nativeElement.appendChild(this.inner.nativeElement);
}
});
});
}
}

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

@ -25,7 +25,6 @@ export * from './angular/jscript-editor.component';
export * from './angular/json-editor.component';
export * from './angular/keys.pipe';
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';

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

@ -41,7 +41,6 @@ import {
KNumberPipe,
LocalStoreService,
LowerCaseInputDirective,
MarkdownEditorComponent,
MessageBus,
ModalTargetDirective,
ModalViewDirective,
@ -106,7 +105,6 @@ import {
KeysPipe,
KNumberPipe,
LowerCaseInputDirective,
MarkdownEditorComponent,
ModalTargetDirective,
ModalViewDirective,
MoneyPipe,
@ -156,7 +154,6 @@ import {
KeysPipe,
KNumberPipe,
LowerCaseInputDirective,
MarkdownEditorComponent,
ModalTargetDirective,
ModalViewDirective,
MoneyPipe,

9
src/Squidex/app/shared/components/markdown-editor.component.html

@ -0,0 +1,9 @@
<div #container class="editor-container">
<div #inner>
<textarea class="form-control" #editor></textarea>
</div>
<div class="file-drop drag drop-area" [class.dragging]="draggedOver" dnd-droppable (onDropSuccess)="onItemDropped($event)">
<div class="drop-text">Drop files or assets here to add them.</div>
</div>
</div>

41
src/Squidex/app/shared/components/markdown-editor.component.scss

@ -0,0 +1,41 @@
@import '_mixins';
@import '_vars';
$background: #fff;
.editor {
height: 30rem;
}
.editor-container {
position: relative;
.drop-area {
& {
@include absolute(80px, 30px, 66px, 30px);
@include border-radius;
z-index: 1;
align-content: center;
align-items: center;
display: none;
border: 2px dashed $color-border;
font-size: 1.2rem;
font-weight: normal;
justify-content: center;
color: darken($color-border, 30%);
}
&.dragging {
@include flex-box;
}
&.drag,
&.dnd-drag-over,
&.dnd-drag-enter {
border-color: darken($color-border, 10%);
cursor: copy;
color: darken($color-border, 40%);
text-decoration: none;
}
}
}

211
src/Squidex/app/shared/components/markdown-editor.component.ts

@ -0,0 +1,211 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AssetDto,
AssetDragged,
MessageBus,
ResourceLoaderService,
Types
} from './../declarations-base';
declare var SimpleMDE: any;
export const SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => MarkdownEditorComponent), multi: true
};
@Component({
selector: 'sqx-markdown-editor',
styleUrls: ['./markdown-editor.component.scss'],
templateUrl: './markdown-editor.component.html',
providers: [SQX_MARKDOWN_EDITOR_CONTROL_VALUE_ACCESSOR]
})
export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewInit, OnDestroy, OnInit {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
private simplemde: any;
private value: string;
private isDisabled = false;
private assetDraggedSubscription: any;
@ViewChild('editor')
public editor: ElementRef;
@ViewChild('container')
public container: ElementRef;
@ViewChild('inner')
public inner: ElementRef;
@Output()
public assetPluginClicked = new EventEmitter<any>();
public draggedOver = false;
constructor(
private readonly resourceLoader: ResourceLoaderService,
private readonly messageBus: MessageBus
) {
this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css');
}
public ngOnDestroy() {
this.assetDraggedSubscription.unsubscribe();
}
public ngOnInit() {
this.assetDraggedSubscription =
this.messageBus.of(AssetDragged).subscribe(message => {
if (message.assetDto.isImage) {
if (message.dragEvent === AssetDragged.DRAG_START) {
this.draggedOver = true;
} else {
this.draggedOver = false;
}
}
});
}
public writeValue(value: string) {
this.value = Types.isString(value) ? value : '';
if (this.simplemde) {
this.simplemde.value(this.value);
}
}
public setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
if (this.simplemde) {
this.simplemde.codemirror.setOption('readOnly', isDisabled);
}
}
public registerOnChange(fn: any) {
this.callChange = fn;
}
public registerOnTouched(fn: any) {
this.callTouched = fn;
}
public ngAfterViewInit() {
const self = this;
this.resourceLoader.loadScript('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js').then(() => {
this.simplemde = new SimpleMDE({
toolbar: [
{
name: 'bold',
action: SimpleMDE.toggleBold,
className: 'fa fa-bold',
title: 'Bold'
}, {
name: 'italic',
action: SimpleMDE.toggleItalic,
className: 'fa fa-italic',
title: 'Italic'
}, {
name: 'heading',
action: SimpleMDE.toggleHeadingSmaller,
className: 'fa fa-header',
title: 'Heading'
}, {
name: 'quote',
action: SimpleMDE.toggleBlockquote,
className: 'fa fa-quote-left',
title: 'Quote'
}, {
name: 'unordered-list',
action: SimpleMDE.toggleUnorderedList,
className: 'fa fa-list-ul',
title: 'Generic List'
}, {
name: 'ordered-list',
action: SimpleMDE.toggleOrderedList,
className: 'fa fa-list-ol',
title: 'Numbered List'
},
'|',
{
name: 'link',
action: SimpleMDE.drawLink,
className: 'fa fa-link',
title: 'Create Link'
}, {
name: 'image',
action: SimpleMDE.drawImage,
className: 'fa fa-picture-o',
title: 'Insert Image'
},
'|',
{
name: 'preview',
action: SimpleMDE.togglePreview,
className: 'fa fa-eye no-disable',
title: 'Toggle Preview'
}, {
name: 'side-by-side',
action: SimpleMDE.toggleSideBySide,
className: 'fa fa-columns no-disable no-mobile',
title: 'Toggle Side by Side'
},
'|',
{
name: 'guide',
action: 'https://simplemde.com/markdown-guide',
className: 'fa fa-question-circle',
title: 'Markdown Guide'
},
'|',
{
name: 'assets',
action: () => {
self.assetPluginClicked.emit();
},
className: 'icon-assets icon-bold',
title: 'Insert Assets'
}
],
element: this.editor.nativeElement
});
this.simplemde.value(this.value || '');
this.simplemde.codemirror.setOption('readOnly', this.isDisabled);
this.simplemde.codemirror.on('change', () => {
const value = this.simplemde.value();
if (this.value !== value) {
this.value = value;
this.callChange(value);
}
});
this.simplemde.codemirror.on('blur', () => {
this.callTouched();
});
});
}
public onItemDropped(event: any) {
const content = event.dragData;
if (content instanceof AssetDto) {
const img = `![${content.fileName}](${content.url} "${content.fileName}")`;
this.simplemde.codemirror.replaceSelection(img);
}
this.draggedOver = false;
}
}

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

@ -14,6 +14,7 @@
& {
@include absolute(115px, 30px, 66px, 30px);
@include border-radius;
z-index: 1;
align-content: center;
align-items: center;
display: none;

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

@ -35,15 +35,15 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
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 assetPluginClicked = new EventEmitter<any>();
public draggedOver = false;
public assetsForm = this.formBuilder.group({
name: ['']
@ -94,17 +94,17 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
removed_menuitems: 'newdocument',
resize: true,
theme: 'modern',
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image media assets',
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | image media | assets',
setup: (editor: any) => {
self.tinyEditor = editor;
self.tinyEditor.setMode(this.isDisabled ? 'readonly' : 'design');
self.tinyEditor.addButton('assets', {
text: '',
icon: 'browse',
icon: 'assets',
tooltip: 'Insert Assets',
onclick: (event: any) => {
self.assetPluginClicked.emit(event);
self.assetPluginClicked.emit();
}
});

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

@ -12,6 +12,7 @@ export * from './components/help.component';
export * from './components/geolocation-editor.component';
export * from './components/history.component';
export * from './components/language-selector.component';
export * from './components/markdown-editor.component';
export * from './components/pipes';
export * from './components/rich-editor.component';

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

@ -37,6 +37,7 @@ import {
HistoryService,
LanguageSelectorComponent,
LanguagesService,
MarkdownEditorComponent,
MustBeAuthenticatedGuard,
MustBeNotAuthenticatedGuard,
PlansService,
@ -79,6 +80,7 @@ import {
HelpComponent,
HistoryComponent,
LanguageSelectorComponent,
MarkdownEditorComponent,
UserDtoPicture,
UserEmailPipe,
UserEmailRefPipe,
@ -99,6 +101,7 @@ import {
HelpComponent,
HistoryComponent,
LanguageSelectorComponent,
MarkdownEditorComponent,
UserDtoPicture,
UserEmailPipe,
UserEmailRefPipe,

15
src/Squidex/app/theme/_common.scss

@ -31,6 +31,21 @@ body {
font-size: .8rem;
}
// Rich editor icon. Must be placed here, because element is not created by angular.
.mce-i-assets {
& {
font-family: 'icomoon' !important;
}
&::before {
content: '\e948';
}
}
.icon-bold {
font-weight: bold;
}
//
// Profile picture in circle
//

Loading…
Cancel
Save