├── .gitignore ├── web ├── public │ ├── js │ │ ├── landing.js │ │ ├── mockupdata.js │ │ ├── upload.js │ │ ├── auth.js │ │ ├── model.js │ │ ├── mustache.min.js │ │ ├── helper.js │ │ ├── spec.js │ │ ├── underscore-min.js │ │ └── backbone-min.js │ ├── favicon.ico │ ├── img │ │ ├── 6v.jpg │ │ ├── 96.jpg │ │ ├── logo.png │ │ └── upload.png │ ├── network.manifest │ ├── app.manifest │ ├── css │ │ ├── landing.css │ │ └── application.css │ ├── 404.html │ ├── dashboard │ │ ├── index.html │ │ ├── pres.html │ │ └── template.html │ └── landing.html └── project.js ├── mobile ├── public │ ├── js │ │ ├── landing.js │ │ ├── model.js │ │ ├── auth.js │ │ ├── mustache.min.js │ │ ├── helper.js │ │ ├── spec.js │ │ ├── underscore-min.js │ │ ├── backbone-min.js │ │ ├── view.js │ │ └── app.js │ ├── favicon.ico │ ├── img │ │ ├── 6v.jpg │ │ ├── 96.jpg │ │ └── logo.png │ ├── network.manifest │ ├── app.manifest │ ├── css │ │ ├── landing.css │ │ └── application.css │ ├── 404.html │ ├── landing.html │ └── dashboard │ │ ├── pres.html │ │ ├── index.html │ │ └── template.html └── project.js ├── facebox ├── loading.gif ├── closelabel.png ├── facebox.css └── facebox.js ├── swfupload ├── swfupload.js └── swfupload.swf ├── README ├── README.org ├── settings.sample.js ├── helper.js ├── apivendor.js └── daemon.js /.gitignore: -------------------------------------------------------------------------------- 1 | settings.js 2 | *~ 3 | 4 | -------------------------------------------------------------------------------- /web/public/js/landing.js: -------------------------------------------------------------------------------- 1 | 2 | function compitable() { 3 | 4 | } -------------------------------------------------------------------------------- /mobile/public/js/landing.js: -------------------------------------------------------------------------------- 1 | 2 | function compitable() { 3 | 4 | } -------------------------------------------------------------------------------- /web/project.js: -------------------------------------------------------------------------------- 1 | 2 | exports.installViews = function (app) { 3 | }; 4 | -------------------------------------------------------------------------------- /facebox/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/facebox/loading.gif -------------------------------------------------------------------------------- /facebox/closelabel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/facebox/closelabel.png -------------------------------------------------------------------------------- /swfupload/swfupload.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/swfupload/swfupload.js -------------------------------------------------------------------------------- /swfupload/swfupload.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/swfupload/swfupload.swf -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/img/6v.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/web/public/img/6v.jpg -------------------------------------------------------------------------------- /web/public/img/96.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/web/public/img/96.jpg -------------------------------------------------------------------------------- /web/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/web/public/img/logo.png -------------------------------------------------------------------------------- /web/public/network.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | NETWORK: 3 | board 4 | api_login 5 | api_callback 6 | * -------------------------------------------------------------------------------- /mobile/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/mobile/public/favicon.ico -------------------------------------------------------------------------------- /mobile/public/img/6v.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/mobile/public/img/6v.jpg -------------------------------------------------------------------------------- /mobile/public/img/96.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/mobile/public/img/96.jpg -------------------------------------------------------------------------------- /mobile/public/network.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | NETWORK: 3 | board 4 | api_login 5 | api_callback 6 | * -------------------------------------------------------------------------------- /web/public/img/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/web/public/img/upload.png -------------------------------------------------------------------------------- /mobile/public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FanfouAPI/node-fanfou/HEAD/mobile/public/img/logo.png -------------------------------------------------------------------------------- /web/public/js/mockupdata.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var home_timeline = [ 4 | {id: 1, name:'mike', 'text': 'I lofo you', 'profile_image_url': '/img/96.jpg'}, 5 | {id: 2, name:'Jake', 'text': 'Mi200', 'profile_image_url': '/img/6v.jpg'} 6 | ]; 7 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | node-fanfou or fanfou.js 2 | 3 | The demonstration of fanfou api via backbone.js + nodejs. 4 | 5 | = Setup and run = 6 | * create a settings.js according to settings.sample.js and fill settings fields. 7 | * run % node daemon.js 8 | * visit http://localhost:3000 9 | 10 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | node-fanfou or fanfou.js 2 | 3 | The demonstration of fanfou api via backbone.js + nodejs. 4 | 5 | h1. Setup and run 6 | * create a settings.js according to settings.sample.js and fill settings fields. 7 | * run % node daemon.js 8 | * visit http://localhost:3000 9 | 10 | -------------------------------------------------------------------------------- /settings.sample.js: -------------------------------------------------------------------------------- 1 | exports.oauth_info = { 2 | login_url: '/landing.html', 3 | api_host: 'http://api.fanfou.com', 4 | request_token_url: 'http://fanfou.com/oauth/request_token', 5 | access_token_url: 'http://fanfou.com/oauth/access_token', 6 | authorize_url: 'http://fanfou.com/oauth/authorize', 7 | consumer_key: 'xxx', 8 | consumer_secret: 'yyy' 9 | }; 10 | 11 | exports.daemon_port = 3000; 12 | exports.project = 'public'; -------------------------------------------------------------------------------- /web/public/app.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | /public/js/jquery-1.7.min.js 3 | /public/js/jquery.form.js 4 | /public/js/underscore-min.js 5 | /public/js/mustache.min.js 6 | /public/js/backbone-min.js 7 | /public/js/helper.js 8 | /public/js/spec.js 9 | /public/js/model.js 10 | /public/js/view.js 11 | /public/js/app.js 12 | /public/js/mockupdata.js 13 | /public/css/application.css 14 | /public/img/logo.png 15 | 16 | NETWORK: 17 | /board 18 | /api_callback 19 | /api_login 20 | * -------------------------------------------------------------------------------- /mobile/public/app.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | /public/mobile/js/jquery-1.7.min.js 3 | /public/mobile/js/jquery.form.js 4 | /public/mobile/js/underscore-min.js 5 | /public/mobile/js/mustache.min.js 6 | /public/mobile/js/backbone-min.js 7 | /public/mobile/js/helper.js 8 | /public/mobile/js/spec.js 9 | /public/mobile/js/model.js 10 | /public/mobile/js/view.js 11 | /public/mobile/js/app.js 12 | /public/mobile/js/mockupdata.js 13 | /public/mobile/css/application.css 14 | /public/mobile/img/logo.png 15 | 16 | NETWORK: 17 | * -------------------------------------------------------------------------------- /web/public/css/landing.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background: #DDD; 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | #content { 8 | margin: 0 auto; 9 | background: white; 10 | padding: 20px 10px; 11 | border: 1px solid #AAA; 12 | border-radius: 8px 8px; 13 | } 14 | 15 | #wording { 16 | min-height: 150px; 17 | } 18 | 19 | #wording strong { 20 | color: red; 21 | } 22 | 23 | #recent-change ul { 24 | /* list-style: none; */ 25 | padding-left: 20px; 26 | } 27 | -------------------------------------------------------------------------------- /mobile/public/css/landing.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background: #DDD; 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | #content { 8 | margin: 0 auto; 9 | background: white; 10 | padding: 20px 10px; 11 | border: 1px solid #AAA; 12 | border-radius: 8px 8px; 13 | } 14 | 15 | #wording { 16 | min-height: 150px; 17 | } 18 | 19 | #wording strong { 20 | color: red; 21 | } 22 | 23 | #recent-change ul { 24 | /* list-style: none; */ 25 | padding-left: 20px; 26 | } 27 | -------------------------------------------------------------------------------- /mobile/project.js: -------------------------------------------------------------------------------- 1 | 2 | var appCacheUrls = [ 3 | '/mobile/public/js/jquery-1.7.min.js', 4 | '/mobile/public/js/jquery.form.js', 5 | '/mobile/public/js/underscore-min.js', 6 | '/mobile/public/js/mustache.min.js', 7 | '/mobile/public/js/backbone-min.js', 8 | 9 | '/mobile/public/js/helper.js', 10 | '/mobile/public/js/spec.js', 11 | '/mobile/public/js/model.js', 12 | '/mobile/public/js/view.js', 13 | '/mobile/public/js/app.js', 14 | '/mobile/public/css/application.css', 15 | '/mobile/public/img/logo.png', 16 | '/mobile/public/dashboard/template.html', 17 | ]; 18 | 19 | exports.installViews = function (app, version) { 20 | app.get('/mobile/app.manifest', function (req, res) { 21 | var c = 'CACHE MANIFEST\n'; 22 | appCacheUrls.forEach(function (url) { 23 | url = url.replace('/mobile/public/', '/mobile/pub.' + version + '/'); 24 | c += url + '\n'; 25 | }); 26 | c += '\n'; 27 | c += 'NETWORK:\n'; 28 | c += '*\n'; 29 | res.setHeader('Content-Type', 'text/cache-manifest'); 30 | res.send(c); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /mobile/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ABC 7 | 34 | 35 | 36 |
37 |
38 |

网站暂时停止服务, 上班再恢复

39 |

饭否ABC是个通过手机访问饭否API的非正式开源网站, 41 | 主要目的探索一些web开发的前沿技术,如HTML5, API等,为各种饭否应用作技术准备。 42 |

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /web/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ABC 7 | 34 | 35 | 36 |
37 |
38 |

网站暂时停止服务, 上班再恢复

39 |

饭否ABC是个通过手机访问饭否API的非正式开源网站, 41 | 主要目的探索一些web开发的前沿技术,如HTML5, API等,为各种饭否应用作技术准备。 42 |

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /mobile/public/js/model.js: -------------------------------------------------------------------------------- 1 | // Backbone model 2 | var Status = Backbone.Model.extend({ 3 | }); 4 | 5 | var User = Backbone.Model.extend({ 6 | }); 7 | 8 | var Query = Backbone.Model.extend({}); 9 | 10 | var DirectMessage = Backbone.Model.extend({}); 11 | 12 | // Collections 13 | var Timeline = Backbone.Collection.extend({ 14 | model: Status, 15 | parse: function (resp, xhr) { 16 | var sk = exports.decodeTimeline(resp); 17 | return sk; 18 | } 19 | }); 20 | 21 | var QueryList = Backbone.Collection.extend({ 22 | model: Query 23 | }); 24 | 25 | var Trends = Backbone.Model.extend({ 26 | }); 27 | 28 | var UserList = Backbone.Collection.extend({ 29 | model: User, 30 | parse: function (resp, xhr) { 31 | var sk = exports.decodeUserList(resp); 32 | return sk; 33 | } 34 | }); 35 | 36 | var DMList = Backbone.Collection.extend({ 37 | model: DirectMessage, 38 | parse: function (resp, xhr) { 39 | var sk = exports.decodeDMList(resp); 40 | return sk; 41 | } 42 | }); 43 | 44 | var DMConvList = Backbone.Collection.extend({ 45 | model: DirectMessage, 46 | parse: function (resp, xhr) { 47 | var sk = exports.decodeDMConvList(resp); 48 | return sk; 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /web/public/js/upload.js: -------------------------------------------------------------------------------- 1 | 2 | var swfu; 3 | 4 | function initSWFUpload (form) { 5 | function on_file_queued(file) { 6 | console.info('file queued', file); 7 | $('#fileinfo', form).html(file.name); 8 | this.startUpload(file.id); 9 | } 10 | 11 | function on_upload_progress(file, loaded, total) { 12 | var prog = loaded * 100/total; 13 | $('#upload-progress', form).css('width', '' + prog + 'px'); 14 | console.info('upload progress', loaded/total); 15 | } 16 | 17 | function on_upload_success(file, data, resp) { 18 | console.info('upload success', file, data); 19 | $('#id_uploaded_file', form).val(data); 20 | } 21 | 22 | var swfoptions = { 23 | 'upload_url': '/upload', 24 | 'flash_url': '/swfupload/swfupload.swf', 25 | 'file_size_limit': '20 MB', 26 | 'button_placeholder_id': 'photo-upload', 27 | //'button_image_url': '/public/img/upload.png', 28 | 'button_width': 36, 29 | 'button_height': 30, 30 | 'button_text': '图片', 31 | //'button_text_style': 'font-size: 20px;, 32 | 'button_text_left_padding': 2, 33 | 'button_text_top_padding': 2, 34 | 'file_types': '*.jpg;*.gif;*.png', 35 | 'file_queue_limit': 1, 36 | 'prevent_swf_caching': false, 37 | 'file_queued_handler': on_file_queued, 38 | 'upload_progress_handler': on_upload_progress, 39 | 'upload_success_handler': on_upload_success 40 | }; 41 | swfu = new SWFUpload(swfoptions); 42 | } -------------------------------------------------------------------------------- /mobile/public/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timeline.CoreC 6 | 7 | 8 | 9 |
10 |
11 |

