项目初始化

This commit is contained in:
Taric Xin
2021-11-26 16:34:35 +08:00
parent 66644bcf0a
commit 5287578452
354 changed files with 45736 additions and 0 deletions

View File

@ -0,0 +1,417 @@
/**
* Part of the code comes from https://github.com/KillerCodeMonkey/ngx-quill/
*/
import { DOCUMENT, isPlatformServer } from '@angular/common';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
forwardRef,
Inject,
Input,
NgZone,
OnChanges,
OnDestroy,
Output,
PLATFORM_ID,
Renderer2,
SimpleChanges
} from '@angular/core';
import { ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import { ModalHelper } from '@delon/theme';
import { BooleanInput, InputBoolean, InputNumber, NumberInput } from '@delon/util';
import ImageResize from 'quill-image-resize-module';
import { FileManagerImgComponent } from '../file-manager/file-manager-img.component';
declare var Quill: any;
const Delta = require('quill-delta');
export interface CustomOption {
import: string;
whitelist: any[];
}
Quill.register('modules/imageResize', ImageResize);
['align', 'background', 'color', 'direction', 'font'].forEach(type => Quill.register(Quill.import(`attributors/style/${type}`), true));
const Size = Quill.import('attributors/style/size');
const VALUES = {
// NOTICE: Should be sync modify `@ql-sizes` in `styles/fix/_quill.less`
size: ['10px', '12px', '14px', '16px', '18px', '20px', '24px']
};
Size.whitelist = VALUES.size;
Quill.register(Size, true);
@Component({
selector: 'editor',
template: ``,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EditorComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => EditorComponent),
multi: true
}
],
host: {
'[class.quill-editor]': 'true'
},
preserveWhitespaces: false
})
export class EditorComponent implements AfterViewInit, ControlValueAccessor, OnChanges, OnDestroy, Validator {
@Input()
set mode(value: 'full' | 'simple') {
this._mode = value;
const handlers = {
image: (state: boolean) => this.image(state)
};
if (value === 'full') {
this.modules = {
imageResize: {},
toolbar: {
handlers,
container: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
// [{ direction: 'rtl' }], // text direction
[{ size: VALUES.size }], // custom dropdown
[{ header: [1, 2, 3, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
// [{ font: [] }],
[{ align: [] }],
['clean'], // remove formatting button
['link', 'image', 'video'] // link and image, video
]
}
};
} else {
this.modules = {
imageResize: {},
toolbar: {
handlers,
container: [
['bold', 'italic', 'underline', 'strike', 'blockquote'], // toggled buttons
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }],
[{ header: [1, 2, 3, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ align: [] }],
['clean'], // remove formatting button
['link', 'image', 'video'] // link and image, video
]
}
};
}
}
constructor(
private elementRef: ElementRef,
@Inject(DOCUMENT) private doc: any,
@Inject(PLATFORM_ID) private platformId: {},
private renderer: Renderer2,
private zone: NgZone,
private modalHelper: ModalHelper
) {}
static ngAcceptInputType_readOnly: BooleanInput;
static ngAcceptInputType_maxLength: NumberInput;
static ngAcceptInputType_minLength: NumberInput;
static ngAcceptInputType_required: BooleanInput;
static ngAcceptInputType_strict: BooleanInput;
quill: any;
editorElem!: HTMLElement;
emptyArray: any[] = [];
content: any;
selectionChangeEvent: any;
textChangeEvent: any;
_mode!: 'full' | 'simple';
@Input() format: 'object' | 'html' | 'text' | 'json' = 'html';
@Input() theme?: string;
@Input() modules?: { [index: string]: any };
@Input() @InputBoolean() readOnly?: boolean;
@Input() placeholder = '';
@Input() @InputNumber() maxLength?: number;
@Input() @InputNumber() minLength?: number;
@Input() @InputBoolean() required?: boolean;
@Input() formats?: string[];
@Input() style: any = { height: '250px' };
@Input() @InputBoolean() strict = true;
@Input() scrollingContainer?: HTMLElement | string;
@Input() bounds?: HTMLElement | string;
@Input() customOptions: CustomOption[] = [];
@Output() readonly editorCreated = new EventEmitter();
@Output() readonly contentChanged = new EventEmitter();
@Output() readonly selectionChanged = new EventEmitter();
private image(_: boolean): void {
this.modalHelper
.create(
FileManagerImgComponent,
{
opt: {
multiple: true,
i: { orderby: 0, cat_id: 0, ps: 20 }
}
},
{
size: 1000,
modalOptions: {
nzClosable: false
}
}
)
.subscribe((res: any[]) => {
// delete
const range = this.quill.getSelection(true);
this.quill.updateContents(new Delta().retain(range.index).delete(range.length));
// install all images
for (const ii of res) {
this.quill.updateContents(new Delta().retain(range.index).delete(range.length).insert({ image: ii.mp }, { alt: ii.title }));
}
});
}
@Input()
valueGetter = (quillEditor: any, editorElement: HTMLElement): any => {
let html: string | null = editorElement.children[0].innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
let modelValue = html;
if (this.format === 'text') {
modelValue = quillEditor.getText();
} else if (this.format === 'object') {
modelValue = quillEditor.getContents();
} else if (this.format === 'json') {
try {
modelValue = JSON.stringify(quillEditor.getContents());
} catch (e) {
modelValue = quillEditor.getText();
}
}
return modelValue;
};
@Input()
valueSetter = (quillEditor: any, value: any, _format: 'object' | 'html' | 'json'): any => {
if (this.format === 'html') {
return quillEditor.clipboard.convert(value);
} else if (this.format === 'json') {
try {
return JSON.parse(value);
} catch (e) {
return value;
}
}
return value;
};
onModelChange = (_: any) => {};
onModelTouched = () => {};
ngAfterViewInit(): void {
if (isPlatformServer(this.platformId)) {
return;
}
if (this._mode == null) {
this.mode = 'full';
}
const modules: any = this.modules;
this.elementRef.nativeElement.insertAdjacentHTML('beforeend', '<div quill-editor-element></div>');
this.editorElem = this.elementRef.nativeElement.querySelector('[quill-editor-element]');
if (this.style) {
Object.keys(this.style).forEach((key: string) => {
this.renderer.setStyle(this.editorElem, key, this.style[key]);
});
}
this.customOptions.forEach(customOption => {
const newCustomOption = Quill.import(customOption.import);
newCustomOption.whitelist = customOption.whitelist;
Quill.register(newCustomOption, true);
});
this.quill = new Quill(this.editorElem, {
modules,
placeholder: this.placeholder,
readOnly: this.readOnly || false,
theme: this.theme || 'snow',
formats: this.formats,
bounds: this.bounds ? (this.bounds === 'self' ? this.editorElem : this.bounds) : this.doc.body,
strict: this.strict,
scrollingContainer: this.scrollingContainer
});
if (this.content) {
if (this.format === 'object') {
this.quill.setContents(this.content, 'silent');
} else if (this.format === 'text') {
this.quill.setText(this.content, 'silent');
} else if (this.format === 'json') {
try {
this.quill.setContents(JSON.parse(this.content), 'silent');
} catch (e) {
this.quill.setText(this.content, 'silent');
}
} else {
const contents = this.quill.clipboard.convert(this.content);
this.quill.setContents(contents, 'silent');
}
this.quill.history.clear();
}
this.editorCreated.emit(this.quill);
// mark model as touched if editor lost focus
this.selectionChangeEvent = this.quill.on('selection-change', (range: any, oldRange: any, source: string) => {
this.zone.run(() => {
this.selectionChanged.emit({
editor: this.quill,
range,
oldRange,
source
});
if (!range) {
this.onModelTouched();
}
});
});
// update model if text changes
this.textChangeEvent = this.quill.on('text-change', (delta: any, oldDelta: any, source: string) => {
const text = this.quill.getText();
const content = this.quill.getContents();
let html: string | null = this.editorElem.children[0].innerHTML;
if (html === '<p><br></p>' || html === '<div><br><div>') {
html = null;
}
this.zone.run(() => {
this.onModelChange(this.valueGetter(this.quill, this.editorElem));
this.contentChanged.emit({
editor: this.quill,
html,
text,
content,
delta,
oldDelta,
source
});
});
});
}
ngOnDestroy(): void {
if (this.selectionChangeEvent) {
this.selectionChangeEvent.removeListener('selection-change');
}
if (this.textChangeEvent) {
this.textChangeEvent.removeListener('text-change');
}
}
ngOnChanges(changes: SimpleChanges): void {
if (!this.quill) {
return;
}
if (changes.readOnly) {
this.quill.enable(!changes.readOnly.currentValue);
}
if (changes.placeholder) {
this.quill.root.dataset.placeholder = changes.placeholder.currentValue;
}
}
writeValue(currentValue: any): void {
this.content = currentValue;
if (this.quill) {
if (currentValue) {
if (this.format === 'text') {
this.quill.setText(currentValue);
} else {
this.quill.setContents(this.valueSetter(this.quill, this.content, this.format));
}
return;
}
this.quill.setText('');
}
}
registerOnChange(fn: (value: any) => void): void {
this.onModelChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onModelTouched = fn;
}
validate(): any {
if (!this.quill) {
return null;
}
const err: {
minLengthError?: { given: number; minLength: number };
maxLengthError?: { given: number; maxLength: number };
requiredError?: { empty: boolean };
} = {};
let valid = true;
const textLength = this.quill.getText().trim().length;
if (this.minLength && textLength && textLength < this.minLength) {
err.minLengthError = {
given: textLength,
minLength: this.minLength
};
valid = false;
}
if (this.maxLength && textLength > this.maxLength) {
err.maxLengthError = {
given: textLength,
maxLength: this.maxLength
};
valid = false;
}
if (this.required && !textLength) {
err.requiredError = {
empty: true
};
valid = false;
}
return valid ? null : err;
}
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { FileManagerModule } from '../file-manager';
import { EditorComponent } from './editor.component';
const COMPONENTS = [EditorComponent];
@NgModule({
imports: [FileManagerModule],
declarations: COMPONENTS,
exports: COMPONENTS
})
export class EditorModule {}

View File

@ -0,0 +1,39 @@
---
order: 60
title: editor
type: Component
---
Based on [quill](https://github.com/quilljs/quill) WYSIWYG editor, [DEMO](https://preview.ng-alain.com/pro/#/ec/ware/edit/10001).
## Feature
- Integration [file-manager](file-manager)
- Integration [quill-image-resize-module](https://github.com/kensnyder/quill-image-resize-module)
## API
| Property | Description | Type | Default |
| ---------------------- | -------------------------------------- | ------------------- | --------------------- |
| `[(ngModel)]` | Value of quill | `string` | - |
| `[mode]` | Mode of quill | `full,simple` | `full` |
| `[theme]` | Theme of quill | `string` | `snow` |
| `[readOnly]` | Whether to readonly | `boolean` | `false` |
| `[required]` | Whether to required | `boolean` | - |
| `[maxLength]` | The maximum number of quill characters | `number` | - |
| `[minLength]` | The minimum number of quill characters | `number` | - |
| `[placeholder]` | Placeholder of quill | `string` | - |
| `[style]` | Styles of quill | `any` | `{ height: '250px' }` |
| `(editorCreated)` | Quill rendered event | `EventEmitter<any>` | - |
| `(contentChanged)` | Quill content change event | `EventEmitter<any>` | - |
| `(selectionChanged)` | `selection-change` event | `EventEmitter<any>` | - |
## sf widget
Widget name: `editor`.
### ui
| Property | Description | Type | Default |
| -------------------- | -------------------------- | ------------------------- | ------- |
| `(contentChanged)` | Quill content change event | `(value: string) => void` | - |

View File

@ -0,0 +1,2 @@
export * from './editor.component';
export * from './editor.module';

View File

@ -0,0 +1,39 @@
---
order: 60
title: editor
type: Component
---
基于 [quill](https://github.com/quilljs/quill) 富文本编辑器,参考[示例](https://preview.ng-alain.com/pro/#/ec/ware/edit/10001)。
## 特性
- 整合 [file-manager](file-manager)
- 整合 [quill-image-resize-module](https://github.com/kensnyder/quill-image-resize-module)
## API
| 参数 | 说明 | 类型 | 默认值 |
| ---------------------- | -------------------------- | ------------------- | --------------------- |
| `[(ngModel)]` | 值 | `string` | - |
| `[mode]` | 模式 | `full,simple` | `full` |
| `[theme]` | 主题 | `string` | `snow` |
| `[readOnly]` | 是否只读 | `boolean` | `false` |
| `[required]` | 是否必填 | `boolean` | - |
| `[maxLength]` | 最大长度 | `number` | - |
| `[minLength]` | 最少长度 | `number` | - |
| `[placeholder]` | 文本框默认值 | `string` | - |
| `[style]` | 样式,可以决定富文本的高度 | `any` | `{ height: '250px' }` |
| `(editorCreated)` | 初始化完成后事件 | `EventEmitter<any>` | - |
| `(contentChanged)` | 内容变更事件 | `EventEmitter<any>` | - |
| `(selectionChanged)` | `selection-change` 事件 | `EventEmitter<any>` | - |
## sf 小部件
小部件名称:`editor`
### ui
| 参数 | 说明 | 类型 | 默认值 |
| -------------------- | ------------ | ------------------------- | ------ |
| `(contentChanged)` | 内容变更事件 | `(value: string) => void` | - |