├── .gitignore ├── Makefile ├── README.md ├── bower.json ├── mobile-console.js ├── mobile-console.min.js ├── mobile_screen.png ├── package.json └── style ├── mobile-console.css └── mobile-console.min.css /.gitignore: -------------------------------------------------------------------------------- 1 | *sublime* 2 | .DS_store 3 | demo/ 4 | index.html -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: min cssmin 2 | 3 | min: 4 | yuglify ./mobile-console.js 5 | 6 | cssmin: 7 | yuglify ./style/mobile-console.css -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | js-mobile-console 2 | ===== 3 | 4 | ![js-mobile-console](mobile_screen.png) 5 | 6 | ## Install 7 | **Zip archive**: 8 | [Download](https://github.com/msemenistyi/js-mobile-console/archive/master.zip) 9 | 10 | **Browserify**: 11 | > npm install js-mobile-console 12 | 13 | [![NPM](https://nodei.co/npm/js-mobile-console.png)](https://nodei.co/npm/js-mobile-console/) 14 | 15 | **Bower**: 16 | > bower install js-mobile-console 17 | 18 | ## Overview 19 | 20 | [Live demo](http://b1narystudio.github.io/js-mobile-console/). 21 | 22 | It is extremely nice, that we are on the road of specifying and unifying remote 23 | debugging protocol and we can already remotely debug web applications on desktop 24 | Chrome, but what if we want to get it working also on Android browser or 25 | Safari for iOs? 26 | 27 | MobileConsole can be embedded within any page for debugging, it will catch errors 28 | and behave exactly as native js console in browser. It also outputs all the logs 29 | you've written via an API of window.console. 30 | 31 | ### How is it different from all the existing instruments? 32 | 33 | There are already plenty of ways for testing mobile applications. The most 34 | advanced is at the moment Chrome remote debugging. Still it is available only 35 | Chrome to Chrome. 36 | 37 | Another way is to use [weinre](http://people.apache.org/~pmuellr/weinre-docs/latest/) - 38 | tool for remote debugging. It is quite nice and suggests rich functionality. Still 39 | it seems quite heavy for me. You should setup a dedicated server for it. 40 | 41 | js-mobile-console is in contrast lightweight and requires almost no configuration, 42 | you can just include it into your page and debug it when an error appears. 43 | 44 | *stevemao* also stated that it may come handy for debugging 45 | [web views](https://github.com/B1naryStudio/js-mobile-console/issues/1). 46 | 47 | ### Loading 48 | Css file should be included within html: 49 | ```html 50 | 51 | ``` 52 | Console may be used without any modular system: 53 | ```html 54 | 55 | ``` 56 | 57 | With help of browserify: 58 | ```js 59 | var mobileConsole = require('js-mobile-console'); 60 | ``` 61 | 62 | Or AMD style (RequireJS): 63 | ```js 64 | define(['js-mobile-console'], function(mobileConsole)) 65 | ``` 66 | 67 | ### Usage 68 | 69 | Simple usage: 70 | ```js 71 | mobileConsole.show(); 72 | ``` 73 | 74 | Advanced usage: 75 | ```js 76 | mobileConsole.options({ 77 | showOnError: true, 78 | proxyConsole: false, 79 | isCollapsed: true, 80 | catchErrors: true 81 | }); 82 | ``` 83 | 84 | Conditional toggling: 85 | ```js 86 | if (condition) { 87 | mobileConsole.show(); 88 | } else { 89 | mobileConsole.hide(); 90 | } 91 | ``` 92 | 93 | Commands specifying: 94 | ```js 95 | mobileConsole.commands({ 96 | 'check': 'var a = 10; a;', 97 | 'run': '10/5' 98 | }); 99 | ``` 100 | 101 | ### API 102 | 103 | - **show** - show console with default options. 104 | - **hide** - hide console. 105 | - **options** - method to initialize console, by default will show console, 106 | accepts hash of options. 107 | - **commands** - method to specify a hash of commands, which later can be 108 | executed within console. 109 | 110 | ### Options 111 | 112 | - **showOnError** - *Default* false. Console will be hidden if no show method 113 | was called, but it will appear if any error occurs. 114 | - **proxyConsole** - *Default* true. Determines if window.console methods are 115 | proxied to mobile console. 116 | - **isCollapsed** - *Default* false. Determines if Console is collpased on startup. 117 | - **catchErrors** - *Default* false. Determines if Console is registring 118 | window.onerror method in order to catch errors. 119 | 120 | ## Created by [msemenistyi](https://github.com/msemenistyi) 121 | 122 | ## Changelog 123 | 124 | ### 0.4.0 (2018-10-24) 125 | - fix handling of undefined 126 | - added ability to style output with %c 127 | 128 | ### 0.3.3 (2018-10-24) 129 | - add possibility to add line breaks in console.log strings with \n 130 | 131 | ### 0.3.0 (2014-11-11) 132 | - add possibility to specify commands; 133 | - add JSON.stringifying for logging; 134 | - set z-index to console 135 | - fix startup error on logValue 136 | 137 | ## Contributing 138 | Feel free to open issues and send PRs. 139 | 140 | 141 | ## License 142 | 143 | The MIT License (MIT) 144 | 145 | [![Binary Studio](https://d3ot0t2g92r1ra.cloudfront.net/img/binary-small-logo.png)](http://binary-studio.com) 146 | Copyright (c) 2014-2018 Semenistyi Mykyta nikeiwe@gmail.com 147 | 148 | Permission is hereby granted, free of charge, to any person obtaining a copy 149 | of this software and associated documentation files (the "Software"), to deal 150 | in the Software without restriction, including without limitation the rights 151 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 152 | copies of the Software, and to permit persons to whom the Software is 153 | furnished to do so, subject to the following conditions: 154 | 155 | The above copyright notice and this permission notice shall be included in 156 | all copies or substantial portions of the Software. 157 | 158 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 159 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 160 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 161 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 162 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 163 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 164 | THE SOFTWARE. 165 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-mobile-console", 3 | "version": "0.4.0", 4 | "authors": [ 5 | "msemenistyi " 6 | ], 7 | "description": "JS console emulator for mobile devices", 8 | "main": [ 9 | "mobile-console.js", 10 | "style/mobile-console.css" 11 | ], 12 | "keywords": [ 13 | "console", 14 | "emulator", 15 | "debug" 16 | ], 17 | "license": "MIT", 18 | "homepage": "https://github.com/B1naryStudio/js-mobile-console", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /mobile-console.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define([], factory); 4 | } else if (typeof exports === 'object') { 5 | module.exports = factory(); 6 | } else { 7 | root.mobileConsole = factory(); 8 | } 9 | })(this, function () { 10 | 11 | var containerHtml = '' + 12 | '
' + 13 | '
×
' + 14 | '
' + 15 | '
' + 16 | '
' + 17 | ' ' + 18 | '
' + 19 | '
' + 20 | '
' + 21 | ' ' + 22 | '
' + 23 | '
' + 24 | ''; 25 | 26 | var logElementHtml = '' + 27 | '
' + 28 | '
' + 29 | ''; 30 | 31 | var mobileConsole = { 32 | props: { 33 | showOnError: false, 34 | proxyConsole: true, 35 | isCollapsed: false, 36 | catchErrors: true 37 | }, 38 | 39 | init: function(){ 40 | this.commandsHash = []; 41 | if (!this.initialized){ 42 | if (this.props.catchErrors){ 43 | this.bindErrorListener(); 44 | } 45 | this.initializeContainers(); 46 | this.bindListeners(); 47 | this.initialized = true; 48 | 49 | if (this.props.proxyConsole){ 50 | this.decorateConsole(); 51 | } else { 52 | this.undecorateConsole(); 53 | } 54 | } 55 | }, 56 | 57 | options: function(options){ 58 | for (var i in options){ 59 | if (typeof this.props[i] !== 'undefined'){ 60 | this.props[i] = options[i]; 61 | } 62 | } 63 | this.init(); 64 | }, 65 | 66 | show: function(options){ 67 | var el = document.getElementById('js-mobile-console'); 68 | if (!el){ 69 | this.init(); 70 | } 71 | this.$el.container.style.display = 'block'; 72 | 73 | if (options && options.expand){ 74 | this.toggleCollapsed(false); 75 | } 76 | }, 77 | 78 | hide: function(){ 79 | if (this.$el && this.$el.container){ 80 | this.$el.container.style.display = 'none'; 81 | } 82 | }, 83 | 84 | commands: function(commands){ 85 | if (typeof commands !== 'object'){ 86 | throw new Error('mobileConsole: commands method accepts object, not ' + typeof commands); 87 | } 88 | this.commandsHash = commands; 89 | this.commandsHashLength = 0; 90 | for (var i in commands){ 91 | if (commands.hasOwnProperty(i)){ 92 | this.commandsHashLength++; 93 | } 94 | } 95 | if (this.commandsHashLength){ 96 | this.populateCommandsContainer_(); 97 | } 98 | }, 99 | 100 | populateCommandsContainer_: function(){ 101 | var self = this; 102 | for (var i in this.commandsHash){ 103 | if (this.commandsHash.hasOwnProperty(i)){ 104 | var commandEl = document.createElement('div'); 105 | commandEl.className = 'jsmc-command'; 106 | commandEl.innerHTML = i; 107 | commandEl.command = this.commandsHash[i]; 108 | 109 | var commandElContainer = document.createElement('div'); 110 | commandElContainer.className = 'jsmc-command-wrapper'; 111 | 112 | commandElContainer.appendChild(commandEl); 113 | this.$el.commandsContainer.appendChild(commandElContainer); 114 | } 115 | } 116 | 117 | if (!this.commandsPopulated){ 118 | this.$el.commandsContainer.addEventListener('click', function(event){ 119 | if (event.target.className === 'jsmc-command'){ 120 | var command = event.target.command; 121 | var res = self.eval(command); 122 | self.logValue(command, false, true); 123 | self.logValue(res.text, res.error); 124 | } 125 | }); 126 | } 127 | 128 | this.commandsPopulated = true; 129 | }, 130 | 131 | destroy: function(){ 132 | var el = document.getElementById('js-mobile-console'); 133 | el.parentNode.removeChild(el); 134 | }, 135 | 136 | initializeContainers: function(options){ 137 | this.$el = {}; 138 | el = this.$el.container = document.createElement('div'); 139 | el.id = 'js-mobile-console'; 140 | el.innerHTML = containerHtml; 141 | el.style.display = 'none'; 142 | document.body.appendChild(el); 143 | 144 | this.$el.input = document.getElementById('jsmc-input'); 145 | this.$el.button = document.getElementById('jsmc-button'); 146 | this.$el.log = document.getElementById('jsmc-log'); 147 | this.$el.content = document.getElementById('jsmc-content'); 148 | this.$el.collapseControl = document.getElementById('jsmc-collapse'); 149 | this.$el.clearControl = document.getElementById('jsmc-clear'); 150 | this.$el.commandsControl = document.getElementById('jsmc-commands'); 151 | this.$el.commandsContainer = document.getElementById('jsmc-commands-container'); 152 | 153 | if (this.props.isCollapsed){ 154 | this.$el.content.style.display = 'none'; 155 | this.$el.clearControl.style.display = 'none'; 156 | this.$el.commandsControl.style.display = 'none'; 157 | this.isCollapsed = true; 158 | this.$el.collapseControl.innerHTML = '▲'; 159 | } else { 160 | this.$el.collapseControl.innerHTML = '▼'; 161 | } 162 | }, 163 | 164 | toggleCollapsed: function(toBeCollapsed){ 165 | this.isCollapsed = typeof toBeCollapsed === 'boolean' ? toBeCollapsed : !this.isCollapsed; 166 | var display = this.isCollapsed ? 'none' : 'block'; 167 | this.$el.content.style.display = display; 168 | this.$el.collapseControl.innerHTML = this.isCollapsed ? '▲' : '▼'; 169 | if (this.isCollapsed){ 170 | this.$el.clearControl.style.display = 'none'; 171 | this.$el.commandsControl.style.display = 'none'; 172 | } else { 173 | this.$el.clearControl.style.display = 'inline-block'; 174 | if (this.commandsHashLength){ 175 | this.$el.commandsControl.style.display = 'inline-block'; 176 | } 177 | } 178 | }, 179 | 180 | bindListeners: function(){ 181 | var self = this; 182 | this.$el.collapseControl.addEventListener('click', function(){ 183 | self.toggleCollapsed(); 184 | }); 185 | 186 | this.$el.clearControl.addEventListener('click', function(){ 187 | self.clearLogs(); 188 | }); 189 | 190 | this.$el.button.addEventListener('click', function(){ 191 | logValue(); 192 | }); 193 | 194 | this.$el.input.addEventListener('keyup', function(e){ 195 | if (e.which === 13){ 196 | logValue(); 197 | } 198 | }); 199 | 200 | this.$el.commandsControl.addEventListener('click', function(){ 201 | self.toggleCommands(); 202 | }); 203 | 204 | function logValue(){ 205 | var val = self.$el.input.value; 206 | var res = self.eval(val); 207 | self.logValue(res.text, res.error); 208 | } 209 | }, 210 | 211 | toggleCommands: function(){ 212 | this.commandsShown = !this.commandsShown; 213 | this.$el.commandsContainer.style.display = this.commandsShown ? 214 | 'inline-block' : 'none'; 215 | }, 216 | 217 | eval: function(command){ 218 | var text; 219 | var error; 220 | try { 221 | text = window.eval(command); 222 | } catch (e){ 223 | text = e.message; 224 | error = true; 225 | } 226 | if (JSON && JSON.stringify){ 227 | try{ 228 | text = JSON.stringify(text); 229 | } catch(e){ 230 | text = e.message; 231 | error = true; 232 | } 233 | } 234 | return { 235 | text: text, 236 | error: error 237 | }; 238 | }, 239 | 240 | clearLogs: function(){ 241 | this.$el.log.innerHTML = ''; 242 | }, 243 | 244 | bindErrorListener: function(){ 245 | var self = this; 246 | window.onerror = function(errorMessage, file, lineNumber, columnNumber){ 247 | if (self.props.showOnError){ 248 | self.show({expand: true}); 249 | } 250 | var error = file + ':' + lineNumber + (columnNumber ? (':' + columnNumber) : ''); 251 | self.logValue(errorMessage, error); 252 | }; 253 | }, 254 | 255 | appendLogEl: function(el){ 256 | this.$el.log.appendChild(el); 257 | this.$el.log.scrollTop = this.$el.log.scrollHeight; 258 | }, 259 | 260 | decorateConsole: function(){ 261 | var self = this; 262 | if (this.consoleDecorated){ 263 | return; 264 | } 265 | self.nativeConsole = {}; 266 | this.consoleDecorated = true; 267 | if (window.console){ 268 | decorateConsoleMethod('log'); 269 | decorateConsoleMethod('info'); 270 | decorateConsoleMethod('warn'); 271 | decorateConsoleMethod('error'); 272 | } 273 | 274 | function decorateConsoleMethod(methodName){ 275 | if (window.console[methodName]){ 276 | self.nativeConsole[methodName] = window.console[methodName]; 277 | window.console[methodName] = function(){ 278 | var args = [].slice.call(arguments); 279 | self.nativeConsole[methodName].apply(window.console, args); 280 | var res = stringifyComponents(args); 281 | self.logValue(res.text, res.error); 282 | }; 283 | } 284 | } 285 | 286 | function stringifyComponents(inputArgs){ 287 | var args = [].slice.call(inputArgs); 288 | if (JSON && JSON.stringify){ 289 | try{ 290 | for (var i = 0; i < args.length; i++){ 291 | if (typeof args[i] === 'string'){ 292 | args[i] = args[i].replace('\n', '
'); 293 | 294 | var res = handleStyles(i, args); 295 | args[i] = res.string; 296 | args.splice(i + 1, res.nextElement - 1); 297 | continue; 298 | } else { 299 | var containsUndefined = false; 300 | args[i] = JSON.stringify(args[i], function(key, value) { 301 | //preserve undefined in output after stringify 302 | if (value === undefined) { 303 | containsUndefined = true; 304 | return '$$_|_undefined_|_$$'; 305 | } 306 | return value; 307 | }); 308 | 309 | if (containsUndefined){ 310 | args[i] = args[i].replace('"$$_|_undefined_|_$$"', 'undefined'); 311 | } 312 | } 313 | } 314 | } catch(e){ 315 | args = [e.message]; 316 | var error = true; 317 | } 318 | } 319 | 320 | function handleStyles(index, args){ 321 | var blocks = args[index].split('%c'); 322 | var string = args[index]; 323 | var nextElement = index + 1; 324 | if (blocks.length > 1){ 325 | var i = 1; 326 | while (i < blocks.length){ 327 | var style = args[nextElement] || ""; 328 | var styleString = '' 329 | blocks.splice(i, 0, styleString); 330 | blocks.splice(i + 2, 0, ''); 331 | nextElement++; 332 | i = i + 3; 333 | } 334 | string = blocks.join(''); 335 | } 336 | return {string: string, nextElement: nextElement} 337 | } 338 | 339 | return {text: args.join(' '), error: error}; 340 | } 341 | }, 342 | 343 | undecorateConsole: function(){ 344 | var self = this; 345 | if (this.consoleDecorated){ 346 | undecorateConsoleMethod['log']; 347 | undecorateConsoleMethod['info']; 348 | undecorateConsoleMethod['warn']; 349 | undecorateConsoleMethod['error']; 350 | } 351 | 352 | function undecorateConsoleMethod(methodName){ 353 | window.console[methodName] = function(){ 354 | var args = [].slice.call(arguments); 355 | self.nativeConsole[methodName].apply(window.console, args); 356 | }; 357 | } 358 | }, 359 | 360 | logValue: function(value, error, command){ 361 | var logEl = document.createElement('div'); 362 | logEl.className = 'jsmc-log-el'; 363 | logEl.innerHTML = logElementHtml; 364 | 365 | if (error){ 366 | logEl.className += ' jsmc-log-error'; 367 | } 368 | 369 | if (command){ 370 | logEl.className += ' jsmc-log-command'; 371 | } 372 | 373 | var logTextEl = logEl.getElementsByClassName('jsmc-log-text')[0]; 374 | logTextEl.innerHTML = value; 375 | 376 | if (typeof error === 'string'){ 377 | var logTargetEl = logEl.getElementsByClassName('jsmc-log-target')[0]; 378 | logTargetEl.innerHTML = error; 379 | } 380 | 381 | this.appendLogEl(logEl); 382 | } 383 | }; 384 | 385 | return mobileConsole; 386 | 387 | }); -------------------------------------------------------------------------------- /mobile-console.min.js: -------------------------------------------------------------------------------- 1 | !function(e,n){"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?module.exports=n():e.mobileConsole=n()}(this,function(){var n='
×
\t\t
\t
\t
\t\t\t
',s='\t
\t
',e={props:{showOnError:!1,proxyConsole:!0,isCollapsed:!1,catchErrors:!0},init:function(){this.commandsHash=[],this.initialized||(this.props.catchErrors&&this.bindErrorListener(),this.initializeContainers(),this.bindListeners(),this.initialized=!0,this.props.proxyConsole?this.decorateConsole():this.undecorateConsole())},options:function(e){for(var n in e)void 0!==this.props[n]&&(this.props[n]=e[n]) 2 | this.init()},show:function(e){document.getElementById("js-mobile-console")||this.init(),this.$el.container.style.display="block",e&&e.expand&&this.toggleCollapsed(!1)},hide:function(){this.$el&&this.$el.container&&(this.$el.container.style.display="none")},commands:function(e){if("object"!=typeof e)throw new Error("mobileConsole: commands method accepts object, not "+typeof e) 3 | for(var n in this.commandsHash=e,this.commandsHashLength=0,e)e.hasOwnProperty(n)&&this.commandsHashLength++ 4 | this.commandsHashLength&&this.populateCommandsContainer_()},populateCommandsContainer_:function(){var e,n,t,o=this 5 | for(e in this.commandsHash)this.commandsHash.hasOwnProperty(e)&&((n=document.createElement("div")).className="jsmc-command",n.innerHTML=e,n.command=this.commandsHash[e],(t=document.createElement("div")).className="jsmc-command-wrapper",t.appendChild(n),this.$el.commandsContainer.appendChild(t)) 6 | this.commandsPopulated||this.$el.commandsContainer.addEventListener("click",function(e){var n,t 7 | "jsmc-command"===e.target.className&&(n=e.target.command,t=o.eval(n),o.logValue(n,!1,!0),o.logValue(t.text,t.error))}),this.commandsPopulated=!0},destroy:function(){var e=document.getElementById("js-mobile-console") 8 | e.parentNode.removeChild(e)},initializeContainers:function(e){this.$el={},el=this.$el.container=document.createElement("div"),el.id="js-mobile-console",el.innerHTML=n,el.style.display="none",document.body.appendChild(el),this.$el.input=document.getElementById("jsmc-input"),this.$el.button=document.getElementById("jsmc-button"),this.$el.log=document.getElementById("jsmc-log"),this.$el.content=document.getElementById("jsmc-content"),this.$el.collapseControl=document.getElementById("jsmc-collapse"),this.$el.clearControl=document.getElementById("jsmc-clear"),this.$el.commandsControl=document.getElementById("jsmc-commands"),this.$el.commandsContainer=document.getElementById("jsmc-commands-container"),this.props.isCollapsed?(this.$el.content.style.display="none",this.$el.clearControl.style.display="none",this.$el.commandsControl.style.display="none",this.isCollapsed=!0,this.$el.collapseControl.innerHTML="▲"):this.$el.collapseControl.innerHTML="▼"},toggleCollapsed:function(e){this.isCollapsed="boolean"==typeof e?e:!this.isCollapsed 9 | var n=this.isCollapsed?"none":"block" 10 | this.$el.content.style.display=n,this.$el.collapseControl.innerHTML=this.isCollapsed?"▲":"▼",this.isCollapsed?(this.$el.clearControl.style.display="none",this.$el.commandsControl.style.display="none"):(this.$el.clearControl.style.display="inline-block",this.commandsHashLength&&(this.$el.commandsControl.style.display="inline-block"))},bindListeners:function(){var t=this 11 | function n(){var e=t.$el.input.value,n=t.eval(e) 12 | t.logValue(n.text,n.error)}this.$el.collapseControl.addEventListener("click",function(){t.toggleCollapsed()}),this.$el.clearControl.addEventListener("click",function(){t.clearLogs()}),this.$el.button.addEventListener("click",function(){n()}),this.$el.input.addEventListener("keyup",function(e){13===e.which&&n()}),this.$el.commandsControl.addEventListener("click",function(){t.toggleCommands()})},toggleCommands:function(){this.commandsShown=!this.commandsShown,this.$el.commandsContainer.style.display=this.commandsShown?"inline-block":"none"},eval:function(e){var n,t 13 | try{n=window.eval(e)}catch(e){n=e.message,t=!0}if(JSON&&JSON.stringify)try{n=JSON.stringify(n)}catch(e){n=e.message,t=!0}return{text:n,error:t}},clearLogs:function(){this.$el.log.innerHTML=""},bindErrorListener:function(){var i=this 14 | window.onerror=function(e,n,t,o){i.props.showOnError&&i.show({expand:!0}) 15 | var s=n+":"+t+(o?":"+o:"") 16 | i.logValue(e,s)}},appendLogEl:function(e){this.$el.log.appendChild(e),this.$el.log.scrollTop=this.$el.log.scrollHeight},decorateConsole:function(){var o=this 17 | function e(t){window.console[t]&&(o.nativeConsole[t]=window.console[t],window.console[t]=function(){var e,n=[].slice.call(arguments) 18 | o.nativeConsole[t].apply(window.console,n),e=function(e){var n,t,o,s,i=[].slice.call(e) 19 | if(JSON&&JSON.stringify)try{for(n=0;n"),t=l(n,i),i[n]=t.string,i.splice(n+1,t.nextElement-1))}catch(e){i=[e.message],s=!0}function l(e,n){var t,o,s,i=n[e].split("%c"),l=n[e],a=e+1 20 | if(1',i.splice(t,0,s),i.splice(t+2,0,""),a++,t+=3 21 | l=i.join("")}return{string:l,nextElement:a}}return{text:i.join(" "),error:s}}(n),o.logValue(e.text,e.error)})}this.consoleDecorated||(o.nativeConsole={},this.consoleDecorated=!0,window.console&&(e("log"),e("info"),e("warn"),e("error")))},undecorateConsole:function(){this.consoleDecorated},logValue:function(e,n,t){var o=document.createElement("div") 22 | o.className="jsmc-log-el",o.innerHTML=s,n&&(o.className+=" jsmc-log-error"),t&&(o.className+=" jsmc-log-command"),o.getElementsByClassName("jsmc-log-text")[0].innerHTML=e,"string"==typeof n&&(o.getElementsByClassName("jsmc-log-target")[0].innerHTML=n),this.appendLogEl(o)}} 23 | return e}) 24 | -------------------------------------------------------------------------------- /mobile_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/B1naryStudio/js-mobile-console/dbfa2458a28ea74f03691a25e5168b417b7998e8/mobile_screen.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-mobile-console", 3 | "version": "0.4.0", 4 | "description": "JS console emulator for mobile devices", 5 | "main": "mobile-console.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:B1naryStudio/js-mobile-console.git" 12 | }, 13 | "keywords": [ 14 | "console", 15 | "emulator", 16 | "debug" 17 | ], 18 | "author": "msemenistyi", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/B1naryStudio/js-mobile-console/issues" 22 | }, 23 | "homepage": "https://github.com/B1naryStudio/js-mobile-console" 24 | } 25 | -------------------------------------------------------------------------------- /style/mobile-console.css: -------------------------------------------------------------------------------- 1 | #js-mobile-console{ 2 | width: 100%; 3 | position: fixed; 4 | bottom: 10px; 5 | z-index: 10000; 6 | } 7 | 8 | #jsmc-log{ 9 | border-top: 1px solid #808080; 10 | max-height: 100px; 11 | overflow-y: auto; 12 | margin-right: 10px; 13 | font-size: 1em; 14 | } 15 | 16 | #jsmc-input-container{ 17 | position: relative; 18 | } 19 | 20 | #jsmc-input{ 21 | width: 100%; 22 | } 23 | 24 | #jsmc-button{ 25 | position: fixed; 26 | bottom: 31px; 27 | right: 48px; 28 | } 29 | 30 | .jsmc-log-el{ 31 | color: red; 32 | } 33 | 34 | .jsmc-log-error .jsmc-log-text{ 35 | color: red; 36 | } 37 | 38 | .jsmc-log-command .jsmc-log-text{ 39 | color: #009EE4; 40 | } 41 | 42 | .jsmc-log-text{ 43 | color: black; 44 | } 45 | 46 | .jsmc-log-target{ 47 | color: #7979EF; 48 | } 49 | 50 | .jsmc-log-target{ 51 | clear: both; 52 | } 53 | 54 | #jsmc-content{ 55 | background: white; 56 | clear: both; 57 | } 58 | 59 | #jsmc-collapse, 60 | #jsmc-commands, 61 | #jsmc-clear{ 62 | cursor: pointer; 63 | width: 25px; 64 | background: white; 65 | text-align: center; 66 | color: gray; 67 | border: 1px solid #808080; 68 | display: inline-block; 69 | } 70 | 71 | #jsmc-clear{ 72 | font-weight: bolder; 73 | height: 19px; 74 | } 75 | 76 | #jsmc-commands{ 77 | display: none; 78 | } 79 | 80 | #jsmc-commands-container{ 81 | background: #FFF; 82 | display: none; 83 | } 84 | 85 | .jsmc-command-wrapper{ 86 | border-bottom: 1px solid #808080; 87 | padding: 0px 14px; 88 | cursor: pointer; 89 | } 90 | 91 | .jsmc-command:last-child{ 92 | border-bottom: none; 93 | } -------------------------------------------------------------------------------- /style/mobile-console.min.css: -------------------------------------------------------------------------------- 1 | #js-mobile-console{width:100%;position:fixed;bottom:10px;z-index:10000}#jsmc-log{border-top:1px solid #808080;max-height:100px;overflow-y:auto;margin-right:10px;font-size:1em}#jsmc-input-container{position:relative}#jsmc-input{width:100%}#jsmc-button{position:fixed;bottom:31px;right:48px}.jsmc-log-el{color:red}.jsmc-log-error .jsmc-log-text{color:red}.jsmc-log-command .jsmc-log-text{color:#009ee4}.jsmc-log-text{color:black}.jsmc-log-target{color:#7979ef}.jsmc-log-target{clear:both}#jsmc-content{background:white;clear:both}#jsmc-collapse,#jsmc-commands,#jsmc-clear{cursor:pointer;width:25px;background:white;text-align:center;color:gray;border:1px solid #808080;display:inline-block}#jsmc-clear{font-weight:bolder;height:19px}#jsmc-commands{display:none}#jsmc-commands-container{background:#FFF;display:none}.jsmc-command-wrapper{border-bottom:1px solid #808080;padding:0 14px;cursor:pointer}.jsmc-command:last-child{border-bottom:0} 2 | --------------------------------------------------------------------------------