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 ()