UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

364 lines (297 loc) 8.38 kB
import { noop } from '../../utils/event.js' function getBlockElement (el, parent) { if (parent && el === parent) { return null } const nodeName = el.nodeName.toLowerCase() if ([ 'div', 'li', 'ul', 'ol', 'blockquote' ].includes(nodeName) === true) { return el } const style = window.getComputedStyle ? window.getComputedStyle(el) : el.currentStyle, display = style.display if (display === 'block' || display === 'table') { return el } return getBlockElement(el.parentNode) } function isChildOf (el, parent, orSame) { return !el || el === document.body ? false : (orSame === true && el === parent) || (parent === document ? document.body : parent).contains(el.parentNode) } function createRange (node, chars, range) { if (!range) { range = document.createRange() range.selectNode(node) range.setStart(node, 0) } if (chars.count === 0) { range.setEnd(node, chars.count) } else if (chars.count > 0) { if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.length < chars.count) { chars.count -= node.textContent.length } else { range.setEnd(node, chars.count) chars.count = 0 } } else { for (let lp = 0; chars.count !== 0 && lp < node.childNodes.length; lp++) { range = createRange(node.childNodes[ lp ], chars, range) } } } return range } const urlRegex = /^https?:\/\// export default class Caret { constructor (el, eVm) { this.el = el this.eVm = eVm this._range = null } get selection () { if (this.el) { const sel = document.getSelection() // only when the selection in element if (isChildOf(sel.anchorNode, this.el, true) && isChildOf(sel.focusNode, this.el, true)) { return sel } } return null } get hasSelection () { return this.selection !== null ? this.selection.toString().length > 0 : false } get range () { const sel = this.selection if (sel !== null && sel.rangeCount) { return sel.getRangeAt(0) } return this._range } get parent () { const range = this.range if (range !== null) { const node = range.startContainer return node.nodeType === document.ELEMENT_NODE ? node : node.parentNode } return null } get blockParent () { const parent = this.parent if (parent !== null) { return getBlockElement(parent, this.el) } return null } save (range = this.range) { if (range !== null) { this._range = range } } restore (range = this._range) { const r = document.createRange(), sel = document.getSelection() if (range !== null) { r.setStart(range.startContainer, range.startOffset) r.setEnd(range.endContainer, range.endOffset) sel.removeAllRanges() sel.addRange(r) } else { sel.selectAllChildren(this.el) sel.collapseToEnd() } } savePosition () { let charCount = -1, node const selection = document.getSelection(), parentEl = this.el.parentNode if (selection.focusNode && isChildOf(selection.focusNode, parentEl)) { node = selection.focusNode charCount = selection.focusOffset while (node && node !== parentEl) { if (node !== this.el && node.previousSibling) { node = node.previousSibling charCount += node.textContent.length } else { node = node.parentNode } } } this.savedPos = charCount } restorePosition (length = 0) { if (this.savedPos > 0 && this.savedPos < length) { const selection = window.getSelection(), range = createRange(this.el, { count: this.savedPos }) if (range) { range.collapse(false) selection.removeAllRanges() selection.addRange(range) } } } hasParent (name, spanLevel) { const el = spanLevel ? this.parent : this.blockParent return el !== null ? el.nodeName.toLowerCase() === name.toLowerCase() : false } hasParents (list, recursive, el = this.parent) { if (el === null) { return false } if (list.includes(el.nodeName.toLowerCase()) === true) { return true } return recursive === true ? this.hasParents(list, recursive, el.parentNode) : false } is (cmd, param) { if (this.selection === null) { return false } switch (cmd) { case 'formatBlock': return (param === 'DIV' && this.parent === this.el) || this.hasParent(param, param === 'PRE') case 'link': return this.hasParent('A', true) case 'fontSize': return document.queryCommandValue(cmd) === param case 'fontName': const res = document.queryCommandValue(cmd) return res === `"${ param }"` || res === param case 'fullscreen': return this.eVm.inFullscreen.value case 'viewsource': return this.eVm.isViewingSource.value case void 0: return false default: const state = document.queryCommandState(cmd) return param !== void 0 ? state === param : state } } getParentAttribute (attrib) { if (this.parent !== null) { return this.parent.getAttribute(attrib) } return null } can (name) { if (name === 'outdent') { return this.hasParents([ 'blockquote', 'li' ], true) } if (name === 'indent') { return this.hasParents([ 'li' ], true) } if (name === 'link') { return this.selection !== null || this.is('link') } } apply (cmd, param, done = noop) { if (cmd === 'formatBlock') { if ([ 'BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].includes(param) && this.is(cmd, param)) { cmd = 'outdent' param = null } if (param === 'PRE' && this.is(cmd, 'PRE')) { param = 'P' } } else if (cmd === 'print') { done() const win = window.open() win.document.write(` <!doctype html> <html> <head> <title>Print - ${ document.title }</title> </head> <body> <div>${ this.el.innerHTML }</div> </body> </html> `) win.print() win.close() return } else if (cmd === 'link') { const link = this.getParentAttribute('href') if (link === null) { const selection = this.selectWord(this.selection) const url = selection ? selection.toString() : '' if (!url.length) { if (!this.range || !this.range.cloneContents().querySelector('img')) { return } } this.eVm.editLinkUrl.value = urlRegex.test(url) ? url : 'https://' document.execCommand('createLink', false, this.eVm.editLinkUrl.value) this.save(selection.getRangeAt(0)) } else { this.eVm.editLinkUrl.value = link this.range.selectNodeContents(this.parent) this.save() } return } else if (cmd === 'fullscreen') { this.eVm.toggleFullscreen() done() return } else if (cmd === 'viewsource') { this.eVm.isViewingSource.value = this.eVm.isViewingSource.value === false this.eVm.setContent(this.eVm.props.modelValue) done() return } document.execCommand(cmd, false, param) done() } selectWord (sel) { if (sel === null || sel.isCollapsed !== true || /* IE 11 */ sel.modify === void 0) { return sel } // Detect if selection is backwards const range = document.createRange() range.setStart(sel.anchorNode, sel.anchorOffset) range.setEnd(sel.focusNode, sel.focusOffset) const direction = range.collapsed ? [ 'backward', 'forward' ] : [ 'forward', 'backward' ] range.detach() // modify() works on the focus of the selection const endNode = sel.focusNode, endOffset = sel.focusOffset sel.collapse(sel.anchorNode, sel.anchorOffset) sel.modify('move', direction[ 0 ], 'character') sel.modify('move', direction[ 1 ], 'word') sel.extend(endNode, endOffset) sel.modify('extend', direction[ 1 ], 'character') sel.modify('extend', direction[ 0 ], 'word') return sel } }