mirror of https://github.com/Squidex/squidex.git
30 changed files with 326 additions and 83 deletions
@ -0,0 +1 @@ |
|||||
|
<textarea class="form-control" #editor></textarea> |
||||
@ -0,0 +1,6 @@ |
|||||
|
@import '_mixins'; |
||||
|
@import '_vars'; |
||||
|
|
||||
|
.editor { |
||||
|
height: 30rem; |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
import { AfterViewInit, Component, forwardRef, ElementRef, ViewChild } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
|
||||
|
import { ResourceLoaderService } from './../services/resource-loader.service'; |
||||
|
|
||||
|
declare var SimpleMDE: any; |
||||
|
|
||||
|
/* tslint:disable:no-empty */ |
||||
|
const NOOP = () => { }; |
||||
|
|
||||
|
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 changeCallback: (value: any) => void = NOOP; |
||||
|
private touchedCallback: () => void = NOOP; |
||||
|
private simplemde: any; |
||||
|
private value: any; |
||||
|
private isDisabled = false; |
||||
|
|
||||
|
@ViewChild('editor') |
||||
|
public editor: ElementRef; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly resourceLoader: ResourceLoaderService |
||||
|
) { |
||||
|
this.resourceLoader.loadStyle('https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css'); |
||||
|
} |
||||
|
|
||||
|
public writeValue(value: any) { |
||||
|
this.value = value; |
||||
|
|
||||
|
if (this.simplemde) { |
||||
|
this.simplemde.value(this.value || ''); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean): void { |
||||
|
this.isDisabled = isDisabled; |
||||
|
|
||||
|
if (this.simplemde) { |
||||
|
this.simplemde.setOption('readOnly', isDisabled); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public registerOnChange(fn: any) { |
||||
|
this.changeCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public registerOnTouched(fn: any) { |
||||
|
this.touchedCallback = 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.on('blur', () => { |
||||
|
const value = this.simplemde.value(); |
||||
|
|
||||
|
this.changeCallback(value); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
<div class="editor" #editor></div> |
||||
@ -0,0 +1,8 @@ |
|||||
|
@import '_mixins'; |
||||
|
@import '_vars'; |
||||
|
|
||||
|
.editor { |
||||
|
background: $color-accent-dark; |
||||
|
border: 1px solid $color-input; |
||||
|
height: 30rem; |
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
/* |
||||
|
* 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 { ResourceLoaderService } from './../services/resource-loader.service'; |
||||
|
|
||||
|
declare var tinymce: any; |
||||
|
|
||||
|
/* tslint:disable:no-empty */ |
||||
|
const NOOP = () => { }; |
||||
|
|
||||
|
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 changeCallback: (value: any) => void = NOOP; |
||||
|
private touchedCallback: () => void = NOOP; |
||||
|
private tinyEditor: any; |
||||
|
private value: any; |
||||
|
private isDisabled = false; |
||||
|
|
||||
|
@ViewChild('editor') |
||||
|
public editor: ElementRef; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly resourceLoader: ResourceLoaderService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public writeValue(value: any) { |
||||
|
this.value = value; |
||||
|
|
||||
|
if (this.tinyEditor) { |
||||
|
this.tinyEditor.setContent(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public setDisabledState(isDisabled: boolean): void { |
||||
|
this.isDisabled = isDisabled; |
||||
|
|
||||
|
if (this.tinyEditor) { |
||||
|
this.tinyEditor.setMode(isDisabled ? 'readonly' : 'design'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public registerOnChange(fn: any) { |
||||
|
this.changeCallback = fn; |
||||
|
} |
||||
|
|
||||
|
public registerOnTouched(fn: any) { |
||||
|
this.touchedCallback = fn; |
||||
|
} |
||||
|
|
||||
|
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.on('blur', () => { |
||||
|
const value = editor.getContent(); |
||||
|
|
||||
|
self.changeCallback(value); |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
self.tinyEditor.setContent(this.value || ''); |
||||
|
}, 500); |
||||
|
}, |
||||
|
target: this.editor.nativeElement |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public ngOnDestroy() { |
||||
|
tinymce.remove(this.editor); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,63 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Sebastian Stehle. All rights reserved |
||||
|
*/ |
||||
|
|
||||
|
export class ResourceLoaderService { |
||||
|
private cache: { [path: string]: Promise<any> } = {}; |
||||
|
|
||||
|
public loadStyle(url: string): Promise<any> { |
||||
|
const key = url.toUpperCase(); |
||||
|
|
||||
|
let result = this.cache[key]; |
||||
|
|
||||
|
if (!result) { |
||||
|
result = new Promise((resolve, reject) => { |
||||
|
const style = document.createElement('link'); |
||||
|
style.rel = 'stylesheet'; |
||||
|
style.href = url; |
||||
|
style.type = 'text/css'; |
||||
|
|
||||
|
style.onload = () => { |
||||
|
resolve(); |
||||
|
}; |
||||
|
|
||||
|
const head = document.getElementsByTagName('head')[0]; |
||||
|
|
||||
|
head.appendChild(style); |
||||
|
}); |
||||
|
|
||||
|
this.cache[key] = result; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public loadScript(url: string): Promise<any> { |
||||
|
const key = url.toUpperCase(); |
||||
|
|
||||
|
let result = this.cache[key]; |
||||
|
|
||||
|
if (!result) { |
||||
|
result = new Promise((resolve, reject) => { |
||||
|
const script = document.createElement('script'); |
||||
|
script.src = url; |
||||
|
script.async = true; |
||||
|
|
||||
|
script.onload = () => { |
||||
|
resolve(); |
||||
|
}; |
||||
|
|
||||
|
const node = document.getElementsByTagName('script')[0]; |
||||
|
|
||||
|
node.parentNode.insertBefore(script, node); |
||||
|
}); |
||||
|
|
||||
|
this.cache[key] = result; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
Binary file not shown.
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue