app.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. jQuery( document ).ready(function( $ ) {
  2. $.ajax( { url: 'config.json', dataType: 'json' } ).done( function( config ) {
  3. var c = config;
  4. var log = $('#requests'), // where the requests are listed
  5. initialWidth = $.cookie('panelWidth'), // save the width of the left panel
  6. panel = $('#panel'), // a reference to the left panel
  7. inputs = $('.inputs'), // all input divs
  8. resizing = false, // if the user is currently resizing the panel
  9. canResize = false, // if the user's mouse is in a location where resizing can be activated
  10. lastX = null, // track the last mouse x position
  11. resizeDetector = function(e){ // function to detect if the user can activate resizing
  12. var padding = 5, width = panel.width(), left = width, right = width + padding;
  13. if (lastX == e.pageX) return;
  14. lastX = e.pageX;
  15. if (lastX > left && lastX < right) {
  16. canResize = true;
  17. document.body.style.cursor = "ew-resize !important";
  18. } else {
  19. canResize = false;
  20. document.body.style.cursor = null;
  21. }
  22. },
  23. resizePerformer = function(e){ // attached as a mouse listener to perform resizing of the panel
  24. var min = 220, max = window.innerWidth - 400, width = Math.max(Math.min(max, e.pageX), min);
  25. e.preventDefault();
  26. panel.width(width);
  27. log.css({left:width});
  28. },
  29. $span = $( '<span/>' ),
  30. safeText = function( string ) {
  31. return $span.text( string ).html();
  32. };
  33. panel.width(initialWidth); // set the initial width of the panel, can come from cookie
  34. log.css({left:initialWidth+'px'}); // resize the log to fit with the panel
  35. $(window).bind('resize', function(){ // listen to resizes to scale the panel/log correctly
  36. var min = 220, max = window.innerWidth - 400, width = Math.max(Math.min(max, panel.width()), min);
  37. panel.width(width);
  38. log.css({left:width});
  39. $.cookie('panelWidth', panel.width());
  40. }).trigger('resize');
  41. $(document).bind('mousemove', resizeDetector); // track the position of the mouse
  42. $(document).bind('mousedown', function(e){ // determine if user is activating panel resize
  43. if(!canResize) return;
  44. e.preventDefault();
  45. resizing = true;
  46. $(document)
  47. .unbind('mousemove', resizeDetector)
  48. .bind('mousemove', resizePerformer);
  49. });
  50. $(document).bind('mouseup', function(e){ // stop resizing action and save the panel width
  51. if(!resizing) return;
  52. resizing = false;
  53. $.cookie('panelWidth', panel.width());
  54. $(document)
  55. .unbind('mousemove', resizePerformer)
  56. .bind('mousemove', resizeDetector);
  57. });
  58. // expand/collapse response views
  59. $('ul#requests').click(function(e){
  60. var $target = $(e.target),
  61. $h2 = $target.is('h2') ? $target : $target.parents('h2');
  62. if (!$h2.is('h2')) return;
  63. $li = $h2.parents('li');
  64. $li[$li.hasClass('expanded') ? 'removeClass' : 'addClass']('expanded');
  65. });
  66. // add expand/collapse interations to the response nodes
  67. var initializeResponse = function(object){
  68. object.find('ul.object, ul.array').each(function(){
  69. var $this = $(this),
  70. $li = $this.parent(),
  71. $disclosure = $("<a>&#x25B8;</a>").addClass('disclosure').click(function(){
  72. $li[$li.hasClass('closed') ? 'removeClass' : 'addClass']('closed');
  73. });
  74. $li.addClass('closed').append($disclosure);
  75. });
  76. return object;
  77. }
  78. // outputs a single line of JSON
  79. var rawResponse = function(object, formatter){
  80. return JSON.stringify(object, null, formatter);
  81. };
  82. // build the response HTML nodes from the parsed JSON object
  83. var formatResponse = function(object, keyPath){
  84. var node, li;
  85. if (!keyPath) keyPath = [];
  86. if (!object || object.constructor == Array && object.length == 0) return;
  87. if ('object' === typeof(object)) {
  88. node = $("<ul></ul>").addClass('object');
  89. li;
  90. for(field in object){
  91. li = $("<li>");
  92. li.append($("<span></span>").addClass('key').text(field)).append(' ');
  93. if ( object && object[field] && ('array' === typeof(object[field]) || Array == object[field].constructor)) {
  94. li
  95. .append(
  96. $("<span></span>")
  97. .text("Array["+object[field].length+"]")
  98. .addClass('hint')
  99. );
  100. } else if('object' === typeof(object[field])) {
  101. li
  102. .append($("<span></span>")
  103. .text("Object")
  104. .addClass('hint')
  105. );
  106. }
  107. li.append(formatResponse(object[field], keyPath.concat([field])));
  108. node.append(li);
  109. }
  110. } else {
  111. if (object === undefined) return $("<span>undefined</span>");;
  112. var str = object.toString(), matches;
  113. node = $('<span>').addClass('value').addClass(typeof(object)).text(str)
  114. if (matches = str.match("^" + config.api_url + "(/.*)")) { // IF it's an API URL make it clickable
  115. node.click(function(){
  116. $('form').trigger('reset');
  117. $('[name=path]').val(matches[1]).parents('form').trigger('submit');
  118. log.animate({scrollTop:'0'}, 'fast');
  119. }).addClass('api-url');
  120. } else if(str.match(/^https?:\/\/[^.]+\.gravatar\.com\/avatar/)){ // If it's an Avatar let's add an img tag
  121. node = $('<img>').attr( 'src', str ).addClass('avatar').add(node);
  122. }
  123. }
  124. return node;
  125. }
  126. // the path build
  127. var $pathField = $("[name=path]"),
  128. $queryField = $("[name=query]"),
  129. $bodyField = $("[name=body]"),
  130. helpTimer = null,
  131. formatHelp = function(variable, help){
  132. var $node = $helpBubble.children().first();
  133. $node.html("");
  134. $node.append(
  135. $("<h1></h1>")
  136. .append($("<code></code>").text(variable))
  137. .append(" ")
  138. .append($("<em></em>").text(help.type))
  139. );
  140. switch(help.description.constructor){
  141. case String:
  142. $node.append($("<p></p>").text(help.description));
  143. break;
  144. case Object:
  145. var $dl = $('<dl>').appendTo($node);
  146. $.each(help.description, function(key, val){
  147. $dl.append("<dt>" + safeText( key ) + "</dt><dd>" + safeText( val ) + "</dd>")
  148. })
  149. break;
  150. default:
  151. $node.append("<p><em>Not available</em></p>")
  152. break;
  153. }
  154. },
  155. objectBuilder = function(){ // creates an interface to allow the user to enter values for each key in an object, also shows hints if available
  156. var $builder = $("<div>").addClass('object-builder'), values ={}, keys = [], help = {};
  157. $builder.setObject = function(object, helpValues){
  158. $builder.html('');
  159. keys = []
  160. help = helpValues;
  161. $.each(object, function(key, value){
  162. if(keys.indexOf(key) == -1) keys.push(key);
  163. values[key] = object[key] ? object[key] : values[key];
  164. $builder.append(
  165. $("<div>")
  166. .addClass('object-property')
  167. .append(
  168. $('<div>').addClass('object-property-key').text(key).bind('click', function(e){
  169. $(this).next().focus();
  170. })
  171. )
  172. .append(
  173. $('<div>').addClass('object-property-value').attr('contenteditable', true).text(values[key] || "").bind('keyup', function(){
  174. values[key] = $(this).text();
  175. $builder.trigger('valuechanged', [key, values[key]]);
  176. }).bind('focus', function(){
  177. // show the help
  178. // formatHelp(key, helpValues[key])
  179. $helpBubble.show().css({
  180. left:panel.width()+3,
  181. top: $(this).parent().offset().top - $helpBubble.height()*0.5 + $(this).parent().height()*0.5
  182. })
  183. }).bind('blur', function(){
  184. $helpBubble.hide();
  185. })
  186. )
  187. .hover(
  188. function(){
  189. var $row = $(this);
  190. helpTimer = setTimeout(function(){
  191. // formatHelp(key, helpValues[key])
  192. $helpBubble.show().css({
  193. left:panel.width() + 3,
  194. top: $row.offset().top - $helpBubble.height()*0.5 + $row.height()*0.5
  195. });
  196. }, 1000);
  197. },
  198. function(){
  199. clearTimeout(helpTimer);
  200. $helpBubble.hide();
  201. }
  202. )
  203. )
  204. }); // each
  205. }
  206. var empty = function(item){
  207. return !item || item == "";
  208. }
  209. $builder.getObject = function(){
  210. var output = {};
  211. $.each(keys, function(index, key){
  212. if ( !empty(values[key]) ){
  213. output[key] = values[key];
  214. }
  215. });
  216. return output;
  217. }
  218. return $builder;
  219. },
  220. $pathBuilder = objectBuilder(),
  221. $queryBuilder = objectBuilder(),
  222. $bodyBuilder = objectBuilder();
  223. // $path, $query, $body
  224. $pathField.parents('div').first().append($pathBuilder);
  225. $queryField.parents('div').first().append($queryBuilder);
  226. $bodyField.parents('div').first().append($bodyBuilder);
  227. // the help bubble used by the object_builder to display contextual help information
  228. var $helpBubble = $('<div id="help-bubble"><div></div></div>');
  229. $helpBubble.appendTo(document.body).hide();
  230. inputs.bind('scroll', function(){
  231. $helpBubble.hide();
  232. });
  233. // load up the help docs from the API and build a clickable list of API endpoints
  234. var $reference = $('#reference'), $list = $reference.find('ul'), $fields = $('.inputs > div').not('#reference');
  235. $reference.append($("<div><div></div></div>").addClass('throbber'));
  236. $.ajax( { url: config.api_url + "/" } ).done( function( response ) {
  237. var auth = wpApiAuth({
  238. oauth_consumer_key: config.client_key,
  239. oauth_secret: config.client_secret,
  240. url: config.api_url,
  241. urls: response.authentication.oauth1
  242. });
  243. window.auth = auth;
  244. $( '#auth a' ).click( function(e) {
  245. e.preventDefault();
  246. auth.authenticate(function() {
  247. $( '#auth a' ).html( 'Authenticated!' );
  248. });
  249. } );
  250. if ( auth.authenticated() ) {
  251. $( '#auth a' ).html( 'Authenticated!' );
  252. }
  253. $reference.find('.throbber').remove();
  254. $.each(response.routes, function(index){
  255. var help = response.routes[index];
  256. var helpGroup = safeText( help.group ),
  257. group = $list.find('.group-'+helpGroup);
  258. if (!group.is('li')) group = $('<li><strong>'+helpGroup+'</strong><ul></ul></li>').addClass('group-'+helpGroup).appendTo($list);
  259. help.supports = help.supports || [];
  260. $.each(help.supports, function (method_index, method) {
  261. if (method === 'HEAD') {
  262. return;
  263. }
  264. group.find('ul').append(
  265. $('<li>')
  266. .append($('<span class="path-details"></span>').text(method + " " + index))
  267. // .append($('<span class="description"></span>').text(help.description))
  268. .click(function(e){
  269. var $target = $(e.target),
  270. $li = $target.is('li') ? $target : $target.parents('li').first()
  271. if($li.hasClass('selected')) return;
  272. $reference.find('li.selected').removeClass('selected');
  273. $li.addClass('selected');
  274. var pathProps = {};
  275. var pathMatches = index.match(/[^<>]+(?=>)/g);
  276. if (pathMatches && pathMatches.length) {
  277. $.each(pathMatches, function (index, value) {
  278. var pattern = "<" + value + ">";
  279. pathProps[pattern] = "";
  280. });
  281. }
  282. $pathBuilder.setObject(pathProps, {});
  283. $.each({'path':$pathBuilder, 'query':$queryBuilder, 'body':$bodyBuilder}, function(prop, builder){
  284. var o = {};
  285. // $.each(help.request[prop], function(key, settings){
  286. // o[key] = ""
  287. // });
  288. // builder.setObject(o, help.request[prop]);
  289. });
  290. $pathField.val(index).trigger('keyup');
  291. $fields.find('.raw-toggle').removeClass('on').show().trigger('toggle', [false]);
  292. $fields.first().hide().find('select').val(method);
  293. $fields.last()[method == 'POST' ? 'show' : 'hide']();
  294. })
  295. )
  296. });
  297. });
  298. })
  299. // create a toggle so the object_builder can be replaced with a raw input field
  300. $fields.has('input, textarea').each(function(){
  301. var $field = $(this),
  302. $toggle = $('<span class="raw-toggle">Raw</span>')
  303. .click(function(){ $(this).toggleClass('on').trigger('toggle', [$(this).hasClass('on')]) })
  304. $field.append($toggle);
  305. $toggle
  306. .bind('toggle', function(e, on){
  307. $field.find('input, textarea')[on ? 'show' : 'hide']();
  308. $field.find('.object-builder')[on ? 'hide' : 'show']();
  309. })
  310. .hide()
  311. .addClass('on')
  312. .trigger('toggle', [true])
  313. });
  314. // clear the form
  315. $('form').bind('reset', function(){
  316. $fields.show();
  317. $fields.find('.raw-toggle').hide().addClass('on').trigger('toggle', [true]);
  318. $reference.find('.selected').removeClass('selected');
  319. })
  320. // replace a string with placeholders with the given values
  321. var interpolate = function(string, values){
  322. var string = string.slice();
  323. $.each(values, function(key, value){
  324. string = string.replace(key, value)
  325. })
  326. return string;
  327. }
  328. // User has submitted the form, build the request and send it
  329. $('form[action="#request"]').submit(function(e){
  330. e.preventDefault();
  331. // let's build a request;
  332. var req = {
  333. path: $(this.path).parent().siblings().is('.raw-toggle.on') ? this.path.value : interpolate(this.path.value, $pathBuilder.getObject()),
  334. method:this.method.value,
  335. data:$(this.body).parent().siblings().is('.raw-toggle.on') ? this.body.value : $bodyBuilder.getObject(),
  336. beforeSend: auth.ajaxBeforeSend
  337. };
  338. req.query = query = $(this.query).parent().siblings().is('.raw-toggle.on') ? this.query.value : $queryBuilder.getObject();
  339. req.url = config.api_url + req.path + '?';
  340. request = $('<li></li>').prependTo(log).addClass('loading'),
  341. title = $("<h2><small>"+safeText( req.method )+"</small> "+safeText( req.path )+"</h2>").appendTo(request);
  342. var q;
  343. if (typeof(req.query) == 'object') {
  344. q = $.param(req.query);
  345. } else {
  346. q = req.query
  347. }
  348. req.url += q;
  349. if(typeof(q) == 'string' && q.trim() != "") {
  350. title.append($('<em>').text((q[0] == "?" ? "" : "?") + q))
  351. }
  352. throbber = $('<div><div></div></div>').addClass('throbber').appendTo(request),
  353. timer = (new Date).getTime();
  354. console.log('test');
  355. console.log(req);
  356. $.ajax( req ).done( function(response, statusCode){
  357. var responseTime = (new Date).getTime() - timer;
  358. request
  359. .append(
  360. $('<span class="response-meta"></span>')
  361. .append($('<span class="response-time"></span>').text(responseTime + " ms "))
  362. .append($('<span class="status-code"></span>').text(statusCode || "?").attr('data-status-code', statusCode || '4xx'))
  363. )
  364. setTimeout(function(){
  365. request.addClass('done').removeClass("loading");
  366. }, 10);
  367. var formats = $('<div>')
  368. .addClass('structured')
  369. .append(
  370. response ?
  371. initializeResponse(formatResponse(response)) :
  372. $('<span class="empty-response">empty response</span>')
  373. );
  374. formats = formats.add($('<pre>')
  375. .addClass('pretty')
  376. .text(rawResponse(response, "\t"))
  377. );
  378. formats = formats.add($('<pre>')
  379. .addClass('raw')
  380. .text(rawResponse(response))
  381. );
  382. $("<div>")
  383. .addClass("response")
  384. .append(formats.hide())
  385. .appendTo(request);
  386. $('<ul>')
  387. .addClass('tabs')
  388. .append("<li>Structured</li><li>Pretty</li><li>Raw</li>").click(function(e){
  389. var $this = $(this), $children = $this.children(), $li = $children.filter(e.target);
  390. $children.not($li).removeClass('selected');
  391. $li.addClass('selected');
  392. formats.hide().eq($children.index($li)).show();
  393. })
  394. .appendTo(request)
  395. .children().first().trigger('click');
  396. request.addClass('expanded');
  397. /* $.post( '<?php echo admin_url( 'admin-ajax.php' ); ?>', { action: 'console_request_performed', path: req.path, nonce: '<?php echo wp_create_nonce( 'console_request_km_nonce' ); ?>' } ); */
  398. });
  399. });
  400. });
  401. var QueryStringToHash = function QueryStringToHash (query) {
  402. var query_string = {};
  403. var vars = query.split("&");
  404. for (var i=0;i<vars.length;i++) {
  405. var pair = vars[i].split("=");
  406. pair[0] = decodeURIComponent(pair[0]);
  407. pair[1] = decodeURIComponent(pair[1]);
  408. // If first entry with this name
  409. if (typeof query_string[pair[0]] === "undefined") {
  410. query_string[pair[0]] = pair[1];
  411. // If second entry with this name
  412. } else if (typeof query_string[pair[0]] === "string") {
  413. var arr = [ query_string[pair[0]], pair[1] ];
  414. query_string[pair[0]] = arr;
  415. // If third or later entry with this name
  416. } else {
  417. query_string[pair[0]].push(pair[1]);
  418. }
  419. }
  420. return query_string;
  421. };
  422. });