UNPKG

suneditor

Version:

Vanilla JavaScript based WYSIWYG web editor

229 lines (192 loc) 8.48 kB
import { dom, env, unicode, keyCodeMap } from '../../../helper'; import { actionExecutor } from '../executor'; import { makePorts } from '../ports'; import { reduceKeydown } from '../reducers/keydown.reducer'; const { _w } = env; const FRONT_ZEROWIDTH = new RegExp(unicode.zeroWidthSpace + '+', ''); const keyState = { ctrl: false, alt: false, }; const _styleNodes = Object.preventExtensions({ value: [] }); /** * @typedef {import('../eventOrchestrator').default} EventManagerThis_handler_ww_key_input */ /** * @this {EventManagerThis_handler_ww_key_input} * @param {SunEditor.FrameContext} fc - Frame context object * @param {KeyboardEvent} e - Event object */ export async function OnKeyDown_wysiwyg(fc, e) { if ((this.isComposing = keyCodeMap.isComposing(e))) return true; if (this.$.ui.selectMenuOn || !e.isTrusted) return; let selectionNode = this.$.selection.getNode(); if (dom.check.isInputElement(selectionNode)) return; if (this.$.menu.currentDropdownName) return; const keyCode = e.code; const shift = keyCodeMap.isShift(e); const ctrl = (keyState.ctrl = keyCodeMap.isCtrl(e)); const alt = (keyState.alt = keyCodeMap.isAlt(e)); if (!ctrl && fc.get('isReadOnly') && !keyCodeMap.isDirectionKey(keyCode)) { e.preventDefault(); return false; } this.$.menu.dropdownOff(); if (this.$.store.mode.isBalloon) { this._hideToolbar(); } else if (this.$.store.mode.isSubBalloon) { this._hideToolbar_sub(); } // user event if ((await this.$.eventManager.triggerEvent('onKeyDown', { frameContext: fc, event: e })) === false) return; /** default key action */ if (keyCodeMap.isEnter(keyCode) && this.$.format.isLine(this.$.selection.getRange()?.startContainer)) { this.$.selection.resetRangeToTextNode(); selectionNode = this.$.selection.getNode(); } const range = this.$.selection.getRange(); const formatEl = /** @type {HTMLElement} */ (this.$.format.getLine(selectionNode, null) || selectionNode); /** Shortcuts */ if (ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.shortcuts.command(e, ctrl, shift, keyCode, '', false, null, null)) { this._onShortcutKey = true; e.preventDefault(); e.stopPropagation(); return false; } else if (!ctrl && !keyCodeMap.isNonTextKey(keyCode) && this.$.format.isLine(formatEl) && range.collapsed && dom.check.isEdgePoint(range.startContainer, 0, 'front')) { const keyword = /** @type {Text} */ (range.startContainer).substringData?.(0, range.startOffset); if (keyword && this.$.shortcuts.command(e, false, shift, keyCode, keyword, true, formatEl, range)) { this._onShortcutKey = true; e.preventDefault(); e.stopPropagation(); return false; } } else if (this._onShortcutKey) { this._onShortcutKey = false; } // plugin event if ((await this._callPluginEventAsync('onKeyDown', { frameContext: fc, event: e, range, line: formatEl })) === false) return; // reducer / actions /** @type {import('../reducers/keydown.reducer').KeydownReducerCtx} */ const ctx = { e, fc, store: this.$.store, options: this.$.options, frameOptions: this.$.frameOptions, range, selectionNode, formatEl, keyCode, ctrl, alt, shift }; const ports = makePorts(this, { _styleNodes }); // action execute const actions = await reduceKeydown(ports, ctx); await actionExecutor(actions, { ports, ctx }); } /** * @this {EventManagerThis_handler_ww_key_input} * @param {SunEditor.FrameContext} fc - Frame context object * @param {KeyboardEvent} e - Event object */ export async function OnKeyUp_wysiwyg(fc, e) { if (this._onShortcutKey || this.$.menu.currentDropdownName) return; const keyCode = e.code; const ctrl = keyCodeMap.isCtrl(e); const alt = keyCodeMap.isAlt(e); if (ctrl) keyState.ctrl = false; if (alt) keyState.alt = false; if (fc.get('isReadOnly')) return; const range = this.$.selection.getRange(); let selectionNode = this.$.selection.getNode(); if ((this.$.store.mode.isBalloon || this.$.store.mode.isSubBalloon) && (((this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) && !keyCodeMap.isEsc(keyCode)) || !range.collapsed)) { if (this.$.store.mode.isBalloonAlways || this.$.store.mode.isSubBalloonAlways) { if (!keyCodeMap.isEsc(keyCode)) this._showToolbarBalloonDelay(); } else { if (this.$.store.mode.isSubBalloon) this.$.subToolbar._showBalloon(); else this.$.toolbar._showBalloon(); return; } } /** when format tag deleted */ if (keyCodeMap.isBackspace(keyCode) && dom.check.isWysiwygFrame(selectionNode) && selectionNode.textContent === '' && selectionNode.children.length === 0) { e.preventDefault(); e.stopPropagation(); selectionNode.innerHTML = ''; const currentNodeName = this.$.store.get('currentNodes')[0]; const oFormatTag = dom.utils.createElement(this.$.format.isLine(currentNodeName) && !dom.check.isListCell(currentNodeName) ? currentNodeName : this.$.options.get('defaultLine'), null, '<br>'); selectionNode.appendChild(oFormatTag); this.$.selection.setRange(oFormatTag, 0, oFormatTag, 0); this.applyTagEffect(); this.$.history.push(false); // document type if (fc.has('documentType_use_header')) { if (keyCodeMap.isDocumentTypeObserverKey(keyCode)) { fc.get('documentType').reHeader(); } } return; } const formatEl = this.$.format.getLine(selectionNode, null); const rangeEl = this.$.format.getBlock(selectionNode, null); const attrs = this._formatAttrsTemp; if (formatEl && attrs) { for (let i = 0, len = attrs.length; i < len; i++) { if (keyCodeMap.isEnter(keyCode) && /^id$/i.test(attrs[i].name)) { formatEl.removeAttribute('id'); continue; } formatEl.setAttribute(attrs[i].name, attrs[i].value); } this._formatAttrsTemp = null; } if ( !this.$.format.isNormalLine(formatEl) && !this.$.format.isBrLine(formatEl) && range.collapsed && !this.$.component.is(selectionNode) && !dom.check.isList(selectionNode) && this._setDefaultLine(this.$.format.isBlock(rangeEl) ? 'DIV' : this.$.options.get('defaultLine')) !== null ) { selectionNode = this.$.selection.getNode(); } const textKey = !keyState.ctrl && !keyState.alt && !keyCodeMap.isNonTextKey(keyCode); if (textKey && selectionNode.nodeType === 3 && unicode.zeroWidthRegExp.test(selectionNode.textContent) && !(e.isComposing !== undefined ? e.isComposing : this.isComposing)) { let so = range.startOffset, eo = range.endOffset; const frontZeroWidthCnt = (selectionNode.textContent.substring(0, eo).match(FRONT_ZEROWIDTH) || '').length; so = range.startOffset - frontZeroWidthCnt; eo = range.endOffset - frontZeroWidthCnt; selectionNode.textContent = selectionNode.textContent.replace(unicode.zeroWidthRegExp, ''); this.$.selection.setRange(selectionNode, so < 0 ? 0 : so, selectionNode, eo < 0 ? 0 : eo); } if (keyCodeMap.isRemoveKey(keyCode) && dom.check.isZeroWidth(formatEl?.textContent) && !formatEl.previousElementSibling && !dom.check.isListCell(formatEl)) { const rsMode = this.$.options.get('retainStyleMode'); if (rsMode !== 'none' && _styleNodes.value?.length > 0) { if (rsMode === 'repeat') { if (this.__retainTimer) { this.__retainTimer = _w.clearTimeout(this.__retainTimer); this._clearRetainStyleNodes(formatEl); } else { // Timer as a "first press" flag — next keydown within the same tick clears and toggles to "remove" mode (repeat mode toggle) this.__retainTimer = _w.setTimeout(() => { this.__retainTimer = null; }, 0); this._retainStyleNodes(formatEl, _styleNodes.value); } } else { this.__retainTimer = null; this._retainStyleNodes(formatEl, _styleNodes.value); } } else { this._clearRetainStyleNodes(formatEl); } } // document type if (fc.has('documentType_use_header')) { if (keyCodeMap.isDocumentTypeObserverKey(keyCode)) { fc.get('documentType').reHeader(); const el = dom.query.getParentElement(this.$.selection.selectionNode, this.$.format.isLine.bind(this.$.format)); fc.get('documentType').on(el); } else { const el = dom.query.getParentElement(selectionNode, (current) => current.nodeType === 1); fc.get('documentType').onChangeText(el); } } // user event if ((await this.$.eventManager.triggerEvent('onKeyUp', { frameContext: fc, event: e })) === false) return; // plugin event if ((await this._callPluginEventAsync('onKeyUp', { frameContext: fc, event: e, range, line: formatEl })) === false) return; if (keyCodeMap.isHistoryRelevantKey(keyCode)) { this.$.history.push(true); } }