Last active
February 12, 2018 06:30
-
-
Save spiralx/fc040100134c5989eae30271cb715b1a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function () { | |
const cssNumbers = new Set([ | |
'column-count', | |
'fill-opacity', | |
'flex-grow', | |
'flex-shrink', | |
'font-weight', | |
'line-height', | |
'opacity', | |
'order', | |
'orphans', | |
'widows', | |
'z-index', | |
'zoom' | |
]) | |
// -------------------------------------------------------------------- | |
const support = (() => { | |
// Taken from https://github.com/jquery/jquery-migrate/blob/master/src/core.js | |
function uaMatch (ua) { | |
ua = ua.toLowerCase() | |
const match = /(chrome)[ /]([\w.]+)/.exec(ua) || | |
/(webkit)[ /]([\w.]+)/.exec(ua) || | |
/(opera)(?:.*version|)[ /]([\w.]+)/.exec(ua) || | |
/(msie) ([\w.]+)/.exec(ua) || | |
(ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) || | |
[] | |
return { | |
browser: match[1] || '', | |
version: match[2] || '0' | |
} | |
} | |
const browserData = uaMatch(navigator.userAgent) | |
return { | |
isIE: browserData.browser === 'msie' || (browserData.browser === 'mozilla' && parseInt(browserData.version, 10) === 11) | |
} | |
})() | |
// -------------------------------------------------------------------- | |
class ConsoleMessage { | |
constructor () { | |
this._rootSpan = { | |
styles: {}, | |
children: [], | |
parent: null | |
} | |
this._currentSpan = this._rootSpan | |
this._waiting = 0 | |
this._readyCallback = null | |
} | |
/** | |
* Begins a group. By default the group is expanded. Provide false if you want the group to be collapsed. | |
* @param {boolean} [expanded = true] - | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
group (expanded) { | |
this._currentSpan.children.push({ | |
type: expanded === false ? 'groupCollapsed' : 'group', | |
parent: this._currentSpan | |
}) | |
return this | |
} | |
/** | |
* Ends the group and returns to writing to the parent message. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
groupEnd () { | |
this._currentSpan.children.push({ | |
type: 'groupEnd', | |
parent: this._currentSpan | |
}) | |
return this | |
} | |
/** | |
* Starts a span with particular style and all appended text after it will use the style. | |
* @param {Object} styles - The CSS styles to be applied to all text until endSpan() is called | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
span (styles = {}) { | |
const span = { | |
type: 'span', | |
styles: Object.setPrototypeOf(styles, this._currentSpan.styles), | |
children: [], | |
parent: this._currentSpan | |
} | |
this._currentSpan.children.push(span) | |
this._currentSpan = span | |
return this | |
} | |
/** | |
* Ends the current span styles and backs to the previous styles or the root if there are no other parents. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
spanEnd () { | |
this._currentSpan = this._currentSpan.parent || this._currentSpan | |
return this | |
} | |
/** | |
* Appends a text to the current message. All styles in the current span are applied. | |
* @param {string} text - The text to be appended | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
text (message, styles = {}) { | |
this.span(styles) | |
this._currentSpan.children.push({ | |
type: 'text', | |
message, | |
parent: this._currentSpan | |
}) | |
return this.spanEnd() | |
} | |
/** | |
* Adds a new line to the output. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
line (type = 'log') { | |
this._currentSpan.children.push({ | |
type, | |
parent: this._currentSpan | |
}) | |
return this | |
} | |
/** | |
* Adds an interactive DOM element to the output. | |
* @param {HTMLElement} element - The DOM element to be added. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
element (element) { | |
this._currentSpan.children.push({ | |
type: 'element', | |
element, | |
parent: this._currentSpan | |
}) | |
return this | |
} | |
/** | |
* Adds an interactive object tree to the output. | |
* @param {*} object - A value to be added to the output. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
object (object) { | |
this._currentSpan.children.push({ | |
type: 'object', | |
object, | |
parent: this._currentSpan | |
}) | |
return this | |
} | |
dump (obj) { | |
try { | |
if (typeof obj.dump === 'function') { | |
obj.dump(this) | |
} else { | |
this.object(obj) | |
} | |
} catch (ex) {} | |
return this | |
} | |
/** | |
* Prints the message to the console. | |
* Until print() is called there will be no result to the console. | |
*/ | |
print () { | |
if (typeof console !== 'undefined') { | |
const messages = [ this._newMessage() ] | |
this._printSpan(this._rootSpan, messages) | |
messages.forEach(message => { | |
if (message.text && message.text !== '%c' && console[ message.type ]) { | |
this._printMessage(message) | |
} | |
}) | |
} | |
return new ConsoleMessage() | |
} | |
_printMessage (message) { | |
Function.prototype.apply.call( | |
console[ message.type ], | |
console, | |
[ message.text, ...message.args ] | |
) | |
} | |
_printSpan (span, messages) { | |
const message = messages[ messages.length - 1 ] | |
this._addSpanData(span, message) | |
span.children.forEach(child => { | |
this._handleChild(child, messages) | |
}) | |
} | |
_handleChild (child, messages) { | |
let message = messages[ messages.length - 1 ] | |
switch (child.type) { | |
case 'group': | |
case 'groupCollapsed': | |
case 'log': | |
messages.push(this._newMessage(child.type)) | |
break | |
case 'groupEnd': | |
message = this._newMessage('groupEnd', ' ') | |
messages.push(message) | |
messages.push(this._newMessage()) | |
break | |
case 'span': | |
this._printSpan(child, messages) | |
this._addSpanData(child, message) | |
this._addSpanData(child.parent, message) | |
break | |
case 'text': | |
message.text += child.message | |
break | |
case 'element': | |
message.text += '%o' | |
message.args.push(child.element) | |
break | |
case 'object': | |
message.text += '%O' | |
message.args.push(child.object) | |
break | |
} | |
} | |
_addSpanData (span, message) { | |
if (!support.isIE) { | |
if (message.text.substring(message.text.length - 2) === '%c') { | |
message.args[ message.args.length - 1 ] = this._stylesString(span.styles) | |
} else { | |
message.text += '%c' | |
message.args.push(this._stylesString(span.styles)) | |
} | |
} | |
} | |
_newMessage (type = 'log', text = '') { | |
return { | |
type, | |
text, | |
args: [] | |
} | |
} | |
_stylesString (styles) { | |
return [ ...Object.entries(styles) ].map(([ property, value ]) => { | |
property = this._toDashName(property) | |
if (typeof value === 'number' && !cssNumbers.has(property)) { | |
value += 'px' | |
} | |
return `${property}: ${value};` | |
}).join(' ') | |
} | |
_toCamelCaseName (key) { | |
return key.replace(/-\w/g, match => match.charAt(1).toUpperCase()) | |
} | |
_toDashName (key) { | |
return key.replace(/[A-Z]/g, match => '-' + match.toLowerCase()) | |
} | |
} | |
if (typeof window !== 'undefined') { | |
if (!window.console) { | |
window.console = {} | |
} | |
/** | |
* Creates a message object. | |
* @returns {ConsoleMessage} - The message object | |
*/ | |
window.console.message = () => new ConsoleMessage() | |
} | |
})() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint asi: true, laxbreak: true, esnext: true */ | |
(function () { | |
const cssNumbers = new Set([ | |
'column-count', | |
'fill-opacity', | |
'flex-grow', | |
'flex-shrink', | |
'font-weight', | |
'line-height', | |
'opacity', | |
'order', | |
'orphans', | |
'widows', | |
'z-index', | |
'zoom' | |
]) | |
// -------------------------------------------------------------------- | |
const support = (() => { | |
// Taken from https://github.com/jquery/jquery-migrate/blob/master/src/core.js | |
function uaMatch (ua) { | |
ua = ua.toLowerCase() | |
const match = /(chrome)[ /]([\w.]+)/.exec(ua) || | |
/(webkit)[ /]([\w.]+)/.exec(ua) || | |
/(opera)(?:.*version|)[ /]([\w.]+)/.exec(ua) || | |
/(msie) ([\w.]+)/.exec(ua) || | |
(ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)) || | |
[] | |
return { | |
browser: match[1] || '', | |
version: match[2] || '0' | |
} | |
} | |
const browserData = uaMatch(navigator.userAgent) | |
return { | |
isIE: browserData.browser === 'msie' || (browserData.browser === 'mozilla' && parseInt(browserData.version, 10) === 11) | |
} | |
})() | |
// -------------------------------------------------------------------- | |
function toCamelCase (key) { | |
return key.replace(/-\w/g, match => match.charAt(1).toUpperCase()) | |
} | |
function toKebabCase (key) { | |
return key.replace(/[A-Z]/g, match => '-' + match.toLowerCase()) | |
} | |
function stylesToCss (styles) { | |
return [ ...Object.entries(styles) ].map(([ property, value ]) => { | |
property = toKebabCase(property) | |
if (typeof value === 'number' && !cssNumbers.has(property)) { | |
value += 'px' | |
} | |
return `${property}: ${value};` | |
}).join(' ') | |
} | |
// -------------------------------------------------------------------- | |
class Item { | |
constructor (type, data = {}) { | |
this.type = type | |
this.parent = null | |
Object.assign(this, data) | |
} | |
write(messageList) { | |
messageList.add(new Message(this.type)) | |
} | |
} | |
class SpanItem extends Item { | |
constructor(styles = {}) { | |
super('span') | |
this._styles = styles | |
this.children = [] | |
} | |
push(item) { | |
item.parent = this | |
this.children.push(item) | |
return item | |
} | |
get styles() { | |
return { | |
...this.parent.styles, | |
...this._styles | |
} | |
} | |
get hasStyles() { | |
return Object.keys(this._styles).length > 0 | |
} | |
get css() { | |
return stylesToCss(this.styles) | |
} | |
/* | |
pushStyle(messageList) { | |
if (this.hasStyles) { | |
messageList.current.pushStyle(this.css) | |
messageList._styleStack.push(this.css) | |
} | |
} | |
popStyle(messageList) { | |
if (this.hasStyles) { | |
messageList.current.pushStyle(this.css) | |
messageList._styleStack.push(this.css) | |
} | |
}*/ | |
write(messageList) { | |
if (this.hasStyles) { | |
messageList.pushStyle(this.css) | |
} | |
this.children.forEach(child => { | |
child.write(messageList) | |
}) | |
if (this.hasStyles) { | |
messageList.popStyle() | |
} | |
// messageList.current.pushStyle(stylesToCss(this.parent.styles)) | |
} | |
} | |
class RootItem extends SpanItem { | |
constructor() { | |
super() | |
this.type = 'root' | |
} | |
get styles() { | |
return this._styles | |
} | |
get css() { | |
return '' | |
} | |
} | |
class TextItem extends Item { | |
constructor(message) { | |
super('text', { message }) | |
} | |
write(messageList) { | |
messageList.current.append(this.message) | |
} | |
} | |
class LineItem extends Item { | |
constructor(type = 'log') { | |
super('line', { type }) | |
} | |
write(messageList) { | |
messageList.add(new TextMessage(this.type)) | |
} | |
} | |
class GroupItem extends Item { | |
constructor(collapsed = false) { | |
super('group', { collapsed }) | |
} | |
write(messageList) { | |
messageList.add(new Message(this.collapsed ? 'groupCollapsed' : 'group')) | |
messageList.add(new TextMessage()) | |
} | |
} | |
class GroupEndItem extends Item { | |
constructor() { | |
super('groupEnd') | |
} | |
write(messageList) { | |
messageList.add(new Message('groupEnd')) | |
messageList.add(new TextMessage()) | |
} | |
} | |
class ElementItem extends Item { | |
constructor(element) { | |
super('element', { element }) | |
} | |
write(messageList) { | |
messageList.current.append('%o', this.element) | |
} | |
} | |
class ObjectItem extends Item { | |
constructor(object) { | |
super('object', { object }) | |
} | |
write(messageList) { | |
messageList.current.append('%O', this.object) | |
} | |
} | |
class TableItem extends Item { | |
constructor(array) { | |
super('table', { array }) | |
} | |
write(messageList) { | |
messageList.add(new Message('table', this.array)) | |
messageList.add(new TextMessage()) | |
} | |
} | |
// -------------------------------------------------------------------- | |
class MessageList { | |
constructor() { | |
this._items = [ new TextMessage() ] | |
this._styleStack = [] | |
} | |
get current() { | |
return this._items[ this._items.length - 1] | |
} | |
pushStyle(css) { | |
this._styleStack.push(css) | |
this.current.pushStyle(css) | |
return css | |
} | |
popStyle() { | |
this._styleStack.pop() | |
const css = this._styleStack[ this._styleStack.length - 1 ] | |
this.current.pushStyle(css) | |
return css | |
} | |
add(message) { | |
this._items.push(message) | |
return message | |
} | |
* [ Symbol.iterator ]() { | |
yield* this._items | |
/*for (const message of this._items) { | |
yield message | |
}*/ | |
} | |
} | |
class Message { | |
constructor (method, ...args) { | |
this.method = method | |
this.args = args | |
} | |
push(arg) { | |
this.args.push(arg) | |
} | |
execute() { | |
console[ this.method ].apply(console, this.args) | |
} | |
dump() { | |
console.log(`console.${this.method} (${'%o, '.repeat(this.args.length).slice(0, -2)})`, ...this.args) | |
} | |
} | |
class TextMessage extends Message { | |
constructor (method = 'log', text = '') { | |
super(method, text) | |
} | |
append (text, arg) { | |
this.text += text || '' | |
if (typeof arg !== 'undefined') { | |
super.push(arg) | |
} | |
} | |
pushStyle (css) { | |
if (this.text.endsWith('%c')) { | |
this.args[ this.args.length - 1 ] = css | |
} else { | |
this.append('%c', css) | |
} | |
} | |
get text () { | |
return this.args[ 0 ] | |
} | |
set text (value) { | |
this.args[ 0 ] = value | |
} | |
} | |
// -------------------------------------------------------------------- | |
class ConsoleMessage { | |
constructor (debug = false) { | |
this._rootSpan = { | |
styles: {}, | |
children: [], | |
parent: null | |
} | |
this._currentSpan = this._rootSpan | |
this._root = this._current = new RootItem() | |
this._waiting = 0 | |
this._readyCallback = null | |
this._debug = debug | |
} | |
/** | |
* Begins a group. By default the group is expanded. Provide false if you want the group to be collapsed. | |
* @param {boolean} [expanded = true] - | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
group (expanded) { | |
this._currentSpan.children.push({ | |
type: expanded === false ? 'groupCollapsed' : 'group', | |
parent: this._currentSpan | |
}) | |
this._current.push(new GroupItem(!expanded)) | |
return this | |
} | |
/** | |
* Ends the group and returns to writing to the parent message. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
groupEnd () { | |
this._currentSpan.children.push({ | |
type: 'groupEnd', | |
parent: this._currentSpan | |
}) | |
this._current.push(new GroupEndItem()) | |
return this | |
} | |
/** | |
* Starts a span with particular style and all appended text after it will use the style. | |
* @param {Object} styles - The CSS styles to be applied to all text until endSpan() is called | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
span (styles = {}) { | |
const span = { | |
type: 'span', | |
styles: Object.setPrototypeOf(styles, this._currentSpan.styles), | |
children: [], | |
parent: this._currentSpan | |
} | |
this._currentSpan.children.push(span) | |
this._currentSpan = span | |
this._current = this._current.push(new SpanItem(styles)) | |
return this | |
} | |
/** | |
* Ends the current span styles and backs to the previous styles or the root if there are no other parents. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
spanEnd () { | |
this._currentSpan = this._currentSpan.parent || this._currentSpan | |
this._current = this._current.parent || this._current | |
return this | |
} | |
/** | |
* Appends a text to the current message. All styles in the current span are applied. | |
* @param {string} text - The text to be appended | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
text (message, styles = null) { | |
if (styles !== null) { | |
this.span(styles) | |
} | |
this._currentSpan.children.push({ | |
type: 'text', | |
message, | |
parent: this._currentSpan | |
}) | |
this._current.push(new TextItem(message)) | |
if (styles !== null) { | |
this.spanEnd() | |
} | |
return this | |
} | |
/** | |
* Adds a new line to the output. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
line (type = 'log') { | |
this._currentSpan.children.push({ | |
type, | |
parent: this._currentSpan | |
}) | |
this._current.push(new LineItem(type)) | |
return this | |
} | |
/** | |
* Adds an interactive DOM element to the output. | |
* @param {HTMLElement} element - The DOM element to be added. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
element (element) { | |
this._currentSpan.children.push({ | |
type: 'element', | |
element, | |
parent: this._currentSpan | |
}) | |
this._current.push(new ElementItem(element)) | |
return this | |
} | |
/** | |
* Adds an interactive object tree to the output. | |
* @param {*} object - A value to be added to the output. | |
* @returns {ConsoleMessage} - Returns the message object itself to allow chaining. | |
*/ | |
object (object) { | |
if (typeof object.dump === 'function') { | |
object.dump(this) | |
} else { | |
this._currentSpan.children.push({ | |
type: 'object', | |
object, | |
parent: this._currentSpan | |
}) | |
this._current.push(new ObjectItem(object)) | |
} | |
return this | |
} | |
trace () { | |
this._currentSpan.children.push({ | |
type: 'trace', | |
parent: this._currentSpan | |
}) | |
this._current.push(new Item('trace')) | |
return this | |
} | |
table (array) { | |
this._current.push(new TableItem(array)) | |
return this | |
} | |
clear () { | |
this._current.push(new Item('clear')) | |
return this | |
} | |
/** | |
* Prints the message to the console. | |
* Until print() is called there will be no result to the console. | |
*/ | |
print () { | |
if (typeof console !== 'undefined') { | |
if (this._debug) { | |
console.group('console.message') | |
console.group('_rootSpan') | |
console.dir(this._rootSpan) | |
console.groupEnd() | |
console.group('_root') | |
console.dir(this._root) | |
console.groupEnd() | |
} | |
const messages = [ this._newMessage() ] | |
this._printSpan(this._rootSpan, messages) | |
const messageList = new MessageList() | |
this._root.write(messageList) | |
if (this._debug) { | |
console.group('messages') | |
messages.forEach(message => { | |
const args = [ message.text, ...message.args ] | |
console.log(`console.${message.type}(${'%o, '.repeat(args.length).slice(0, -2)})`, ...args) | |
}) | |
console.dir(messages) | |
console.groupEnd() | |
console.group('messageList') | |
for (const message of messageList) { | |
message.dump() | |
} | |
console.groupEnd() | |
console.groupEnd() | |
} | |
messages.forEach(message => { | |
if (message.text && message.text !== '%c' && console[ message.type ]) { | |
this._printMessage(message) | |
} | |
}) | |
} | |
return new ConsoleMessage(this._debug) | |
} | |
_printMessage (message) { | |
Function.prototype.apply.call( | |
console[ message.type ], | |
console, | |
[ message.text, ...message.args ] | |
) | |
} | |
_printSpan (span, messages) { | |
const message = messages[ messages.length - 1 ] | |
this._addSpanData(span, message) | |
span.children.forEach(child => { | |
this._handleChild(child, messages) | |
}) | |
} | |
_handleChild (child, messages) { | |
let message = messages[ messages.length - 1 ] | |
this._log(`_handleChild(child: %o, message.type: %s, message.args: %o)`, child, message.type, message.args) | |
switch (child.type) { | |
case 'groupEnd': | |
message = this._newMessage('groupEnd', ' ') | |
messages.push(message) | |
messages.push(this._newMessage()) | |
break | |
case 'span': | |
this._printSpan(child, messages) | |
this._addSpanData(child, message) | |
this._addSpanData(child.parent, message) | |
break | |
case 'text': | |
message.text += child.message | |
break | |
case 'element': | |
message.text += '%o' | |
message.args.push(child.element) | |
break | |
case 'object': | |
message.text += '%O' | |
message.args.push(child.object) | |
break | |
case 'log': | |
case 'info': | |
case 'warn': | |
case 'error': | |
messages.push(this._newMessage(child.type)) | |
break | |
case 'group': | |
case 'groupCollapsed': | |
messages.push(this._newMessage(child.type)) | |
break | |
} | |
} | |
_addSpanData (span, message) { | |
const css = this._stylesString(span.styles) | |
this._log(`addSpanData(css: %s, message.type: %s, message.args: %o)`, span, message.type, message.args) | |
if (!support.isIE) { | |
if (message.text.endsWith('%c')) { | |
message.args[ message.args.length - 1 ] = css | |
} else { | |
message.text += '%c' | |
message.args.push(css) | |
} | |
} | |
} | |
_newMessage (type = 'log', text = '') { | |
return { | |
type, | |
text, | |
args: [] | |
} | |
} | |
_stylesString (styles) { | |
return [ ...Object.entries(styles) ].map(([ property, value ]) => { | |
property = this._toDashName(property) | |
if (typeof value === 'number' && !cssNumbers.has(property)) { | |
value += 'px' | |
} | |
return `${property}: ${value};` | |
}).join(' ') | |
} | |
_toCamelCaseName (key) { | |
return key.replace(/-\w/g, match => match.charAt(1).toUpperCase()) | |
} | |
_toDashName (key) { | |
return key.replace(/[A-Z]/g, match => '-' + match.toLowerCase()) | |
} | |
_log (...args) { | |
if (this._debug) { | |
console.log(...args) | |
} | |
} | |
} | |
if (typeof window !== 'undefined') { | |
if (!window.console) { | |
window.console = {} | |
} | |
/** | |
* Creates a message object. | |
* @returns {ConsoleMessage} - The message object | |
*/ | |
window.console.message = (debug = false) => new ConsoleMessage(debug) | |
} | |
})() | |
/** | |
let fs = { | |
fontWeight: 'bold', | |
backgroundColor: '#5af' | |
} | |
let t = { | |
text: 'Woohoo', | |
backgroundColor: 'yellow', | |
dump(msg) { | |
return msg.text(this.text, { backgroundColor: this.backgroundColor }) | |
} | |
} | |
console.clear() | |
console.message(true) | |
.text('thing: ', { fontWeight: 'bold' }) | |
.object(t) | |
.line() | |
.span({ padding: '2px 4px', borderRadius: 4 }) | |
.text('foo', { backgroundColor: '#05f', color: 'white' }) | |
.text('bar', { backgroundColor: '#0f0' }) | |
.spanEnd() | |
.text(' and cows') | |
.print() | |
.group() | |
.text('trace:') | |
.line() | |
.trace() | |
.groupEnd() | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment