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