UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

259 lines (258 loc) 10.3 kB
import { unit } from '../util'; /** * Processes incoming DOM "input" events (such as "beforeinput", "input", * "keydown", etc.) and translates them into Peritext events. */ export class InputController { opts; source; txt; et; constructor(opts) { this.opts = opts; this.source = opts.source; this.txt = opts.txt; this.et = opts.et; } start() { const onBeforeInput = (event) => { // TODO: prevent default more selectively? const et = this.et; const inputType = event.inputType; switch (inputType) { case 'insertParagraph': { // insert a paragraph break event.preventDefault(); et.marker({ action: 'ins' }); break; } // case 'insertFromComposition': case 'insertFromDrop': case 'insertFromPaste': case 'insertFromYank': case 'insertReplacementText': // case 'insertFromYank': { // replace the current selection with content stored in a kill buffer // } // case 'insertFromDrop': { // insert content by means of drop // } // case 'insertFromPaste': { // paste content from clipboard or paste image from client provided image library // } // case 'insertFromPasteAsQuotation': { // paste content from the clipboard as a quotation // } // case 'insertTranspose': { // transpose the last two grapheme cluster. that were entered // } // case 'insertCompositionText': { // replace the current composition string // } case 'insertText': { // TODO: handle `dataTransfer` Image drops, URL drops // TODO: handle `dataTransfer` HTML drops event.preventDefault(); if (typeof event.data === 'string') { et.insert(event.data); } else { const dataTransfer = event.dataTransfer; if (dataTransfer) { const text = dataTransfer.getData('text/plain'); if (text) et.insert(text); else dataTransfer.items[0]?.getAsString((text) => et.insert(text)); } } break; } case 'deleteContent': { event.preventDefault(); et.delete({}); break; } case 'deleteContentBackward': { event.preventDefault(); et.delete('start', 'char', -1); break; } case 'deleteContentForward': { event.preventDefault(); et.delete('end', 'char', 1); break; } case 'deleteWordBackward': { event.preventDefault(); et.delete('start', 'word', -1); break; } case 'deleteWordForward': { event.preventDefault(); et.delete('end', 'word', 1); break; } case 'deleteHardLineBackward': { event.preventDefault(); et.delete('start', 'line', -1); break; } case 'deleteHardLineForward': { event.preventDefault(); et.delete('end', 'line', 1); break; } case 'deleteSoftLineBackward': { event.preventDefault(); et.delete('start', 'vline', -1); break; } case 'deleteSoftLineForward': { event.preventDefault(); et.delete('end', 'vline', 1); break; } case 'deleteEntireSoftLine': { event.preventDefault(); et.delete({ move: [ ['start', 'vline', -1], ['end', 'vline', 1], ], }); break; } case 'insertLineBreak': { event.preventDefault(); et.insert('\n'); break; } // case 'insertOrderedList': { // insert a numbered list // } // case 'insertUnorderedList': { // insert a bulleted list // } // case 'insertHorizontalRule': { // insert a horizontal rule // } // case 'insertLink': { // insert a link // } // case 'deleteByDrag': { // remove content from the DOM by means of drag // } case 'deleteByCut': { event.preventDefault(); et.buffer({ action: 'cut' }); break; } // case 'historyUndo': {} // case 'historyRedo': {} case 'formatBold': { event.preventDefault(); et.format(-3 /* SliceTypeCon.b */); break; } case 'formatItalic': { event.preventDefault(); et.format(-4 /* SliceTypeCon.i */); break; } case 'formatUnderline': { event.preventDefault(); et.format(-5 /* SliceTypeCon.u */); break; } case 'formatStrikeThrough': { event.preventDefault(); et.format(-6 /* SliceTypeCon.s */); break; } case 'formatSuperscript': { event.preventDefault(); et.format(-13 /* SliceTypeCon.sup */); break; } case 'formatSubscript': { event.preventDefault(); et.format(-14 /* SliceTypeCon.sub */); break; } // case 'formatJustifyFull': { // make the current selection fully justified // } // case 'formatJustifyCenter': { // center align the current selection // } // case 'formatJustifyRight': { // right align the current selection // } // case 'formatJustifyLeft': { // left align the current selection // } // case 'formatIndent': { // indent the current selection // } // case 'formatOutdent': { // outdent the current selection // } // case 'formatRemove': { // remove all formatting from the current selection // } // case 'formatSetBlockTextDirection': { // set the text block direction // } // case 'formatSetInlineTextDirection': { // set the text inline direction // } // case 'formatBackColor': { // change the background color // } // case 'formatFontColor': { // change the font color // } // case 'formatFontName': { // change the font name // } } }; const onKeyDown = (event) => { const key = event.key; if (event.isComposing || key === 'Dead') return; const et = this.et; switch (key) { case 'Backspace': case 'Delete': { const direction = key === 'Delete' ? 1 : -1; const deleteUnit = unit(event); if (deleteUnit) { event.preventDefault(); et.delete('focus', deleteUnit, direction); } break; } case 'Escape': { // TODO: Use rendering surface imperative UI API here. const div = this.opts.source; if (div instanceof HTMLElement) { event.preventDefault(); div.blur(); } break; } } }; const onCopy = (event) => { event.preventDefault(); this.et.buffer({ action: 'copy' }); }; const onCut = (event) => { event.preventDefault(); this.et.buffer({ action: 'cut' }); }; const onPaste = (event) => { event.preventDefault(); const text = event.clipboardData?.getData('text/plain'); const html = event.clipboardData?.getData('text/html'); if (text || html) { const data = { text, html }; this.et.buffer({ action: 'paste', data }); } }; const el = this.opts.source; el.contentEditable = 'true'; el.addEventListener('beforeinput', onBeforeInput); el.addEventListener('keydown', onKeyDown); el.addEventListener('copy', onCopy); el.addEventListener('cut', onCut); el.addEventListener('paste', onPaste); return () => { el.contentEditable = 'false'; el.removeEventListener('beforeinput', onBeforeInput); el.removeEventListener('keydown', onKeyDown); el.removeEventListener('copy', onCopy); el.removeEventListener('cut', onCut); el.removeEventListener('paste', onPaste); }; } }