UNPKG

zx-editor

Version:

ZxEditor is a HTML5 rich text editor

869 lines (801 loc) 20.7 kB
/** * Created by Capricorncd. * User: https://github.com/capricorncd * Date: 2019/04/17 20:36 */ import util from '../util/index' import { document, window } from 'ssr-window' import { unique, changeNodeName, addEventListener, removeEventListener } from './helper' /** * DOM操作 * @param selector * @param context * @constructor */ export function ZxQuery (selector, context) { // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) return this if (selector instanceof ZxQuery) return selector let doms // Handle HTML strings if ( typeof selector === 'string' ) { if ( selector[ 0 ] === '<' && selector[ selector.length - 1 ] === '>' && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check let tempDiv = document.createElement('div') tempDiv.innerHTML = selector doms = util.slice(tempDiv.childNodes) } else if (/^[#\w\d_,\s-]+$/.test(selector)) { if (context instanceof ZxQuery) { context = context[0] } context = util.isElement(context) && context.nodeType === 1 ? context : document doms = util.slice(context.querySelectorAll(util.strip(selector))) } // HANDLE: $(DOMElement) } else if ( selector.nodeType || selector === window || selector === document ) { doms = [selector] // HANDLE: $(DOMElements) } else if (Array.isArray(selector) && selector.every(item => util.isElement(item))) { doms = selector } if (doms) { doms.forEach((el, i) => { this[i] = el }) this.length = doms.length } return this } /** * prototype */ ZxQuery.prototype = { /** * constructor */ constructor: ZxQuery, /** * length */ length: 0, /** * ******************************************** * find item * ******************************************** */ /** * get children * @param selector * @param anyNode Boolean * @return {*|HTMLElement} */ children (selector, anyNode) { // check arguments if (typeof selector === 'boolean') { anyNode = selector selector = void 0 } const children = [] let el for (let i = 0; i < this.length; i++) { const childNodes = this[i].childNodes for (let j = 0; j < childNodes.length; j++) { el = childNodes[j] if (!selector) { if (anyNode) { children.push(el) } else if (el.nodeType === 1) { children.push(el) } } else if (el.nodeType === 1 && $(el).is(selector)) { children.push(el) } } } return $(unique(children)) }, /** * Find the nearest matching element in the parent * @param selector * @return {*} */ closest (selector) { let closest = this if (typeof selector === 'undefined') { return new $() } if (!closest.is(selector)) { closest = closest.parents(selector).eq(0) } return closest }, /** * equal * @param i * @return {*|jQuery|HTMLElement} */ eq (i) { let len = this.length let index = util.int(i) + (i < 0 ? len : 0) return $(this[index]) }, /** * find item * @param selector * @return {*|jQuery|HTMLElement} */ find (selector) { let found let foundElements = [] for (let i = 0; i < this.length; i++) { if (this[i].nodeType !== 1) continue found = this[i].querySelectorAll(selector) for (let j = 0; j < found.length; j++) { foundElements.push(found[j]) } } return $(foundElements) }, /** * * @param parent * @return {*} */ findParentFrom (parent) { if (parent instanceof ZxQuery) parent = parent[0] let current = this[0] // if (!current || !current.nodeType || !parent || !parent.nodeType) return null if (current === parent) return null let tmpParent while (current.parentNode) { tmpParent = current.parentNode if (tmpParent === parent) return $(current) current = tmpParent } return null }, /** * get first child * @return {*|jQuery|HTMLElement} */ firstChild () { return this.children().eq(0) }, /** * get last child * @return {*|jQuery|HTMLElement} */ lastChild () { return this.children().eq(-1) }, /** * get next siblings * @param selector * @return {*|jQuery|HTMLElement} */ next (selector) { if (this.length > 0) { if (selector) { if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) { return $([this[0].nextElementSibling]) } return $() } if (this[0].nextElementSibling) return $([this[0].nextElementSibling]) } return $() }, /** * get nearest parents * @param selector * @return {*|jQuery|HTMLElement} */ parent (selector) { const parents = [] for (let i = 0; i < this.length; i++) { if (this[i].parentNode !== null) { if (selector) { if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode) } else { parents.push(this[i].parentNode) } } } return $(unique(parents)) }, /** * get all parents * @param selector * @return {*|jQuery|HTMLElement} */ parents (selector) { const parents = [] for (let i = 0; i < this.length; i++) { let parent = this[i].parentNode while (parent) { if (selector) { if ($(parent).is(selector)) parents.push(parent) } else { parents.push(parent) } parent = parent.parentNode } } return $(unique(parents)) }, /** * ******************************************** * class * ******************************************** */ /** * add className * @param className * @return {ZxQuery} */ addClass (className) { if (!className) return this let classes = className.split(' ') classes.forEach(cls => { for (let j = 0; j < this.length; j++) { this[j] && this[j].classList && this[j].classList.add(cls) } }) return this }, /** * remove className * @param className * @return {ZxQuery} */ removeClass (className) { let classes = className.split(' ') classes.forEach(cls => { for (let j = 0; j < this.length; j++) { this[j] && this[j].classList && this[j].classList.remove(cls) } }) return this }, /** * check className * @param className * @return {boolean} */ hasClass (className) { if (!this[0]) return false return this[0].classList.contains(className) }, /** * ******************************************** * attribute, data * ******************************************** */ /** * get/set attribute * @param attrs attribute or object * @param value * @return {*} */ attr (attrs, value) { if (arguments.length === 1 && typeof attrs === 'string') { // Get attr if (this[0]) return this[0].getAttribute(attrs) return void 0 } // Set attrs for (let i = 0; i < this.length; i++) { if (arguments.length === 2) { // String this[i].setAttribute(attrs, value) } else { // Object for (let attr in attrs) { this[i][attr] = attrs[attr] this[i].setAttribute(attr, attrs[attr]) } } } return this }, /** * remove attribute * @param attr * @return {ZxQuery} */ removeAttr (attr) { for (let i = 0; i < this.length; i++) { this[i].removeAttribute(attr) } return this }, /** * get/set data- * @param key * @param value * @return {*} */ data (key, value) { let el if (typeof value === 'undefined') { el = this[0] if (el) { if (el.dataStorage) return el.dataStorage[key] let val = el.getAttribute(`data-${key}`) return /^\d+\.?\d*$/.test(val) ? +val : val } return undefined } // Set value for (let i = 0; i < this.length; i++) { el = this[i] if (!el.dataStorage) el.dataStorage = {} el.dataStorage[key] = value } return this }, /** * ******************************************** * insert node * ******************************************** */ /** * appendChild * @param args * @return {ZxQuery} */ append (...args) { let newChild for (let k = 0; k < args.length; k++) { newChild = args[k]; for (let i = 0; i < this.length; i++) { if (typeof newChild === 'string') { const tempDiv = document.createElement('div') tempDiv.innerHTML = newChild while (tempDiv.firstChild) { this[i].appendChild(tempDiv.firstChild) } } else if (newChild instanceof ZxQuery) { for (let j = 0; j < newChild.length; j++) { this[i].appendChild(newChild[j]) } } else { this[i].appendChild(newChild) } } } return this }, /** * append child to parent * @param parent * @return {ZxQuery} */ appendTo (parent) { $(parent).append(this) return this }, /** * insert after selector * @param selector */ insertAfter(selector) { const after = $(selector) for (let i = 0; i < this.length; i++) { if (after.length === 1) { after[0].parentNode.insertBefore(this[i], after[0].nextSibling) } else if (after.length > 1) { for (let j = 0; j < after.length; j++) { after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling) } } } }, insertBefore (selector) { const before = $(selector) for (let i = 0; i < this.length; i++) { if (before.length === 1) { before[0].parentNode.insertBefore(this[i], before[0]) } else if (before.length > 1) { for (let j = 0; j < before.length; j++) { before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]) } } } }, prepend (newChild) { let i let j for (i = 0; i < this.length; i++) { if (typeof newChild === 'string') { const tempDiv = document.createElement('div') tempDiv.innerHTML = newChild for (j = tempDiv.childNodes.length - 1; j >= 0; j--) { this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]) } } else if (newChild instanceof ZxQuery) { for (j = 0; j < newChild.length; j++) { this[i].insertBefore(newChild[j], this[i].childNodes[0]) } } else { this[i].insertBefore(newChild, this[i].childNodes[0]) } } return this }, prependTo (parent) { $(parent).prepend(this) return this }, /** * replace child * @param selector * @return {ZxQuery} */ replace (selector) { let newChild = $(selector) let parentNode for (let i = 0; i < this.length; i++) { parentNode = this[i].parentNode if (parentNode) { parentNode.replaceChild(newChild[0].cloneNode(true), this[i]) } } return this }, /** * remove item * @param index * @return {ZxQuery} */ remove (index) { let el if (typeof index !== 'number') { for (let i = 0; i < this.length; i++) { el = this[i] if (el.parentNode) el.parentNode.removeChild(el) } } else { el = this[index] if (el && el.parentNode) el.parentNode.removeChild(el) } return this }, /** * ******************************************** * check * ******************************************** */ /** * is * @param selector * @return {*} */ is (selector) { const el = this[0] let compareWith let i if (!el || typeof selector === 'undefined') return false; if (typeof selector === 'string') { if (el.matches) return el.matches(selector) else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector) else if (el.msMatchesSelector) return el.msMatchesSelector(selector) compareWith = $(selector) for (i = 0; i < compareWith.length; i++) { if (compareWith[i] === el) return true } return false } else if (selector === document) return el === document else if (selector === window) return el === window if (selector.nodeType || selector instanceof ZxQuery) { compareWith = selector.nodeType ? [selector] : selector for (i = 0; i < compareWith.length; i++) { if (compareWith[i] === el) return true } return false } return false }, isTextNode () { let el = this[0] if (!el || !el.nodeType) return false return el.nodeType === 3 }, /** * 子元素(不包含br)是否为空 * @return {boolean} */ isEmpty () { return !util.strip(this.text()) && !this.find('img, video, audio')[0] }, /** * target是否为this的第一个子元素 * @param target * @return {boolean} */ isFirstChildren (target) { return this.children().eq(0).is(target) }, /** * target是否为this的最后一个子元素 * @param target * @return {boolean} */ isLastChildren (target) { return this.children().eq(-1).is(target) }, indexOf(el) { if (el instanceof ZxQuery) el = el[0] for (let i = 0; i < this.length; i++) { if (this[i] === el) return i } return -1 }, /** * this is in $parent children * @param $parent * @return {boolean} */ // isInChild ($parent) { // let el = this[0] // let childNodes = $parent[0].childNodes // let tmp // for (let i = 0; i < childNodes.length; i++) { // tmp = childNodes[i] // if (tmp === el) return true // if (tmp.nodeType === 1 && this.isInChild($(tmp))) return true // } // return false // }, /** * every * @param fn * @return {boolean} */ every (fn) { if (typeof fn !== 'function') throw new TypeError(`every(fn) fn is not a function`) let len = this.length let thisArg = arguments.length >= 2 ? arguments[1] : void 0 for (let i = 0; i < len; i++) { if (fn.call(thisArg, $(this[i]), i)) return false } return true }, /** * ******************************************** * content * ******************************************** */ text (text) { if (typeof text === 'undefined') { if (this[0]) { return this[0].textContent.trim() } return '' } for (let i = 0; i < this.length; i++) { this[i].textContent = text } return this }, html (html) { if (typeof html === 'undefined') { return this[0] ? this[0].innerHTML : '' } for (let i = 0; i < this.length; i++) { this[i].innerHTML = html } return this }, /** * ******************************************** * node * ******************************************** */ nodeName () { return this[0] ? this[0].nodeName.toLowerCase() : null }, // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType nodeType () { return this[0] ? this[0].nodeType : 0 }, changeNodeName (nodeName) { for (let i = 0; i < this.length; i++) { // change nodeName this[i] = changeNodeName(this[i], nodeName) } return this }, /** * ******************************************** * visible * ******************************************** */ hide () { for (let i = 0; i < this.length; i++) { this[i].style.display = 'none' } return this }, show () { for (let i = 0; i < this.length; i++) { const el = this[i] if (el.style.display === 'none') { el.style.display = '' } if (window.getComputedStyle(el, null).getPropertyValue('display') === 'none') { // Still not visible el.style.display = 'block' } } return this }, /** * ******************************************** * Events * ******************************************** */ on (...args) { let [eventType, fn, useCapture] = args let el for (let i = 0; i < this.length; i++) { el = this[i] if (el.nodeType === 1 || el === document || el === window) { addEventListener(el, eventType, fn, useCapture) } } return this }, off(...args) { let [eventType, fn, useCapture] = args let el for (let i = 0; i < this.length; i++) { el = this[i] if (el.nodeType === 1 || el === document || el === window) { removeEventListener(el, eventType, fn, useCapture) } } return this; }, trigger(...args) { const events = args[0].split(' ') const eventData = args[1] for (let i = 0; i < events.length; i++) { const event = events[i] for (let j = 0; j < this.length; j++) { const el = this[j] let evt try { evt = new window.CustomEvent(event, { detail: eventData, bubbles: true, cancelable: true, }) } catch (e) { evt = document.createEvent('Event') evt.initEvent(event, true, true) evt.detail = eventData } el.dispatchEvent(evt) } } return this }, /** * ******************************************** * css * ******************************************** */ css(props, value) { let i if (arguments.length === 1) { if (typeof props === 'string') { if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props) return void 0 } else { for (i = 0; i < this.length; i += 1) { for (let prop in props) { this[i].style[prop] = props[prop] } } } } if (arguments.length === 2 && typeof props === 'string') { for (i = 0; i < this.length; i += 1) { this[i].style[props] = value; } } return this }, styles() { if (this[0]) return window.getComputedStyle(this[0], null) return {} }, /** * ******************************************** * size * ******************************************** */ width() { if (this[0] === window) return window.innerWidth return this.length > 0 ? parseFloat(this.css('width')) : null }, outerWidth(includeMargins) { if (this.length > 0) { let offsetWidth = this[0].offsetWidth if (includeMargins) { const styles = this.styles() return offsetWidth + parseFloat(styles.getPropertyValue('margin-right')) + parseFloat(styles.getPropertyValue('margin-left')) } return offsetWidth } return null }, height() { if (this[0] === window) return window.innerHeight return this.length > 0 ? parseFloat(this.css('height')) : null }, outerHeight(includeMargins) { if (this.length > 0) { let offsetHeight = this[0].offsetHeight if (includeMargins) { let styles = this.styles() return offsetHeight + parseFloat(styles.getPropertyValue('margin-top')) + parseFloat(styles.getPropertyValue('margin-bottom')) } return offsetHeight } return null }, offset () { if (this.length > 0) { let el = this[0] let box = el.getBoundingClientRect() let body = document.body let clientTop = el.clientTop || body.clientTop || 0 let clientLeft = el.clientLeft || body.clientLeft || 0 let scrollTop = el === window ? window.scrollY : el.scrollTop let scrollLeft = el === window ? window.scrollX : el.scrollLeft return { top: (box.top + scrollTop) - clientTop, left: (box.left + scrollLeft) - clientLeft, } } return null }, /** * ******************************************** * for * ******************************************** */ each (fn) { if (!fn) return this for (let i = 0; i < this.length; i++) { if (fn.call(this[i], i, this[i]) === false) { return this } } return this }, /** * ******************************************** * scroll * ******************************************** */ scrollTop (value) { if (!this.length) return let hasScrollTop = 'scrollTop' in this[0] if (value === undefined) return hasScrollTop ? this[0].scrollTop : this[0].pageYOffset return this.each(hasScrollTop ? function(){ this.scrollTop = value } : function(){ this.scrollTo(this.scrollX, value) }) } } const $ = function (selector, context) { return new ZxQuery(selector, context) } export default $