From 50e6ca95ab14494191f53c2a3fde4a0de3ba3a35 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 3 Feb 2019 16:32:50 +0100 Subject: [PATCH] Copy and paste images. --- .../assets/pages/assets-page.component.ts | 2 - .../shared/assets-editor.component.html | 4 +- .../content/shared/assets-editor.component.ts | 22 +------ .../shared/references-editor.component.ts | 2 - .../angular/forms/file-drop.directive.ts | 56 +++++++++++++++++- .../components/assets-list.component.html | 2 +- .../components/assets-list.component.ts | 22 +------ .../components/assets-selector.component.ts | 2 - .../components/markdown-editor.component.html | 6 +- .../components/markdown-editor.component.ts | 47 +++++++++++++++ .../components/rich-editor.component.html | 6 +- .../components/rich-editor.component.ts | 57 ++++++++++++++++++- .../app/shared/state/contents.forms.ts | 1 - 13 files changed, 165 insertions(+), 64 deletions(-) diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.ts b/src/Squidex/app/features/assets/pages/assets-page.component.ts index aa0e805ce..2f795ab59 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.ts +++ b/src/Squidex/app/features/assets/pages/assets-page.component.ts @@ -5,8 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -// tslint:disable:prefer-for-of - import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { onErrorResumeNext } from 'rxjs/operators'; diff --git a/src/Squidex/app/features/content/shared/assets-editor.component.html b/src/Squidex/app/features/content/shared/assets-editor.component.html index 176b2dfb9..eb98de9f4 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.html +++ b/src/Squidex/app/features/content/shared/assets-editor.component.html @@ -1,8 +1,8 @@ -
+
-
+
Drop files or click here to add assets.
diff --git a/src/Squidex/app/features/content/shared/assets-editor.component.ts b/src/Squidex/app/features/content/shared/assets-editor.component.ts index 5a59e5951..bf725821b 100644 --- a/src/Squidex/app/features/content/shared/assets-editor.component.ts +++ b/src/Squidex/app/features/content/shared/assets-editor.component.ts @@ -5,8 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -// tslint:disable:prefer-for-of - import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subscription } from 'rxjs'; @@ -127,23 +125,9 @@ export class AssetsEditorComponent implements ControlValueAccessor, OnInit, OnDe this.callTouched = fn; } - public pasteFiles(event: ClipboardEvent) { - for (let i = 0; i < event.clipboardData.items.length; i++) { - const file = event.clipboardData.items[i].getAsFile(); - - if (file) { - this.newAssets = this.newAssets.pushFront(file); - } - } - } - - public addFiles(files: FileList) { - for (let i = 0; i < files.length; i++) { - const file = files[i]; - - if (file) { - this.newAssets = this.newAssets.pushFront(file); - } + public addFiles(files: File[]) { + for (let file of files) { + this.newAssets = this.newAssets.pushFront(file); } } diff --git a/src/Squidex/app/features/content/shared/references-editor.component.ts b/src/Squidex/app/features/content/shared/references-editor.component.ts index bbe28ba16..0fc562f6e 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.ts +++ b/src/Squidex/app/features/content/shared/references-editor.component.ts @@ -5,8 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -// tslint:disable:prefer-for-of - import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; diff --git a/src/Squidex/app/framework/angular/forms/file-drop.directive.ts b/src/Squidex/app/framework/angular/forms/file-drop.directive.ts index 4167ed6be..30bbcff60 100644 --- a/src/Squidex/app/framework/angular/forms/file-drop.directive.ts +++ b/src/Squidex/app/framework/angular/forms/file-drop.directive.ts @@ -5,18 +5,33 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Directive, ElementRef, EventEmitter, HostListener, Output, Renderer2 } from '@angular/core'; +// tslint:disable:prefer-for-of + +import { Directive, ElementRef, EventEmitter, HostListener, Input, Output, Renderer2 } from '@angular/core'; import { Types } from './../../utils/types'; +const ImageTypes = [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' +]; + @Directive({ selector: '[sqxFileDrop]' }) export class FileDropDirective { private dragCounter = 0; + @Input() + public allowedFiles: string[]; + + @Input() + public onlyImages: boolean; + @Output('sqxFileDrop') - public drop = new EventEmitter(); + public drop = new EventEmitter(); constructor( private readonly element: ElementRef, @@ -24,6 +39,25 @@ export class FileDropDirective { ) { } + @HostListener('paste', ['$event']) + public onPaste(event: ClipboardEvent) { + const result: File[] = []; + + for (let i = 0; i < event.clipboardData.items.length; i++) { + const file = event.clipboardData.items[i].getAsFile(); + + if (this.isAllowedFile(file)) { + result.push(file!); + } + } + + if (result.length > 0) { + this.drop.emit(result); + } + + this.stopEvent(event); + } + @HostListener('dragend', ['$event']) @HostListener('dragleave', ['$event']) public onDragEnd(event: DragDropEvent) { @@ -57,7 +91,19 @@ export class FileDropDirective { const hasFiles = this.hasFiles(event.dataTransfer.types); if (hasFiles) { - this.drop.emit(event.dataTransfer.files); + const result: File[] = []; + + for (let i = 0; i < event.dataTransfer.files.length; i++) { + const file = event.dataTransfer.files.item(i); + + if (this.isAllowedFile(file)) { + result.push(file!); + } + } + + if (result.length > 0) { + this.drop.emit(result); + } this.dragEnd(0); this.stopEvent(event); @@ -85,6 +131,10 @@ export class FileDropDirective { } } + private isAllowedFile(file: File | null) { + return file && (!this.allowedFiles || this.allowedFiles.indexOf(file.type) >= 0) && (!this.onlyImages || ImageTypes.indexOf(file.type) >= 0); + } + private hasFiles(types: any): boolean { if (!types) { return false; diff --git a/src/Squidex/app/shared/components/assets-list.component.html b/src/Squidex/app/shared/components/assets-list.component.html index d2f849a28..fb8b2b304 100644 --- a/src/Squidex/app/shared/components/assets-list.component.html +++ b/src/Squidex/app/shared/components/assets-list.component.html @@ -1,4 +1,4 @@ -
+