饭否TimelineCore是个通过手机访问饭否API的非正式开源网站, 13 | 主要目的探索一些web开发的前沿技术,如HTML5, API等,为各种饭否应用作技术准备。 14 |

15 |

16 | 为了集中力量在研究上,我们在UI上并没有过多加工,喜欢漂亮简洁界面的同学请访问 17 | fanfou.com. 18 |

19 |

由于HTML5/CSS3的技术要求,您的浏览器可能不能正常访问饭否ABC. 最好使用 20 | ios/android设备访问。 21 |

22 |
23 |
24 | 最新修改 25 | 35 |
36 |
37 | 登录(智能选择) >> 38 |
39 | 40 |
41 | 登录Mobile >> 42 |
43 |
44 | 登录Web >> 45 |
46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /facebox/facebox.css: -------------------------------------------------------------------------------- 1 | #facebox { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | z-index: 100; 6 | text-align: left; 7 | } 8 | 9 | 10 | #facebox .popup{ 11 | position:relative; 12 | border:3px solid rgba(0,0,0,0); 13 | -webkit-border-radius:5px; 14 | -moz-border-radius:5px; 15 | border-radius:5px; 16 | -webkit-box-shadow:0 0 18px rgba(0,0,0,0.4); 17 | -moz-box-shadow:0 0 18px rgba(0,0,0,0.4); 18 | box-shadow:0 0 18px rgba(0,0,0,0.4); 19 | } 20 | 21 | #facebox .content { 22 | display:table; 23 | width: 370px; 24 | padding: 10px; 25 | background: #fff; 26 | -webkit-border-radius:4px; 27 | -moz-border-radius:4px; 28 | border-radius:4px; 29 | } 30 | 31 | #facebox .content > p:first-child{ 32 | margin-top:0; 33 | } 34 | #facebox .content > p:last-child{ 35 | margin-bottom:0; 36 | } 37 | 38 | #facebox .close{ 39 | position:absolute; 40 | top:5px; 41 | right:5px; 42 | padding:2px; 43 | background:#fff; 44 | } 45 | #facebox .close img{ 46 | opacity:0.3; 47 | } 48 | #facebox .close:hover img{ 49 | opacity:1.0; 50 | } 51 | 52 | #facebox .loading { 53 | text-align: center; 54 | } 55 | 56 | #facebox .image { 57 | text-align: center; 58 | } 59 | 60 | #facebox img { 61 | border: 0; 62 | margin: 0; 63 | } 64 | 65 | #facebox_overlay { 66 | position: fixed; 67 | top: 0px; 68 | left: 0px; 69 | height:100%; 70 | width:100%; 71 | } 72 | 73 | .facebox_hide { 74 | z-index:-100; 75 | } 76 | 77 | .facebox_overlayBG { 78 | background-color: #000; 79 | z-index: 99; 80 | } -------------------------------------------------------------------------------- /web/public/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timeline.Core 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web/public/landing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Timeline.Core 6 | 7 | 8 | 9 |
10 | 25 | 26 |
27 | 28 |
29 |
30 |

饭否Timeline.Core是个API的试验平台 31 | 开源网站, 33 | 主要目的探索一些web开发的前沿技术,如HTML5, API等,为各种饭否应用作技术准备。 34 |

35 |

36 | 为了集中力量在研究上,我们在UI上并没有过多加工,喜欢漂亮简洁界面的同学请访问 37 | fanfou.com. 38 |

39 |

由于HTML5/CSS3的技术要求,您的浏览器可能不能正常访问最好使用HTML5/CSS3兼容浏览器访问

