index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. // TODO - Add initial page loading and handlers
  2. (function($,History,console){
  3. var State = History.getState(),
  4. Old = {},
  5. Key = null,
  6. flags = [],
  7. templates = [],
  8. flag = window.flag = function(name,value){
  9. if(exists(value)){
  10. flags[name] = value;
  11. }else{
  12. return exists(flags[name])?flags[name]:false;
  13. }
  14. },
  15. settings = {},
  16. exists = function(v){
  17. return typeof v != 'undefined';
  18. },
  19. get = window.get = function(s){
  20. return settings[s];
  21. },
  22. set = window.set = function(s,v){
  23. settings[s] = v;
  24. return v;
  25. },
  26. setKey = window.setKey = function(key){
  27. if(key !== null){
  28. console.log('Key change to '+key);
  29. Key = key;
  30. var d = new Date();
  31. d.setTime(d.getTime()+get('expire'));
  32. $.cookie('key',key,{
  33. expires: d
  34. });
  35. }else{
  36. console.log('Key deleted');
  37. Key = null;
  38. $.removeCookie('key');
  39. }
  40. },
  41. getKey = window.getKey = function(){
  42. return Key;
  43. },
  44. template = window.template = function(name,template){
  45. var d = +new Date,
  46. id = (function(name){
  47. for(var i in templates){
  48. if(templates[i].name == name){
  49. return i;
  50. }
  51. }
  52. return false;
  53. })(name);
  54. if(exists(template)){
  55. if(template === null){
  56. if(id!==false){
  57. templates.splice(id,1);
  58. }
  59. $.localStorage('templates',templates);
  60. console.log('Dropping template for: '+name);
  61. return '';
  62. }else{
  63. var o = {
  64. name: name,
  65. template: template,
  66. date: get('expire')+d
  67. }
  68. if(id===false){
  69. console.log('Storing new template for: '+name);
  70. templates.push(o);
  71. }else{
  72. console.log('Replacing old template for: '+name);
  73. templates[id] = o;
  74. }
  75. $.localStorage('templates',templates);
  76. }
  77. }else if(id!==false){
  78. console.log('Using cached template for: '+name);
  79. var template = templates[id].template;
  80. if(templates[id].date < d){
  81. delete templates[name];
  82. $.localStorage('templates',templates);
  83. }
  84. return template;
  85. }else{
  86. console.log('No cached template stored for: '+name);
  87. return '';
  88. }
  89. },
  90. apiCall = window.apiCall = function(data,callback){
  91. console.log('apiCall('+data.type+'-'+data.id+')');
  92. if(!flag('error')){
  93. loading(true);
  94. data.get = 'api';
  95. data.back = State.data.back;
  96. data.timestamp = +new Date;
  97. if(''!=template(data.type+'-'+data.id)){
  98. data.template = false;
  99. }
  100. $.get(location.href,data,function(d){
  101. if(exists(d['error'])){
  102. error(d);
  103. }else{
  104. console.log(d);
  105. d.state.title = d.state.title.capitalize();
  106. if(location.href.substr(location.href.lastIndexOf('/')+1) != d.state.url && d.state.url != ''){
  107. console.log('Forced redirection to '+d.state.url);
  108. History.replaceState(d.state.data,d.state.title,d.state.url);
  109. getNewState();
  110. }
  111. document.title = d.state.title;
  112. }
  113. if(exists(callback)){
  114. console.log('Running apiCall callback');
  115. callback(d);
  116. }
  117. },'json');
  118. }
  119. },
  120. loadState = window.loadState = function(href,callback){
  121. console.log('loadState('+href+')');
  122. if(!flag('error')){
  123. loading(true);
  124. var data = {
  125. get:'state',
  126. timestamp: +new Date,
  127. back: location.href
  128. };
  129. ajax = $.ajax(href,{
  130. data: data,
  131. async: true,
  132. type: 'GET',
  133. success: function(d){
  134. if(exists(d['error'])){
  135. error(d);
  136. }else{
  137. d.state.title = d.state.title.capitalize();
  138. d.state.url = href;
  139. console.log('pushState: '+d.state.title+'['+href+']');
  140. History.pushState(d.state.data,d.state.title,href);
  141. getNewState();
  142. }
  143. if(exists(callback)){
  144. callback(d);
  145. }
  146. if(!exists(d['error'])){
  147. flag('handled',true);
  148. stateChange();
  149. flag('handled',false);
  150. }
  151. },
  152. error: function(x,t,e){
  153. error({
  154. error: '['+t+'] '+e
  155. });
  156. if(exists(callback)){
  157. callback({
  158. error: '['+t+'] '+e
  159. });
  160. }
  161. },
  162. dataType: 'json'
  163. });
  164. }
  165. },
  166. replaceState = window.replaceState = function(href,callback){
  167. console.log('apiState('+href+')');
  168. if(!flag('error')){
  169. loading(true);
  170. var data = {
  171. get:'state',
  172. timestamp: +new Date,
  173. back: State.data.back
  174. };
  175. $.ajax(href,{
  176. data: data,
  177. async: true,
  178. type: 'GET',
  179. success: function(d){
  180. if(exists(d['error'])){
  181. error(d);
  182. }else{
  183. d.state.title = d.state.title.capitalize();
  184. d.state.url = href;
  185. console.log('pushState: '+d.state.title+'['+href+']');
  186. History.replaceState(d.state.data,d.state.title,href);
  187. getNewState();
  188. }
  189. if(exists(callback)){
  190. callback(d);
  191. }
  192. console.log(d.state.title);
  193. if(!exists(d['error'])){
  194. flag('handled',true);
  195. stateChange();
  196. flag('handled',false);
  197. }
  198. },
  199. error: function(x,t,e){
  200. error({
  201. error: '['+t+'] '+e
  202. });
  203. if(exists(callback)){
  204. callback({
  205. error: '['+t+'] '+e
  206. });
  207. }
  208. },
  209. dataType: 'json'
  210. });
  211. }
  212. },
  213. error = function(e,callback){
  214. if(!flag('error')){
  215. flag('error',true);
  216. var msg = '['+State.url+']'+e.error;
  217. console.error(msg.trim()+"\n"+(exists(e.state)?JSON.stringify(e.state):''));
  218. alert(msg.trim(),'Error',callback);
  219. }
  220. },
  221. getNewState = function(state){
  222. State = History.getState();
  223. console.log("State change: ["+State.title+"] "+JSON.stringify(State.data));
  224. if(!window.location.origin){
  225. window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
  226. }
  227. },
  228. stateChange = function(){
  229. getNewState();
  230. if(!equal(State.data,Old)){
  231. switch(State.data.type){
  232. case 'page':case 'user':case 'project':
  233. apiCall(State.data,function(d){
  234. if(!exists(d.error)){
  235. if(exists(d.context)){
  236. if(!exists(d.context.key)&&Key!==null){
  237. console.log('Context detected console.logout');
  238. setKey(null);
  239. }
  240. if(exists(d.template)){
  241. template(State.data.type+'-'+State.data.id,d.template);
  242. }else{
  243. d.template = template(State.data.type+'-'+State.data.id);
  244. }
  245. render.topbar(d.topbar.template,d.topbar.context);
  246. render.content(d.template,d.context);
  247. $(window).resize();
  248. loading(false);
  249. }else{
  250. console.error('No context given');
  251. back();
  252. }
  253. }else{
  254. error(d);
  255. back();
  256. }
  257. });
  258. break;
  259. case 'action':break;
  260. default:
  261. error({
  262. url: State.url,
  263. error: "Something went wrong!"
  264. });
  265. }
  266. Old = State.data;
  267. }else{
  268. console.log(State.data,Old);
  269. console.warn('Stopped double load of '+Old.type+'-'+Old.id);
  270. loading(false);
  271. }
  272. },
  273. equal = function(o1,o2){
  274. for(var i in o1){
  275. if(!exists(o2[i])||o2[i]!=o1[i]){
  276. return false;
  277. }
  278. }
  279. for(i in o2){
  280. if(!exists(o1[i])||o2[i]!=o1[i]){
  281. return false;
  282. }
  283. }
  284. return true;
  285. },
  286. render = window.render = {
  287. topbar: function(t,c){
  288. $('#topbar').html(Handlebars.compile(t)(c));
  289. render.links('#topbar');
  290. render.buttons('#topbar');
  291. render.menus('#topbar');
  292. render.time('#topbar');
  293. if(State.url == location.origin+'/page-index'){
  294. $('#topbar').find('.topbar-history').hide();
  295. }
  296. $('#topbar').addClass('overflow-hide');
  297. $(window).resize();
  298. },
  299. content: function(t,c){
  300. $(document).unbind('ready');
  301. $('#content').html(
  302. Handlebars.compile(t)(c)
  303. );
  304. render.links('#content');
  305. render.buttons('#content');
  306. render.accordions('#content');
  307. render.menus('#content');
  308. render.form('#content');
  309. render.time('#content');
  310. $(window).resize();
  311. },
  312. time: function(selector){
  313. $(selector).find('time.timeago').each(function(){
  314. var time = new Date($(this).text()*1000);
  315. $(this).replaceWith(
  316. $('<abbr>').attr({
  317. 'title': time.toISOString(),
  318. 'style': $(this).attr('style')
  319. }).addClass($(this).attr('class')).timeago()
  320. );
  321. });
  322. },
  323. accordions: function(selector){
  324. $(selector).find('.accordion').each(function(){
  325. var icons = {};
  326. if($(this).children('.icons').length == 1){
  327. icons = JSON.parse($(this).children('.icons').text());
  328. }
  329. $(this).children('.icons').remove();
  330. $(this).accordion({
  331. collapsible: true,
  332. icons: icons,
  333. active: false
  334. });
  335. });
  336. },
  337. buttons: function(selector){
  338. $(selector).find('.button').button();
  339. $(selector).find('input[type=submit]').button();
  340. $(selector).find('input[type=button]').button();
  341. $(selector).find('button').button();
  342. render.comment.buttons(selector);
  343. },
  344. comment: {
  345. buttons: function(selector){
  346. $(selector).find('.comment').each(function(){
  347. var context = JSON.parse($(this).text());
  348. $(this).text(context.text);
  349. $(this).button();
  350. $(this).click(function(){
  351. render.comment.dialog(context.id,context.type,context.title);
  352. });
  353. });
  354. },
  355. dialog: function(id,type,title){
  356. loading(true);
  357. flag('ignore_statechange',true);
  358. $('#comment').find('form').find('input[name=comment_type]').val(type);
  359. $('#comment').find('form').find('input[name=comment_id]').val(id);
  360. $('#comment').find('form').find('textarea[name=message]').val('');
  361. $('#comment').dialog({
  362. close: function(){
  363. $('#comment').find('form').find('input[name=comment_type]').val('');
  364. $('#comment').find('form').find('input[name=comment_id]').val('');
  365. },
  366. resizable: false,
  367. draggable: false,
  368. title: title,
  369. buttons: [
  370. {
  371. text: 'Ok',
  372. class: 'recommend-force',
  373. click: function(){
  374. var diag = $(this),
  375. context = diag.find('form').serializeObject();
  376. if(context.message !== ''){
  377. context.type = 'action';
  378. context.id = 'comment';
  379. context.url = State.url;
  380. context.title = State.title;
  381. apiCall(context,function(d){
  382. if(!exists(d.error)){
  383. diag.dialog('close');
  384. flag('ignore_statechange',false);
  385. loading(false);
  386. }
  387. });
  388. }
  389. }
  390. },{
  391. text: 'Cancel',
  392. class: 'cancel-force',
  393. click: function(){
  394. $(this).dialog('close');
  395. flag('ignore_statechange',false);
  396. loading(false);
  397. }
  398. }
  399. ]
  400. });
  401. },
  402. },
  403. menus: function(selector){
  404. $(selector).find('.menu').css({
  405. 'list-style':'none'
  406. }).menu({
  407. icons:{
  408. submenu: "ui-icon-circle-triangle-e"
  409. }
  410. }).removeClass('ui-corner-all').addClass('ui-corner-bottom').parent().click(function(e){
  411. e.stopPropagation();
  412. });
  413. },
  414. form: function(selector){
  415. $(selector).find('#form').width('320px').children();
  416. render.inputs(selector);
  417. },
  418. inputs: function(selector){
  419. $(selector).find('input[type=text],input[type=password]').each(function(){
  420. var input = $(this),
  421. height = input.height()>=17?17:input.height();
  422. input.siblings('.input-clear').remove();
  423. input.off('focus').off('blur').after(
  424. $('<div>').css({
  425. position: 'absolute',
  426. right: $(window).width() - (input.outerWidth() + input.position().left)+2,
  427. top: input.position().top+2,
  428. 'background-image': 'url(img/headers/icons/clear.png)',
  429. 'background-position': 'center',
  430. 'background-size': height+'px '+height+'px',
  431. 'background-repeat': 'no-repeat',
  432. width: input.height(),
  433. height: input.height(),
  434. cursor: 'pointer'
  435. }).hide().addClass('input-clear').mousedown(function(){
  436. input.val('');
  437. })
  438. );
  439. input.focus(function(){
  440. input.next().show();
  441. }).blur(function(e){
  442. input.next().hide();
  443. });
  444. });
  445. $(selector).find('input[type=text],input[type=password],textarea').each(function(){
  446. var input = $(this);
  447. if(input.hasClass('fill-width')){
  448. input.css('width','calc(100% - '+(input.outerWidth()-input.width())+'px)');
  449. }
  450. });
  451. },
  452. dialog: function(selector,title){
  453. $(selector).dialog({
  454. close: function(){
  455. flag('error',false);
  456. var fn = $(this).data('callback');
  457. if(exists(fn)){
  458. fn();
  459. }
  460. loading(false);
  461. },
  462. resizable: false,
  463. draggable: false,
  464. title: title,
  465. buttons: [
  466. {
  467. text: 'Ok',
  468. class: 'recommend-force',
  469. click: function(){
  470. $(this).dialog('close');
  471. }
  472. }
  473. ]
  474. });
  475. },
  476. links: function(selector){
  477. $(selector).find('a').each(function(){
  478. var href = this.href;
  479. if(href.indexOf('#')!=-1&&(href.indexOf(location.origin)!=-1||href.indexOf('#')==0)){
  480. href = href.substr(href.indexOf('#')+1);
  481. $(this).click(function(e){
  482. try{
  483. if(($(this).hasClass('topbar-home') || $(this).hasClass('topbar-back'))&&$(window).width()<767){
  484. $('#topbar').children('div.topbar-right,div.topbar-left').toggle();
  485. $('#topbar').toggleClass('overflow-hide');
  486. $(window).resize();
  487. }else if($(this).hasClass('topbar-history')){
  488. back();
  489. }else{
  490. loadState(href);
  491. }
  492. }catch(error){
  493. console.error(error);
  494. }
  495. e.preventDefault();
  496. return false;
  497. });
  498. }
  499. });
  500. }
  501. },
  502. back = window.back = function(reload){
  503. console.log('reload',exists(reload));
  504. var go = function(url){
  505. if(exists(reload)){
  506. console.log('FORCING RELOAD');
  507. location = url;
  508. }else{
  509. console.log('FORCING LOADSTATE');
  510. loadState(url);
  511. }
  512. }
  513. if(exists(State.data.back)){
  514. if(!History.back()){
  515. loadState(State.data.back);
  516. }
  517. }else if(State.data.type != 'page' || State.data.id != 'index'){
  518. go('page-index');
  519. }else{
  520. location.reload();
  521. }
  522. },
  523. alert = function(text,title,callback){
  524. $('#dialog').text(text).data('callback',callback);
  525. render.dialog('#dialog',title,callback);
  526. },
  527. loading = function(state){
  528. if(!flag('ignore_statechange')){
  529. state = exists(state)?state:false;
  530. console.log('loading state '+state);
  531. if(!flag('error') && !state){
  532. $('#loading').hide();
  533. }else if(state){
  534. $('#loading').show();
  535. }
  536. }
  537. };
  538. if(exists($.cookie('key'))){
  539. setKey($.cookie('key'));
  540. }else{
  541. setKey(null);
  542. }
  543. $(document).ready(function(){
  544. $.ajaxSetup({
  545. async: false,
  546. cache: false,
  547. timeout: 30000 // 30 seconds
  548. });
  549. $(document).ajaxError(function(event, request, settings) {
  550. error({error:'Request timed out'});
  551. });
  552. if(!exists($.support.touch)){
  553. $.support.touch = 'ontouchstart' in window || 'onmsgesturechange' in window;
  554. }
  555. $('#content').niceScroll({
  556. cursorwidth: 10,
  557. nativeparentscrolling: false,
  558. preservenativescrolling: false
  559. });
  560. $('#content,#topbar').click(function(){
  561. $('.menu').hide();
  562. });
  563. document.addEventListener('touchmove',function(e){
  564. e.preventDefault();
  565. });
  566. $(window).resize(function(){
  567. if($(window).width()>767){
  568. $('#topbar div.topbar-right, #topbar div.topbar-left').css({
  569. 'display': ''
  570. });
  571. }
  572. $('#content').height($('body').height()-$('#topbar').height());
  573. $('#content').getNiceScroll().resize();
  574. render.inputs('#content');
  575. render.inputs('#topbar');
  576. });
  577. $.get(location.href,{
  578. get: 'settings',
  579. timestamp: +new Date,
  580. back: false
  581. },function(d){
  582. if(!exists(d.error)){
  583. settings = d.settings;
  584. if(d.version != $.localStorage('version')){
  585. $.localStorage('version',d.version);
  586. $.localStorage('templates',null);
  587. templates = [];
  588. }else{
  589. templates = $.localStorage('templates');
  590. if(templates === null){
  591. templates = [];
  592. }
  593. }
  594. replaceState(location.href);
  595. }else{
  596. error(d.error);
  597. }
  598. },'json');
  599. $(window).on('statechange',function(){
  600. if(!flag('handled')){
  601. console.log('unhandled state change event');
  602. stateChange();
  603. }
  604. });
  605. var getState = History.getState;
  606. History.getState = function(flag){
  607. if(exists(flag)){
  608. return State;
  609. }else{
  610. return getState.call(History);
  611. }
  612. };
  613. });
  614. shortcut.add('f12',function(){
  615. if(!flag('firebug-lite')){
  616. $('head').append(
  617. $('<script>').attr({
  618. 'type': 'text/javascript',
  619. 'src': 'https://getfirebug.com/firebug-lite.js#startOpened',
  620. 'id': 'FirebugLite'
  621. })
  622. );
  623. $('<image>').attr('src','https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png');
  624. flag('firebug-lite',true);
  625. }
  626. });
  627. shortcut.add('Ctrl+f12',function(){
  628. if(!flag('manifesto')){
  629. if(window.applicationCache){
  630. if(window.applicationCache.status==window.applicationCache.UNCACHED){
  631. $('head').append(
  632. $('<script>').attr({
  633. 'type': 'text/javascript',
  634. 'src': 'http://manifesto.ericdelabar.com/manifesto.js?x="+(Math.random())'
  635. })
  636. );
  637. (function wait(){
  638. if($('#cacheStatus').length == 0){
  639. setTimeout(wait,10);
  640. }else{
  641. $('#cacheStatus').niceScroll();
  642. }
  643. })();
  644. }else{
  645. alert("Manifest file is valid.");
  646. }
  647. }else{
  648. alert("This browser does not support HTML5 Offline Application Cache.");
  649. }
  650. flag('manifesto',true);
  651. }
  652. });
  653. shortcut.add('Shift+f12',function(){
  654. templates = [];
  655. $.localStorage('templates',null);
  656. console.log('Templates cleared.');
  657. });
  658. $.fn.serializeObject = function(){
  659. var o = {},
  660. a = this.serializeArray();
  661. $.each(a,function(){
  662. if(o[this.name] !== undefined){
  663. if(!o[this.name].push){
  664. o[this.name] = [o[this.name]];
  665. }
  666. o[this.name].push(this.value || '');
  667. }else{
  668. o[this.name] = this.value || '';
  669. }
  670. });
  671. return o;
  672. };
  673. String.prototype.capitalize = function(lower) {
  674. return (lower?this.toLowerCase():this).replace(/(?:^|\s)\S/g, function(a){
  675. return a.toUpperCase();
  676. });
  677. };
  678. })(jQuery,History,console);