Drop files here to upload

or
diff --git a/src/Squidex/app/shared/components/assets-list.component.ts b/src/Squidex/app/shared/components/assets-list.component.ts index 7909634b5..eb518e3c9 100644 --- a/src/Squidex/app/shared/components/assets-list.component.ts +++ b/src/Squidex/app/shared/components/assets-list.component.ts @@ -5,8 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -// tslint:disable:prefer-for-of - import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { onErrorResumeNext } from 'rxjs/operators'; @@ -78,23 +76,9 @@ export class AssetsListComponent { this.newFiles = this.newFiles.remove(file); } - public pasteFiles(event: ClipboardEvent) { - for (let i = 0; i < event.clipboardData.items.length; i++) { - const file = event.clipboardData.items[i].getAsFile(); - - if (file) { - this.newFiles = this.newFiles.pushFront(file); - } - } - } - - public addFiles(files: FileList) { - for (let i = 0; i < files.length; i++) { - const file = files[i]; - - if (file) { - this.newFiles = this.newFiles.pushFront(file); - } + public addFiles(files: File[]) { + for (let file of files) { + this.newFiles = this.newFiles.pushFront(file); } return true; diff --git a/src/Squidex/app/shared/components/assets-selector.component.ts b/src/Squidex/app/shared/components/assets-selector.component.ts index ab9cbd615..711af55e0 100644 --- a/src/Squidex/app/shared/components/assets-selector.component.ts +++ b/src/Squidex/app/shared/components/assets-selector.component.ts @@ -5,8 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -// tslint:disable:prefer-for-of - import { ChangeDetectionStrategy, Component, EventEmitter, OnInit, Output } from '@angular/core'; import { onErrorResumeNext } from 'rxjs/operators'; diff --git a/src/Squidex/app/shared/components/markdown-editor.component.html b/src/Squidex/app/shared/components/markdown-editor.component.html index 7480c92da..042807d13 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.html +++ b/src/Squidex/app/shared/components/markdown-editor.component.html @@ -1,11 +1,7 @@ -
+
- -
-
Drop assets here to add them.
-
diff --git a/src/Squidex/app/shared/components/markdown-editor.component.ts b/src/Squidex/app/shared/components/markdown-editor.component.ts index 3d673176a..dde539e6e 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.ts +++ b/src/Squidex/app/shared/components/markdown-editor.component.ts @@ -9,7 +9,11 @@ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, E import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { + AppsState, AssetDto, + AssetsService, + AuthService, + DateTime, DialogModel, ResourceLoaderService, Types @@ -49,6 +53,9 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI public isFullscreen = false; constructor( + private readonly appsState: AppsState, + private readonly assetsService: AssetsService, + private readonly authState: AuthService, private readonly changeDetector: ChangeDetectorRef, private readonly renderer: Renderer2, private readonly resourceLoader: ResourceLoaderService @@ -214,4 +221,44 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI this.assetsDialog.hide(); } + + public insertFiles(files: File[]) { + const doc = this.simplemde.codemirror.getDoc(); + + for (let file of files) { + this.uploadFile(doc, file); + } + } + + private uploadFile(doc: any, file: File) { + const uploadCursor = doc.getCursor(); + const uploadText = `![Uploading file...${new Date()}]()`; + + doc.replaceSelection(uploadText); + + const replaceText = (replacement: string) => { + const cursor = doc.getCursor(); + + const text = doc.getValue().replace(uploadText, replacement); + + doc.setValue(text); + + if (uploadCursor && uploadCursor.line === cursor.line) { + const offset = replacement.length - uploadText.length; + + doc.setCursor({ line: cursor.line, ch: cursor.ch + offset }); + } else { + doc.setCursor(cursor); + } + }; + + this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now()) + .subscribe(asset => { + if (Types.is(asset, AssetDto)) { + replaceText(`![${asset.fileName}](${asset.url} '${asset.fileName}')`); + } + }, () => { + replaceText('FAILED'); + }); + } } \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html index 29b87e620..8e4c1e999 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.html +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -1,9 +1,5 @@ -
+
- -
-
Drop assets here to add them.
-
diff --git a/src/Squidex/app/shared/components/rich-editor.component.ts b/src/Squidex/app/shared/components/rich-editor.component.ts index 5eee435e5..44f797847 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.ts +++ b/src/Squidex/app/shared/components/rich-editor.component.ts @@ -5,11 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ +// tslint:disable:prefer-for-of + import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, OnDestroy, Output, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { + AppsState, AssetDto, + AssetsService, + AuthService, + DateTime, DialogModel, ResourceLoaderService, Types @@ -21,6 +27,13 @@ export const SQX_RICH_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => RichEditorComponent), multi: true }; +const ImageTypes = [ + 'image/jpeg', + 'image/png', + 'image/jpg', + 'image/gif' +]; + @Component({ selector: 'sqx-rich-editor', styleUrls: ['./rich-editor.component.scss'], @@ -45,6 +58,9 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, public assetPluginClicked = new EventEmitter(); constructor( + private readonly appsState: AppsState, + private readonly assetsService: AssetsService, + private readonly authState: AuthService, private readonly changeDetector: ChangeDetectorRef, private readonly resourceLoader: ResourceLoaderService ) { @@ -59,7 +75,7 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, public ngAfterViewInit() { const self = this; - this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.5.4/tinymce.min.js').then(() => { + this.resourceLoader.loadScript('https://cdnjs.cloudflare.com/ajax/libs/tinymce/4.9.3/tinymce.min.js').then(() => { tinymce.init(self.getEditorOptions()); }); } @@ -76,10 +92,9 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, return { convert_fonts_to_spans: true, convert_urls: false, - plugins: 'code image media link lists advlist', + plugins: 'code image media link lists advlist paste', removed_menuitems: 'newdocument', resize: true, - theme: 'modern', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter | bullist numlist outdent indent | link image media | assets', setup: (editor: any) => { self.tinyEditor = editor; @@ -102,6 +117,15 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, } }); + self.tinyEditor.on('paste', (event: ClipboardEvent) => { + for (let i = 0; i < event.clipboardData.items.length; i++) { + const file = event.clipboardData.items[i].getAsFile(); + + if (file && ImageTypes.indexOf(file.type) >= 0) { + self.uploadFile(file); + } + } + }); self.tinyEditor.on('blur', () => { self.callTouched(); }); @@ -153,4 +177,31 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit, this.assetsDialog.hide(); } + + public insertFiles(files: File[]) { + for (let file of files) { + this.uploadFile(file); + } + } + + private uploadFile(file: File) { + const uploadText = `[Uploading file...${new Date()}]`; + + this.tinyEditor.execCommand('mceInsertContent', false, uploadText); + + const replaceText = (replacement: string) => { + const content = this.tinyEditor.getContent().replace(uploadText, replacement); + + this.tinyEditor.setContent(content); + }; + + this.assetsService.uploadFile(this.appsState.appName, file, this.authState.user!.token, DateTime.now()) + .subscribe(asset => { + if (Types.is(asset, AssetDto)) { + replaceText(`${asset.fileName}`); + } + }, () => { + replaceText('FAILED'); + }); + } } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index 956d5e255..9b6e0f795 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -5,7 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ - // tslint:disable:prefer-for-of import { FormArray, FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms';