index.js 18 KB

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