From a5066f20f78ff91a3de813bc33c161dbe5945af1 Mon Sep 17 00:00:00 2001 From: surunzi Date: Sun, 1 Jan 2017 13:42:26 +0800 Subject: [PATCH] Add: Search snippet --- doc/UTIL_API.md | 118 ++++++++++ src/Elements/Elements.es6 | 54 ++--- src/Snippets/Snippets.scss | 8 + src/Snippets/defSnippets.es6 | 89 ++++++-- src/lib/util.js | 404 ++++++++++++++++++++++++++++++++--- 5 files changed, 593 insertions(+), 80 deletions(-) diff --git a/doc/UTIL_API.md b/doc/UTIL_API.md index 77b9b9c..b5a4c4a 100644 --- a/doc/UTIL_API.md +++ b/doc/UTIL_API.md @@ -407,6 +407,79 @@ $test.find('.test').each(function (idx, element) }); ``` +## Url + +Simple url manipulator. + +### constructor + +|Name |Type |Desc | +|---------------------|------|----------| +|[url=window.location]|string|Url string| + +### setQuery + +Set query value. + +|Name |Type |Desc | +|------|------|-----------| +|name |string|Query name | +|value |string|Query value| +|return|Url |this | + +|Name |Type |Desc | +|------|------|------------| +|names |object|query object| +|return|Url |this | + +### rmQuery + +Remove query value. + +|Name |Type |Desc | +|------|------------|----------| +|name |string array|Query name| +|return|Url |this | + +### parse + +[static] Parse url into an object. + +|Name |Type |Desc | +|------|------|----------| +|url |string|Url string| +|return|object|Url object| + +### stringify + +[static] Stringify url object into a string. + +|Name |Type |Desc | +|------|------|----------| +|url |object|Url object| +|return|string|Url string| + +An url object contains the following properties: + +|Name |Desc | +|--------|--------------------------------------------------------------------------------------| +|protocol|The protocol scheme of the URL (e.g. http:) | +|slashes |A boolean which indicates whether the protocol is followed by two forward slashes (//)| +|auth |Authentication information portion (e.g. username:password) | +|hostname|Host name without port number | +|port |Optional port number | +|pathname|URL path | +|query |Parsed object containing query string | +|hash |The "fragment" portion of the URL including the pound-sign (#) | + +```javascript +var url = new Url('http://example.com:8080?eruda=true'); +console.log(url.port); // -> '8080' +url.query.foo = 'bar'; +url.rmQuery('eruda'); +utl.toString(); // -> 'http://example.com:8080/?foo=bar' +``` + ## allKeys Retrieve all the names of object's own and inherited properties. @@ -1174,6 +1247,34 @@ sub(20); // -> 15 No documentation. +## query + +Parse and stringify url query strings. + +### parse + +Parse a query string into an object. + +|Name |Type |Desc | +|------|------|------------| +|str |string|Query string| +|return|object|Query object| + +### stringify + +Stringify an object into a query string. + +|Name |Type |Desc | +|------|------|------------| +|obj |object|Query object| +|return|string|Query string| + +```javascript +query.parse('foo=bar&eruda=true'); // -> {foo: 'bar', eruda: 'true'} +query.stringify({foo: 'bar', eruda: 'true'}); // -> 'foo=bar&eruda=true' +query.parse('name=eruda&name=eustia'); // -> {name: ['eruda', 'eustia']} +``` + ## repeat Repeat string n-times. @@ -1225,6 +1326,23 @@ rtrim('_abc_', ['c', '_']); // -> '_ab' Create callback based on input value. +## safeGet + +Get object property, don't throw undefined error. + +|Name |Type |Desc | +|------|------------|-------------------------| +|obj |object |Object to query | +|path |array string|Path of property to get | +|return|* |Target value or undefined| + +```javascript +var obj = {a: {aa: {aaa: 1}}}; +safeGet(obj, 'a.aa.aaa'); // -> 1 +safeGet(obj, ['a', 'aa']); // -> {aaa: 1} +safeGet(obj, 'a.b'); // -> undefined +``` + ## safeStorage No documentation. diff --git a/src/Elements/Elements.es6 b/src/Elements/Elements.es6 index d57f706..4d4fc03 100644 --- a/src/Elements/Elements.es6 +++ b/src/Elements/Elements.es6 @@ -45,9 +45,9 @@ export default class Elements extends Tool } overrideEventTarget() { - var winEventProto = getWinEventProto(); + let winEventProto = getWinEventProto(); - var origAddEvent = this._origAddEvent = winEventProto.addEventListener, + let origAddEvent = this._origAddEvent = winEventProto.addEventListener, origRmEvent = this._origRmEvent = winEventProto.removeEventListener; winEventProto.addEventListener = function (type, listener, useCapture) @@ -64,7 +64,7 @@ export default class Elements extends Tool } restoreEventTarget() { - var winEventProto = getWinEventProto(); + let winEventProto = getWinEventProto(); if (this._origAddEvent) winEventProto.addEventListener = this._origAddEvent; if (this._origRmEvent) winEventProto.removeEventListener = this._origRmEvent; @@ -149,7 +149,7 @@ export default class Elements extends Tool !isElExist(el) ? self._render() : self._setElAndRender(el); }).on('click', '.toggle-all-computed-style', () => this._toggleAllComputedStyle()); - var $bottomBar = this._$el.find('.eruda-bottom-bar'); + let $bottomBar = this._$el.find('.eruda-bottom-bar'); $bottomBar.on('click', '.eruda-refresh', () => this._render()) .on('click', '.eruda-highlight', () => this._toggleHighlight()) @@ -175,7 +175,7 @@ export default class Elements extends Tool } _toggleSelect() { - var select = this._select; + let select = this._select; this._$el.find('.eruda-select').toggleClass('eruda-active'); if (!this._selectElement && !this._highlightElement) this._toggleHighlight(); @@ -198,9 +198,9 @@ export default class Elements extends Tool this._highlight.setEl(el); this._rmDefComputedStyle = true; - var parentQueue = []; + let parentQueue = []; - var parent = el.parentNode; + let parent = el.parentNode; while (parent) { parentQueue.push(parent); @@ -215,29 +215,29 @@ export default class Elements extends Tool } _getData() { - var ret = {}; + let ret = {}; - var el = this._curEl, + let el = this._curEl, cssStore = this._curCssStore; - var {className, id, attributes, tagName} = el; + let {className, id, attributes, tagName} = el; ret.parents = getParents(el); ret.children = formatChildNodes(el.childNodes); ret.attributes = formatAttr(attributes); ret.name = formatElName({tagName, id, className, attributes}); - var events = el.erudaEvents; + let events = el.erudaEvents; if (events && util.keys(events).length !== 0) ret.listeners = events; if (needNoStyle(tagName)) return ret; - var computedStyle = cssStore.getComputedStyle(); + let computedStyle = cssStore.getComputedStyle(); if (this._rmDefComputedStyle) computedStyle = rmDefComputedStyle(computedStyle); processStyleRules(computedStyle); ret.computedStyle = computedStyle; - var styles = cssStore.getMatchedCSSRules(); + let styles = cssStore.getMatchedCSSRules(); styles.unshift(getInlineStyle(el.style)); styles.forEach(style => processStyleRules(style.style)); ret.styles = styles; @@ -259,7 +259,7 @@ export default class Elements extends Tool } _initConfig() { - var cfg = this.config = config.create('eruda-elements'); + let cfg = this.config = config.create('eruda-elements'); cfg.set(util.defaults(cfg.get(), {overrideEventTarget: true})); @@ -273,7 +273,7 @@ export default class Elements extends Tool } }); - var settings = this._parent.get('settings'); + let settings = this._parent.get('settings'); settings.text('Elements') .switch(cfg, 'overrideEventTarget', 'Catch Event Listeners') .separator(); @@ -298,9 +298,9 @@ const isElExist = val => util.isEl(val) && val.parentNode; function formatElName(data, {noAttr = false} = {}) { - var {id, className, attributes} = data; + let {id, className, attributes} = data; - var ret = `${data.tagName.toLowerCase()}`; + let ret = `${data.tagName.toLowerCase()}`; if (id !== '') ret += `#${id}`; @@ -340,7 +340,7 @@ var formatAttr = attributes => util.map(attributes, attr => function formatChildNodes(nodes) { - var ret = []; + let ret = []; for (let i = 0, len = nodes.length; i < len; i++) { @@ -377,7 +377,7 @@ function formatChildNodes(nodes) function getParents(el) { - var ret = [], + let ret = [], i = 0, parent = el.parentNode; @@ -396,14 +396,14 @@ function getParents(el) function getInlineStyle(style) { - var ret = { + let ret = { selectorText: 'element.style', style: {} }; for (let i = 0, len = style.length; i < len; i++) { - var s = style[i]; + let s = style[i]; ret.style[s] = style[s]; } @@ -415,7 +415,7 @@ var defComputedStyle = require('./defComputedStyle.json'); function rmDefComputedStyle(computedStyle) { - var ret = {}; + let ret = {}; util.each(computedStyle, (val, key) => { @@ -435,7 +435,7 @@ function addEvent(el, type, listener, useCapture = false) { if (!util.isFn(listener) || !util.isBool(useCapture)) return; - var events = el.erudaEvents = el.erudaEvents || {}; + let events = el.erudaEvents = el.erudaEvents || {}; events[type] = events[type] || []; events[type].push({ @@ -449,11 +449,11 @@ function rmEvent(el, type, listener, useCapture = false) { if (!util.isFn(listener) || !util.isBool(useCapture)) return; - var events = el.erudaEvents; + let events = el.erudaEvents; if (!(events && events[type])) return; - var listeners = events[type]; + let listeners = events[type]; for (let i = 0, len = listeners.length; i < len; i++) { @@ -468,6 +468,6 @@ function rmEvent(el, type, listener, useCapture = false) if (util.keys(events).length === 0) delete el.erudaEvents; } -var getWinEventProto = () => (window.EventTarget && window.EventTarget.prototype) || window.Node.prototype; +var getWinEventProto = () => util.safeGet(window, 'EventTarget.prototype') || window.Node.prototype; -let wrapLink = link => `${link}`; +var wrapLink = link => `${link}`; diff --git a/src/Snippets/Snippets.scss b/src/Snippets/Snippets.scss index 4c30fcd..a65f5b2 100644 --- a/src/Snippets/Snippets.scss +++ b/src/Snippets/Snippets.scss @@ -32,3 +32,11 @@ } } } +.search-highlight-block { + display: inline; + .keyword { + background: $yellow; + color: #fff; + } +} + diff --git a/src/Snippets/defSnippets.es6 b/src/Snippets/defSnippets.es6 index 952bf2b..2394807 100644 --- a/src/Snippets/defSnippets.es6 +++ b/src/Snippets/defSnippets.es6 @@ -1,20 +1,9 @@ import util from '../lib/util' -var borderCss = '', - selector = 'html', - colors = ['f5f5f5', 'dabb3a', 'abc1c7', '472936', 'c84941', '296dd1', '67adb4', '1ea061']; - -util.each(colors, function (color, idx) -{ - selector += (idx === 0) ? '>*:not([class^="eruda-"])' : '>*'; - - borderCss += selector + `{border: 2px solid #${color} !important}`; -}); - export default [ { name: 'Border All', - fn: function () + fn() { util.evalCss(borderCss); }, @@ -22,22 +11,80 @@ export default [ }, { name: 'Refresh Page', - fn: function () + fn() { - var url = window.location.href; + let url = new util.Url(); + url.setQuery('timestamp', util.now()); - window.location.replace(query(url, 'timestamp', util.now())); + window.location.replace(url.toString()); }, desc: 'Add timestamp to url and refresh' + }, + { + name: 'Search Text', + fn() + { + var keyword = prompt('Enter the text'); + + search(keyword); + }, + desc: 'Highlight given text on page' } ]; -function query(uri, key, val) +var borderCss = '', + selector = 'html', + colors = ['f5f5f5', 'dabb3a', 'abc1c7', '472936', 'c84941', '296dd1', '67adb4', '1ea061']; + +util.each(colors, (color, idx) => { - var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i'), - separator = uri.indexOf('?') !== -1 ? '&' : '?'; + selector += (idx === 0) ? '>*:not([class^="eruda-"])' : '>*'; - if (uri.match(re)) return uri.replace(re, '$1' + key + '=' + val + '$2'); + borderCss += selector + `{border: 2px solid #${color} !important}`; +}); - return uri + separator + key + '=' + val; -} \ No newline at end of file +function search(text) +{ + let root = document.documentElement, + regText = new RegExp(text, 'ig'); + + traverse(root, node => + { + let $node = util.$(node); + + if (!$node.hasClass('eruda-search-highlight-block')) return; + + return document.createTextNode($node.text()); + }); + + traverse(root, node => + { + if (node.nodeType !== 3) return; + + let val = node.nodeValue; + val = val.replace(regText, match => `${match}`); + if (val === node.nodeValue) return; + + let $ret = util.$(document.createElement('div')); + + $ret.html(val); + $ret.addClass('eruda-search-highlight-block'); + + return $ret.get(0); + }); +} + +function traverse(root, processor) +{ + let childNodes = root.childNodes; + + if (util.isErudaEl(root)) return; + + for (let i = 0, len = childNodes.length; i < len; i++) + { + let newNode = traverse(childNodes[i], processor); + if (newNode) root.replaceChild(newNode, childNodes[i]); + } + + return processor(root); +} diff --git a/src/lib/util.js b/src/lib/util.js index 3b94750..ac50405 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -1127,6 +1127,38 @@ module.exports = (function () return exports; })(); + /* ------------------------------ isEmpty ------------------------------ */ + + var isEmpty = _.isEmpty = (function () + { + /* Check if value is an empty object or array. + * + * |Name |Type |Desc | + * |------|-------|----------------------| + * |val |* |Value to check | + * |return|boolean|True if value is empty| + * + * ```javascript + * isEmpty([]); // -> true + * isEmpty({}); // -> true + * ``` + */ + + function exports(val) + { + if (val == null) return true; + + if (isArrLike(val) && (isArr(val) || isStr(val) || isArgs(val))) + { + return val.length === 0; + } + + return keys(val).length === 0; + } + + return exports; + })(); + /* ------------------------------ isBool ------------------------------ */ var isBool = _.isBool = (function () @@ -1216,38 +1248,6 @@ module.exports = (function () return exports; })(); - /* ------------------------------ isEmpty ------------------------------ */ - - var isEmpty = _.isEmpty = (function () - { - /* Check if value is an empty object or array. - * - * |Name |Type |Desc | - * |------|-------|----------------------| - * |val |* |Value to check | - * |return|boolean|True if value is empty| - * - * ```javascript - * isEmpty([]); // -> true - * isEmpty({}); // -> true - * ``` - */ - - function exports(val) - { - if (val == null) return true; - - if (isArrLike(val) && (isArr(val) || isStr(val) || isArgs(val))) - { - return val.length === 0; - } - - return keys(val).length === 0; - } - - return exports; - })(); - /* ------------------------------ isErr ------------------------------ */ var isErr = _.isErr = (function () @@ -3494,6 +3494,346 @@ module.exports = (function () return exports; })(); + /* ------------------------------ query ------------------------------ */ + + var query = _.query = (function (exports) + { + /* Parse and stringify url query strings. + * + * ### parse + * + * Parse a query string into an object. + * + * |Name |Type |Desc | + * |------|------|------------| + * |str |string|Query string| + * |return|object|Query object| + * + * ### stringify + * + * Stringify an object into a query string. + * + * |Name |Type |Desc | + * |------|------|------------| + * |obj |object|Query object| + * |return|string|Query string| + * + * ```javascript + * query.parse('foo=bar&eruda=true'); // -> {foo: 'bar', eruda: 'true'} + * query.stringify({foo: 'bar', eruda: 'true'}); // -> 'foo=bar&eruda=true' + * query.parse('name=eruda&name=eustia'); // -> {name: ['eruda', 'eustia']} + * ``` + */ + + exports = { + parse: function (str) + { + var ret = {}; + + str = trim(str).replace(regIllegalChars, ''); + + each(str.split('&'), function (param) + { + var parts = param.split('='); + + var key = parts.shift(), + val = parts.length > 0 ? parts.join('=') : null; + + val = decodeURIComponent(val); + + if (isUndef(ret[key])) + { + ret[key] = val; + } else if (isArr(ret[key])) + { + ret[key].push(val); + } else + { + ret[key] = [ret[key], val]; + } + }); + + return ret; + }, + stringify: function (obj, arrKey) + { + return filter(map(obj, function (val, key) + { + if (isObj(val) && isEmpty(val)) return ''; + if (isArr(val)) return exports.stringify(val, key); + + return (arrKey || key) + '=' + encodeURIComponent(val); + }), function (str) + { + return str.length > 0; + }).join('&'); + } + }; + + var regIllegalChars = /^(\?|#|&)/g; + + return exports; + })({}); + + /* ------------------------------ Url ------------------------------ */ + + var Url = _.Url = (function (exports) + { + /* Simple url manipulator. + * + * ### constructor + * + * |Name |Type |Desc | + * |---------------------|------|----------| + * |[url=window.location]|string|Url string| + * + * ### setQuery + * + * Set query value. + * + * |Name |Type |Desc | + * |------|------|-----------| + * |name |string|Query name | + * |value |string|Query value| + * |return|Url |this | + * + * |Name |Type |Desc | + * |------|------|------------| + * |names |object|query object| + * |return|Url |this | + * + * ### rmQuery + * + * Remove query value. + * + * |Name |Type |Desc | + * |------|------------|----------| + * |name |string array|Query name| + * |return|Url |this | + * + * ### parse + * + * [static] Parse url into an object. + * + * |Name |Type |Desc | + * |------|------|----------| + * |url |string|Url string| + * |return|object|Url object| + * + * ### stringify + * + * [static] Stringify url object into a string. + * + * |Name |Type |Desc | + * |------|------|----------| + * |url |object|Url object| + * |return|string|Url string| + * + * An url object contains the following properties: + * + * |Name |Desc | + * |--------|--------------------------------------------------------------------------------------| + * |protocol|The protocol scheme of the URL (e.g. http:) | + * |slashes |A boolean which indicates whether the protocol is followed by two forward slashes (//)| + * |auth |Authentication information portion (e.g. username:password) | + * |hostname|Host name without port number | + * |port |Optional port number | + * |pathname|URL path | + * |query |Parsed object containing query string | + * |hash |The "fragment" portion of the URL including the pound-sign (#) | + * + * ```javascript + * var url = new Url('http://example.com:8080?eruda=true'); + * console.log(url.port); // -> '8080' + * url.query.foo = 'bar'; + * url.rmQuery('eruda'); + * utl.toString(); // -> 'http://example.com:8080/?foo=bar' + * ``` + */ + + exports = Class({ + className: 'Url', + initialize: function (url) + { + extend(this, exports.parse(url || window.location.href)); + }, + setQuery: function (name, val) + { + var query = this.query; + + if (isObj(name)) + { + each(name, function (val, key) + { + query[key] = val; + }); + } else + { + query[name] = val; + } + + return this; + }, + rmQuery: function (name) + { + var query = this.query; + + if (!isArr(name)) name = toArr(name); + each(name, function (key) + { + delete query[key]; + }); + + return this; + }, + toString: function () + { + return exports.stringify(this); + } + }, { + parse: function (url) + { + var ret = { + protocol: '', + auth: '', + hostname: '', + hash: '', + query: {}, + port: '', + pathname: '', + slashes: false + }, + rest = trim(url); + + var proto = rest.match(regProto); + if (proto) + { + proto = proto[0]; + ret.protocol = proto.toLowerCase(); + rest = rest.substr(proto.length); + } + + if (proto) + { + var slashes = rest.substr(0, 2) === '//'; + if (slashes) + { + rest = rest.slice(2); + ret.slashes = true; + } + } + + if (slashes) + { + var hostEnd = -1; + for (var i = 0, len = hostEndingChars.length; i < len; i++) + { + var pos = rest.indexOf(hostEndingChars[i]); + if (pos !== -1 && (hostEnd === -1 || pos < hostEnd)) hostEnd = pos; + } + + var host = rest.slice(0, hostEnd); + rest = rest.slice(hostEnd); + + var atSign = host.lastIndexOf('@'); + + if (atSign !== -1) + { + ret.auth = decodeURIComponent(host.slice(0, atSign)); + host = host.slice(atSign + 1); + } + + ret.hostname = host; + var port = host.match(regPort); + if (port) + { + port = port[0]; + if (port !== ':') ret.port = port.substr(1); + ret.hostname = host.substr(0, host.length - port.length); + } + } + + var hash = rest.indexOf('#'); + + if (hash !== -1) + { + ret.hash = rest.substr(hash); + rest = rest.slice(0, hash); + } + + var queryMark = rest.indexOf('?'); + + if (queryMark !== -1) + { + ret.query = query.parse(rest.substr(queryMark + 1)); + rest = rest.slice(0, queryMark); + } + + ret.pathname = rest || '/'; + + return ret; + }, + stringify: function (obj) + { + var ret = obj.protocol + + (obj.slashes ? '//' : '') + + (obj.auth ? encodeURIComponent(obj.auth) + '@' : '') + + obj.hostname + + (obj.port ? (':' + obj.port) : '') + + obj.pathname; + + if (!isEmpty(obj.query)) ret += '?' + query.stringify(obj.query); + if (obj.hash) ret += obj.hash; + + return ret; + } + }); + + var regProto = /^([a-z0-9.+-]+:)/i, + regPort = /:[0-9]*$/, + hostEndingChars = ['/', '?', '#']; + + return exports; + })({}); + + /* ------------------------------ safeGet ------------------------------ */ + + var safeGet = _.safeGet = (function () + { + /* Get object property, don't throw undefined error. + * + * |Name |Type |Desc | + * |------|------------|-------------------------| + * |obj |object |Object to query | + * |path |array string|Path of property to get | + * |return|* |Target value or undefined| + * + * ```javascript + * var obj = {a: {aa: {aaa: 1}}}; + * safeGet(obj, 'a.aa.aaa'); // -> 1 + * safeGet(obj, ['a', 'aa']); // -> {aaa: 1} + * safeGet(obj, 'a.b'); // -> undefined + * ``` + */ + + function exports(obj, path) + { + if (isStr(path)) path = path.split('.'); + + var prop; + + while (prop = path.shift()) + { + obj = obj[prop]; + if (isUndef(obj)) return; + } + + return obj; + } + + return exports; + })(); + /* ------------------------------ safeStorage ------------------------------ */ var safeStorage = _.safeStorage = (function ()