40 |
41 | 登录(智能选择模式) >> 42 |
43 |
44 | 登录Web模式 >> 45 |
46 |
47 | 登录简版(手机版) >> 48 |
49 |
50 |
51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /web/public/js/auth.js: -------------------------------------------------------------------------------- 1 | // Define the router 2 | var AuthRouter = Backbone.Router.extend({ 3 | routes: { 4 | '!/login': 'login', 5 | '!/register': 'register', 6 | '': "enter", 7 | }, 8 | 9 | enter: function () { 10 | window.location.hash = '#!/login'; 11 | }, 12 | 13 | register: function () { 14 | var view = new RegisterView({ 15 | 'el': $('#content'), 16 | }); 17 | view.render(); 18 | }, 19 | 20 | login: function () { 21 | var view = new LoginView({ 22 | 'el': $('#content'), 23 | }); 24 | view.render(); 25 | }, 26 | }); 27 | 28 | var Auth = function () { 29 | var auth_router; 30 | 31 | function template(temp_selector, data) { 32 | return Mustache.to_html($(temp_selector).html(), 33 | data); 34 | } 35 | 36 | function notify(text) { 37 | $('.notify').html(text).show(); 38 | } 39 | 40 | function headElement(leftel, centerel, rightel) { 41 | function setel(pos, el) { 42 | var e = $('#head-' + pos).empty(); 43 | if(el) { 44 | var h = $(''); 45 | var text; 46 | if(typeof el == 'string') { 47 | text = el; 48 | } else { 49 | text = el.text; 50 | if(el.href) { 51 | h.attr('href', el.href); 52 | } 53 | } 54 | h.html(text); 55 | e.append(h); 56 | } 57 | } 58 | 59 | setel('left', leftel); 60 | setel('center', centerel); 61 | setel('right', rightel); 62 | } 63 | 64 | 65 | function initialize() { 66 | auth_router = new AuthRouter(); 67 | Backbone.history.start(); 68 | $('#loading').bind('ajaxSend', function (){ 69 | $(this).show(); 70 | }).ajaxComplete(function () { 71 | $(this).hide(); 72 | }); 73 | 74 | $('.notify').click(function () { 75 | $(this).hide(); 76 | }); 77 | } 78 | // Exports 79 | return { 80 | 'notify': notify, 81 | 'initialize': initialize, 82 | 'template': template, 83 | 'headElement': headElement 84 | }; 85 | }(); -------------------------------------------------------------------------------- /mobile/public/js/auth.js: -------------------------------------------------------------------------------- 1 | // Define the router 2 | var AuthRouter = Backbone.Router.extend({ 3 | routes: { 4 | '!/login': 'login', 5 | '!/register': 'register', 6 | '': "enter", 7 | }, 8 | 9 | enter: function () { 10 | window.location.hash = '#!/login'; 11 | }, 12 | 13 | register: function () { 14 | var view = new RegisterView({ 15 | 'el': $('#content'), 16 | }); 17 | view.render(); 18 | }, 19 | 20 | login: function () { 21 | var view = new LoginView({ 22 | 'el': $('#content'), 23 | }); 24 | view.render(); 25 | }, 26 | }); 27 | 28 | var Auth = function () { 29 | var auth_router; 30 | 31 | function template(temp_selector, data) { 32 | return Mustache.to_html($(temp_selector).html(), 33 | data); 34 | } 35 | 36 | function notify(text) { 37 | $('.notify').html(text).show(); 38 | } 39 | 40 | function headElement(leftel, centerel, rightel) { 41 | function setel(pos, el) { 42 | var e = $('#head-' + pos).empty(); 43 | if(el) { 44 | var h = $(''); 45 | var text; 46 | if(typeof el == 'string') { 47 | text = el; 48 | } else { 49 | text = el.text; 50 | if(el.href) { 51 | h.attr('href', el.href); 52 | } 53 | } 54 | h.html(text); 55 | e.append(h); 56 | } 57 | } 58 | 59 | setel('left', leftel); 60 | setel('center', centerel); 61 | setel('right', rightel); 62 | } 63 | 64 | 65 | function initialize() { 66 | auth_router = new AuthRouter(); 67 | Backbone.history.start(); 68 | $('#loading').bind('ajaxSend', function (){ 69 | $(this).show(); 70 | }).ajaxComplete(function () { 71 | $(this).hide(); 72 | }); 73 | 74 | $('.notify').click(function () { 75 | $(this).hide(); 76 | }); 77 | } 78 | // Exports 79 | return { 80 | 'notify': notify, 81 | 'initialize': initialize, 82 | 'template': template, 83 | 'headElement': headElement 84 | }; 85 | }(); -------------------------------------------------------------------------------- /web/public/js/model.js: -------------------------------------------------------------------------------- 1 | // Backbone model 2 | var Status = Backbone.Model.extend({ 3 | }); 4 | 5 | var User = Backbone.Model.extend({ 6 | set_background: function () { 7 | var u = this.toJSON(); 8 | var body = $(document.body); 9 | if(u.profile_background_image_url) { 10 | body.removeClass('default') 11 | .css('background-color', u.profile_background_color) 12 | .css('background-image', 'url(' + u.profile_background_image_url + ')'); 13 | if(u.profile_background_tile) { 14 | body.css('background-repeat', 'repeat'); 15 | } else { 16 | body.css('background-repeat', 'no-repeat'); 17 | } 18 | } else { 19 | body.css('background-color', '') 20 | .css('background-image', '') 21 | .css('background-repeat', '') 22 | .addClass('default'); 23 | } 24 | } 25 | }); 26 | 27 | var Query = Backbone.Model.extend({}); 28 | var DirectMessage = Backbone.Model.extend({}); 29 | var List = Backbone.Model.extend({}); 30 | 31 | // Collections 32 | var Timeline = Backbone.Collection.extend({ 33 | model: Status, 34 | parse: function (resp, xhr) { 35 | var sk = exports.decodeTimeline(resp); 36 | return sk; 37 | } 38 | }); 39 | 40 | var QueryList = Backbone.Collection.extend({ 41 | model: Query 42 | }); 43 | 44 | var Trends = Backbone.Model.extend({ 45 | }); 46 | 47 | var UserList = Backbone.Collection.extend({ 48 | model: User, 49 | parse: function (resp, xhr) { 50 | var sk = exports.decodeUserList(resp); 51 | return sk; 52 | } 53 | }); 54 | 55 | var DMList = Backbone.Collection.extend({ 56 | model: DirectMessage, 57 | parse: function (resp, xhr) { 58 | var sk = exports.decodeDMList(resp); 59 | return sk; 60 | } 61 | }); 62 | 63 | var DMConvList = Backbone.Collection.extend({ 64 | model: DirectMessage, 65 | parse: function (resp, xhr) { 66 | var sk = exports.decodeDMConvList(resp); 67 | return sk; 68 | } 69 | }); 70 | 71 | var ListCollection = Backbone.Collection.extend({ 72 | model: List, 73 | }); -------------------------------------------------------------------------------- /web/public/dashboard/pres.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | abc 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 37 | 38 | 51 | 52 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /mobile/public/dashboard/pres.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | abc 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 37 | 38 | 51 | 52 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /helper.js: -------------------------------------------------------------------------------- 1 | //var querystring = require('querystring'); 2 | var fs = require('fs'); 3 | 4 | exports.compose_multipart = function (fields, files, callback) { 5 | var boundary = '-----------ksadfsfwXXX'; 6 | var buffer = new Buffer(1024 * 1024 * 4, 'binary'); 7 | var offset = 0; 8 | 9 | for(var key in files) { 10 | offset += buffer.write('--' + boundary + '\r\n', offset); 11 | var entry = files[key]; 12 | offset += buffer.write('Content-Disposition: form-data; name="' + 13 | key + '"; filename="' + 'aaa.png' + 14 | '"\r\n', offset); 15 | offset += buffer.write('Content-Type: ' + entry.type + '\r\n\r\n', offset); 16 | //var fbuffer = fs.readFileSync(entry.path, 'binary'); 17 | var fd = fs.openSync(entry.path, 'r'); 18 | var fbuffer = new Buffer(entry.size, 'binary'); 19 | var sz = fs.readSync(fd, fbuffer, 0, entry.size, 0); 20 | fbuffer.copy(buffer, offset, 0, sz); 21 | offset += sz; 22 | //offset += buffer.write(fbuffer, offset); 23 | offset += buffer.write('\r\n', offset); 24 | fs.closeSync(fd); 25 | } 26 | 27 | for(var key in fields) { 28 | offset += buffer.write('--' + boundary + '\r\n', offset); 29 | offset += buffer.write('Content-Disposition: form-data; name="' + key + '"\r\n\r\n', offset); 30 | //offset += buffer.write(querystring.escape(fields[key]), offset); 31 | offset += buffer.write(fields[key], offset); 32 | offset += buffer.write('\r\n', offset); 33 | } 34 | 35 | // End boundary 36 | offset += buffer.write('--' + boundary + '--\r\n', offset); 37 | var data = buffer.toString('binary', 0, offset); 38 | callback(boundary, data); 39 | } 40 | 41 | function get_last_modified() { 42 | var lm = 0; 43 | var modified = {}; 44 | for(var i=0; i lm) { 63 | lm = m; 64 | } 65 | } 66 | } else { 67 | m = new Date(stat.mtime).getTime() / 1000; 68 | modified[f] = m; 69 | if(m > lm) { 70 | lm = m; 71 | } 72 | } 73 | } 74 | modified['__last_modified'] = lm; 75 | return modified; 76 | } 77 | 78 | exports.get_last_modified = get_last_modified; 79 | -------------------------------------------------------------------------------- /mobile/public/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ABC Lab 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 |
29 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 |
47 |
48 |
49 |
50 |
51 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /web/public/js/mustache.min.js: -------------------------------------------------------------------------------- 1 | var Mustache=function(){var j=function(){};j.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":!0},context:{},render:function(a,b,d,c){if(!c){this.context=b,this.buffer=[];}if(!this.includes("",a)){if(c){return a;}else{this.send(a);return;}}a=this.render_pragmas(a);a=this.render_section(a,b,d);if(c){return this.render_tags(a,b,d,c);}this.render_tags(a,b,d,c);},send:function(a){a!=""&&this.buffer.push(a);},render_pragmas:function(a){if(!this.includes("%",a)){return a;}var b=this;return a.replace(RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag),function(a,c,e){if(!b.pragmas_implemented[c]){throw {message:"This implementation of mustache doesn't understand the '"+c+"' pragma"};}b.pragmas[c]={};e&&(a=e.split("="),b.pragmas[c][a[0]]=a[1]);return "";});},render_partial:function(a,b,d){a=this.trim(a);if(!d||d[a]===void 0){throw {message:"unknown_partial '"+a+"'"};}if(typeof b[a]!="object"){return this.render(d[a],b,d,!0);}return this.render(d[a],b[a],d,!0);},render_section:function(a,b,d){if(!this.includes("#",a)&&!this.includes("^",a)){return a;}var c=this;return a.replace(RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg"),function(a,g,i,f){a=c.find(i,b);if(g=="^"){return !a||c.is_array(a)&&a.length===0?c.render(f,b,d,!0):"";}else{if(g=="#"){return c.is_array(a)?c.map(a,function(a){return c.render(f,c.create_context(a),d,!0);}).join(""):c.is_object(a)?c.render(f,c.create_context(a),d,!0):typeof a==="function"?a.call(b,f,function(a){return c.render(a,b,d,!0);}):a?c.render(f,b,d,!0):"";}}});},render_tags:function(a,b,d,c){for(var e=this,g=function(){return RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g");},i=g(),f=function(a,c,f){switch(c){case "!":return "";case "=":return e.set_delimiters(f),i=g(),"";case ">":return e.render_partial(f,b,d);case "{":return e.find(f,b);default:return e.escape(e.find(f,b));}},a=a.split("\n"),h=0;h\\]/g,function(a){switch(a){case "&":return "&";case "\\":return "\\\\";case "\"":return "\"";case "<":return "<";case ">":return ">";default:return a;}});},create_context:function(a){if(this.is_object(a)){return a;}else{var b=".";if(this.pragmas["IMPLICIT-ITERATOR"]){b=this.pragmas["IMPLICIT-ITERATOR"].iterator;}var d={};d[b]=a;return d;}},is_object:function(a){return a&&typeof a=="object";},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]";},trim:function(a){return a.replace(/^\s*|\s*$/g,"");},map:function(a,b){if(typeof a.map=="function"){return a.map(b);}else{for(var d=[],c=a.length,e=0;e|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g");},i=g(),f=function(a,c,f){switch(c){case "!":return "";case "=":return e.set_delimiters(f),i=g(),"";case ">":return e.render_partial(f,b,d);case "{":return e.find(f,b);default:return e.escape(e.find(f,b));}},a=a.split("\n"),h=0;h\\]/g,function(a){switch(a){case "&":return "&";case "\\":return "\\\\";case "\"":return "\"";case "<":return "<";case ">":return ">";default:return a;}});},create_context:function(a){if(this.is_object(a)){return a;}else{var b=".";if(this.pragmas["IMPLICIT-ITERATOR"]){b=this.pragmas["IMPLICIT-ITERATOR"].iterator;}var d={};d[b]=a;return d;}},is_object:function(a){return a&&typeof a=="object";},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]";},trim:function(a){return a.replace(/^\s*|\s*$/g,"");},map:function(a,b){if(typeof a.map=="function"){return a.map(b);}else{for(var d=[],c=a.length,e=0;e 0) { 95 | path += '?' + qstr; 96 | } 97 | inst.oa.post(path, 98 | req.session.oauth_access_token, 99 | req.session.oauth_access_token_secret, 100 | data, 101 | opts.post_content_type || 'application/x-www-form-urlencoded', 102 | function (error, data, resp) { 103 | if(error) { 104 | opts.error && opts.error(error, data, resp); 105 | } else { 106 | if(opts.success) { 107 | opts.success(data, resp); 108 | } 109 | } 110 | }); 111 | }; 112 | 113 | inst.get = function (url, opts) { 114 | if(typeof opts == 'function') { 115 | opts = {'success': opts}; 116 | } 117 | opts = opts || {}; 118 | var path = module.exports.api_host + url + '.json'; 119 | var qstr = querystring.stringify(opts.query); 120 | if(qstr.length > 0) { 121 | path += '?' + qstr; 122 | } 123 | 124 | inst.oa.get(path, 125 | req.session.oauth_access_token, 126 | req.session.oauth_access_token_secret, 127 | function (error, data, response) { 128 | if(error) { 129 | opts.error && opts.error(error, data, response); 130 | } else { 131 | try { 132 | data = JSON.parse(data); 133 | } catch(e) { 134 | console.error(e, data, response); 135 | e.statusCode = 500; //response.statusCode; 136 | opts.error && opts.error(e, data, response); 137 | return; 138 | } 139 | opts.success && opts.success(data, response); 140 | } 141 | }); 142 | }; 143 | 144 | inst.get_access_token = function (callback) { 145 | inst.oa 146 | .getOAuthAccessToken( 147 | req.session.oauth_token, 148 | req.session.oauth_token_secret, 149 | //req.cookies.oauth_access_token, 150 | //req.cookies.oauth_access_token_secret, 151 | req.param('oauth_verifier'), 152 | function(error, oauth_access_token, oauth_access_token_secret, results2) { 153 | if(error) { 154 | console.error(error); 155 | } 156 | else { 157 | req.session.oauth_access_token = oauth_access_token; 158 | req.session.oauth_access_token_secret = oauth_access_token_secret; 159 | //req.res.cookie('oauth_access_token', oauth_access_token); 160 | //req.res.cookie('oauth_access_token_secret', oauth_access_token_secret); 161 | 162 | callback(oauth_access_token, oauth_access_token_secret); 163 | } 164 | }); 165 | } 166 | return inst; 167 | }; -------------------------------------------------------------------------------- /mobile/public/dashboard/template.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 12 | 13 | 54 | 55 | 76 | 77 | 95 | 96 | 102 | 103 | 111 | 112 | 129 | 130 | 144 | 145 | 162 | 163 | 189 | 190 | 195 | 196 | 201 | -------------------------------------------------------------------------------- /web/public/js/helper.js: -------------------------------------------------------------------------------- 1 | var rconsole = function () { 2 | function makelog(level, w) { 3 | $.ajax('/rconsole/' + level, { 4 | data: {w: w}, 5 | success: function () {} 6 | }); 7 | } 8 | 9 | return { 10 | log: function (w) { makelog('log', w); }, 11 | info: function (w) { makelog('info', w); } 12 | } 13 | }(); 14 | 15 | if(window.exports == undefined) { 16 | window.exports = {}; 17 | } 18 | 19 | window.load_template = function(callback) { 20 | var ver = check_version(); 21 | return $.ajax('/web/pub.' + ver + '/dashboard/template.html', { 22 | cache: true, 23 | success: function (resp) { 24 | if(typeof resp == 'string') { 25 | document.body.innerHTML += resp; 26 | } else if(resp.status == 200) { 27 | document.body.innerHTML += resp.responseText; 28 | } 29 | if(typeof callback == 'function') { 30 | callback(); 31 | } 32 | }, 33 | error: function (err, resp) { 34 | console.error(err, resp); 35 | //window.location = '/'; 36 | } 37 | }); 38 | } 39 | 40 | function process_status_dom(dom, stobj) { 41 | $('a.former', dom).each(function () { 42 | var userid = $(this).attr('href').replace(/.*\//g, ''); 43 | $(this).addClass('user') 44 | .attr('href', '#') 45 | .attr('rel', userid); 46 | }); 47 | 48 | $('a', dom).each(function () { 49 | var href = $(this).attr('href').replace('http://fanfou.com/', '#!/'); 50 | 51 | if(/^\/q\/./.test(href)) { 52 | $(this).attr('href', '#!/q/' + encodeURIComponent(href.substr(3))); 53 | } else if(/^#!\/photo\//.test(href)) { 54 | if(stobj.photo) { 55 | $(this).remove(); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | function parse_date(reprdate) { 62 | function tendigit(d) { 63 | if(d < 10) { 64 | return '0' + d; 65 | } else { 66 | return '' + d; 67 | } 68 | } 69 | var date = new Date(reprdate); 70 | var currDate = new Date(); 71 | if(date.getFullYear() != currDate.getFullYear()) { 72 | return date.getFullYear() + '年'; 73 | } else if (date.getMonth() != currDate.getMonth() || 74 | date.getDate() != currDate.getDate()){ 75 | return (date.getMonth() + 1) + '月' + date.getDate() + '日'; 76 | } else { 77 | // Same day 78 | return tendigit(date.getHours()) + ':' + tendigit(date.getMinutes()); 79 | } 80 | } 81 | 82 | function parse_date_str(reprdate) { 83 | var date = new Date(reprdate); 84 | var currDate = new Date(); 85 | var diff = Math.floor((currDate - date)/1000.0); 86 | if(diff <= 2) { 87 | return '刚刚'; 88 | } else if(diff <= 60) { 89 | return diff + '秒前'; 90 | } else if(diff <= 3600) { 91 | return Math.floor(diff/60) + '分钟前'; 92 | } else if(diff <= 86400) { 93 | return Math.floor(diff/3600) + '小时前'; 94 | } else { 95 | return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate(); 96 | } 97 | } 98 | 99 | window.check_version = function() { 100 | var t = /^\/(\w+)\/p\/(\d+)/.exec(window.location.pathname); 101 | if(t) { 102 | var ver = t[2]; 103 | var curr_ver = sessionStorage.getItem('version'); 104 | if(ver != curr_ver) { 105 | sessionStorage.setItem('version', ver); 106 | } 107 | return ver; 108 | } 109 | } 110 | 111 | function ModelCache(model, prefix) { 112 | var _cache = {}; 113 | var _model = model; 114 | var _prefix = prefix + '_'; 115 | 116 | function erase(key) { 117 | sessionStorage.removeItem(_prefix + key); 118 | } 119 | 120 | function get(key) { 121 | var json = sessionStorage.getItem(_prefix + key); 122 | if(json) { 123 | json = JSON.parse(json); 124 | var cache_set_time = json.cache_set_time; 125 | if(json.magic) { 126 | json = json.array; 127 | } 128 | var obj = new _model(json); 129 | obj.cache_set_time = cache_set_time; 130 | return obj; 131 | } else { 132 | return null; 133 | } 134 | } 135 | 136 | function set(key, obj) { 137 | obj = obj.toJSON(); 138 | if(obj instanceof Array) { 139 | obj = {'magic': true, array: obj, cache_set_time: new Date().getTime()}; 140 | } else { 141 | obj.cache_set_time = new Date().getTime(); 142 | } 143 | sessionStorage.setItem(_prefix + key, JSON.stringify(obj)); 144 | } 145 | 146 | function updateModel(key, obj, use_set) { 147 | var orig = get(key); 148 | if(orig && 149 | typeof orig == 'object' && 150 | typeof obj == 'object') { 151 | orig.set(obj); 152 | set(key, orig); 153 | } else if(use_set){ 154 | set(key, new _model(obj)); 155 | } 156 | } 157 | 158 | var prefetching = {}; 159 | 160 | function prefetch (url) { 161 | var model = new _model(); 162 | model.url = url; 163 | var callobjs = prefetching[url]; 164 | if(!callobjs) { 165 | callobjs = Array(); 166 | prefetching[url] = callobjs; 167 | } 168 | 169 | model.fetch({ 170 | 'complete': function () { 171 | //console.info('prefetching complete'); 172 | delete prefetching[url]; 173 | }, 174 | 'success': function (data) { 175 | var callobjs = prefetching[url]; 176 | set(url, data); 177 | if(callobjs) { 178 | _.map(callobjs, function (opts) { 179 | //console.info('calling'); 180 | if(typeof opts.success == 'function') { 181 | opts.success(data); 182 | } 183 | }); 184 | } 185 | }, 186 | 'error': function (err, resp) { 187 | var callobjs = prefetching[url]; 188 | if(callobjs) { 189 | _.map(callobjs, function (opts) { 190 | if(typeof opts.error == 'function') { 191 | opts.error(err, resp); 192 | } 193 | }); 194 | } 195 | } 196 | }); 197 | } 198 | 199 | function fetch(url, opts) { 200 | if(typeof opts == 'function') { 201 | opts = {'success': opts}; 202 | } 203 | var disable_cache = opts.disable_cache || false; 204 | var key = url; 205 | if(!disable_cache) { 206 | var u = get(key); 207 | if(opts.only_cache == undefined) { 208 | opts.only_cache = true; 209 | } 210 | if(u) { 211 | // Cache hit. 212 | opts.success(u); 213 | if(opts.only_cache || 214 | (new Date().getTime() - u.cache_set_time < 2000)) { 215 | // cache within one second is considiered the desired one. 216 | //console.info('found', url); 217 | return; 218 | } 219 | } 220 | } 221 | // Request from server 222 | var fetch_opts = { 223 | success: function (data) { 224 | if(!disable_cache) { 225 | set(key, data); 226 | } 227 | if(typeof opts.success == 'function') { 228 | opts.success(data); 229 | } else { 230 | console.warn('opts.success is not a function'); 231 | } 232 | }, 233 | error: function () { 234 | if(typeof opts.error == 'function') { 235 | opts.error.apply(u, arguments); 236 | } else { 237 | App.notifyTitle('找不着对象' + key); 238 | } 239 | } 240 | }; 241 | if(prefetching[url] != undefined) { 242 | //console.info('prefetching push opts'); 243 | prefetching[url].push(fetch_opts); 244 | } else { 245 | //console.info('no cache fetching it'); 246 | u = new _model(); 247 | u.url = url; 248 | u.fetch(fetch_opts); 249 | } 250 | } 251 | return { 252 | 'get': get, 253 | 'set': set, 254 | 'erase': erase, 255 | 'fetch': fetch, 256 | 'prefetch': prefetch, 257 | 'updateModel': updateModel 258 | }; 259 | } 260 | -------------------------------------------------------------------------------- /mobile/public/js/spec.js: -------------------------------------------------------------------------------- 1 | 2 | var timeline_path = {'/statuses/public_timeline': true, 3 | '/statuses/friends_timeline': true, 4 | '/statuses/user_timeline': true, 5 | '/statuses/mentions': true, 6 | '/statuses/context_timeline': true, 7 | '/search/public_timeline': true 8 | }; 9 | 10 | var userlist_path = {'/users/friends': true, 11 | '/users/followers': true 12 | }; 13 | 14 | var dmconvlist_path = {'/direct_messages/conversation_list': true}; 15 | var dmconv_path = {'/direct_messages/conversation': true}; 16 | 17 | var status_fields = ['id', 'text', 'created_at', 18 | 'in_reply_to_status_id', 19 | 'repost_status_id', 'location', 20 | ['photo', 'largeurl']]; 21 | var user_fields = ['id', 'name', 'screen_name', 'profile_image_url', 22 | 'friends_count', 'followers_count', 'statuses_count']; 23 | 24 | var dmconv_fields = ['otherid', 'msg_num', 25 | ['dm', 'created_at'], 26 | ['dm', 'id'], 27 | ['dm', 'text'], 28 | ['dm', 'recipient', 'id'], 29 | ['dm', 'recipient', 'name'], 30 | ['dm', 'recipient', 'profile_image_url'], 31 | ['dm', 'sender', 'id'], 32 | ['dm', 'sender', 'name'], 33 | ['dm', 'sender', 'profile_image_url']]; 34 | 35 | var dm_fields = ['id', 'created_at', 'text', 36 | ['recipient', 'id'], 37 | ['recipient', 'name'], 38 | ['recipient', 'profile_image_url'], 39 | ['sender', 'id'], 40 | ['sender', 'name'], 41 | ['sender', 'profile_image_url']]; 42 | 43 | for(var i=0; i= 0) { 146 | var narr = new Array(); 147 | for(var i=0; i= 0) { 177 | var narr = new Array(); 178 | for(var i=0; i= 0) { 203 | var narr = new Array(); 204 | for(var i=0; i= 0) { 151 | var narr = new Array(); 152 | for(var i=0; i= 0) { 182 | var narr = new Array(); 183 | for(var i=0; i= 0) { 208 | var narr = new Array(); 209 | for(var i=0; i 0) || 198 | fields.uploaded_file) { 199 | if(fields.uploaded_file) { 200 | files = {'photo': JSON.parse(fields.uploaded_file)}; 201 | delete fields['uploaded_file']; 202 | } 203 | var api = apivendor.from_request(req); 204 | helper.compose_multipart(fields, files, function (b, payload) { 205 | api.post('/photos/upload', payload, { 206 | 'post_content_type': 'multipart/form-data; boundary=' + b, 207 | 'success': function (data) { 208 | res.header('Content-Type: application/json'); 209 | res.send(data); 210 | }, 211 | 'error': function (err) { 212 | res.send(err.data, {'Content-Type': 'application/json'}, err.statusCode); 213 | } 214 | }); 215 | }); 216 | 217 | } else { 218 | req.body = fields; 219 | var api = apivendor.from_request(req); 220 | api.post('/statuses/update', fields, { 221 | 'success': function (data) { 222 | res.header('Content-Type: application/json'); 223 | res.send(data); 224 | }, 225 | 'error': function (err) { 226 | res.send(err.data, {'Content-Type': 'application/json'}, err.statusCode); 227 | } 228 | }); 229 | } 230 | }); 231 | }); 232 | 233 | app.post('/proxy/:section/:action', apivendor.require_login, function(req, res) { 234 | var path = '/' + req.params.section + '/' + req.params.action; 235 | var api = apivendor.from_request(req); 236 | api.post(path, req.body, { 237 | 'success': function (data) { 238 | res.send(data); 239 | }, 240 | 'error': function (err, data, resp) { 241 | res.send(err.data, 242 | //{'Content-Type': 'application/json'}, 243 | {'Content-Type': resp.headers['content-type'] || 'application/json'}, 244 | err.statusCode); 245 | } 246 | }); 247 | }); 248 | 249 | app.get('/rconsole/:level', apivendor.require_login, function (req, res) { 250 | var log = console[req.params.level]; 251 | if(log == undefined) { 252 | log = console.log; 253 | } 254 | log('REMOTE', req.query.w); 255 | res.send('ok'); 256 | }); 257 | 258 | app.get('/', apivendor.require_login, function(req, res){ 259 | var project = req.session.project || default_project; 260 | res.redirect('/' + project + '/p/' + version); //dashboard_url(project)); 261 | }); 262 | 263 | app.listen(settings.daemon_port); 264 | console.log("listening on http://localhost:" + settings.daemon_port); 265 | 266 | 267 | -------------------------------------------------------------------------------- /mobile/public/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a);};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.7";var h=b.each=b.forEach=function(a,c,b){if(a!=null){if(s&&a.forEach===s){a.forEach(c,b);}else{if(a.length===+a.length){for(var e=0,k=a.length;e=e.computed&&(e={value:a,computed:b});});return e.value;};b.min=function(a,c,d){if(!c&&b.isArray(a)){return Math.min.apply(Math,a);}var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;bd?1:0;}),"value");};b.groupBy=function(a,b){var d={};h(a,function(a,f){var g=b(a,f);(d[g]||(d[g]=[])).push(a);});return d;};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0;});});};b.difference=function(a,c){return b.filter(a,function(a){return !b.include(c,a);});};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--){b=[a[d].apply(this,b)];}return b[0];};};b.after=function(a,b){return function(){if(--a<1){return b.apply(this,arguments);}};};b.keys=F||function(a){if(a!==Object(a)){throw new TypeError("Invalid object");}var b=[],d;for(d in a){l.call(a,d)&&(b[b.length]=d);}return b;};b.values=function(a){return b.map(a,b.identity);};b.functions=b.methods=function(a){var c=[],d;for(d in a){b.isFunction(a[d])&&c.push(d);}return c.sort();};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b){b[d]!==void 0&&(a[d]=b[d]);}});return a;};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b){a[d]==null&&(a[d]=b[d]);}});return a;};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a);};b.tap=function(a,b){b(a);return a;};b.isEqual=function(a,c){if(a===c){return !0;}var d=typeof a;if(d!=typeof c){return !1;}if(a==c){return !0;}if(!a&&c||a&&!c){return !1;}if(a._chain){a=a._wrapped;}if(c._chain){c=c._wrapped;}if(a.isEqual){return a.isEqual(c);}if(c.isEqual){return c.isEqual(a);}if(b.isDate(a)&&b.isDate(c)){return a.getTime()===c.getTime();}if(b.isNaN(a)&&b.isNaN(c)){return !1;}if(b.isRegExp(a)&&b.isRegExp(c)){return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;}if(d!=="object"){return !1;}if(a.length&&a.length!==c.length){return !1;}d=b.keys(a);var e=b.keys(c);if(d.length!=e.length){return !1;}for(var f in a){if(!(f in c)||!b.isEqual(a[f],c[f])){return !1;}}return !0;};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a)){return a.length===0;}for(var c in a){if(l.call(a,c)){return !1;}}return !0;};b.isElement=function(a){return !!(a&&a.nodeType==1);};b.isArray=n||function(a){return E.call(a)==="[object Array]";};b.isObject=function(a){return a===Object(a);};b.isArguments=function(a){return !(!a||!l.call(a,"callee"));};b.isFunction=function(a){return !(!a||!a.constructor||!a.call||!a.apply);};b.isString=function(a){return !!(a===""||a&&a.charCodeAt&&a.substr);};b.isNumber=function(a){return !!(a===0||a&&a.toExponential&&a.toFixed);};b.isNaN=function(a){return a!==a;};b.isBoolean=function(a){return a===!0||a===!1;};b.isDate=function(a){return !(!a||!a.getTimezoneOffset||!a.setUTCFullYear);};b.isRegExp=function(a){return !(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1));};b.isNull=function(a){return a===null;};b.isUndefined=function(a){return a===void 0;};b.noConflict=function(){p._=C;return this;};b.identity=function(a){return a;};b.times=function(a,b,d){for(var e=0;e/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return "',"+b.replace(/\\'/g,"'")+",'";}).replace(d.evaluate||null,function(a,b){return "');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('";}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d;};var j=function(a){this._wrapped=a;};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a;},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain);};};b.mixin(b);h(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain);};});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain);};});j.prototype.chain=function(){this._chain=!0;return this;};j.prototype.value=function(){return this._wrapped;};})(); -------------------------------------------------------------------------------- /web/public/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | (function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a);};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.7";var h=b.each=b.forEach=function(a,c,b){if(a!=null){if(s&&a.forEach===s){a.forEach(c,b);}else{if(a.length===+a.length){for(var e=0,k=a.length;e=e.computed&&(e={value:a,computed:b});});return e.value;};b.min=function(a,c,d){if(!c&&b.isArray(a)){return Math.min.apply(Math,a);}var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;bd?1:0;}),"value");};b.groupBy=function(a,b){var d={};h(a,function(a,f){var g=b(a,f);(d[g]||(d[g]=[])).push(a);});return d;};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0;});});};b.difference=function(a,c){return b.filter(a,function(a){return !b.include(c,a);});};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--){b=[a[d].apply(this,b)];}return b[0];};};b.after=function(a,b){return function(){if(--a<1){return b.apply(this,arguments);}};};b.keys=F||function(a){if(a!==Object(a)){throw new TypeError("Invalid object");}var b=[],d;for(d in a){l.call(a,d)&&(b[b.length]=d);}return b;};b.values=function(a){return b.map(a,b.identity);};b.functions=b.methods=function(a){var c=[],d;for(d in a){b.isFunction(a[d])&&c.push(d);}return c.sort();};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b){b[d]!==void 0&&(a[d]=b[d]);}});return a;};b.defaults=function(a){h(f.call(arguments,1),function(b){for(var d in b){a[d]==null&&(a[d]=b[d]);}});return a;};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a);};b.tap=function(a,b){b(a);return a;};b.isEqual=function(a,c){if(a===c){return !0;}var d=typeof a;if(d!=typeof c){return !1;}if(a==c){return !0;}if(!a&&c||a&&!c){return !1;}if(a._chain){a=a._wrapped;}if(c._chain){c=c._wrapped;}if(a.isEqual){return a.isEqual(c);}if(c.isEqual){return c.isEqual(a);}if(b.isDate(a)&&b.isDate(c)){return a.getTime()===c.getTime();}if(b.isNaN(a)&&b.isNaN(c)){return !1;}if(b.isRegExp(a)&&b.isRegExp(c)){return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;}if(d!=="object"){return !1;}if(a.length&&a.length!==c.length){return !1;}d=b.keys(a);var e=b.keys(c);if(d.length!=e.length){return !1;}for(var f in a){if(!(f in c)||!b.isEqual(a[f],c[f])){return !1;}}return !0;};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a)){return a.length===0;}for(var c in a){if(l.call(a,c)){return !1;}}return !0;};b.isElement=function(a){return !!(a&&a.nodeType==1);};b.isArray=n||function(a){return E.call(a)==="[object Array]";};b.isObject=function(a){return a===Object(a);};b.isArguments=function(a){return !(!a||!l.call(a,"callee"));};b.isFunction=function(a){return !(!a||!a.constructor||!a.call||!a.apply);};b.isString=function(a){return !!(a===""||a&&a.charCodeAt&&a.substr);};b.isNumber=function(a){return !!(a===0||a&&a.toExponential&&a.toFixed);};b.isNaN=function(a){return a!==a;};b.isBoolean=function(a){return a===!0||a===!1;};b.isDate=function(a){return !(!a||!a.getTimezoneOffset||!a.setUTCFullYear);};b.isRegExp=function(a){return !(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1));};b.isNull=function(a){return a===null;};b.isUndefined=function(a){return a===void 0;};b.noConflict=function(){p._=C;return this;};b.identity=function(a){return a;};b.times=function(a,b,d){for(var e=0;e/g,interpolate:/<%=([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return "',"+b.replace(/\\'/g,"'")+",'";}).replace(d.evaluate||null,function(a,b){return "');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('";}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d;};var j=function(a){this._wrapped=a;};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a;},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain);};};b.mixin(b);h(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain);};});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,arguments),this._chain);};});j.prototype.chain=function(){this._chain=!0;return this;};j.prototype.value=function(){return this._wrapped;};})(); -------------------------------------------------------------------------------- /facebox/facebox.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Facebox (for jQuery) 3 | * version: 1.3 4 | * @requires jQuery v1.2 or later 5 | * @homepage https://github.com/defunkt/facebox 6 | * 7 | * Licensed under the MIT: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * Copyright Forever Chris Wanstrath, Kyle Neath 11 | * 12 | * Usage: 13 | * 14 | * jQuery(document).ready(function() { 15 | * jQuery('a[rel*=facebox]').facebox() 16 | * }) 17 | * 18 | *
Terms 19 | * Loads the #terms div in the box 20 | * 21 | * Terms 22 | * Loads the terms.html page in the box 23 | * 24 | * Terms 25 | * Loads the terms.png image in the box 26 | * 27 | * 28 | * You can also use it programmatically: 29 | * 30 | * jQuery.facebox('some html') 31 | * jQuery.facebox('some html', 'my-groovy-style') 32 | * 33 | * The above will open a facebox with "some html" as the content. 34 | * 35 | * jQuery.facebox(function($) { 36 | * $.get('blah.html', function(data) { $.facebox(data) }) 37 | * }) 38 | * 39 | * The above will show a loading screen before the passed function is called, 40 | * allowing for a better ajaxy experience. 41 | * 42 | * The facebox function can also display an ajax page, an image, or the contents of a div: 43 | * 44 | * jQuery.facebox({ ajax: 'remote.html' }) 45 | * jQuery.facebox({ ajax: 'remote.html' }, 'my-groovy-style') 46 | * jQuery.facebox({ image: 'stairs.jpg' }) 47 | * jQuery.facebox({ image: 'stairs.jpg' }, 'my-groovy-style') 48 | * jQuery.facebox({ div: '#box' }) 49 | * jQuery.facebox({ div: '#box' }, 'my-groovy-style') 50 | * 51 | * Want to close the facebox? Trigger the 'close.facebox' document event: 52 | * 53 | * jQuery(document).trigger('close.facebox') 54 | * 55 | * Facebox also has a bunch of other hooks: 56 | * 57 | * loading.facebox 58 | * beforeReveal.facebox 59 | * reveal.facebox (aliased as 'afterReveal.facebox') 60 | * init.facebox 61 | * afterClose.facebox 62 | * 63 | * Simply bind a function to any of these hooks: 64 | * 65 | * $(document).bind('reveal.facebox', function() { ...stuff to do after the facebox and contents are revealed... }) 66 | * 67 | */ 68 | (function($) { 69 | $.facebox = function(data, klass) { 70 | $.facebox.loading(data.settings || []) 71 | 72 | if (data.ajax) fillFaceboxFromAjax(data.ajax, klass) 73 | else if (data.image) fillFaceboxFromImage(data.image, klass) 74 | else if (data.div) fillFaceboxFromHref(data.div, klass) 75 | else if ($.isFunction(data)) data.call($) 76 | else $.facebox.reveal(data, klass) 77 | } 78 | 79 | /* 80 | * Public, $.facebox methods 81 | */ 82 | 83 | $.extend($.facebox, { 84 | settings: { 85 | opacity : 0.2, 86 | overlay : true, 87 | loadingImage : '/facebox/loading.gif', 88 | closeImage : '/facebox/closelabel.png', 89 | imageTypes : [ 'png', 'jpg', 'jpeg', 'gif' ], 90 | faceboxHtml : '\ 91 | ' 98 | }, 99 | 100 | loading: function() { 101 | init() 102 | if ($('#facebox .loading').length == 1) return true 103 | showOverlay() 104 | 105 | $('#facebox .content').empty(). 106 | append('
') 107 | 108 | $('#facebox').show().css({ 109 | top: getPageScroll()[1] + (getPageHeight() / 10), 110 | left: $(window).width() / 2 - ($('#facebox .popup').outerWidth() / 2) 111 | }) 112 | 113 | $(document).bind('keydown.facebox', function(e) { 114 | if (e.keyCode == 27) $.facebox.close() 115 | return true 116 | }) 117 | $(document).trigger('loading.facebox') 118 | }, 119 | 120 | reveal: function(data, klass) { 121 | $(document).trigger('beforeReveal.facebox') 122 | if (klass) $('#facebox .content').addClass(klass) 123 | $('#facebox .content').empty().append(data) 124 | $('#facebox .popup').children().fadeIn('normal') 125 | $('#facebox').css('left', $(window).width() / 2 - ($('#facebox .popup').outerWidth() / 2)) 126 | $(document).trigger('reveal.facebox').trigger('afterReveal.facebox') 127 | }, 128 | 129 | close: function() { 130 | $(document).trigger('close.facebox') 131 | return false 132 | } 133 | }) 134 | 135 | /* 136 | * Public, $.fn methods 137 | */ 138 | 139 | $.fn.facebox = function(settings) { 140 | if ($(this).length == 0) return 141 | 142 | init(settings) 143 | 144 | function clickHandler() { 145 | $.facebox.loading(true) 146 | 147 | // support for rel="facebox.inline_popup" syntax, to add a class 148 | // also supports deprecated "facebox[.inline_popup]" syntax 149 | var klass = this.rel.match(/facebox\[?\.(\w+)\]?/) 150 | if (klass) klass = klass[1] 151 | 152 | fillFaceboxFromHref(this.href, klass) 153 | return false 154 | } 155 | 156 | return this.bind('click.facebox', clickHandler) 157 | } 158 | 159 | /* 160 | * Private methods 161 | */ 162 | 163 | // called one time to setup facebox on this page 164 | function init(settings) { 165 | if ($.facebox.settings.inited) return true 166 | else $.facebox.settings.inited = true 167 | 168 | $(document).trigger('init.facebox') 169 | makeCompatible() 170 | 171 | var imageTypes = $.facebox.settings.imageTypes.join('|') 172 | $.facebox.settings.imageTypesRegexp = new RegExp('\\.(' + imageTypes + ')(\\?.*)?$', 'i') 173 | 174 | if (settings) $.extend($.facebox.settings, settings) 175 | $('body').append($.facebox.settings.faceboxHtml) 176 | 177 | var preload = [ new Image(), new Image() ] 178 | preload[0].src = $.facebox.settings.closeImage 179 | preload[1].src = $.facebox.settings.loadingImage 180 | 181 | $('#facebox').find('.b:first, .bl').each(function() { 182 | preload.push(new Image()) 183 | preload.slice(-1).src = $(this).css('background-image').replace(/url\((.+)\)/, '$1') 184 | }) 185 | 186 | $('#facebox .close') 187 | .click($.facebox.close) 188 | .append('') 191 | } 192 | 193 | // getPageScroll() by quirksmode.com 194 | function getPageScroll() { 195 | var xScroll, yScroll; 196 | if (self.pageYOffset) { 197 | yScroll = self.pageYOffset; 198 | xScroll = self.pageXOffset; 199 | } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict 200 | yScroll = document.documentElement.scrollTop; 201 | xScroll = document.documentElement.scrollLeft; 202 | } else if (document.body) {// all other Explorers 203 | yScroll = document.body.scrollTop; 204 | xScroll = document.body.scrollLeft; 205 | } 206 | return new Array(xScroll,yScroll) 207 | } 208 | 209 | // Adapted from getPageSize() by quirksmode.com 210 | function getPageHeight() { 211 | var windowHeight 212 | if (self.innerHeight) { // all except Explorer 213 | windowHeight = self.innerHeight; 214 | } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode 215 | windowHeight = document.documentElement.clientHeight; 216 | } else if (document.body) { // other Explorers 217 | windowHeight = document.body.clientHeight; 218 | } 219 | return windowHeight 220 | } 221 | 222 | // Backwards compatibility 223 | function makeCompatible() { 224 | var $s = $.facebox.settings 225 | 226 | $s.loadingImage = $s.loading_image || $s.loadingImage 227 | $s.closeImage = $s.close_image || $s.closeImage 228 | $s.imageTypes = $s.image_types || $s.imageTypes 229 | $s.faceboxHtml = $s.facebox_html || $s.faceboxHtml 230 | } 231 | 232 | // Figures out what you want to display and displays it 233 | // formats are: 234 | // div: #id 235 | // image: blah.extension 236 | // ajax: anything else 237 | function fillFaceboxFromHref(href, klass) { 238 | // div 239 | if (href.match(/#/)) { 240 | var url = window.location.href.split('#')[0] 241 | var target = href.replace(url,'') 242 | if (target == '#') return 243 | $.facebox.reveal($(target).html(), klass) 244 | 245 | // image 246 | } else if (href.match($.facebox.settings.imageTypesRegexp)) { 247 | fillFaceboxFromImage(href, klass) 248 | // ajax 249 | } else { 250 | fillFaceboxFromAjax(href, klass) 251 | } 252 | } 253 | 254 | function fillFaceboxFromImage(href, klass) { 255 | var image = new Image() 256 | image.onload = function() { 257 | $.facebox.reveal('
', klass) 258 | } 259 | image.src = href 260 | } 261 | 262 | function fillFaceboxFromAjax(href, klass) { 263 | $.facebox.jqxhr = $.get(href, function(data) { $.facebox.reveal(data, klass) }) 264 | } 265 | 266 | function skipOverlay() { 267 | return $.facebox.settings.overlay == false || $.facebox.settings.opacity === null 268 | } 269 | 270 | function showOverlay() { 271 | if (skipOverlay()) return 272 | 273 | if ($('#facebox_overlay').length == 0) 274 | $("body").append('
') 275 | 276 | $('#facebox_overlay').hide().addClass("facebox_overlayBG") 277 | .css('opacity', $.facebox.settings.opacity) 278 | .click(function() { $(document).trigger('close.facebox') }) 279 | .fadeIn(200) 280 | return false 281 | } 282 | 283 | function hideOverlay() { 284 | if (skipOverlay()) return 285 | 286 | $('#facebox_overlay').fadeOut(200, function(){ 287 | $("#facebox_overlay").removeClass("facebox_overlayBG") 288 | $("#facebox_overlay").addClass("facebox_hide") 289 | $("#facebox_overlay").remove() 290 | }) 291 | 292 | return false 293 | } 294 | 295 | /* 296 | * Bindings 297 | */ 298 | 299 | $(document).bind('close.facebox', function() { 300 | if ($.facebox.jqxhr) { 301 | $.facebox.jqxhr.abort() 302 | $.facebox.jqxhr = null 303 | } 304 | $(document).unbind('keydown.facebox') 305 | $('#facebox').fadeOut(function() { 306 | $('#facebox .content').removeClass().addClass('content') 307 | $('#facebox .loading').remove() 308 | $(document).trigger('afterClose.facebox') 309 | }) 310 | hideOverlay() 311 | }) 312 | 313 | })(jQuery); 314 | -------------------------------------------------------------------------------- /web/public/js/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(){var h=this,p=h.Backbone,e;e=typeof exports!=="undefined"?exports:h.Backbone={};e.VERSION="0.5.3";var f=h._;if(!f&&typeof require!=="undefined"){f=require("underscore")._;}var g=h.jQuery||h.Zepto;e.noConflict=function(){h.Backbone=p;return this;};e.emulateHTTP=!1;e.emulateJSON=!1;e.Events={bind:function(a,b,c){var d=this._callbacks||(this._callbacks={});(d[a]||(d[a]=[])).push([b,c]);return this;},unbind:function(a,b){var c;if(a){if(c=this._callbacks){if(b){c=c[a];if(!c){return this;}for(var d=0,e=c.length;d/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/");},has:function(a){return this.attributes[a]!=null;},set:function(a,b){b||(b={});if(!a){return this;}if(a.attributes){a=a.attributes;}var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b)){return !1;}if(this.idAttribute in a){this.id=a[this.idAttribute];}var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h)){c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b);}}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this;},unset:function(a,b){if(!(a in this.attributes)){return this;}b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b)){return !1;}delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed=!0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this;},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c){d[b]=void 0;}if(!a.silent&&this.validate&&!this._performValidation(d,a)){return !1;}this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c){this.trigger("change:"+b,this,void 0,a);}this.change(a);}return this;},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a)){return !1;}c&&c(b,d);};a.error=i(a.error,b,a);return (this.sync||e.sync).call(this,"read",this,a);},save:function(a,b){b||(b={});if(a&&!this.set(a,b)){return !1;}var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b)){return !1;}d&&d(c,a,f);};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return (this.sync||e.sync).call(this,f,this,b);},destroy:function(a){a||(a={});if(this.isNew()){return this.trigger("destroy",this,this.collection,a);}var b=this,c=a.success;a.success=function(d){b.trigger("destroy",b,b.collection,a);c&&c(b,d);};a.error=i(a.error,b,a);return (this.sync||e.sync).call(this,"delete",this,a);},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew()){return a;}return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id);},parse:function(a){return a;},clone:function(){return new this.constructor(this);},isNew:function(){return this.id==null;},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1;},hasChanged:function(a){if(a){return this._previousAttributes[a]!=this.attributes[a];}return this._changed;},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a){f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);}return c;},previous:function(a){if(!a||!this._previousAttributes){return null;}return this._previousAttributes[a];},previousAttributes:function(){return f.clone(this._previousAttributes);},_performValidation:function(a,b){var c=this.validate(a);if(c){return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;}return !0;}});e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;}f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments);};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON();});},add:function(a,b){if(f.isArray(a)){for(var c=0,d=a.length;c").hide().appendTo("body")[0].contentWindow,this.navigate(a);}this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange" in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b){return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;}else{if(this._wantsPushState&&this._hasPushState&&b&&a.hash){this.fragment=a.hash.replace(j,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);}}if(!this.options.silent){return this.loadUrl();}},route:function(a,b){this.handlers.unshift({route:a,callback:b});},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment)){return !1;}this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash);},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b)){return a.callback(b),!0;}});},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c);}else{if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash)){this.iframe.document.open().close(),this.iframe.location.hash=c;}}b&&this.loadUrl(a);}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments);};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el);},initialize:function(){},render:function(){return this;},remove:function(){g(this.el).remove();return this;},make:function(a,b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a;},delegateEvents:function(a){if(a||(a=this.events)){for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c){throw Error("Event \""+a[b]+"\" does not exist");}var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c);}}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=n.length;b/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/");},has:function(a){return this.attributes[a]!=null;},set:function(a,b){b||(b={});if(!a){return this;}if(a.attributes){a=a.attributes;}var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b)){return !1;}if(this.idAttribute in a){this.id=a[this.idAttribute];}var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h)){c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b);}}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this;},unset:function(a,b){if(!(a in this.attributes)){return this;}b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b)){return !1;}delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed=!0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this;},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c){d[b]=void 0;}if(!a.silent&&this.validate&&!this._performValidation(d,a)){return !1;}this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c){this.trigger("change:"+b,this,void 0,a);}this.change(a);}return this;},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a)){return !1;}c&&c(b,d);};a.error=i(a.error,b,a);return (this.sync||e.sync).call(this,"read",this,a);},save:function(a,b){b||(b={});if(a&&!this.set(a,b)){return !1;}var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b)){return !1;}d&&d(c,a,f);};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return (this.sync||e.sync).call(this,f,this,b);},destroy:function(a){a||(a={});if(this.isNew()){return this.trigger("destroy",this,this.collection,a);}var b=this,c=a.success;a.success=function(d){b.trigger("destroy",b,b.collection,a);c&&c(b,d);};a.error=i(a.error,b,a);return (this.sync||e.sync).call(this,"delete",this,a);},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew()){return a;}return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id);},parse:function(a){return a;},clone:function(){return new this.constructor(this);},isNew:function(){return this.id==null;},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1;},hasChanged:function(a){if(a){return this._previousAttributes[a]!=this.attributes[a];}return this._changed;},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a){f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);}return c;},previous:function(a){if(!a||!this._previousAttributes){return null;}return this._previousAttributes[a];},previousAttributes:function(){return f.clone(this._previousAttributes);},_performValidation:function(a,b){var c=this.validate(a);if(c){return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;}return !0;}});e.Collection=function(a,b){b||(b={});if(b.comparator){this.comparator=b.comparator;}f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments);};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON();});},add:function(a,b){if(f.isArray(a)){for(var c=0,d=a.length;c").hide().appendTo("body")[0].contentWindow,this.navigate(a);}this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange" in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b){return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;}else{if(this._wantsPushState&&this._hasPushState&&b&&a.hash){this.fragment=a.hash.replace(j,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);}}if(!this.options.silent){return this.loadUrl();}},route:function(a,b){this.handlers.unshift({route:a,callback:b});},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment)){return !1;}this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash);},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b)){return a.callback(b),!0;}});},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c);}else{if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash)){this.iframe.document.open().close(),this.iframe.location.hash=c;}}b&&this.loadUrl(a);}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments);};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el);},initialize:function(){},render:function(){return this;},remove:function(){g(this.el).remove();return this;},make:function(a,b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a;},delegateEvents:function(a){if(a||(a=this.events)){for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c){throw Error("Event \""+a[b]+"\" does not exist");}var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c);}}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=n.length;b 2 |
3 | 26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 40 |
41 | 42 |
43 | 44 | 49 |
50 | 51 | 52 | 53 | 86 | 87 | 93 | 94 | 115 | 116 | 123 | 124 | 127 | 128 | 138 | 139 | 179 | 180 | 220 | 221 | 235 | 236 | 245 | 246 | 263 | 264 | 278 | 279 | 296 | 297 | 323 | 324 | 329 | 330 | 335 | 336 | 362 | 363 | 374 | 375 | 397 | 398 | 408 | 409 | 415 | -------------------------------------------------------------------------------- /mobile/public/js/view.js: -------------------------------------------------------------------------------- 1 | var NotifyView = Backbone.View.extend({ 2 | events: {}, 3 | initialize: function (opts) { 4 | this.content = opts.content; 5 | }, 6 | render: function () { 7 | var html = App.template('#notify-template', {content: this.content}); 8 | var dom = $(html); 9 | //dom.height(window.innerHeight - 10); 10 | this.el.html(dom); 11 | } 12 | }); 13 | 14 | var SearchView = Backbone.View.extend({ 15 | events: { 16 | 'click #exec': 'do_search', 17 | 'keydown #id_q': 'do_keydown' 18 | }, 19 | do_keydown: function (evt) { 20 | if(evt.keyCode == 13) { 21 | this.$('#exec').focus().click(); 22 | } 23 | }, 24 | do_search: function (evt) { 25 | var v = $(evt.currentTarget).siblings('#id_q').val(); 26 | if(v) { 27 | App.gohash('#!/q/' + encodeURIComponent(v)); 28 | } 29 | }, 30 | initialize: function () {}, 31 | 32 | render: function () { 33 | var html = App.template('#query-template', {}); 34 | this.el.html(html); 35 | } 36 | }); 37 | 38 | var StatusView = Backbone.View.extend({ 39 | events: { 40 | 'click a.user': 'click_userlink' 41 | }, 42 | 43 | click_userlink: function (evt) { 44 | evt.preventDefault(); 45 | var userid = $(evt.currentTarget).attr('rel'); 46 | if(userid) { 47 | App.gotoUserTimeline(userid); 48 | } 49 | evt.stopPropagation(); 50 | }, 51 | 52 | initialize: function (){}, 53 | 54 | render: function() { 55 | var status = this.model.toJSON(); 56 | var re = /^(\-?[\d\.]+)[,\s](\-?[\d\.]+)/.exec(status.location); 57 | if(re) { 58 | var loc = re[1] + ',' + re[2]; 59 | status.map_image = ('http://maps.googleapis.com/maps/api/staticmap?center=' + 60 | loc + '&zoom=13&size=200x200&sensor=false'); 61 | } 62 | status.created_at = parse_date(status.created_at); 63 | status.load_avatar = App.load_avatar; 64 | var html = App.template('#status-template', status); 65 | var dom = $(html); 66 | process_status_dom(dom); 67 | this.el.html(dom); 68 | } 69 | }); 70 | 71 | var TimelineView = Backbone.View.extend({ 72 | events: { 73 | 'click a.user': 'click_userlink', 74 | 'touchstart .status-row': 'touchstart_status', 75 | 'touchend .status-row': 'touchend_status', 76 | 'mousedown .status-row': 'touchstart_status', 77 | 'mouseup .status-row': 'touchend_status' 78 | }, 79 | 80 | touchend_status: function (evt) { 81 | if(this.touched_row) { 82 | var target = $(evt.currentTarget); 83 | if(!target.hasClass('status-row')) { 84 | target = target.parents('.status-row'); 85 | } 86 | var statusid = target.attr('rel'); 87 | if(statusid && statusid == this.touched_row.statusid) { 88 | var pageX = evt.type == 'mouseup'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 89 | if(Math.abs(pageX - this.touched_row.pageX) > 80) { 90 | this.show_commands(target); 91 | } 92 | } 93 | this.touched_row = null; 94 | } 95 | }, 96 | 97 | touchstart_status: function (evt) { 98 | var target = $(evt.currentTarget); 99 | if(!target.hasClass('status-row')) { 100 | target = target.parents('.status-row'); 101 | } 102 | if(target.length) { 103 | var pageX = evt.type == 'mousedown'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 104 | this.touched_row = { 105 | 'statusid': target.attr('rel'), 106 | 'pageX': pageX 107 | }; 108 | } 109 | }, 110 | 111 | click_userlink: function (evt) { 112 | evt.preventDefault(); 113 | var userid = $(evt.currentTarget).attr('rel'); 114 | if(userid) { 115 | App.gotoUserTimeline(userid); 116 | } 117 | evt.stopPropagation(); 118 | }, 119 | 120 | show_commands: function (dock) { 121 | if($('#status-commands', dock).length) { 122 | $('#status-commands').remove(); 123 | } else { 124 | $('#status-commands').remove(); 125 | var statusid = dock.attr('rel'); 126 | var repost_status_id = dock.attr('repost'); 127 | 128 | var status = null; 129 | var created_at = ''; 130 | for(var i=0; i= wwidth) { 341 | if(this.page <= 0) { 342 | tocenter(this.box, center); 343 | } else { 344 | this.box.animate({'left': wwidth + 'px'}, 'fast', 345 | function () { 346 | v.prev_page(); 347 | }); 348 | } 349 | } else if(this.box.offset().left <= 10) { 350 | if((this.page + 1) * this.pagesize >= this.collection.models.length) { 351 | tocenter(this.box, center); 352 | } else { 353 | this.box.animate({'left': -box_width + 'px'}, 'fast', 354 | function() { 355 | v.next_page(); 356 | }); 357 | } 358 | } else { 359 | tocenter(this.box, center); 360 | } 361 | } 362 | }, 363 | 364 | drag_move: function (evt) { 365 | if(this.start_x) { 366 | var pageX = evt.type == 'mousemove'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 367 | this.delta_x = pageX - this.start_x; 368 | } 369 | }, 370 | 371 | drag_end: function (evt) { 372 | if(this.start_x) { 373 | this.start_x = null; 374 | /*if(this.dragging_userid) { 375 | if($(evt.target).attr('id') == 'userlist-area') { 376 | App.gotoUserTimeline(this.dragging_userid); 377 | } 378 | } 379 | this.dragging_userid = null; */ 380 | 381 | } 382 | }, 383 | 384 | initialize: function () { 385 | this.start_x = null; 386 | this.page = 0; 387 | this.pagesize = 10; 388 | this.fromleft = 0; 389 | }, 390 | 391 | next_page: function () { 392 | this.page += 1; 393 | this.fromleft = 0; 394 | this.render(); 395 | }, 396 | 397 | prev_page: function () { 398 | this.page -= 1; 399 | this.fromleft = 1; 400 | this.render(); 401 | }, 402 | 403 | render: function () { 404 | var start = this.page * this.pagesize; 405 | var end = start + this.pagesize; 406 | var userlist = _.map(this.collection.models.slice(start, end), function (obj) { 407 | obj = obj.toJSON(); 408 | obj.short_name = obj.name.substr(0, 4); 409 | return obj; 410 | }); 411 | 412 | var html = App.template('#userlist-template', { 413 | userlist: userlist 414 | }); 415 | var boxdom = $(html); 416 | if(this.fromleft) { 417 | $('#user-box', boxdom).css('left', '-300px'); 418 | } else { 419 | $('#user-box', boxdom).css('left', this.el.width() + 'px'); 420 | } 421 | this.el.html(boxdom); 422 | var box = this.$('#user-box'); 423 | var center = (this.el.width() - box.width()) / 2; 424 | 425 | this.$('#user-box').animate({ 426 | 'left': center 427 | }, 'slow'); 428 | } 429 | }); 430 | 431 | var DMConvListView = Backbone.View.extend({ 432 | events: { 433 | //'click .dm-row': 'click_dm', 434 | 'touchstart .dm-row': 'touchstart_dm', 435 | 'touchend .dm-row': 'touchend_dm', 436 | 'mousedown .dm-row': 'touchstart_dm', 437 | 'mouseup .dm-row': 'touchend_dm' 438 | }, 439 | initialize: function() { 440 | }, 441 | 442 | click_dm: function (evt) { 443 | 444 | }, 445 | touchend_dm: function (evt) { 446 | if(this.touched_row) { 447 | var target = $(evt.currentTarget); 448 | if(!target.hasClass('dm-row')) { 449 | target = target.parents('.dm-row'); 450 | } 451 | var dmid = target.attr('rel'); 452 | if(dmid && dmid == this.touched_row.dmid) { 453 | var pageX = evt.type == 'mouseup'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 454 | if(Math.abs(pageX - this.touched_row.pageX) > 80) { 455 | this.to_conversation(target); 456 | } 457 | } 458 | this.touched_row = null; 459 | } 460 | }, 461 | 462 | touchstart_dm: function (evt) { 463 | var target = $(evt.currentTarget); 464 | if(!target.hasClass('dm-row')) { 465 | target = target.parents('.dm-row'); 466 | } 467 | if(target.length) { 468 | var pageX = evt.type == 'mousedown'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 469 | this.touched_row = { 470 | 'dmid': target.attr('rel'), 471 | 'pageX': pageX 472 | }; 473 | } 474 | }, 475 | 476 | to_conversation: function (dock) { 477 | var peerid = dock.attr('rel'); 478 | if(peerid) { 479 | App.gohash('#!/dm/' + encodeURIComponent(peerid)); 480 | } 481 | }, 482 | 483 | render: function () { 484 | var dmlist = _.map(this.collection.models, function (obj) { 485 | var msg = obj.toJSON(); 486 | var is_sender = msg.dm.recipient.id == msg.otherid; 487 | msg.peer = (is_sender? 488 | msg.dm.recipient: 489 | msg.dm.sender); 490 | msg.peer.sender_class = is_sender? 'sender': ''; 491 | return msg; 492 | }); 493 | var html = App.template('#dmconv-list-template', { 494 | dmlist: dmlist 495 | }); 496 | this.el.html(html); 497 | } 498 | }); 499 | 500 | var DMConversationView = Backbone.View.extend({ 501 | events: { 502 | 'click .dm-send img': 'click_dm', 503 | 'touchstart .dm-row': 'touchstart_dm', 504 | 'touchend .dm-row': 'touchend_dm', 505 | 'mousedown .dm-row': 'touchstart_dm', 506 | 'mouseup .dm-row': 'touchend_dm' 507 | }, 508 | initialize: function(opts) { 509 | this.user = opts.user; 510 | }, 511 | 512 | click_dm: function (evt) { 513 | $(evt.currentTarget).parents('form').submit(); 514 | }, 515 | 516 | touchend_dm: function (evt) { 517 | if(this.touched_row) { 518 | var target = $(evt.currentTarget); 519 | if(!target.hasClass('dm-row')) { 520 | target = target.parents('.dm-row'); 521 | } 522 | var dmid = target.attr('rel'); 523 | if(dmid && dmid == this.touched_row.dmid) { 524 | var pageX = evt.type == 'mouseup'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 525 | if(Math.abs(pageX - this.touched_row.pageX) > 80) { 526 | this.show_commands(target); 527 | } 528 | } 529 | this.touched_row = null; 530 | } 531 | }, 532 | 533 | touchstart_dm: function (evt) { 534 | var target = $(evt.currentTarget); 535 | if(!target.hasClass('dm-row')) { 536 | target = target.parents('.dm-row'); 537 | } 538 | if(target.length) { 539 | var pageX = evt.type == 'mousedown'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 540 | this.touched_row = { 541 | 'dmid': target.attr('rel'), 542 | 'pageX': pageX 543 | }; 544 | } 545 | }, 546 | 547 | show_commands: function (dock) { 548 | if($('#dm-commands', dock).length) { 549 | $('#dm-commands').remove(); 550 | } else { 551 | $('#dm-commands').remove(); 552 | var peerid = dock.attr('rel'); 553 | var html = App.template('#dm-commands-template', { 554 | peerid: encodeURIComponent(peerid) 555 | }); 556 | var dom = $(html); 557 | //dom.insertAfter(dock); 558 | dock.append(dom); 559 | } 560 | }, 561 | 562 | 563 | render: function () { 564 | var dmlist = _.map(this.collection.models, function (obj) { 565 | var msg = obj.toJSON(); 566 | msg.sender.avatar_align = (msg.sender.id == App.loginuser.get('id')? 567 | 'avatar-right': 'avatar-left'); 568 | return msg; 569 | }); 570 | var html = App.template('#dm-conversation-template', { 571 | dmlist: dmlist, 572 | user: this.user, 573 | loginuser: App.loginuser.toJSON() 574 | }); 575 | this.el.html(html); 576 | this.$('.dm-send form').ajaxForm({ 577 | dataType: 'json', 578 | success: function (data) { 579 | App.gohash(window.location.hash); 580 | }, 581 | error: function (err, req) { 582 | App.handleError(err, req); 583 | }, 584 | complete: function () { 585 | console.info('completed'); 586 | } 587 | }); 588 | 589 | } 590 | }); 591 | -------------------------------------------------------------------------------- /mobile/public/js/app.js: -------------------------------------------------------------------------------- 1 | // Define the router 2 | var AppRouter = Backbone.Router.extend({ 3 | routes: { 4 | '!/update': 'update_status', 5 | '!/reply/:statusid': 'reply', 6 | '!/repost/:statusid': 'repost', 7 | '!/mentions': 'mentions', 8 | '!/browse': 'public_timeline', 9 | '!/statuses/:id': 'status_detail', 10 | '!/q/:query': 'search', 11 | '!/search': 'search_form', 12 | '!/friends': 'friends_list', 13 | '!/dm/:peerid': 'direct_message_conversation', 14 | '!/dm': 'direct_messages', 15 | '!/:id': "user", 16 | '': "home" 17 | }, 18 | 19 | friends_list: function () { 20 | App.ready(function (app) { 21 | app.getFriends(); 22 | }); 23 | }, 24 | 25 | update_status: function () { 26 | App.ready(function (app) { 27 | app.updateStatus(); 28 | }); 29 | }, 30 | 31 | reply: function (statusid) { 32 | App.ready(function (app) { 33 | app.getStatus(statusid, function (orig) { 34 | orig = orig.toJSON(); 35 | app.updateStatus({ 36 | text: '@' + orig.user.name + ' ', 37 | in_reply_to_status_id: statusid 38 | }); 39 | }); 40 | }); 41 | }, 42 | repost: function (statusid) { 43 | App.ready(function (app) { 44 | app.getStatus(statusid, function (orig) { 45 | orig = orig.toJSON(); 46 | var s = orig.text.replace(/<(.|\n)*?>/g, ''); 47 | app.updateStatus({ 48 | text: '转@' + orig.user.name + ' ' + s, 49 | repost_status_id: statusid 50 | }); 51 | }); 52 | }); 53 | }, 54 | 55 | search: function (query) { 56 | App.ready(function (app) { 57 | app.search(query); 58 | }); 59 | }, 60 | 61 | search_form: function () { 62 | App.ready(function (app) { 63 | app.search_form(); 64 | }); 65 | }, 66 | 67 | public_timeline: function () { 68 | App.ready(function (app) { 69 | app.getPublicTimeline(); 70 | }); 71 | }, 72 | 73 | mentions: function () { 74 | App.ready(function (app) { 75 | app.getMentions(); 76 | }); 77 | }, 78 | 79 | user: function (userid) { 80 | App.ready(function (app) { 81 | $(document).scrollTop(0); 82 | app.getUserTimeline(userid); 83 | }); 84 | }, 85 | 86 | home: function () { 87 | App.ready(function (app) { 88 | $(document).scrollTop(0); 89 | app.getHomeTimeline(); 90 | }); 91 | }, 92 | 93 | 94 | status_detail: function (id) { 95 | App.ready(function (app) { 96 | $(document).scrollTop(0); 97 | app.getStatusPage(id); 98 | }); 99 | }, 100 | 101 | direct_message_conversation: function (peerid) { 102 | App.ready(function (app) { 103 | $(document).scrollTop(0); 104 | app.getDMConversation(peerid); 105 | }); 106 | }, 107 | 108 | direct_messages: function () { 109 | App.ready(function (app) { 110 | $(document).scrollTop(0); 111 | app.getDMConvList(); 112 | }); 113 | } 114 | }); 115 | 116 | var App = function () { 117 | var app_router; 118 | var app = new Object(); 119 | app.loginuser = null; 120 | app.statusCache = new ModelCache(Status, 'status'); 121 | app.load_avatar = undefined; 122 | 123 | app.ready = function(fn) { 124 | if(app.loginuser) { 125 | fn(app); 126 | } else { 127 | $(document).bind('metadata.ready', 128 | function () { 129 | fn(app); 130 | }); 131 | } 132 | }; 133 | 134 | app.template = function (temp_selector, data) { 135 | return Mustache.to_html($(temp_selector).html(), 136 | data); 137 | }; 138 | 139 | app.gohash = function(h) { 140 | if(h == '#') { 141 | h = ''; 142 | } 143 | if(window.location.hash == h) { 144 | Backbone.history.loadUrl(h); 145 | } else { 146 | window.location.hash = h; 147 | } 148 | }; 149 | app.getContentArea = function () { 150 | var cnt = $('#content'); 151 | cnt.unbind(); 152 | return cnt; 153 | }; 154 | 155 | app.initialize = function() { 156 | app_router = new AppRouter(); 157 | 158 | window.applicationCache.addEventListener('updateready', function(e) { 159 | if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { 160 | // Browser downloaded a new app cache. 161 | // Swap it in and reload the page to get the new hotness. 162 | window.applicationCache.swapCache(); 163 | if (confirm('有新版本了. 安装?')) { 164 | window.location.reload(); 165 | } 166 | } else { 167 | // Manifest didn't changed. Nothing new to server. 168 | } 169 | }, false); 170 | 171 | setInterval(function () { 172 | var text = $('#loading').html(); 173 | if(text.length >= 3) { 174 | text = '.'; 175 | } else if(text.length == 2) { 176 | text = '...'; 177 | } else { 178 | text = '..'; 179 | } 180 | $('#loading').html(text); 181 | }, 100); 182 | 183 | var loading_counter = 0; 184 | $(document).bind('ajaxSend', function (evt, req, settings) { 185 | loading_counter++; 186 | $('#loading').show(); 187 | }); 188 | $(document).bind('ajaxComplete', function (evt, req) { 189 | loading_counter--; 190 | if(loading_counter <= 0) { 191 | $('#loading').hide(); 192 | loading_counter = 0; 193 | } 194 | }); 195 | $('#loading').hide(); 196 | 197 | var header_touch_point = null; 198 | function header_touch_end(evt) { 199 | if(header_touch_point) { 200 | var pageX = evt.type == 'mouseup'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 201 | if(Math.abs(pageX - header_touch_point.pageX) > 80) { 202 | $('#commands').toggle(); 203 | } 204 | header_touch_point = null; 205 | } 206 | } 207 | 208 | $(document).delegate('#header-wrapper', 'touchend', header_touch_end); 209 | //$(document).delegate('#commands', 'touchend', header_touch_end); 210 | $(document).delegate('#header-wrapper', 'mouseup', header_touch_end); 211 | 212 | function header_touch_start(evt) { 213 | var pageX = evt.type == 'mousedown'?evt.pageX:evt.originalEvent.changedTouches[0].pageX; 214 | header_touch_point = { 215 | 'pageX': pageX 216 | }; 217 | } 218 | $(document).delegate('#header-wrapper', 'touchstart', header_touch_start); 219 | //$(document).delegate('#commands', 'touchstart', header_touch_start); 220 | $(document).delegate('#header-wrapper', 'mousedown', header_touch_start); 221 | 222 | $.ajaxSetup({cache: false}); 223 | 224 | $(document).delegate('#notify-area', 'click', function (evt) { 225 | $('#notify-area').hide(); 226 | }); 227 | 228 | app.fetchNotification(); 229 | setInterval(function () { 230 | app.fetchNotification(); 231 | }, 30 * 1000); 232 | 233 | app.getUser(null, function (u) { 234 | app.loginuser = u; 235 | $(document).trigger('metadata.ready'); 236 | }); 237 | Backbone.history.start(); 238 | }; 239 | app._timelineCache = {}; 240 | app.loadTimelineCache = function (key) { 241 | var cached_model; 242 | if(window.localStorage) { 243 | cached_model = localStorage.getItem('timeline.' + key); 244 | if(cached_model) { 245 | cached_model = new Timeline(_.map(JSON.parse(cached_model), 246 | function (s) { 247 | return new Status(s); 248 | })); 249 | } 250 | } else { 251 | cached_model = app._timelineCache[key]; 252 | } 253 | if(cached_model) { 254 | var v = new TimelineView({ 255 | el: app.getContentArea(), 256 | collection: cached_model 257 | }); 258 | v.render(); 259 | } 260 | return cached_model; 261 | }; 262 | 263 | app.storeTimelineCache = function (key, timeline) { 264 | if(window.localStorage) { 265 | localStorage.setItem('timeline.' + key, 266 | JSON.stringify(_.map(timeline.models, function (m) { 267 | return m.toJSON(); 268 | }))); 269 | } else { 270 | app._timelineCache[key] = timeline; 271 | } 272 | }; 273 | 274 | app.getTimeline = function (url, opts) { 275 | if(typeof opts == 'function') { 276 | opts = {success: opts}; 277 | } else if(!opts) { 278 | opts = {}; 279 | } 280 | var usecache = opts.usecache || true; 281 | var cachekey = url; 282 | if(usecache) { 283 | var cached_model = app.loadTimelineCache(cachekey); 284 | if(cached_model && (opts.cache_once || false)) { 285 | return; 286 | } 287 | } 288 | var startDate = new Date(); 289 | var timeline = new Timeline(); 290 | timeline.url = url; 291 | timeline.fetch({ 292 | 'success': function (data) { 293 | var now = new Date(); 294 | if(app.load_avatar == undefined) { 295 | // Don't show avatar if the network condition is bad 296 | app.load_avatar = (now - startDate) < 1000; 297 | } 298 | 299 | if(opts.success) { 300 | opts.success(data); 301 | } else { 302 | var v = new TimelineView({ 303 | el: app.getContentArea(), 304 | collection: data 305 | }); 306 | v.render(); 307 | if(usecache) { 308 | app.storeTimelineCache(cachekey, data); 309 | } 310 | } 311 | _.map(data.models, function (status) { 312 | app.statusCache.set(status.id, status); 313 | }); 314 | }, 'error': function (err, req) { 315 | if(opts.error) { 316 | opts.error(err, req); 317 | } else { 318 | app.handleError(err, req); 319 | } 320 | } 321 | }); 322 | }; 323 | 324 | app.getMentions = function () { 325 | app.getTimeline('/proxy/statuses/mentions?format=html'); 326 | }; 327 | 328 | app.getPublicTimeline = function () { 329 | app.getTimeline('/proxy/statuses/public_timeline?format=html'); 330 | }; 331 | 332 | app.getHomeTimeline = function () { 333 | app.getTimeline('/proxy/statuses/friends_timeline?format=html'); 334 | }; 335 | 336 | app.search = function (query) { 337 | app.getTimeline('/proxy/search/public_timeline?format=html&q=' + query); 338 | }; 339 | 340 | app.search_form = function () { 341 | var v = new SearchView({ 342 | el: app.getContentArea(), 343 | }); 344 | v.render(); 345 | app.getSavedSearch(); 346 | }; 347 | 348 | app.logout = function () { 349 | localStorage.clear(); 350 | window.location = '/logout'; 351 | }; 352 | 353 | app.getUser = function (userid, opts) { 354 | if(!userid) { 355 | userid = '*'; 356 | } 357 | if(typeof opts == 'function') { 358 | opts = {'success': opts}; 359 | } 360 | opts = opts || {} 361 | var url = '/proxy/users/show' + ((userid != '*')? 362 | ('?id=' + userid): ''); 363 | var user = new User(); 364 | user.url = url; 365 | user.fetch({ 366 | 'success': function (u) { 367 | if(opts.success) { 368 | opts.success(u); 369 | } 370 | }, 371 | 'error': function (err, req) { 372 | if(opts.error) { 373 | opts.error(err, req); 374 | } else { 375 | app.handleError(err, req); 376 | } 377 | } 378 | }); 379 | return; 380 | }; 381 | 382 | app.getUserTimeline = function (userid) { 383 | app.getTimeline( 384 | '/proxy/statuses/user_timeline?format=html&id=' + userid, { 385 | error: function (err, req) { 386 | if(req.status == 403) { 387 | app.notify('隐私用户'); 388 | } else { 389 | app.handleError(err, req); 390 | } 391 | } 392 | }); 393 | }; 394 | 395 | app.getStatusPage = function (statusid) { 396 | app.getStatus(statusid, { 397 | 'success': function (data) { 398 | var view = new StatusView({ 399 | el: app.getContentArea(), 400 | model: data, 401 | }); 402 | view.render(); 403 | app.getStatusContext(statusid); 404 | }, 'error': function (err, req) { 405 | if(req.status == 403) { 406 | app.notify('隐私消息'); 407 | } else { 408 | app.handleError(err, req); 409 | } 410 | } 411 | }); 412 | 413 | }; 414 | 415 | app.fetchNotification = function () { 416 | var url = '/proxy/account/notification'; 417 | $.ajax(url, { 418 | 'success': function(data) { 419 | var s = ''; 420 | if(data.direct_messages > 0) { 421 | s += ' 有新的私信'; 422 | } 423 | if(data.mentions > 0) { 424 | s += ' 有新提示消息'; 425 | } 426 | if(s) { 427 | app.notifyTitle(s); 428 | } 429 | } 430 | }); 431 | } 432 | 433 | app.getStatusContext = function (statusid) { 434 | var url = '/proxy/statuses/context_timeline?format=html&id=' + statusid; 435 | app.getTimeline(url, { 436 | //usecache: false, 437 | cache_once: false, 438 | success: function (timeline) { 439 | if(timeline.models.length > 1) { 440 | var v = new TimelineView({ 441 | el: $('#context'), 442 | prefix: '

消息上下文

', 443 | collection: timeline 444 | }); 445 | v.render(); 446 | } 447 | } 448 | }); 449 | }; 450 | 451 | var cached_trends = null; 452 | $(document).bind('cacheRefresh', function () { 453 | cached_trends = null; 454 | }); 455 | app.getTrends = function () { 456 | if(cached_trends) { 457 | var v = new TrendsView({ 458 | el: $('#query'), 459 | title: '饭否热词', 460 | model: cached_trends 461 | }); 462 | v.render(); 463 | return; 464 | } 465 | 466 | var trends = new Trends(); 467 | trends.url = '/proxy/trends/index'; 468 | trends.fetch({ 469 | 'success': function (data) { 470 | var v = new TrendsView({ 471 | el: $('#query'), 472 | title: '饭否热词', 473 | model: data 474 | }); 475 | v.render(); 476 | cached_trends = data; 477 | }, 'error': function (err, req) { 478 | app.handleError(err, req); 479 | } 480 | }); 481 | }; 482 | var cached_saved_search = null; 483 | $(document).bind('cacheRefresh', function () { 484 | cached_saved_search = null; 485 | }); 486 | app.getSavedSearch = function () { 487 | if(cached_saved_search) { 488 | var v = new QueryListView({ 489 | el: $('#query'), 490 | title: '保存搜索', 491 | collection: cached_saved_search 492 | }); 493 | v.render(); 494 | app.getTrends(); 495 | return; 496 | } 497 | 498 | var trends = new QueryList(); 499 | trends.url = '/proxy/saved_searches/index'; 500 | trends.fetch({ 501 | 'success': function (data) { 502 | var v = new QueryListView({ 503 | el: $('#query'), 504 | title: '保存搜索', 505 | collection: data 506 | }); 507 | v.render(); 508 | cached_saved_search = data; 509 | app.getTrends(); 510 | }, 'error': function (err, req) { 511 | app.handleError(err, req); 512 | } 513 | }); 514 | }; 515 | 516 | app.handleError = function (err, req) { 517 | if(req.status == 410) { 518 | window.location = '/'; 519 | } else if(req.status == 401) { 520 | app.notify('访问错误!'); 521 | } else if(req.status == 403) { 522 | app.notify('无权限'); 523 | } else if(req.status == 404) { 524 | app.notify('对象不存在'); 525 | } else { 526 | console.error(err, req); 527 | } 528 | }; 529 | 530 | app.notify = function (content) { 531 | var v = new NotifyView({ 532 | el: app.getContentArea(), 533 | content: content 534 | }); 535 | v.render(); 536 | }; 537 | 538 | app.updateStatus = function (opts) { 539 | opts = opts || {}; 540 | opts.el = app.getContentArea(); 541 | var v = new UpdateStatusView(opts); 542 | v.render(); 543 | }; 544 | 545 | app.gotoUserTimeline = function (userid) { 546 | var hash = '#!/' + encodeURIComponent(userid); 547 | app.gohash(hash); 548 | }; 549 | 550 | app.refresh = function () { 551 | localStorage.clear(); 552 | $(document).trigger('cacheRefresh'); 553 | }; 554 | 555 | app.getStatus = function (id, opts) { 556 | app.statusCache.getModel(id, '/proxy/statuses/show?format=html&id=' + id, opts); 557 | return; 558 | 559 | if(typeof opts == 'function') { 560 | opts = {success: opts}; 561 | } 562 | opts = opts || {}; 563 | var status = new Status(); 564 | status.url = '/proxy/statuses/show?id=' + id; 565 | status.fetch({ 566 | success: function(st) { 567 | if(opts.success) { 568 | opts.success(st); 569 | } 570 | }, 571 | error: function (err) { 572 | if(opts.error) { 573 | opts.error(err); 574 | } 575 | } 576 | }); 577 | }; 578 | 579 | var cached_friends = null; 580 | $(document).bind('cacheRefresh', function () { 581 | cached_friends = null; 582 | }); 583 | app.getFriends = function () { 584 | if(cached_friends) { 585 | var view = new UserListView({ 586 | el: app.getContentArea(), 587 | collection: cached_friends 588 | }); 589 | view.render(); 590 | return; 591 | } 592 | 593 | var friends = new UserList(); 594 | friends.url = '/proxy/users/friends'; 595 | friends.fetch({ 596 | success: function (frds) { 597 | var view = new UserListView({ 598 | el: app.getContentArea(), 599 | collection: frds 600 | }); 601 | view.render(); 602 | cached_friends = frds; 603 | }, error: function (err, req) { 604 | app.handleError(err, req); 605 | } 606 | }); 607 | }; 608 | 609 | app.getDMConvList = function () { 610 | var dmlist = new DMConvList(); 611 | dmlist.url = '/proxy/direct_messages/conversation_list?mode=lite&count=100'; 612 | dmlist.fetch({ 613 | 'success': function (dms) { 614 | var view = new DMConvListView({ 615 | el: app.getContentArea(), 616 | collection: dms 617 | }); 618 | view.render(); 619 | }, 620 | 'error': function (err, req) { 621 | app.handleError(err, req); 622 | } 623 | }); 624 | }; 625 | 626 | app.getDMConversation = function (peerid) { 627 | var dmlist = new DMList(); 628 | dmlist.url = '/proxy/direct_messages/conversation?mode=lite&id=' + peerid; 629 | dmlist.fetch({ 630 | 'success': function (dms) { 631 | var view = new DMConversationView({ 632 | el: app.getContentArea(), 633 | collection: dms, 634 | user: peerid 635 | }); 636 | view.render(); 637 | }, 638 | 'error': function (err, req) { 639 | app.handleError(err, req); 640 | } 641 | }); 642 | }; 643 | 644 | app.notifyTitle = function (w) { 645 | $('#notify-area').html(w).show(); 646 | }; 647 | return app; 648 | }(); 649 | --------------------------------------------------------------------------------