From 6665ddb2ed1398ba77a9de9d763fdba52eb74fcc Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 23 Jan 2018 16:06:16 +0100 Subject: [PATCH] Drag and drop assets to markdown editor. --- .../content/content-field.component.html | 4 +- .../pages/content/content-field.component.ts | 2 +- .../angular/markdown-editor.component.html | 5 - .../angular/markdown-editor.component.scss | 15 -- .../angular/markdown-editor.component.ts | 106 --------- src/Squidex/app/framework/declarations.ts | 1 - src/Squidex/app/framework/module.ts | 3 - .../components/markdown-editor.component.html | 9 + .../components/markdown-editor.component.scss | 41 ++++ .../components/markdown-editor.component.ts | 211 ++++++++++++++++++ .../components/rich-editor.component.scss | 1 + .../components/rich-editor.component.ts | 12 +- src/Squidex/app/shared/declarations.ts | 1 + src/Squidex/app/shared/module.ts | 3 + src/Squidex/app/theme/_common.scss | 15 ++ 15 files changed, 290 insertions(+), 139 deletions(-) delete mode 100644 src/Squidex/app/framework/angular/markdown-editor.component.html delete mode 100644 src/Squidex/app/framework/angular/markdown-editor.component.scss delete mode 100644 src/Squidex/app/framework/angular/markdown-editor.component.ts create mode 100644 src/Squidex/app/shared/components/markdown-editor.component.html create mode 100644 src/Squidex/app/shared/components/markdown-editor.component.scss create mode 100644 src/Squidex/app/shared/components/markdown-editor.component.ts diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 802caaafd..3fc4b9f7b 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -53,10 +53,10 @@
- +
- +
-
- \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/markdown-editor.component.scss b/src/Squidex/app/framework/angular/markdown-editor.component.scss deleted file mode 100644 index 834b5b55c..000000000 --- a/src/Squidex/app/framework/angular/markdown-editor.component.scss +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/markdown-editor.component.ts b/src/Squidex/app/framework/angular/markdown-editor.component.ts deleted file mode 100644 index d477d30b8..000000000 --- a/src/Squidex/app/framework/angular/markdown-editor.component.ts +++ /dev/null @@ -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); - } - }); - }); - } -} \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 26438792f..252c9e4ef 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/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'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index e5416757e..4668d7517 100644 --- a/src/Squidex/app/framework/module.ts +++ b/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, diff --git a/src/Squidex/app/shared/components/markdown-editor.component.html b/src/Squidex/app/shared/components/markdown-editor.component.html new file mode 100644 index 000000000..13b5918f0 --- /dev/null +++ b/src/Squidex/app/shared/components/markdown-editor.component.html @@ -0,0 +1,9 @@ +
+
+ +
+ +
+
Drop files or assets here to add them.
+
+
\ No newline at end of file diff --git a/src/Squidex/app/shared/components/markdown-editor.component.scss b/src/Squidex/app/shared/components/markdown-editor.component.scss new file mode 100644 index 000000000..0d2f0ff6d --- /dev/null +++ b/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; + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/markdown-editor.component.ts b/src/Squidex/app/shared/components/markdown-editor.component.ts new file mode 100644 index 000000000..b5013cc74 --- /dev/null +++ b/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(); + + 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; + } +} \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.scss b/src/Squidex/app/shared/components/rich-editor.component.scss index a5fb04b83..7828059a4 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.scss +++ b/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; diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts index d8d91c567..eba596a2b 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.ts +++ b/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(); + public assetPluginClicked = new EventEmitter(); + + 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(); } }); diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 259363a2b..3096be8f9 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/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'; diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index 71cccd317..5291dc978 100644 --- a/src/Squidex/app/shared/module.ts +++ b/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, diff --git a/src/Squidex/app/theme/_common.scss b/src/Squidex/app/theme/_common.scss index 0da1f1b33..f05bf3eaa 100644 --- a/src/Squidex/app/theme/_common.scss +++ b/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 //