From c3ca34bd2a098b7629ded23b5c598b84de5a226e Mon Sep 17 00:00:00 2001 From: fofolee Date: Wed, 22 Jan 2025 21:29:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=8E=B7=E5=8F=96=E5=85=83?= =?UTF-8?q?=E7=B4=A0=E9=80=89=E6=8B=A9=E5=99=A8=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=80=E4=BC=98=20CSS=20=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E7=AE=97=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/lib/quickcomposer/browser/browser.js | 80 - .../lib/quickcomposer/browser/getSelector.js | 180 ++ plugin/lib/quickcomposer/browser/index.js | 6 +- .../quickcomposer/browser/optimalSelect.js | 1767 +++++++++++++++++ 4 files changed, 1952 insertions(+), 81 deletions(-) create mode 100644 plugin/lib/quickcomposer/browser/getSelector.js create mode 100644 plugin/lib/quickcomposer/browser/optimalSelect.js diff --git a/plugin/lib/quickcomposer/browser/browser.js b/plugin/lib/quickcomposer/browser/browser.js index 7ca06c3..195c64e 100644 --- a/plugin/lib/quickcomposer/browser/browser.js +++ b/plugin/lib/quickcomposer/browser/browser.js @@ -376,85 +376,6 @@ const waitForElement = async (selector, timeout = 5000) => { throw new Error(`等待元素 ${selector} 超时`); }; -const getSelector = async () => { - return await executeScript(` - return new Promise((resolve) => { - // 创建高亮元素 - const highlight = document.createElement('div'); - highlight.style.cssText = 'position: fixed; pointer-events: none; z-index: 10000; background: rgba(130, 180, 230, 0.4); border: 2px solid rgba(130, 180, 230, 0.8); transition: all 0.2s;'; - document.body.appendChild(highlight); - - // 获取最优选择器 - function getOptimalSelector(element) { - if (!element || element === document.body) return null; - - // 尝试使用id - if (element.id) { - return '#' + element.id; - } - - // 尝试使用类名组合 - if (element.className && typeof element.className === 'string') { - const classes = element.className.trim().split(/\\s+/); - if (classes.length) { - const selector = '.' + classes.join('.'); - if (document.querySelectorAll(selector).length === 1) { - return selector; - } - } - } - - // 获取元素在同级中的索引 - const parent = element.parentElement; - if (!parent) return null; - - const siblings = Array.from(parent.children); - const index = siblings.indexOf(element); - - // 递归获取父元素选择器 - const parentSelector = getOptimalSelector(parent); - if (!parentSelector) return null; - - return \`\${parentSelector} > \${element.tagName.toLowerCase()}:nth-child(\${index + 1})\`; - } - - // 处理鼠标移动 - function handleMouseMove(e) { - const target = e.target; - if (!target || target === highlight) return; - - const rect = target.getBoundingClientRect(); - highlight.style.left = rect.left + 'px'; - highlight.style.top = rect.top + 'px'; - highlight.style.width = rect.width + 'px'; - highlight.style.height = rect.height + 'px'; - } - - // 处理点击 - function handleClick(e) { - e.preventDefault(); - e.stopPropagation(); - - const target = e.target; - if (!target || target === highlight) return; - - const selector = getOptimalSelector(target); - - // 清理 - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('click', handleClick, true); - highlight.remove(); - - resolve(selector); - return false; - } - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('click', handleClick, true); - }); - `); -}; - module.exports = { launchBrowser, getUrl, @@ -476,5 +397,4 @@ module.exports = { getScrollPosition, getPageSize, waitForElement, - getSelector, }; diff --git a/plugin/lib/quickcomposer/browser/getSelector.js b/plugin/lib/quickcomposer/browser/getSelector.js new file mode 100644 index 0000000..849fb5b --- /dev/null +++ b/plugin/lib/quickcomposer/browser/getSelector.js @@ -0,0 +1,180 @@ +const { executeScript } = require("./browser"); +const fs = require("fs"); +const path = require("path"); + +const getOptimalSelector = () => { + return ` + // 获取最优选择器 + function getOptimalSelector_secondary(element) { + if (!element || element === document.body) return 'body'; + + // 尝试使用id + if (element.id) { + return '#' + element.id; + } + + // 构建当前元素的选择器 + let currentSelector = element.tagName.toLowerCase(); + if (element.className && typeof element.className === 'string') { + const classes = element.className.trim().split(/\\s+/); + if (classes.length) { + currentSelector += '.' + classes.join('.'); + } + } + + // 1. 尝试仅使用类名组合 + if (element.className && typeof element.className === 'string') { + const classes = element.className.trim().split(/\\s+/); + if (classes.length) { + const classSelector = '.' + classes.join('.'); + if (document.querySelectorAll(classSelector).length === 1) { + return classSelector; + } + } + } + + // 2. 尝试仅使用标签名和类名组合 + if (document.querySelectorAll(currentSelector).length === 1) { + return currentSelector; + } + + // 3. 如果需要使用 nth-child,先尝试简单组合 + const siblings = Array.from(element.parentElement?.children || []); + const index = siblings.indexOf(element); + if (index !== -1) { + const nthSelector = currentSelector + ':nth-child(' + (index + 1) + ')'; + if (document.querySelectorAll(nthSelector).length === 1) { + return nthSelector; + } + } + + // 4. 向上查找最近的有id的祖先元素 + let ancestor = element; + let foundSelectors = []; + + while (ancestor && ancestor !== document.body) { + if (ancestor.id) { + foundSelectors.push({ + selector: '#' + ancestor.id, + element: ancestor + }); + } + + // 收集所有可能有用的类名组合 + if (ancestor.className && typeof ancestor.className === 'string') { + const classes = ancestor.className.trim().split(/\\s+/); + if (classes.length) { + const classSelector = ancestor.tagName.toLowerCase() + '.' + classes.join('.'); + if (document.querySelectorAll(classSelector).length < 10) { // 只收集相对独特的选择器 + foundSelectors.push({ + selector: classSelector, + element: ancestor + }); + } + } + } + + ancestor = ancestor.parentElement; + } + + // 5. 尝试各种组合,找到最短的唯一选择器 + for (const {selector: anchorSelector} of foundSelectors) { + // 尝试直接组合 + const simpleSelector = anchorSelector + ' ' + currentSelector; + if (document.querySelectorAll(simpleSelector).length === 1) { + return simpleSelector; + } + + // 如果直接组合不唯一,尝试加上 nth-child + if (index !== -1) { + const nthSelector = anchorSelector + ' ' + currentSelector + ':nth-child(' + (index + 1) + ')'; + if (document.querySelectorAll(nthSelector).length === 1) { + return nthSelector; + } + } + } + + // 6. 如果还是找不到唯一选择器,使用两层有特征的选择器组合 + for (let i = 0; i < foundSelectors.length - 1; i++) { + for (let j = i + 1; j < foundSelectors.length; j++) { + const combinedSelector = foundSelectors[i].selector + ' ' + foundSelectors[j].selector + ' ' + currentSelector; + if (document.querySelectorAll(combinedSelector).length === 1) { + return combinedSelector; + } + } + } + + // 7. 最后的后备方案:使用完整的父子选择器 + const parent = element.parentElement; + if (!parent) return null; + + const parentSelector = getOptimalSelector(parent); + if (!parentSelector) return null; + + return parentSelector + ' ' + currentSelector + (index !== -1 ? ':nth-child(' + (index + 1) + ')' : ''); + } + `; +}; + +const getSelector = async () => { + return await executeScript(` + return new Promise((resolve) => { + // 创建高亮元素 + const highlight = document.createElement('div'); + highlight.style.cssText = 'position: fixed; pointer-events: none; z-index: 10000; background: rgba(130, 180, 230, 0.4); border: 2px solid rgba(130, 180, 230, 0.8); transition: all 0.2s;'; + document.body.appendChild(highlight); + + if (typeof OptimalSelect === 'undefined') { + ${fs.readFileSync(path.join(__dirname, "optimalSelect.js"), "utf-8")} + } + + function getOptimalSelector(element) { + return OptimalSelect.select(element) + } + + ${getOptimalSelector()} + + // 处理鼠标移动 + function handleMouseMove(e) { + const target = e.target; + if (!target || target === highlight) return; + + const rect = target.getBoundingClientRect(); + highlight.style.left = rect.left + 'px'; + highlight.style.top = rect.top + 'px'; + highlight.style.width = rect.width + 'px'; + highlight.style.height = rect.height + 'px'; + } + + // 处理点击 + function handleClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const target = e.target; + if (!target || target === highlight) return; + let selector = null; + try { + selector = getOptimalSelector(target); + } catch (e) { + selector = getOptimalSelector_secondary(target); + } + + // 清理 + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('click', handleClick, true); + highlight.remove(); + + resolve(selector); + return false; + } + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('click', handleClick, true); + }); + `); +}; + +module.exports = { + getSelector, +}; diff --git a/plugin/lib/quickcomposer/browser/index.js b/plugin/lib/quickcomposer/browser/index.js index bc979db..eb7a8dc 100644 --- a/plugin/lib/quickcomposer/browser/index.js +++ b/plugin/lib/quickcomposer/browser/index.js @@ -1,3 +1,7 @@ const browser = require("./browser"); +const getSelector = require("./getSelector"); -module.exports = browser; +module.exports = { + ...browser, + ...getSelector, +}; diff --git a/plugin/lib/quickcomposer/browser/optimalSelect.js b/plugin/lib/quickcomposer/browser/optimalSelect.js new file mode 100644 index 0000000..8aed702 --- /dev/null +++ b/plugin/lib/quickcomposer/browser/optimalSelect.js @@ -0,0 +1,1767 @@ +// https://cdn.jsdelivr.net/npm/optimal-select@4.0.1/dist/optimal-select.js +(function webpackUniversalModuleDefinition(root, factory) { + if (typeof exports === "object" && typeof module === "object") + module.exports = factory(); + else if (typeof define === "function" && define.amd) define([], factory); + else if (typeof exports === "object") exports["OptimalSelect"] = factory(); + else root["OptimalSelect"] = factory(); +})(this, function () { + return /******/ (function (modules) { + // webpackBootstrap + /******/ // The module cache + /******/ var installedModules = {}; + /******/ + /******/ // The require function + /******/ function __webpack_require__(moduleId) { + /******/ + /******/ // Check if module is in cache + /******/ if (installedModules[moduleId]) + /******/ return installedModules[moduleId].exports; + /******/ + /******/ // Create a new module (and put it into the cache) + /******/ var module = (installedModules[moduleId] = { + /******/ i: moduleId, + /******/ l: false, + /******/ exports: {}, + /******/ + }); + /******/ + /******/ // Execute the module function + /******/ modules[moduleId].call( + module.exports, + module, + module.exports, + __webpack_require__ + ); + /******/ + /******/ // Flag the module as loaded + /******/ module.l = true; + /******/ + /******/ // Return the exports of the module + /******/ return module.exports; + /******/ + } + /******/ + /******/ + /******/ // expose the modules object (__webpack_modules__) + /******/ __webpack_require__.m = modules; + /******/ + /******/ // expose the module cache + /******/ __webpack_require__.c = installedModules; + /******/ + /******/ // identity function for calling harmony imports with the correct context + /******/ __webpack_require__.i = function (value) { + return value; + }; + /******/ + /******/ // define getter function for harmony exports + /******/ __webpack_require__.d = function (exports, name, getter) { + /******/ if (!__webpack_require__.o(exports, name)) { + /******/ Object.defineProperty(exports, name, { + /******/ configurable: false, + /******/ enumerable: true, + /******/ get: getter, + /******/ + }); + /******/ + } + /******/ + }; + /******/ + /******/ // getDefaultExport function for compatibility with non-harmony modules + /******/ __webpack_require__.n = function (module) { + /******/ var getter = + module && module.__esModule + ? /******/ function getDefault() { + return module["default"]; + } + : /******/ function getModuleExports() { + return module; + }; + /******/ __webpack_require__.d(getter, "a", getter); + /******/ return getter; + /******/ + }; + /******/ + /******/ // Object.prototype.hasOwnProperty.call + /******/ __webpack_require__.o = function (object, property) { + return Object.prototype.hasOwnProperty.call(object, property); + }; + /******/ + /******/ // __webpack_public_path__ + /******/ __webpack_require__.p = ""; + /******/ + /******/ // Load entry module and return exports + /******/ return __webpack_require__((__webpack_require__.s = 6)); + /******/ + })( + /************************************************************************/ + /******/ [ + /* 0 */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.convertNodeList = convertNodeList; + exports.escapeValue = escapeValue; + /** + * # Utilities + * + * Convenience helpers. + */ + + /** + * Create an array with the DOM nodes of the list + * + * @param {NodeList} nodes - [description] + * @return {Array.} - [description] + */ + function convertNodeList(nodes) { + var length = nodes.length; + + var arr = new Array(length); + for (var i = 0; i < length; i++) { + arr[i] = nodes[i]; + } + return arr; + } + + /** + * Escape special characters and line breaks as a simplified version of 'CSS.escape()' + * + * Description of valid characters: https://mathiasbynens.be/notes/css-escapes + * + * @param {String?} value - [description] + * @return {String} - [description] + */ + function escapeValue(value) { + return ( + value && + value + .replace(/['"`\\/:\?&!#$%^()[\]{|}*+;,.<=>@~]/g, "\\$&") + .replace(/\n/g, "A") + ); + } + + /***/ + }, + /* 1 */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.getCommonAncestor = getCommonAncestor; + exports.getCommonProperties = getCommonProperties; + /** + * # Common + * + * Process collections for similarities. + */ + + /** + * Find the last common ancestor of elements + * + * @param {Array.} elements - [description] + * @return {HTMLElement} - [description] + */ + function getCommonAncestor(elements) { + var options = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}; + var _options$root = options.root, + root = _options$root === undefined ? document : _options$root; + + var ancestors = []; + + elements.forEach(function (element, index) { + var parents = []; + while (element !== root) { + element = element.parentNode; + parents.unshift(element); + } + ancestors[index] = parents; + }); + + ancestors.sort(function (curr, next) { + return curr.length - next.length; + }); + + var shallowAncestor = ancestors.shift(); + + var ancestor = null; + + var _loop = function _loop() { + var parent = shallowAncestor[i]; + var missing = ancestors.some(function (otherParents) { + return !otherParents.some(function (otherParent) { + return otherParent === parent; + }); + }); + + if (missing) { + // TODO: find similar sub-parents, not the top root, e.g. sharing a class selector + return "break"; + } + + ancestor = parent; + }; + + for (var i = 0, l = shallowAncestor.length; i < l; i++) { + var _ret = _loop(); + + if (_ret === "break") break; + } + + return ancestor; + } + + /** + * Get a set of common properties of elements + * + * @param {Array.} elements - [description] + * @return {Object} - [description] + */ + function getCommonProperties(elements) { + var commonProperties = { + classes: [], + attributes: {}, + tag: null, + }; + + elements.forEach(function (element) { + var commonClasses = commonProperties.classes, + commonAttributes = commonProperties.attributes, + commonTag = commonProperties.tag; + + // ~ classes + + if (commonClasses !== undefined) { + var classes = element.getAttribute("class"); + if (classes) { + classes = classes.trim().split(" "); + if (!commonClasses.length) { + commonProperties.classes = classes; + } else { + commonClasses = commonClasses.filter(function (entry) { + return classes.some(function (name) { + return name === entry; + }); + }); + if (commonClasses.length) { + commonProperties.classes = commonClasses; + } else { + delete commonProperties.classes; + } + } + } else { + // TODO: restructure removal as 2x set / 2x delete, instead of modify always replacing with new collection + delete commonProperties.classes; + } + } + + // ~ attributes + if (commonAttributes !== undefined) { + (function () { + var elementAttributes = element.attributes; + var attributes = Object.keys(elementAttributes).reduce( + function (attributes, key) { + var attribute = elementAttributes[key]; + var attributeName = attribute.name; + // NOTE: workaround detection for non-standard phantomjs NamedNodeMap behaviour + // (issue: https://github.com/ariya/phantomjs/issues/14634) + if (attribute && attributeName !== "class") { + attributes[attributeName] = attribute.value; + } + return attributes; + }, + {} + ); + + var attributesNames = Object.keys(attributes); + var commonAttributesNames = Object.keys(commonAttributes); + + if (attributesNames.length) { + if (!commonAttributesNames.length) { + commonProperties.attributes = attributes; + } else { + commonAttributes = commonAttributesNames.reduce(function ( + nextCommonAttributes, + name + ) { + var value = commonAttributes[name]; + if (value === attributes[name]) { + nextCommonAttributes[name] = value; + } + return nextCommonAttributes; + }, + {}); + if (Object.keys(commonAttributes).length) { + commonProperties.attributes = commonAttributes; + } else { + delete commonProperties.attributes; + } + } + } else { + delete commonProperties.attributes; + } + })(); + } + + // ~ tag + if (commonTag !== undefined) { + var tag = element.tagName.toLowerCase(); + if (!commonTag) { + commonProperties.tag = tag; + } else if (tag !== commonTag) { + delete commonProperties.tag; + } + } + }); + + return commonProperties; + } + + /***/ + }, + /* 2 */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.default = optimize; + + var _adapt = __webpack_require__(3); + + var _adapt2 = _interopRequireDefault(_adapt); + + var _utilities = __webpack_require__(0); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + /** + * Apply different optimization techniques + * + * @param {string} selector - [description] + * @param {HTMLElement|Array.} element - [description] + * @param {Object} options - [description] + * @return {string} - [description] + */ + /** + * # Optimize + * + * 1.) Improve efficiency through shorter selectors by removing redundancy + * 2.) Improve robustness through selector transformation + */ + + function optimize(selector, elements) { + var options = + arguments.length > 2 && arguments[2] !== undefined + ? arguments[2] + : {}; + + // convert single entry and NodeList + if (!Array.isArray(elements)) { + elements = !elements.length + ? [elements] + : (0, _utilities.convertNodeList)(elements); + } + + if ( + !elements.length || + elements.some(function (element) { + return element.nodeType !== 1; + }) + ) { + throw new Error( + 'Invalid input - to compare HTMLElements its necessary to provide a reference of the selected node(s)! (missing "elements")' + ); + } + + var globalModified = (0, _adapt2.default)(elements[0], options); + + // chunk parts outside of quotes (http://stackoverflow.com/a/25663729) + var path = selector + .replace(/> /g, ">") + .split(/\s+(?=(?:(?:[^"]*"){2})*[^"]*$)/); + + if (path.length < 2) { + return optimizePart("", selector, "", elements); + } + + var shortened = [path.pop()]; + while (path.length > 1) { + var current = path.pop(); + var prePart = path.join(" "); + var postPart = shortened.join(" "); + + var pattern = prePart + " " + postPart; + var matches = document.querySelectorAll(pattern); + if (matches.length !== elements.length) { + shortened.unshift( + optimizePart(prePart, current, postPart, elements) + ); + } + } + shortened.unshift(path[0]); + path = shortened; + + // optimize start + end + path[0] = optimizePart( + "", + path[0], + path.slice(1).join(" "), + elements + ); + path[path.length - 1] = optimizePart( + path.slice(0, -1).join(" "), + path[path.length - 1], + "", + elements + ); + + if (globalModified) { + delete true; + } + + return path.join(" ").replace(/>/g, "> ").trim(); + } + + /** + * Improve a chunk of the selector + * + * @param {string} prePart - [description] + * @param {string} current - [description] + * @param {string} postPart - [description] + * @param {Array.} elements - [description] + * @return {string} - [description] + */ + function optimizePart(prePart, current, postPart, elements) { + if (prePart.length) prePart = prePart + " "; + if (postPart.length) postPart = " " + postPart; + + // robustness: attribute without value (generalization) + if (/\[*\]/.test(current)) { + var key = current.replace(/=.*$/, "]"); + var pattern = "" + prePart + key + postPart; + var matches = document.querySelectorAll(pattern); + if (compareResults(matches, elements)) { + current = key; + } else { + // robustness: replace specific key-value with base tag (heuristic) + var references = document.querySelectorAll("" + prePart + key); + + var _loop = function _loop() { + var reference = references[i]; + if ( + elements.some(function (element) { + return reference.contains(element); + }) + ) { + var description = reference.tagName.toLowerCase(); + pattern = "" + prePart + description + postPart; + matches = document.querySelectorAll(pattern); + + if (compareResults(matches, elements)) { + current = description; + } + return "break"; + } + }; + + for (var i = 0, l = references.length; i < l; i++) { + var pattern; + var matches; + + var _ret = _loop(); + + if (_ret === "break") break; + } + } + } + + // robustness: descendant instead child (heuristic) + if (/>/.test(current)) { + var descendant = current.replace(/>/, ""); + var pattern = "" + prePart + descendant + postPart; + var matches = document.querySelectorAll(pattern); + if (compareResults(matches, elements)) { + current = descendant; + } + } + + // robustness: 'nth-of-type' instead 'nth-child' (heuristic) + if (/:nth-child/.test(current)) { + // TODO: consider complete coverage of 'nth-of-type' replacement + var type = current.replace(/nth-child/g, "nth-of-type"); + var pattern = "" + prePart + type + postPart; + var matches = document.querySelectorAll(pattern); + if (compareResults(matches, elements)) { + current = type; + } + } + + // efficiency: combinations of classname (partial permutations) + if (/\.\S+\.\S+/.test(current)) { + var names = current + .trim() + .split(".") + .slice(1) + .map(function (name) { + return "." + name; + }) + .sort(function (curr, next) { + return curr.length - next.length; + }); + while (names.length) { + var partial = current.replace(names.shift(), "").trim(); + var pattern = ("" + prePart + partial + postPart).trim(); + if ( + !pattern.length || + pattern.charAt(0) === ">" || + pattern.charAt(pattern.length - 1) === ">" + ) { + break; + } + var matches = document.querySelectorAll(pattern); + if (compareResults(matches, elements)) { + current = partial; + } + } + + // robustness: degrade complex classname (heuristic) + names = current && current.match(/\./g); + if (names && names.length > 2) { + var _references = document.querySelectorAll( + "" + prePart + current + ); + + var _loop2 = function _loop2() { + var reference = _references[i]; + if ( + elements.some(function (element) { + return reference.contains(element); + }) + ) { + // TODO: + // - check using attributes + regard excludes + var description = reference.tagName.toLowerCase(); + pattern = "" + prePart + description + postPart; + matches = document.querySelectorAll(pattern); + + if (compareResults(matches, elements)) { + current = description; + } + return "break"; + } + }; + + for (var i = 0, l = _references.length; i < l; i++) { + var pattern; + var matches; + + var _ret2 = _loop2(); + + if (_ret2 === "break") break; + } + } + } + + return current; + } + + /** + * Evaluate matches with expected elements + * + * @param {Array.} matches - [description] + * @param {Array.} elements - [description] + * @return {Boolean} - [description] + */ + function compareResults(matches, elements) { + var length = matches.length; + + return ( + length === elements.length && + elements.every(function (element) { + for (var i = 0; i < length; i++) { + if (matches[i] === element) { + return true; + } + } + return false; + }) + ); + } + module.exports = exports["default"]; + + /***/ + }, + /* 3 */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _typeof = + typeof Symbol === "function" && typeof Symbol.iterator === "symbol" + ? function (obj) { + return typeof obj; + } + : function (obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + + var _slicedToArray = (function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + try { + for ( + var _i = arr[Symbol.iterator](), _s; + !(_n = (_s = _i.next()).done); + _n = true + ) { + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + return _arr; + } + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError( + "Invalid attempt to destructure non-iterable instance" + ); + } + }; + })(); + + exports.default = adapt; + /** + * # Adapt + * + * Check and extend the environment for universal usage. + */ + + /** + * Modify the context based on the environment + * + * @param {HTMLELement} element - [description] + * @param {Object} options - [description] + * @return {boolean} - [description] + */ + function adapt(element, options) { + // detect environment setup + if (true) { + return false; + } else { + global.document = + options.context || + (function () { + var root = element; + while (root.parent) { + root = root.parent; + } + return root; + })(); + } + + // https://github.com/fb55/domhandler/blob/master/index.js#L75 + var ElementPrototype = Object.getPrototypeOf(true); + + // alternative descriptor to access elements with filtering invalid elements (e.g. textnodes) + if (!Object.getOwnPropertyDescriptor(ElementPrototype, "childTags")) { + Object.defineProperty(ElementPrototype, "childTags", { + enumerable: true, + get: function get() { + return this.children.filter(function (node) { + // https://github.com/fb55/domelementtype/blob/master/index.js#L12 + return ( + node.type === "tag" || + node.type === "script" || + node.type === "style" + ); + }); + }, + }); + } + + if ( + !Object.getOwnPropertyDescriptor(ElementPrototype, "attributes") + ) { + // https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes + // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap + Object.defineProperty(ElementPrototype, "attributes", { + enumerable: true, + get: function get() { + var attribs = this.attribs; + + var attributesNames = Object.keys(attribs); + var NamedNodeMap = attributesNames.reduce(function ( + attributes, + attributeName, + index + ) { + attributes[index] = { + name: attributeName, + value: attribs[attributeName], + }; + return attributes; + }, + {}); + Object.defineProperty(NamedNodeMap, "length", { + enumerable: false, + configurable: false, + value: attributesNames.length, + }); + return NamedNodeMap; + }, + }); + } + + if (!ElementPrototype.getAttribute) { + // https://docs.webplatform.org/wiki/dom/Element/getAttribute + // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute + ElementPrototype.getAttribute = function (name) { + return this.attribs[name] || null; + }; + } + + if (!ElementPrototype.getElementsByTagName) { + // https://docs.webplatform.org/wiki/dom/Document/getElementsByTagName + // https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName + ElementPrototype.getElementsByTagName = function (tagName) { + var HTMLCollection = []; + traverseDescendants(this.childTags, function (descendant) { + if (descendant.name === tagName || tagName === "*") { + HTMLCollection.push(descendant); + } + }); + return HTMLCollection; + }; + } + + if (!ElementPrototype.getElementsByClassName) { + // https://docs.webplatform.org/wiki/dom/Document/getElementsByClassName + // https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName + ElementPrototype.getElementsByClassName = function (className) { + var names = className.trim().replace(/\s+/g, " ").split(" "); + var HTMLCollection = []; + traverseDescendants([this], function (descendant) { + var descendantClassName = descendant.attribs.class; + if ( + descendantClassName && + names.every(function (name) { + return descendantClassName.indexOf(name) > -1; + }) + ) { + HTMLCollection.push(descendant); + } + }); + return HTMLCollection; + }; + } + + if (!ElementPrototype.querySelectorAll) { + // https://docs.webplatform.org/wiki/css/selectors_api/querySelectorAll + // https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll + ElementPrototype.querySelectorAll = function (selectors) { + var _this = this; + + selectors = selectors.replace(/(>)(\S)/g, "$1 $2").trim(); // add space for '>' selector + + // using right to left execution => https://github.com/fb55/css-select#how-does-it-work + var instructions = getInstructions(selectors); + var discover = instructions.shift(); + + var total = instructions.length; + return discover(this).filter(function (node) { + var step = 0; + while (step < total) { + node = instructions[step](node, _this); + if (!node) { + // hierarchy doesn't match + return false; + } + step += 1; + } + return true; + }); + }; + } + + if (!ElementPrototype.contains) { + // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains + ElementPrototype.contains = function (element) { + var inclusive = false; + traverseDescendants([this], function (descendant, done) { + if (descendant === element) { + inclusive = true; + done(); + } + }); + return inclusive; + }; + } + + return true; + } + + /** + * Retrieve transformation steps + * + * @param {Array.} selectors - [description] + * @return {Array.} - [description] + */ + function getInstructions(selectors) { + return selectors + .split(" ") + .reverse() + .map(function (selector, step) { + var discover = step === 0; + + var _selector$split = selector.split(":"), + _selector$split2 = _slicedToArray(_selector$split, 2), + type = _selector$split2[0], + pseudo = _selector$split2[1]; + + var validate = null; + var instruction = null; + + (function () { + switch (true) { + // child: '>' + case />/.test(type): + instruction = function checkParent(node) { + return function (validate) { + return validate(node.parent) && node.parent; + }; + }; + break; + + // class: '.' + case /^\./.test(type): + var names = type.substr(1).split("."); + validate = function validate(node) { + var nodeClassName = node.attribs.class; + return ( + nodeClassName && + names.every(function (name) { + return nodeClassName.indexOf(name) > -1; + }) + ); + }; + instruction = function checkClass(node, root) { + if (discover) { + return node.getElementsByClassName(names.join(" ")); + } + return typeof node === "function" + ? node(validate) + : getAncestor(node, root, validate); + }; + break; + + // attribute: '[key="value"]' + case /^\[/.test(type): + var _type$replace$split = type + .replace(/\[|\]|"/g, "") + .split("="), + _type$replace$split2 = _slicedToArray( + _type$replace$split, + 2 + ), + attributeKey = _type$replace$split2[0], + attributeValue = _type$replace$split2[1]; + + validate = function validate(node) { + var hasAttribute = + Object.keys(node.attribs).indexOf(attributeKey) > -1; + if (hasAttribute) { + // regard optional attributeValue + if ( + !attributeValue || + node.attribs[attributeKey] === attributeValue + ) { + return true; + } + } + return false; + }; + instruction = function checkAttribute(node, root) { + if (discover) { + var _ret2 = (function () { + var NodeList = []; + traverseDescendants([node], function (descendant) { + if (validate(descendant)) { + NodeList.push(descendant); + } + }); + return { + v: NodeList, + }; + })(); + + if ( + (typeof _ret2 === "undefined" + ? "undefined" + : _typeof(_ret2)) === "object" + ) + return _ret2.v; + } + return typeof node === "function" + ? node(validate) + : getAncestor(node, root, validate); + }; + break; + + // id: '#' + case /^#/.test(type): + var id = type.substr(1); + validate = function validate(node) { + return node.attribs.id === id; + }; + instruction = function checkId(node, root) { + if (discover) { + var _ret3 = (function () { + var NodeList = []; + traverseDescendants( + [node], + function (descendant, done) { + if (validate(descendant)) { + NodeList.push(descendant); + done(); + } + } + ); + return { + v: NodeList, + }; + })(); + + if ( + (typeof _ret3 === "undefined" + ? "undefined" + : _typeof(_ret3)) === "object" + ) + return _ret3.v; + } + return typeof node === "function" + ? node(validate) + : getAncestor(node, root, validate); + }; + break; + + // universal: '*' + case /\*/.test(type): + validate = function validate(node) { + return true; + }; + instruction = function checkUniversal(node, root) { + if (discover) { + var _ret4 = (function () { + var NodeList = []; + traverseDescendants([node], function (descendant) { + return NodeList.push(descendant); + }); + return { + v: NodeList, + }; + })(); + + if ( + (typeof _ret4 === "undefined" + ? "undefined" + : _typeof(_ret4)) === "object" + ) + return _ret4.v; + } + return typeof node === "function" + ? node(validate) + : getAncestor(node, root, validate); + }; + break; + + // tag: '...' + default: + validate = function validate(node) { + return node.name === type; + }; + instruction = function checkTag(node, root) { + if (discover) { + var _ret5 = (function () { + var NodeList = []; + traverseDescendants([node], function (descendant) { + if (validate(descendant)) { + NodeList.push(descendant); + } + }); + return { + v: NodeList, + }; + })(); + + if ( + (typeof _ret5 === "undefined" + ? "undefined" + : _typeof(_ret5)) === "object" + ) + return _ret5.v; + } + return typeof node === "function" + ? node(validate) + : getAncestor(node, root, validate); + }; + } + })(); + + if (!pseudo) { + return instruction; + } + + var rule = pseudo.match(/-(child|type)\((\d+)\)$/); + var kind = rule[1]; + var index = parseInt(rule[2], 10) - 1; + + var validatePseudo = function validatePseudo(node) { + if (node) { + var compareSet = node.parent.childTags; + if (kind === "type") { + compareSet = compareSet.filter(validate); + } + var nodeIndex = compareSet.findIndex(function (child) { + return child === node; + }); + if (nodeIndex === index) { + return true; + } + } + return false; + }; + + return function enhanceInstruction(node) { + var match = instruction(node); + if (discover) { + return match.reduce(function (NodeList, matchedNode) { + if (validatePseudo(matchedNode)) { + NodeList.push(matchedNode); + } + return NodeList; + }, []); + } + return validatePseudo(match) && match; + }; + }); + } + + /** + * Walking recursive to invoke callbacks + * + * @param {Array.} nodes - [description] + * @param {Function} handler - [description] + */ + function traverseDescendants(nodes, handler) { + nodes.forEach(function (node) { + var progress = true; + handler(node, function () { + return (progress = false); + }); + if (node.childTags && progress) { + traverseDescendants(node.childTags, handler); + } + }); + } + + /** + * Bubble up from bottom to top + * + * @param {HTMLELement} node - [description] + * @param {HTMLELement} root - [description] + * @param {Function} validate - [description] + * @return {HTMLELement} - [description] + */ + function getAncestor(node, root, validate) { + while (node.parent) { + node = node.parent; + if (validate(node)) { + return node; + } + if (node === root) { + break; + } + } + return null; + } + module.exports = exports["default"]; + + /***/ + }, + /* 4 */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + + var _typeof = + typeof Symbol === "function" && typeof Symbol.iterator === "symbol" + ? function (obj) { + return typeof obj; + } + : function (obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + /** + * # Select + * + * Construct a unique CSS query selector to access the selected DOM element(s). + * For longevity it applies different matching and optimization strategies. + */ + + exports.getSingleSelector = getSingleSelector; + exports.getMultiSelector = getMultiSelector; + exports.default = getQuerySelector; + + var _adapt = __webpack_require__(3); + + var _adapt2 = _interopRequireDefault(_adapt); + + var _match = __webpack_require__(5); + + var _match2 = _interopRequireDefault(_match); + + var _optimize = __webpack_require__(2); + + var _optimize2 = _interopRequireDefault(_optimize); + + var _utilities = __webpack_require__(0); + + var _common = __webpack_require__(1); + + function _interopRequireDefault(obj) { + return obj && obj.__esModule ? obj : { default: obj }; + } + + /** + * Get a selector for the provided element + * + * @param {HTMLElement} element - [description] + * @param {Object} options - [description] + * @return {string} - [description] + */ + function getSingleSelector(element) { + var options = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}; + + if (element.nodeType === 3) { + element = element.parentNode; + } + + if (element.nodeType !== 1) { + throw new Error( + 'Invalid input - only HTMLElements or representations of them are supported! (not "' + + (typeof element === "undefined" + ? "undefined" + : _typeof(element)) + + '")' + ); + } + + var globalModified = (0, _adapt2.default)(element, options); + + var selector = (0, _match2.default)(element, options); + var optimized = (0, _optimize2.default)(selector, element, options); + + // debug + // console.log(` + // selector: ${selector} + // optimized: ${optimized} + // `) + + if (globalModified) { + delete true; + } + + return optimized; + } + + /** + * Get a selector to match multiple descendants from an ancestor + * + * @param {Array.|NodeList} elements - [description] + * @param {Object} options - [description] + * @return {string} - [description] + */ + function getMultiSelector(elements) { + var options = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}; + + if (!Array.isArray(elements)) { + elements = (0, _utilities.convertNodeList)(elements); + } + + if ( + elements.some(function (element) { + return element.nodeType !== 1; + }) + ) { + throw new Error( + "Invalid input - only an Array of HTMLElements or representations of them is supported!" + ); + } + + var globalModified = (0, _adapt2.default)(elements[0], options); + + var ancestor = (0, _common.getCommonAncestor)(elements, options); + var ancestorSelector = getSingleSelector(ancestor, options); + + // TODO: consider usage of multiple selectors + parent-child relation + check for part redundancy + var commonSelectors = getCommonSelectors(elements); + var descendantSelector = commonSelectors[0]; + + var selector = (0, _optimize2.default)( + ancestorSelector + " " + descendantSelector, + elements, + options + ); + var selectorMatches = (0, _utilities.convertNodeList)( + document.querySelectorAll(selector) + ); + + if ( + !elements.every(function (element) { + return selectorMatches.some(function (entry) { + return entry === element; + }); + }) + ) { + // TODO: cluster matches to split into similar groups for sub selections + return console.warn( + "\n The selected elements can't be efficiently mapped.\n Its probably best to use multiple single selectors instead!\n ", + elements + ); + } + + if (globalModified) { + delete true; + } + + return selector; + } + + /** + * Get selectors to describe a set of elements + * + * @param {Array.} elements - [description] + * @return {string} - [description] + */ + function getCommonSelectors(elements) { + var _getCommonProperties = (0, _common.getCommonProperties)(elements), + classes = _getCommonProperties.classes, + attributes = _getCommonProperties.attributes, + tag = _getCommonProperties.tag; + + var selectorPath = []; + + if (tag) { + selectorPath.push(tag); + } + + if (classes) { + var classSelector = classes + .map(function (name) { + return "." + name; + }) + .join(""); + selectorPath.push(classSelector); + } + + if (attributes) { + var attributeSelector = Object.keys(attributes) + .reduce(function (parts, name) { + parts.push("[" + name + '="' + attributes[name] + '"]'); + return parts; + }, []) + .join(""); + selectorPath.push(attributeSelector); + } + + if (selectorPath.length) { + // TODO: check for parent-child relation + } + + return [selectorPath.join("")]; + } + + /** + * Choose action depending on the input (multiple/single) + * + * NOTE: extended detection is used for special cases like the