import { cloneDeep, flatten } from 'lodash-es'; import { EventEmitter, Component, HostBinding, OnInit, Output, Input, OnDestroy, ElementRef, ChangeDetectorRef, ViewChild, TemplateRef, Renderer2 } from '@angular/core'; import { RichCommandGroup, RichEditorCommand, RICH_COMMANDS, SelectedCommand } from '@farris/ui-common'; import { BsModalService } from '@farris/ui-modal'; import { MessagerService } from '@farris/ui-messager'; import { NotifyService } from '@farris/ui-notify'; import { TOOLBAR_DEFAUTLS_ADVANCE, TOOLBAR_DEFAUTLS_CONCISE } from './toolbar-defauts'; type MOVETYPE = 'group' | 'position'; @Component({ selector: 'rich-text-designer', templateUrl: './rich-text-designer.component.html', styleUrls: ['./rich-text-designer.component.scss'] }) export class RichTextDesignerComponent implements OnInit, OnDestroy { @HostBinding('class') cls = 'd-flex flex-column h-100'; @Input() editorType: 'concise' | 'advanced' = 'concise'; @Input() toolbar: SelectedCommand = null; /** 启用效果预览 */ @Input() enablePreview = false; /** 启用查看代码 */ @Input() enableCode = false; @Output() closeHandler = new EventEmitter(); @Output() submit = new EventEmitter(); @ViewChild('movetoTemplate') movetoTemplate: TemplateRef; @ViewChild('selectionsContainer') selectionsContainer: ElementRef; @ViewChild('desginerForm') desginerForm: ElementRef; @ViewChild('toolbarPreView') toolbarPreView: ElementRef; allCMDs: RichEditorCommand[] = []; selectItems = []; currentSelected = null; currentGroup: RichCommandGroup = null; currentElement: any = null; // 移动到数据源 posData = []; // 移动类型 movetype: 'group' | 'position' = 'group'; // 默认位置 positionValue: 'before' | 'after' = 'before'; // 参考目标 targetValue = null; // 显示效果预览视图 showPreview = false; previewData = []; constructor(private elRef: ElementRef, private modalSer: BsModalService, private messager: MessagerService, private cd: ChangeDetectorRef, private notify: NotifyService, private render: Renderer2) { } ngOnInit(): void { this.initData(); } ngOnDestroy(): void { this.selectItems = []; this.currentGroup = null; this.currentElement = null; this.currentSelected = null; } // 初始化 private initData() { if (this.toolbar && this.toolbar.length) { this.editorType = this.editorType || 'concise'; const defaultToolbarItems = RICH_COMMANDS[this.editorType] as []; this.allCMDs = cloneDeep(RICH_COMMANDS[this.editorType]); const _selectedcmds = cloneDeep(this.toolbar || []); const _selectItems = cloneDeep(_selectedcmds).map(n => { if (typeof n === 'object') { n.values = n.values.map((s: string) => { this.toggleLeftItemVisible(s, false); return defaultToolbarItems.find((c: any) => c.code === s); }).filter(t => t); return n; } else { this.toggleLeftItemVisible(n, false); return defaultToolbarItems.find((c: any) => c.code === n); } }).filter(t => t); _selectItems.forEach((n, i) => { if (n.groupName) { n.values.forEach(c => { c.selected = false; }); } n.selected = false; }); this.selectItems = cloneDeep(_selectItems); } else { if (!this.editorType) { this.editorType = 'concise'; } this.allCMDs = cloneDeep(RICH_COMMANDS[this.editorType]); } } private convert2toolbars(e) { if (!e) { return []; } e = cloneDeep(e); return e.map( n => { if (n.groupName) { n.values = n.values.map(v => v.code); return n; } return n.code; }); } // 提交 onSubmit() { this.toolbar = this.convert2toolbars(this.selectItems); this.submit.emit(this.toolbar); } // 添加全部命令 onAddAll($event) { if (!this.selectItems || !this.selectItems.length) { this.selectItems = cloneDeep(this.allCMDs); } else { const items = this.allCMDs.filter(n => n.visible); this.selectItems = this.selectItems.concat(items); } this.toggleLeftData(false); } // 添加选中的编辑器命令 onAddEditorCmd(t, $event) { t.visible = false; if (this.currentSelected && this.currentSelected.groupName) { this.currentSelected.values = this.currentSelected.values || []; this.currentSelected.values.push(t); } else { this.selectItems.push(t); } } // 新建组 onNewGroup($event) { const groupTotal = this.getGroups().length + 1; this.messager.prompt2('新建分组', `分组${groupTotal}`, { inputType: 'text', placeholder: '请输入分组名称,如:字体设置', showMaxButton: false, resizable: false, closeWhenever: false }).subscribe((e: any) => { if (e) { const { val, dlg } = e; if (!val) { this.notify.warning(`请输入分组名称。`); return; } if (this.hasSameGroupName(val)) { this.notify.warning(`${val} 已存在,请修改。`); return; } const selections = this.getSelections(true); const group = { groupName: val, values: [], selected: true }; if (selections && selections.length) { selections.forEach(n => { n.selected = false; }); group.values = selections; this.selectItems = this.selectItems.filter(n => { if (n.values) { n.values = n.values.filter( v => !selections.find(s => s.code === v.code) ); return true; } else { return !selections.find(s => s.code === n.code); } }); } this.clearSelections(); this.selectItems.push(group); this.currentSelected = group; setTimeout(() => { const activeEl = this.selectionsContainer.nativeElement.querySelector('li.active'); if (activeEl) { this.currentElement = activeEl; activeEl.scrollIntoView(); } }); dlg.close(); } }); } // 清空已选记录 onClearSelections($event) { $event.stopPropagation(); this.messager.confirm('您确实要清空当前的配置吗?').subscribe((b) => { if (b) { this.selectItems = []; this.currentGroup = null; this.currentElement = null; this.currentSelected = null; this.toggleLeftData(); } }); } // 右侧已选记录中,点击后清空其他选中项,并选中当前点击项 onEditorCmdItemClick2(t: any, $event: MouseEvent, groupItem = null) { $event.stopPropagation(); if ($event.ctrlKey || $event.shiftKey) { t.selected = !t.selected; return; } if (this.currentSelected !== t) { this.clearSelections(); this.currentGroup = groupItem; this.currentSelected = t; t.selected = !t.selected; const e = $event.target as any; if (e.nodeName === 'LI') { this.currentElement = e; } else { this.currentElement = e.closest('li'); } } else { t.selected = false; this.clearSelections(); } } // 移除指定的已选记录 onRemoveSelectItem(t: RichEditorCommand, groupIndex: number, $event: MouseEvent) { $event.stopPropagation(); const g: RichCommandGroup = this.selectItems[groupIndex]; if (g) { if (g.values && g.values.length && t) { g.values = g.values.filter((n: RichEditorCommand) => n.code !== t.code); } else { this.selectItems.splice(groupIndex, 1); this.selectItems.splice(groupIndex, 0, ...g.values); if (this.currentGroup) { if (g.groupName === this.currentGroup.groupName) { this.currentGroup = null; } } if (this.currentSelected && this.currentSelected.groupName) { if (g.groupName === this.currentSelected.groupName) { this.currentSelected = null; } } } } else { this.selectItems = this.selectItems.filter(n => n.code !== t.code); } if (t) { this.toggleLeftItemVisible(t.code); } } private moveItem(items: any[], currentIndex, type: 'up' | 'down' | 'top' | 'bottom' = 'up') { if (type === 'up') { if (currentIndex !== 0) { const tempArr = items.splice(currentIndex, 1); items.splice(currentIndex - 1, 0, ...tempArr); } } if (type === 'down') { if (currentIndex === 0 || currentIndex !== items.length - 1) { const tempArr = items.splice(currentIndex, 1); items.splice(currentIndex + 1, 0, ...tempArr); } } if (type === 'top') { items.unshift(items[currentIndex]); items.splice(currentIndex + 1, 1); } if (type === 'bottom') { items.push(items[currentIndex]); items.splice(currentIndex, 1); } } // 移动展示顺序 onMove(type: 'up' | 'down' | 'top' | 'bottom' = 'up') { if (this.currentSelected) { if (this.currentGroup) { const index = this.currentGroup.values.findIndex((n: any) => n.code === this.currentSelected.code); this.moveItem(this.currentGroup.values, index, type); } else { let index = -1; if (this.currentElement && this.currentSelected.groupName) { index = parseInt(this.currentElement.getAttribute('data-index'), 10); } else { index = this.selectItems.findIndex((n: any) => n.code === this.currentSelected.code); } this.moveItem(this.selectItems, index, type); } setTimeout(() => { if (this.currentElement) { if (this.currentElement.scrollIntoViewIfNeeded) { this.currentElement.scrollIntoViewIfNeeded(); } else { this.currentElement.scrollIntoView(type === 'up'); } } }, 10); } } onShowCode($event) { $event.stopPropagation(); const toolbars = this.convert2toolbars(this.selectItems) || ''; this.messager.prompt2('工具栏配置代码', toolbars ? JSON.stringify(toolbars, null, 4) : '', { showMaxButton: false, resizable: false, width: 500, height: 400, showOkButton: false, showCancelButton: false }); } private insertAt = (arr, i, ...v) => { arr.splice(i + 1, 0, ...v); return arr; } // 移动到 onMoveTo($event: MouseEvent) { $event.stopPropagation(); const selections = this.getSelections(); if (!selections.length) { this.messager.warning('请选中一条或多条命令项。'); return; } this.initPosData('group'); const dlg = this.modalSer.show(this.movetoTemplate, { showMaxButton: false, resizable: false, title: '移动到...', width: 400, height: 200, closed: () => { this.movetype = 'group'; this.targetValue = null; this.positionValue = 'before'; }, buttons: [ { text: '取消', cls: 'btn btn-secondary mr-3 btn-lg', handle: () => { dlg.close(); } }, { text: '确定', cls: 'btn btn-primary btn-lg', handle: () => { if (!this.targetValue) { this.notify.info('请选择分组名称。'); return; } const selectedItems = this.getSelections(); this.clearSelections(); const _selectItems = this.selectItems.filter(n => { if (n.values) { n.values = n.values.filter(v => !selectedItems.find(s => s.code === v.code)); return !selectedItems.find(s => s.groupName === n.groupName); } return !selectedItems.find(s => s.code === n.code); }); if (this.movetype === 'position') { const targetItemIndex = _selectItems.findIndex(n => { return n.code === this.targetValue || n.groupName === this.targetValue; }); if (this.positionValue === 'before') { this.selectItems = this.insertAt(_selectItems, targetItemIndex - 1, ...selectedItems); } else { // after this.selectItems = this.insertAt(_selectItems, targetItemIndex, ...selectedItems); } } if (this.movetype === 'group') { const g: any = this.selectItems.find(n => n.groupName === this.targetValue); if (g) { g.values = (g.values || []).concat(selectedItems.filter(n => !n.groupName)); this.selectItems = _selectItems; } } dlg.close(); } } ] }); } onMoveTypeChange($event: MOVETYPE) { this.initPosData($event); } private initPosData(movetype: MOVETYPE) { if (movetype === 'group') { this.posData = this.getGroups().filter(n => !n.selected).map(n => { return { id: n.groupName, label: n.groupName }; }); } else { this.posData = this.selectItems.filter( n => { return !n.selected; }).map(n => { if (n.groupName) { return { id: n.groupName, label: n.groupName, }; } return { id: n.code, label: n.title, cls: n.cls }; }); } if (this.posData && this.posData.length) { this.targetValue = this.posData[0].id; } } /** * 左侧数据源显示状态 * @param show 是否显示,默认为 true */ private toggleLeftData(show = true) { this.allCMDs.forEach(t => { t.visible = show; }); this.allCMDs = this.allCMDs.concat(); } private toggleLeftItemVisible(code: string, show = true) { const item = this.allCMDs.find(n => n.code === code); if (item) { item.visible = show; item.selected = false; // this.cd.detectChanges(); } } // 清空右铡选中状态 private clearSelections() { this.currentGroup = null; this.currentSelected = null; this.currentElement = null; this.selectItems.forEach((n, i) => { if (n.groupName) { n.values.forEach(c => { c.selected = false; }); } n.selected = false; }); } // 获取右侧选中的记录 private getSelections(isNewGroup = false) { const items = flatten(this.selectItems.map(n => { if (n.values) { if (isNewGroup) { return n.values; } return [n, ...n.values]; } return n; })); return flatten(items.filter(n => n.selected).map(n => { if (n.values) { if (isNewGroup) { return n.values; } return [n, ...n.values]; } return n; })); } private getGroups() { return this.selectItems.filter(n => n.groupName); } private hasSameGroupName(newName: string) { if (newName) { return this.getGroups().find(n => n.groupName === newName); } return false; } onRestoreDefault($event: MouseEvent) { $event.stopPropagation(); this.messager.confirm('您确认要恢复默认配置吗?').subscribe((b) => { if (b) { this.toolbar = cloneDeep(this.editorType === 'concise' ? TOOLBAR_DEFAUTLS_CONCISE : TOOLBAR_DEFAUTLS_ADVANCE); this.initData(); } }); } private updateToolbarItemCls(toolbarItem: any) { if (toolbarItem.code === 'formatselect' || toolbarItem.code === 'fontselect' || toolbarItem.code === 'fontsizeselect') { toolbarItem.cls = 'mr-1 rich-toolbar-icons-preview rich-' + toolbarItem.code; } } onPreview($event) { $event.stopPropagation(); this.showPreview = !this.showPreview; if (this.showPreview) { this.previewData = cloneDeep(this.selectItems).map( (n: any) => { if (n.values && n.values.length) { n.values.forEach(v => { this.updateToolbarItemCls(v); }); } else { this.updateToolbarItemCls(n); } return n; } ); this.render.addClass(this.desginerForm.nativeElement, 'hide'); this.render.removeClass(this.desginerForm.nativeElement, 'show'); this.render.addClass(this.toolbarPreView.nativeElement, 'show'); this.render.removeClass(this.toolbarPreView.nativeElement, 'hide'); } else { this.render.addClass(this.desginerForm.nativeElement, 'show'); this.render.removeClass(this.desginerForm.nativeElement, 'hide'); this.render.addClass(this.toolbarPreView.nativeElement, 'hide'); this.render.removeClass(this.toolbarPreView.nativeElement, 'show'); } } }