app.js 19 KB

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