├── .meteor ├── .gitignore └── packages ├── public ├── img │ ├── qq.png │ ├── arrow.png │ ├── reply.png │ ├── shadow.png │ ├── bg_item.png │ ├── bg_blended.png │ └── dock_shadow.png ├── desktop.css └── mobile.css ├── smart.json ├── .gitignore ├── models └── models.coffee ├── smart.lock ├── server └── server.coffee ├── README.md ├── lib └── moment-lang-zh-cn.js └── client ├── client.coffee ├── index.html └── shared.css /.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | meteorite 3 | -------------------------------------------------------------------------------- /public/img/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/qq.png -------------------------------------------------------------------------------- /public/img/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/arrow.png -------------------------------------------------------------------------------- /public/img/reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/reply.png -------------------------------------------------------------------------------- /public/img/shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/shadow.png -------------------------------------------------------------------------------- /public/img/bg_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/bg_item.png -------------------------------------------------------------------------------- /public/img/bg_blended.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/bg_blended.png -------------------------------------------------------------------------------- /public/img/dock_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuangbo/meteor-bbs/HEAD/public/img/dock_shadow.png -------------------------------------------------------------------------------- /public/desktop.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 24px; 3 | font-weight: 500; 4 | line-height: 150%; 5 | margin: 0px 0px 10px 0px; 6 | padding: 0px; 7 | } -------------------------------------------------------------------------------- /.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | coffeescript 7 | backbone 8 | http 9 | router 10 | cookie 11 | moment 12 | accounts-qq 13 | -------------------------------------------------------------------------------- /smart.json: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": { 3 | "git": "https://github.com/meteor/meteor.git", 4 | "branch": "devel" 5 | }, 6 | "packages": { 7 | "moment": {}, 8 | "router": { 9 | "git": "https://github.com/chuangbo/meteor-router.git" 10 | }, 11 | "cookie": {}, 12 | "accounts-qq": {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | .*.sw[a-z] 3 | *.un~ 4 | Session.vim 5 | 6 | # SublimeText project files 7 | /*.sublime-project 8 | *.sublime-workspace 9 | 10 | # TextMate 11 | *.tmproj 12 | *.tmproject 13 | tmtags 14 | 15 | .DS_Store 16 | 17 | # Thumbnails 18 | ._* 19 | 20 | # Files that might appear on external disk 21 | .Spotlight-V100 22 | .Trashes 23 | 24 | *~ 25 | -------------------------------------------------------------------------------- /models/models.coffee: -------------------------------------------------------------------------------- 1 | Topics = new Meteor.Collection 'topics' 2 | Replys = new Meteor.Collection 'replys' 3 | Nodes = new Meteor.Collection 'nodes' 4 | 5 | simpleAcl = 6 | insert: (userId, doc) -> 7 | userId and doc.userId is userId 8 | update: (userId, docs, fields, modifier) -> 9 | for field in ['_id', 'userId', 'content', 'created', 'nodes', 'title'] 10 | return false if field in fields 11 | true 12 | remove: (userId, doc) -> 13 | false 14 | 15 | Topics.allow simpleAcl 16 | Replys.allow simpleAcl 17 | 18 | Nodes.allow 19 | insert: -> 20 | true 21 | update: -> 22 | false 23 | remove: -> 24 | false 25 | 26 | 27 | Pages = new Meteor.Collection 'pages' 28 | Pages.allow 29 | insert: -> false 30 | update: -> false 31 | remove: -> false -------------------------------------------------------------------------------- /public/mobile.css: -------------------------------------------------------------------------------- 1 | body { 2 | -webkit-text-size-adjust: none; 3 | } 4 | 5 | h1 { 6 | font-size: 18px; 7 | font-weight: 500; 8 | line-height: 140%; 9 | margin: 5px 0px 15px 0px; 10 | padding: 0px; 11 | } 12 | 13 | #Wrapper .content { 14 | width: auto; 15 | padding: 5px; 16 | } 17 | 18 | #Top .content { 19 | width: auto; 20 | } 21 | 22 | #Main { 23 | margin: 0; 24 | } 25 | 26 | #Main .sep20:first-child { 27 | display: none; 28 | } 29 | .sep20 { 30 | height: 10px; 31 | } 32 | 33 | #Rightbar { 34 | display: none; 35 | } 36 | 37 | #Bottom .content { 38 | width: auto; 39 | } 40 | 41 | .header { 42 | padding: 5px; 43 | } 44 | 45 | .cell { 46 | padding: 5px; 47 | } 48 | 49 | .inner { 50 | padding: 5px; 51 | } 52 | 53 | .caution { 54 | padding: 5px; 55 | } 56 | 57 | .sl { 58 | width: 210px; 59 | } 60 | 61 | .ml { 62 | width: 210px; 63 | } 64 | 65 | .mll { 66 | width: 100%; 67 | padding: 0px; 68 | margin: 0px -1px 0px -1px; 69 | } 70 | 71 | .mle { 72 | width: 100%; 73 | padding: 0px; 74 | margin: 0px -1px 0px -1px; 75 | } 76 | 77 | .tall { 78 | height: 220px; 79 | } 80 | 81 | .imgly { 82 | max-width: 280px; 83 | } 84 | -------------------------------------------------------------------------------- /smart.lock: -------------------------------------------------------------------------------- 1 | { 2 | "meteor": { 3 | "git": "https://github.com/meteor/meteor.git", 4 | "branch": "devel", 5 | "commit": "8eacb1ab9f3e0d268f584b6b606b73db5f0d3da9" 6 | }, 7 | "dependencies": { 8 | "basePackages": { 9 | "moment": {}, 10 | "router": { 11 | "git": "https://github.com/chuangbo/meteor-router.git", 12 | "branch": "master" 13 | }, 14 | "cookie": {}, 15 | "accounts-qq": {} 16 | }, 17 | "packages": { 18 | "moment": { 19 | "git": "https://github.com/possibilities/meteor-moment.git", 20 | "tag": "v1.7.0", 21 | "commit": "c64b6ec0e714b9556f4b6643d430b868ba69d3d7" 22 | }, 23 | "router": { 24 | "git": "https://github.com/chuangbo/meteor-router.git", 25 | "branch": "master", 26 | "commit": "ec77449129afc7fa23fc108e6a0fc6fba64183cb" 27 | }, 28 | "cookie": { 29 | "git": "https://github.com/chuangbo/meteor-cookie.git", 30 | "tag": "v1.0.2", 31 | "commit": "263fd43ef3c7eaaa3f412b97bbc8d4622b348a6e" 32 | }, 33 | "accounts-qq": { 34 | "git": "https://github.com/yonggao/meteor-accounts-qq.git", 35 | "tag": "v0.2.5", 36 | "commit": "655c7951b1d34a112e72d43e4d7cd5271dc9bfdd" 37 | }, 38 | "page-js": { 39 | "git": "https://github.com/chuangbo/meteor-page-js.git", 40 | "branch": "master", 41 | "commit": "84b9c5fe2ecb749a8e0d2a570e208ce83e4e358e" 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/server.coffee: -------------------------------------------------------------------------------- 1 | # server 2 | 3 | Meteor.startup -> 4 | NODES = 5 | dnspod: '闲聊' 6 | tech: '技术' 7 | web: 'Web' 8 | dns: 'DNS' 9 | python: 'Python' 10 | 11 | if Nodes.find({}).count() is 0 12 | for k, v of NODES 13 | Nodes.insert name: k, zh: v 14 | 15 | # server: publish all nodes 16 | Meteor.publish "all_nodes", -> 17 | Nodes.find() 18 | 19 | PAGE_ITEM = 20 20 | 21 | # pages count 22 | Meteor.publish 'pages_count', -> 23 | Pages.find() 24 | 25 | Meteor.methods 26 | updatePagesCount: (tab) -> 27 | sel = if tab == '/' then {} else {nodes: tab} 28 | count = Math.ceil ( Topics.find(sel).count() / PAGE_ITEM ) 29 | 30 | old = Pages.findOne tab: tab 31 | if not old? 32 | Pages.insert {tab: tab, count: count} 33 | else if old.count isnt count 34 | Pages.update {tab: tab}, {$set: {count: count}} 35 | 36 | 37 | # current page topics 38 | Meteor.publish "topics", (tab, page) -> 39 | sel = if tab == '/' then {} else {nodes: tab} 40 | start = PAGE_ITEM * (page - 1) 41 | 42 | Topics.find sel, {sort: {updated: -1}, skip: start, limit: PAGE_ITEM} 43 | 44 | # current topic replys 45 | Meteor.publish 'replys', (topic_id) -> 46 | Replys.find topic_id: topic_id 47 | 48 | 49 | # publish all users' profile 50 | Meteor.publish "allUserData", -> 51 | Meteor.users.find({}, {fields: {'profile': 1}}) 52 | 53 | 54 | Meteor.publish 'member_topics', (member_id) -> 55 | Topics.find {userId: member_id}, {sort: {updated: -1}, limit: 20} 56 | 57 | Meteor.publish 'member_replys', (member_id) -> 58 | Replys.find {userId: member_id}, {sort: {created: -1}, limit: 20} 59 | 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Clone of Project Babel 3 in Meteor 2 | 这是一份对 Project Babel 3 的克隆,用 Meteor 写成。 3 | 4 | Demo:https://meteor-bbs.meteor.com 5 | 6 | ## Project Babel 3 是什么? 7 | 8 | [PB3](http://www.v2ex.com/go/babel) 是一套非常简洁的社区软件,作者 @livid 为了运营一个叫做 [V2EX](http://www.v2ex.com) 的社区而开源了它。 9 | 10 | 准确的说,截至到2012年9月20日,只有 PB1(PHP)和 PB2(GAE)分享了源码,据说使用 Tornado 写成,可以部署在本地服务器上的PB3还没有开源,无法部署自己的版本。 11 | 12 | ## Why clone? 13 | 14 | 这份克隆源于我自己的需求,我们团队内部需要一套讨论软件,用来分享、讨论、沉淀,我非常喜欢 V2EX 的简洁,并感觉这就是我们需要的,于是我克隆了它。 15 | 16 | ## Features 17 | 18 | - 与 V2EX 一致的「主题」、「回复」功能 19 | - 使用 QQ 帐号登录 20 | - 同一主题可以属于多个节点 21 | - 单页应用,所有操作都无刷新,数据变化实时展现在所有浏览器上 22 | 23 | ## Tech Specs 24 | 25 | - [Meteor](http://www.meteor.com):非常新颖的一站式Web框架,Meteor-BBS 主要 ([讨论1](http://www.v2ex.com/t/33961),[讨论2](http://www.v2ex.com/t/48084)) 26 | - [Backbone.js](http://documentcloud.github.com/backbone/):前端 MVC 框架,便于前端实现复杂的js单页应用(类似Gmail) 27 | - [CoffeeScript](http://coffeescript.org):一个语言,可以编译为js,语法简洁,隐藏了js中的难以驾驭的部分 28 | 29 | 30 | ## How to start 31 | 32 | 1. ~~安装 Meteor(目前基于 0.4.2,因为 Meteor 变化很快,新版本不一定支持)~~ 33 | 34 | 1. 安装 meteorite(使用了 atmosphere 的 package 35 | 36 | ~~~ 37 | npm install -g meteorite 38 | ~~~ 39 | 40 | 1. git clone 41 | 42 | ~~~ 43 | $ git clone https://github.com/chuangbo/meteor-bbs.git 44 | $ cd meteor-bbs 45 | ~~~ 46 | 47 | 3. run 48 | 49 | ~~~ 50 | $ # meteor 51 | $ mrt 52 | ~~~ 53 | 54 | 4. 配置QQ登录 55 | 56 | 1. 准备工作:到[QQ互联开放平台](http://connect.qq.com/manage/)注册一个「网站/应用」",获取应用ID和密钥 secretKey,该步需要验证网站的 meta 信息,需要修改 client/index.html 头部 `` 部分 57 | 1. 在开发环境下,为了使腾讯的OAuth回调能真正成功,您还需要将您的应用运行在虚拟环境下 58 | 59 | ~~~sh 60 | $ export ROOT_URL=http://your.dev 61 | sudo -E mrt -p 80 62 | ~~~ 63 | 64 | 1. 在开发和生产环境下,首次使用需要配置第一步获得的两个参数 65 | 66 | ~~~js 67 | Meteor.call('configureLoginService', {service: 'qq', clientId:'your_clientId', secret: 'your_secret'}) 68 | ~~~ 69 | 70 | ## Thank Project Babel 71 | 72 | 这份代码完全没有作为产品的计划,开源仅仅只是为了分享、学习 Meteor,别无他意。 73 | 74 | ## Contributer 75 | Thanks @yonggao 76 | 77 | ## Changelog 78 | 79 | - 2012-12-09 v0.2.2 兼容 router 最新 api;添加少量权限控制 80 | - 2012-10-23 v0.2.1 显示合适的头像分辨率 81 | - 2012-10-17 v0.2 发布,合并了 @yonggao 的 QQ 登录分支 #2 82 | - 2012-09-21 使用 meteroite,使用 smart packages 减少代码量 83 | - 2012-09-20 v0.1 发布 -------------------------------------------------------------------------------- /lib/moment-lang-zh-cn.js: -------------------------------------------------------------------------------- 1 | // moment.js language configuration 2 | // language : chinese 3 | // author : suupic : https://github.com/suupic 4 | (function () { 5 | var lang = { 6 | months : "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), 7 | monthsShort : "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"), 8 | weekdays : "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), 9 | weekdaysShort : "周日_周一_周二_周三_周四_周五_周六".split("_"), 10 | weekdaysMin : "日_一_二_三_四_五_六".split("_"), 11 | longDateFormat : { 12 | LT : "Ah点mm", 13 | L : "YYYY年MMMD日", 14 | LL : "YYYY年MMMD日", 15 | LLL : "YYYY年MMMD日LT", 16 | LLLL : "YYYY年MMMD日ddddLT" 17 | }, 18 | meridiem : function (hour, minute, isLower) { 19 | if (hour < 9) { 20 | return "早上"; 21 | } else if (hour < 11 && minute < 30) { 22 | return "上午"; 23 | } else if (hour < 13 && minute < 30) { 24 | return "中午"; 25 | } else if (hour < 18) { 26 | return "下午"; 27 | } else { 28 | return "晚上"; 29 | } 30 | }, 31 | calendar : { 32 | sameDay : '[今天]LT', 33 | nextDay : '[明天]LT', 34 | nextWeek : '[下]ddddLT', 35 | lastDay : '[昨天]LT', 36 | lastWeek : '[上]ddddLT', 37 | sameElse : 'L' 38 | }, 39 | relativeTime : { 40 | future : "%s内", 41 | past : "%s前", 42 | s : "几秒", 43 | m : "1分钟", 44 | mm : "%d分钟", 45 | h : "1小时", 46 | hh : "%d小时", 47 | d : "1天", 48 | dd : "%d天", 49 | M : "1个月", 50 | MM : "%d个月", 51 | y : "1年", 52 | yy : "%d年" 53 | }, 54 | ordinal : function (number) { 55 | return ''; 56 | } 57 | }; 58 | 59 | // Node 60 | if (typeof module !== 'undefined' && module.exports) { 61 | module.exports = lang; 62 | } 63 | // Browser 64 | if (typeof window !== 'undefined' && this.moment && this.moment.lang) { 65 | this.moment.lang('zh-cn', lang); 66 | } 67 | }()); 68 | -------------------------------------------------------------------------------- /client/client.coffee: -------------------------------------------------------------------------------- 1 | # init 2 | 3 | Session.set 'view', null 4 | Session.set 'topic_id', null 5 | Session.set 'tab', '/' 6 | Session.set 'memberId', null 7 | Session.set 'page', 1 8 | 9 | PAGE_ITEM = 20 10 | 11 | 12 | 13 | 14 | # all nodes 15 | Meteor.subscribe 'all_nodes' 16 | 17 | # current page topics 18 | Meteor.autosubscribe -> 19 | Meteor.subscribe "topics", Session.get('tab'), Session.get('page') 20 | 21 | # current page reply 22 | Meteor.autosubscribe -> 23 | Meteor.subscribe "replys", Session.get('topic_id') 24 | 25 | # client: declare collection to hold count object 26 | Meteor.subscribe 'pages_count' 27 | 28 | # FIXME: subscribe all user profile 29 | Meteor.subscribe 'allUserData' 30 | 31 | # FIXME 32 | # subscribe member's topic & replys 33 | # Meteor.autosubscribe -> 34 | # Meteor.subscribe 'member_topics', Session.get('memberId') 35 | # Meteor.autosubscribe -> 36 | # Meteor.subscribe 'member_replys', Session.get 'memberId' 37 | 38 | 39 | # staff 40 | logined = -> 41 | Meteor.user() 42 | 43 | 44 | showerror = (message) -> 45 | $('.problem li').html message 46 | $('.problem').show() 47 | 48 | 49 | 50 | formData = (form) -> 51 | data = {} 52 | for i in $(form).serializeArray() 53 | data[i.name] = i.value 54 | data 55 | 56 | userof = (userId) -> 57 | Meteor.users.findOne({_id: userId})?.profile.name 58 | 59 | 60 | 61 | # view helpers 62 | 63 | view_helpers = 64 | 65 | all_nodes: -> 66 | Nodes.find() 67 | 68 | node_name: (key) -> 69 | Nodes.findOne(name: key)?.zh 70 | 71 | userof: userof 72 | 73 | user: -> 74 | userof this.userId 75 | 76 | gravatar: (size) -> 77 | profile = Meteor.users.findOne({'_id': this.userId})?.profile 78 | if size <= 30 79 | profile?.figureUrl 80 | else if size <= 50 81 | profile?.figureUrlAt50 82 | else 83 | profile?.figureUrlAt100 84 | 85 | fromnow: (t) -> 86 | moment.utc(t).fromNow() 87 | 88 | content: -> 89 | ct = this.content 90 | for parser in content_parser 91 | ct = parser ct 92 | ct 93 | 94 | no_reply: -> 95 | this.reply_count == 0 96 | 97 | 98 | content_parser = [ 99 | # escape 100 | (o) -> Handlebars._escape o 101 | # br 102 | (o) -> o.replace /\n/g, '
' 103 | # mention 104 | (o) -> o.replace /@([a-zA-z0-9]+)/g, '@$1' 105 | # linkify 106 | (o) -> o.replace /(http|https|ftp)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9\-\._\?\,\'\/\\\+&%\$#\=~])*/g, '$&' 107 | # gist 108 | # (o) -> o.replace /(https?:\/\/gist.github.com\/[\d]+)/g, '' 109 | ] 110 | 111 | 112 | # main 113 | # Template.main.events 114 | # 'click a:not([href^="http"]):not([href^="#"])': (e) -> 115 | # e.preventDefault() 116 | # href = $(e.currentTarget).attr 'href' 117 | # Router.navigate href, {trigger: true} 118 | 119 | 120 | 121 | 122 | 123 | # right bar 124 | 125 | Template.rightbar.helpers view_helpers 126 | 127 | Template.rightbar.helpers 128 | logined: -> 129 | Meteor.user() 130 | member: -> 131 | Meteor.user() 132 | 133 | Template.rightbar.preserve ['img'] 134 | 135 | Template.rightbar.events 136 | 'click #loginBtn': => 137 | Meteor.loginWithQq() 138 | 139 | 140 | 141 | # index 142 | Template.index.helpers view_helpers 143 | 144 | Template.index.helpers 145 | topics: -> 146 | 147 | # tab 148 | tab = Session.get 'tab' 149 | 150 | sel = if tab == '/' then {} else {nodes: tab} 151 | r = Topics.find sel, {sort: {updated: -1}} 152 | 153 | r.fetch().slice 0, PAGE_ITEM 154 | 155 | current_tab: (node) -> 156 | node == Session.get 'tab' 157 | 158 | next_page: -> 159 | page = Session.get('page') - 0 + 1 160 | tab = Session.get 'tab' 161 | 162 | if tab == '/' 163 | "/p#{page}" 164 | else 165 | "/go/#{tab}/p#{page}" 166 | 167 | prev_page: -> 168 | page = Session.get('page') - 1 169 | tab = Session.get 'tab' 170 | 171 | if tab == '/' 172 | "/p#{page}" 173 | else 174 | "/go/#{tab}/p#{page}" 175 | 176 | current_page: -> 177 | Session.get('page') 178 | 179 | page_count: -> 180 | tab = Session.get 'tab' 181 | Pages.findOne(tab: tab)?.count 182 | 183 | has_prev_page: -> 184 | Session.get('page')-0 != 1 185 | 186 | has_next_page: -> 187 | tab = Session.get 'tab' 188 | max_page = Pages.findOne(tab: tab)?.count 189 | 190 | max_page != 0 and Session.get('page')-0 != max_page 191 | 192 | Template.index.rendered = -> 193 | Meteor.call 'updatePagesCount', Session.get('tab') 194 | 195 | # topic item line 196 | Template.topic_item.helpers view_helpers 197 | Template.topic_item.preserve ['img'] 198 | 199 | 200 | 201 | 202 | 203 | 204 | # new topic 205 | Template.new.helpers view_helpers 206 | 207 | Template.new.helpers 208 | default_node: (name) -> 209 | tab = if (tab = Session.get('tab')) == '/' then 'dnspod' else tab 210 | 'checked' if name == tab 211 | 212 | Template.new.events 213 | 'submit form': (e) -> 214 | e.preventDefault() 215 | 216 | data = formData e.currentTarget 217 | data.userId = Meteor.userId() 218 | data.created = new Date() 219 | data.updated = new Date() 220 | data.reply_count = 0 221 | data.views = 0 222 | 223 | delete data.node 224 | data.nodes = [] 225 | 226 | $('input[name="node"]:checked').each -> 227 | data.nodes.push $(this).val() 228 | 229 | if data.title == '' 230 | showerror '主题标题不能为空' 231 | return 232 | 233 | if data.nodes.length > 3 234 | showerror '一个主题只能发布在最多3个节点下' 235 | return 236 | 237 | topic_id = Topics.insert data 238 | 239 | Meteor.Router.to "/t/#{topic_id}#reply0" 240 | 241 | 242 | 243 | 244 | 245 | 246 | # one topic 247 | Template.topic.helpers view_helpers 248 | Template.topic.helpers 249 | topic: -> 250 | topic_id = Session.get 'topic_id' 251 | Topics.update {_id: topic_id}, {$inc: {views: 1}} 252 | Topics.find _id: topic_id 253 | 254 | replys: -> 255 | r = Replys.find(topic_id: this._id).fetch() 256 | _i = 1 257 | for i in r 258 | i.index = _i 259 | _i += 1 260 | r 261 | logined: logined 262 | 263 | Template.topic.events 264 | 'submit form': (e) -> 265 | e.preventDefault() 266 | 267 | data = formData e.currentTarget 268 | data.topic_id = this._id 269 | data.userId = Meteor.userId() 270 | data.created = new Date() 271 | 272 | 273 | if data.content == '' 274 | showerror '回复内容不能为空' 275 | return 276 | 277 | Replys.insert data 278 | 279 | # update topic last_reply & replys 280 | Topics.update {_id: this._id}, 281 | { 282 | $set: {last_reply: Meteor.userId(), updated: new Date()} 283 | $inc: {reply_count: 1} 284 | } 285 | 286 | reply_count = Topics.findOne(_id: this._id).reply_count 287 | Meteor.Router.to "/t/#{this._id}#reply#{reply_count}" 288 | 289 | $('#content').val('') 290 | 291 | "click .ReplyOne": (e) -> 292 | e.preventDefault() 293 | 294 | i = $('#content') 295 | old = i.val() 296 | 297 | prefix = "@#{userof this.userId} " 298 | 299 | i.focus() 300 | if old.length > 0 and old != prefix 301 | i.val "#{old}\n#{prefix}" 302 | else 303 | i.val prefix 304 | 305 | # reply 306 | Template.reply.helpers view_helpers 307 | Template.reply.preserve ['img'] 308 | 309 | 310 | 311 | 312 | # member 313 | Template.member.helpers view_helpers 314 | 315 | Template.member.helpers 316 | member: -> 317 | Meteor.users.find '_id': Session.get 'memberId' 318 | 319 | topics: -> 320 | r = Topics.find {userId: this._id}, {sort: {updated: -1}} 321 | r.fetch().slice 0, 20 322 | 323 | replys: -> 324 | r = Replys.find {userId: this._id}, {sort: {created: -1}} 325 | r.fetch().slice 0, 20 326 | 327 | reply_to: (topic_id) -> 328 | Topics.find _id: topic_id 329 | 330 | 331 | # APP 332 | 333 | 334 | Meteor.Router.add 335 | '/': -> 336 | Session.set 'tab', '/' 337 | Cookie.set 'tab', '/' 338 | Session.set 'page', 1 339 | 'index' 340 | '/p:page': (page) -> 341 | Session.set 'tab', '/' 342 | Cookie.set 'tab', '/' 343 | Session.set 'page', page 344 | 'index' 345 | '/new': -> 346 | if not logined() 347 | return 348 | else 349 | 'new' 350 | '/login': -> 351 | if logined() 352 | this.navigate '/', {trigger: true} 353 | else 354 | 'login' 355 | '/t/:topic_id': (topic_id) -> 356 | topic_id = topic_id.split('#', 2)[0] 357 | Session.set 'topic_id', topic_id 358 | 'topic' 359 | '/go/:node': (node) -> 360 | Session.set 'tab', node 361 | Cookie.set 'tab', node 362 | Session.set 'page', 1 363 | 'index' 364 | "/go/:node/p:page": (node, page) -> 365 | Session.set 'tab', node 366 | Cookie.set 'tab', node 367 | Session.set 'page', page 368 | 'index' 369 | "/member/:id": (id) -> 370 | Session.set 'memberId', id 371 | 'member' 372 | 373 | Meteor.Router.filters 374 | 'checkLoggedIn': (page) -> 375 | if Meteor.user() 376 | if Meteor.user() 377 | 'loading' 378 | else 379 | page 380 | else 381 | 'signin' 382 | 383 | Meteor.startup -> 384 | tab = Cookie.get 'tab' 385 | 386 | if location.pathname == '/' and tab? and tab != '/' 387 | Meteor.Router.to "/go/#{tab}" 388 | 389 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Meteor V2EX 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{> main}} 14 | 15 | 16 | 31 | 32 | 71 | 72 | 73 | 74 | 109 | 110 | 111 | 112 | 113 | 143 | 144 | 145 | 146 | 147 | 148 | 215 | 216 | 217 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 313 | 314 | 315 | 316 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /client/shared.css: -------------------------------------------------------------------------------- 1 | html { 2 | padding: 0px; 3 | margin: 0px; 4 | } 5 | 6 | body { 7 | padding: 0px; 8 | margin: 0px; 9 | font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 10 | background-color: #d0d0d0; 11 | background-image: url("img/shadow.png"); 12 | background-repeat: repeat-x; 13 | } 14 | 15 | h1 { 16 | font-size: 24px; 17 | font-weight: 500; 18 | line-height: 100%; 19 | margin: 5px 0px 20px 0px; 20 | padding: 0px; 21 | } 22 | 23 | h2 { 24 | font-size: 18px; 25 | font-weight: 500; 26 | line-height: 100%; 27 | margin: 5px 0px 20px 0px; 28 | padding: 0px; 29 | } 30 | 31 | h3 { 32 | font-size: 16px; 33 | font-weight: 500; 34 | line-height: 100%; 35 | margin: 5px 0px 20px 0px; 36 | padding: 0px; 37 | } 38 | 39 | a:link, a:visited, a:active { 40 | color: #778087; 41 | text-decoration: none; 42 | } 43 | 44 | a:hover { 45 | color: #4d5256; 46 | text-decoration: underline; 47 | } 48 | 49 | a.dark:link, a.dark:visited, a.dark:active { 50 | color: gray; 51 | text-decoration: none; 52 | } 53 | 54 | a.dark:hover { 55 | color: #385f8a; 56 | text-decoration: none; 57 | } 58 | 59 | a.top:link, a.top:visited, a.top:active { 60 | color: #f0f0f0; 61 | text-decoration: none; 62 | text-shadow: 0px -1px 1px #000; 63 | } 64 | 65 | a.top:hover { 66 | color: #fff; 67 | text-decoration: none; 68 | text-shadow: 0px 0px 3px #fff; 69 | } 70 | 71 | a.tab:link, a.tab:visited, a.tab:active { 72 | display: inline-block; 73 | font-size: 13px; 74 | line-height: 13px; 75 | padding: 5px 8px 5px 8px; 76 | margin-right: 5px; 77 | border-radius: 3px; 78 | background-color: #fff; 79 | color: #555; 80 | } 81 | 82 | a.tab:hover { 83 | background-color: #f5f5f5; 84 | color: #000; 85 | text-decoration: none; 86 | } 87 | 88 | a.tab_current:link, a.tab_current:visited, a.tab_current:active { 89 | display: inline-block; 90 | font-size: 13px; 91 | line-height: 13px; 92 | padding: 5px 8px 5px 8px; 93 | margin-right: 5px; 94 | border-radius: 3px; 95 | background-color: #334; 96 | color: #fff; 97 | } 98 | 99 | a.tab_current:hover { 100 | background-color: #445; 101 | color: #fff; 102 | text-decoration: none; 103 | } 104 | 105 | a.node:link, a.node:visited, a.node:active { 106 | background-color: #f5f5f5; 107 | font-size: 10px; 108 | line-height: 10px; 109 | display: inline-block; 110 | padding: 4px 4px 4px 4px; 111 | -moz-border-radius: 2px; 112 | -webkit-border-radius: 2px; 113 | border-radius: 2px; 114 | text-decoration: none; 115 | color: #999; 116 | } 117 | 118 | a.node:hover { 119 | text-decoration: none; 120 | background-color: #e2e2e2; 121 | color: #777; 122 | } 123 | 124 | a.op:link, a.op:visited, a.op:active { 125 | background-color: #f0f0f0; 126 | font-size: 10px; 127 | line-height: 10px; 128 | display: inline-block; 129 | padding: 3px 4px 3px 4px; 130 | border-radius: 3px; 131 | text-decoration: none; 132 | border: 1px solid #ddd; 133 | color: #666; 134 | } 135 | 136 | a.op:hover { 137 | text-decoration: none; 138 | background-color: #e0e0e0; 139 | border: 1px solid #c0c0c0; 140 | color: #333; 141 | } 142 | 143 | a.opo:link, a.opo:visited, a.opo:active { 144 | background-color: #333; 145 | font-size: 10px; 146 | line-height: 10px; 147 | display: inline-block; 148 | padding: 3px 4px 3px 4px; 149 | border-radius: 3px; 150 | text-decoration: none; 151 | border: 1px solid #000; 152 | color: #eee; 153 | } 154 | 155 | a.tb:link, a.tb:visited, a.tb:active { 156 | font-size: 11px; 157 | line-height: 12px; 158 | color: #333; 159 | text-decoration: none; 160 | display: inline-block; 161 | padding: 3px 10px 3px 10px; 162 | border-radius: 15px; 163 | text-shadow: 0px 1px 0px #fff; 164 | } 165 | 166 | a.tb:hover { 167 | background-color: rgba(255, 255, 255, 0.3); 168 | color: #000; 169 | text-decoration: none; 170 | border-radius: 15px; 171 | } 172 | 173 | a.opo:hover { 174 | text-decoration: none; 175 | background-color: #666; 176 | border: 1px solid #333; 177 | color: #fff; 178 | } 179 | 180 | a.black:link, a.black:visited, a.black:active { 181 | color: rgba(0, 0, 0, 1); 182 | text-decoration: none; 183 | } 184 | 185 | a.black:hover { 186 | color: rgba(0, 0, 0, 1); 187 | text-decoration: underline; 188 | text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.2); 189 | } 190 | 191 | ul { 192 | list-style: square; 193 | margin: 1em 0px 0em 1em; 194 | padding: 0px; 195 | } 196 | 197 | ul li { 198 | padding: 0px; 199 | margin: 0px; 200 | } 201 | 202 | #Top { 203 | text-align: center; 204 | background-color: #000; 205 | height: 44px; 206 | background-image: url("img/top.png"); 207 | font-size: 15px; 208 | border-bottom: 1px solid #000; 209 | font-weight: 500; 210 | } 211 | 212 | #Wrapper { 213 | text-align: center; 214 | } 215 | 216 | #Bottom { 217 | border-top: 1px solid #ccc; 218 | background-color: #fff; 219 | text-align: center; 220 | color: #999; 221 | } 222 | 223 | #Leftbar { 224 | width: 0px; 225 | float: left; 226 | } 227 | 228 | #Rightbar { 229 | width: 270px; 230 | float: right; 231 | } 232 | 233 | #Main { 234 | width: auto; 235 | margin: 0px 290px 0px 0px; 236 | } 237 | 238 | #q { 239 | border: none; 240 | width: 222px; 241 | height: 26px; 242 | margin: 1px 0px 1px 30px; 243 | background-color: transparent; 244 | font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 245 | font-size: 12px; 246 | line-height: 20px; 247 | outline: none; 248 | } 249 | 250 | .alt { 251 | background-color: #f5f5f5; 252 | } 253 | 254 | .corner_left { 255 | border-top-left-radius: 3px; 256 | border-bottom-left-radius: 3px; 257 | } 258 | 259 | .corner_right { 260 | border-top-right-radius: 3px; 261 | border-bottom-right-radius: 3px; 262 | } 263 | 264 | .gray { 265 | color: #999; 266 | } 267 | 268 | .fade { 269 | color: #ccc; 270 | } 271 | 272 | .snow { 273 | color: #e2e2e2; 274 | } 275 | 276 | .green { 277 | color: #393; 278 | } 279 | 280 | .bigger { 281 | font-size: 16px; 282 | } 283 | 284 | .small { 285 | font-size: 11px; 286 | } 287 | 288 | .content { 289 | width: 960px; 290 | margin: 0px auto 0px auto; 291 | } 292 | 293 | .box { 294 | background-color: #fff; 295 | -moz-border-radius: 3px; 296 | border-radius: 3px; 297 | -moz-box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); 298 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15); 299 | border-bottom: 2px solid #fef8ee; 300 | border-bottom: 2px solid #e2e2e9; 301 | } 302 | 303 | .transparent { 304 | background-color: transparent; 305 | border: 2px dashed rgba(0, 0, 0, 0.1); 306 | border-radius: 3px; 307 | box-shadow: none; 308 | } 309 | 310 | .page { 311 | font-size: 14px; 312 | line-height: 180%; 313 | padding: 0px 20px 20px 20px; 314 | } 315 | 316 | .inner { 317 | padding: 10px; 318 | font-size: 12px; 319 | line-height: 150%; 320 | text-align: left; 321 | } 322 | 323 | .header { 324 | padding: 10px; 325 | font-size: 14px; 326 | line-height: 120%; 327 | text-align: left; 328 | border-bottom: 1px solid #e2e2e2; 329 | } 330 | 331 | .caution { 332 | padding: 10px; 333 | font-size: 12px; 334 | line-height: 150%; 335 | text-align: left; 336 | 337 | background-color: #f0f0f0; 338 | border-top: 1px solid #e2e2e2; 339 | border-bottom: 1px solid #e2e2e2; 340 | } 341 | 342 | .dock_area { 343 | background-color: #edf3f5; 344 | background-image: url("img/dock_shadow.png"); 345 | background-repeat: repeat-x; 346 | padding: 0px; 347 | } 348 | 349 | .cell { 350 | padding: 10px; 351 | font-size: 12px; 352 | line-height: 120%; 353 | text-align: left; 354 | border-bottom: 1px solid #e2e2e2; 355 | } 356 | 357 | .grid { 358 | padding: 0px; 359 | font-size: 13px; 360 | line-height: 120%; 361 | text-align: left; 362 | } 363 | 364 | .problem { 365 | padding: 10px; 366 | font-size: 13px; 367 | line-height: 120%; 368 | text-align: left; 369 | background-color: #ffffc0; 370 | border-left: 5px solid #fff000; 371 | border-bottom: 1px solid #e2e2e2; 372 | color: #333; 373 | display: none; 374 | } 375 | 376 | .inner a.thank, .cell a.thank { 377 | display: inline-block; 378 | line-height: 12px; 379 | visibility: hidden; 380 | } 381 | 382 | .inner:hover a.thank, .cell:hover a.thank { 383 | display: inline-block; 384 | line-height: 12px; 385 | border-radius: 5px; 386 | visibility: visible; 387 | } 388 | 389 | .inner:hover a.thank:hover, .cell:hover a.thank:hover { 390 | background-color: #f5f5f5; 391 | text-decoration: none; 392 | } 393 | 394 | .thank_area { 395 | display: inline-block; 396 | padding: 2px 5px 2px 5px; 397 | line-height: 12px; 398 | } 399 | 400 | .thanked { 401 | display: inline-block; 402 | background-color: #f9f9f9; 403 | color: #e0e0e0; 404 | border-radius: 3px; 405 | } 406 | 407 | .sep20 { 408 | height: 20px; 409 | } 410 | 411 | .sep10 { 412 | height: 10px; 413 | } 414 | 415 | .sep5 { 416 | height: 5px; 417 | } 418 | 419 | .sep3 { 420 | height: 3px; 421 | } 422 | 423 | .c { 424 | clear: both; 425 | } 426 | 427 | .chevron { 428 | font-family: "Lucida Grande"; 429 | font-weight: 500; 430 | } 431 | 432 | .fr { 433 | float: right; 434 | text-align: right; 435 | } 436 | 437 | .fl { 438 | float: left; 439 | } 440 | 441 | .f11 { 442 | font-size: 11px; 443 | } 444 | 445 | .f12 { 446 | font-size: 12px; 447 | } 448 | 449 | .imgly { 450 | max-width: 580px; 451 | } 452 | 453 | .no { 454 | font-size: 9px; 455 | line-height: 9px; 456 | font-weight: 500; 457 | border-radius: 10px; 458 | display: inline-block; 459 | background-color: #f0f0f0; 460 | color: #ccc; 461 | padding: 2px 5px 2px 5px; 462 | } 463 | 464 | .reply_ico { 465 | display: inline-block; 466 | vertical-align: top; 467 | width: 20px; 468 | height: 16px; 469 | background-image: url('/img/reply.png'); 470 | } 471 | 472 | .reply_content, .topic_content { 473 | font-size: 14px; 474 | line-height: 180%; 475 | color: #000; 476 | overflow:hidden; 477 | word-break: break-word; 478 | } 479 | 480 | .topic_buttons { 481 | padding: 5px; 482 | font-size: 14px; 483 | line-height: 120%; 484 | 485 | background: #eeeeee; 486 | background: -moz-linear-gradient(top, #eeeeee 0%, #cccccc 100%); 487 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eeeeee), color-stop(100%,#cccccc)); 488 | background: -webkit-linear-gradient(top, #eeeeee 0%,#cccccc 100%); 489 | background: -o-linear-gradient(top, #eeeeee 0%,#cccccc 100%); 490 | background: -ms-linear-gradient(top, #eeeeee 0%,#cccccc 100%); 491 | background: linear-gradient(to bottom, #eeeeee 0%,#cccccc 100%); 492 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#cccccc',GradientType=0 ); 493 | 494 | border-radius: 0px 0px 3px 3px; 495 | text-align: left; 496 | } 497 | 498 | #topic_thank { 499 | display: inline-block; 500 | } 501 | 502 | /* FORM */ 503 | 504 | .sl { 505 | border-radius: 4px; 506 | padding: 5px; 507 | font-size: 14px; 508 | border: 1px solid #ccc; 509 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset; 510 | width: 310px; 511 | font-family: "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 512 | 513 | } 514 | 515 | .sl:focus { 516 | border: 1px solid rgba(128, 128, 160, 0.6); 517 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset, 0px 0px 5px rgba(128, 128, 160, 0.5); 518 | outline: none; 519 | } 520 | 521 | .sll { 522 | border-radius: 4px; 523 | padding: 5px; 524 | font-size: 14px; 525 | border: 1px solid #ccc; 526 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset; 527 | width: 638px; 528 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 529 | } 530 | 531 | .sll:focus { 532 | border: 1px solid rgba(128, 128, 160, 0.6); 533 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset, 0px 0px 5px rgba(128, 128, 160, 0.5); 534 | outline: none; 535 | } 536 | 537 | .ml { 538 | border-radius: 3px; 539 | padding: 5px; 540 | font-size: 14px; 541 | border: 1px solid #ccc; 542 | display: block; 543 | width: 310px; 544 | height: 160px; 545 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset; 546 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 547 | } 548 | 549 | .ml:focus { 550 | border: 1px solid rgba(128, 128, 160, 0.6); 551 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset, 0px 0px 5px rgba(128, 128, 160, 0.5); 552 | outline: none; 553 | } 554 | 555 | /* Multi Line Large */ 556 | 557 | .mll { 558 | border-radius: 3px; 559 | padding: 5px; 560 | font-size: 14px; 561 | border: 1px solid #ccc; 562 | display: block; 563 | width: 640px; 564 | height: 8em; 565 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset; 566 | overflow-y: auto; 567 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 568 | } 569 | 570 | .mll:focus { 571 | border: 1px solid rgba(128, 128, 160, 0.6); 572 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset, 0px 0px 5px rgba(128, 128, 160, 0.5); 573 | outline: none; 574 | } 575 | 576 | /* Multi Line Embedded */ 577 | 578 | .mle { 579 | border-radius: 3px; 580 | padding: 5px; 581 | font-size: 14px; 582 | border: 1px solid #ccc; 583 | display: block; 584 | width: 630px; 585 | height: 100px; 586 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset; 587 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti !important; 588 | } 589 | 590 | .mle:focus { 591 | border: 1px solid rgba(128, 128, 160, 0.6); 592 | box-shadow: 0pt 1px 2px rgba(0, 0, 0, 0.18) inset, 0px 0px 5px rgba(128, 128, 160, 0.5); 593 | outline: none; 594 | } 595 | 596 | .short { 597 | height: 52px; 598 | } 599 | 600 | .tall { 601 | height: 320px; 602 | } 603 | 604 | .super.button { 605 | background-image: url("img/bg_blended.png"); 606 | padding: 4px 15px 4px 15px; 607 | border: 1px solid rgba(80,80,90, 0.2); 608 | border-bottom-color: rgba(80,80,90, 0.35); 609 | border-radius: 3px; 610 | font-size: 12px; 611 | line-height: 12px; 612 | outline: none; 613 | } 614 | 615 | .normal.button { 616 | background-color: #f0f4f7; 617 | color: #333; 618 | text-shadow: 0px 1px 0px #fff; 619 | text-decoration: none; 620 | font-weight: bold; 621 | box-shadow: 0px 1px 0px rgba(66, 66, 77, 0.25); 622 | } 623 | 624 | .normal.button:hover { 625 | background-color: #fff; 626 | color: #333; 627 | text-shadow: 0px 1px 0px #fff; 628 | text-decoration: none; 629 | font-weight: bold; 630 | cursor: pointer; 631 | box-shadow: 0px 1px 0px rgba(66, 66, 77, 0.2); 632 | } 633 | 634 | .normal.button:active { 635 | background-color: #e2e2e2; 636 | color: #333; 637 | text-shadow: 0px 1px 0px #fff; 638 | text-decoration: none; 639 | font-weight: bold; 640 | cursor: pointer; 641 | box-shadow: 0px 1px 0px rgba(66, 66, 77, 0.2); 642 | } 643 | 644 | .special.button { background-color: #ffcc00; color: #532b17; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.6); text-decoration: none; font-weight: 600; -moz-box-shadow: 0px 1px 2px rgba(233, 175, 0, 0.6); border: 1px solid rgba(200, 150, 0, 0.8); } 645 | 646 | .special.button:hover { background-color: #ffdf00; color: #402112; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.7); text-decoration: none; text-decoration: none; font-weight: 600; cursor: pointer; -moz-box-shadow: 0px 1px 2px rgba(233, 175, 0, 0.5); border: 1px solid rgba(200, 150, 0, 1); } 647 | 648 | .special.button:active { background-color: #ffbb00; color: #402112; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.7); text-decoration: none; text-decoration: none; font-weight: 600; cursor: pointer; -moz-box-shadow: 0px 1px 2px rgba(233, 175, 0, 0.5); border: 1px solid rgba(200, 150, 0, 1); } 649 | 650 | .inverse.button { background-color: #ccc; color: #999; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.6); text-decoration: none; font-weight: 600; -moz-box-shadow: 0px 1px 2px rgba(200, 200, 200, 0.8); border: 1px solid rgba(150, 150, 150, 0.8); } 651 | 652 | .inverse.button:hover { background-color: #999; color: #fff; text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5); text-decoration: none; text-decoration: none; font-weight: 600; cursor: pointer; -moz-box-shadow: 0px 1px 2px rgba(200, 200, 200, 1); border: 1px solid rgba(150, 150, 150, 0.6); } 653 | 654 | .inverse.button:active { background-color: #888; color: #fff; text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5); text-decoration: none; text-decoration: none; font-weight: 600; cursor: pointer; -moz-box-shadow: 0px 1px 2px rgba(200, 200, 200, 1); border: 1px solid rgba(150, 150, 150, 0.6); } 655 | 656 | .item { 657 | background-image: url("img/bg_item.png"); 658 | background-position: 0 bottom; 659 | background-repeat: repeat-x; 660 | } 661 | 662 | .item_node { 663 | font-size: 12px; 664 | line-height: 12px; 665 | padding: 4px 10px 4px 10px; 666 | margin: 0px 5px 5px 0px; 667 | border-radius: 4px; 668 | display: inline-block; 669 | 670 | background: #ffffff; 671 | background: -moz-linear-gradient(top, #ffffff 0%, #f3f3f3 50%, #ededed 51%, #ffffff 100%); 672 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(50%,#f3f3f3), color-stop(51%,#ededed), color-stop(100%,#ffffff)); 673 | background: -webkit-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); 674 | background: -o-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); 675 | background: -ms-linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); 676 | background: linear-gradient(top, #ffffff 0%,#f3f3f3 50%,#ededed 51%,#ffffff 100%); 677 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); 678 | 679 | border: 1px solid #e5e5e5; 680 | } 681 | 682 | .item_node:hover { 683 | text-decoration: none; 684 | color: #333; 685 | 686 | border: 1px solid #ccc; 687 | } 688 | 689 | .item_title { 690 | font-size: 16px; 691 | line-height: 130%; 692 | text-shadow: 0px 1px 0px #fff; 693 | } 694 | 695 | .item_hot_topic_title { 696 | font-size: 13px; 697 | line-height: 120%; 698 | text-shadow: 0px 1px 0px #fff; 699 | } 700 | 701 | /* COUNT */ 702 | 703 | a.count_orange:link, a.count_orange:active { 704 | line-height: 12px; 705 | font-weight: bold; 706 | color: white; 707 | background-color: #ff9900; 708 | display: inline-block; 709 | padding: 2px 10px 2px 10px; 710 | -moz-border-radius: 12px; 711 | -webkit-border-radius: 12px; 712 | border-radius: 12px; 713 | text-decoration: none; 714 | margin-right: 5px; 715 | } 716 | 717 | a.count_orange:hover { 718 | line-height: 12px; 719 | font-weight: bold; 720 | color: white; 721 | background-color: #ffa722; 722 | display: inline-block; 723 | padding: 2px 10px 2px 10px; 724 | -moz-border-radius: 12px; 725 | -webkit-border-radius: 12px; 726 | border-radius: 12px; 727 | text-decoration: none; 728 | } 729 | 730 | a.count_livid:link, a.count_livid:active { 731 | line-height: 12px; 732 | font-weight: bold; 733 | color: white; 734 | background-color: #aab0c6; 735 | display: inline-block; 736 | padding: 2px 10px 2px 10px; 737 | -moz-border-radius: 12px; 738 | -webkit-border-radius: 12px; 739 | border-radius: 12px; 740 | text-decoration: none; 741 | margin-right: 5px; 742 | } 743 | 744 | a.count_livid:hover { 745 | line-height: 12px; 746 | font-weight: bold; 747 | color: white; 748 | background-color: #969cb1; 749 | display: inline-block; 750 | padding: 2px 10px 2px 10px; 751 | -moz-border-radius: 12px; 752 | -webkit-border-radius: 12px; 753 | border-radius: 12px; 754 | text-decoration: none; 755 | } 756 | 757 | a.count_blue:visited, a.count_green:visited, a.count_orange:visited, a.count_livid:visited { 758 | line-height: 12px; 759 | font-weight: bold; 760 | color: white; 761 | background-color: #e5e5e5; 762 | display: inline-block; 763 | padding: 2px 10px 2px 10px; 764 | -moz-border-radius: 12px; 765 | -webkit-border-radius: 12px; 766 | border-radius: 12px; 767 | text-decoration: none; 768 | margin-right: 5px; 769 | } 770 | 771 | /* PAGE */ 772 | 773 | .page_current { 774 | display: inline-block; 775 | font-weight: bold; 776 | font-size: 14px; 777 | line-height: 14px; 778 | padding: 2px 5px 2px 5px; 779 | background-color: #f0f0f0; 780 | -moz-border-radius: 3px; 781 | -webkit-border-radius: 3px; 782 | border-radius: 3px; 783 | margin: 0px 2px 0px 2px; 784 | } 785 | 786 | .page_normal:link, .page_normal:visited, .page_normal:active { 787 | display: inline-block; 788 | font-weight: bold; 789 | font-size: 14px; 790 | line-height: 14px; 791 | padding: 2px 5px 2px 5px; 792 | background-color: #fff; 793 | -moz-border-radius: 3px; 794 | -webkit-border-radius: 3px; 795 | border-radius: 3px; 796 | margin: 0px 2px 0px 2px; 797 | text-decoration: none; 798 | } 799 | 800 | .page_normal:hover { 801 | display: inline-block; 802 | font-weight: bold; 803 | font-size: 14px; 804 | line-height: 14px; 805 | padding: 2px 5px 2px 5px; 806 | background-color: #ff9933; 807 | color: #fff; 808 | -moz-border-radius: 3px; 809 | -webkit-border-radius: 3px; 810 | border-radius: 3px; 811 | margin: 0px 2px 0px 2px; 812 | text-decoration: none; 813 | } 814 | 815 | .online { 816 | color: #fff; 817 | font-size: 10px; 818 | line-height: 10px; 819 | font-weight: 500; 820 | padding: 2px 5px 2px 5px; 821 | -moz-border-radius: 10px; 822 | -webkit-border-radius: 10px; 823 | border-radius: 10px; 824 | display: inline-block; 825 | 826 | background: #52bf1c; 827 | background: -moz-linear-gradient(top, #52bf1c 0%, #438906 100%); 828 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#52bf1c), color-stop(100%,#438906)); 829 | background: -webkit-linear-gradient(top, #52bf1c 0%,#438906 100%); 830 | background: -o-linear-gradient(top, #52bf1c 0%,#438906 100%); 831 | background: -ms-linear-gradient(top, #52bf1c 0%,#438906 100%); 832 | background: linear-gradient(top, #52bf1c 0%,#438906 100%); 833 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#52bf1c', endColorstr='#438906',GradientType=0 ); 834 | } 835 | 836 | .payload { 837 | display: inline-block; 838 | background-color: #f5f5f5; 839 | padding: 5px 10px 5px 10px; 840 | font-size: 14px; 841 | line-height: 120%; 842 | -moz-border-radius: 3px; 843 | -webkit-border-radius: 3px; 844 | border-radius: 3px; 845 | word-break: break-word; 846 | } 847 | 848 | /* BALANCE */ 849 | 850 | a.balance_area:link, a.balance_area:visited, .balance_area { 851 | color: #000; 852 | font-size: 11px; 853 | line-height: 16px; 854 | padding: 5px 10px 5px 10px; 855 | -moz-border-radius: 20px; 856 | -webkit-border-radius: 20px; 857 | border-radius: 20px; 858 | text-decoration: none; 859 | color: #666; 860 | text-shadow: 0px 1px 0px white; 861 | display: inline-block; 862 | margin: -4px -5px 0px 0px; 863 | 864 | background: #f5f5f5; 865 | background: -moz-linear-gradient(top, #f5f5f5 0%, #e2e2e2 100%); 866 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(100%,#e2e2e2)); 867 | background: -webkit-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 868 | background: -o-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 869 | background: -ms-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 870 | background: linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 871 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#e2e2e2',GradientType=0 ); 872 | } 873 | 874 | a.balance_area:active { 875 | text-decoration: none; 876 | color: #333; 877 | color: #000; 878 | 879 | background: #f0f0f0; 880 | background: -moz-linear-gradient(top, #f0f0f0 0%, #c9c9c9 100%); 881 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f0f0f0), color-stop(100%,#c9c9c9)); 882 | background: -webkit-linear-gradient(top, #f0f0f0 0%,#c9c9c9 100%); 883 | background: -o-linear-gradient(top, #f0f0f0 0%,#c9c9c9 100%); 884 | background: -ms-linear-gradient(top, #f0f0f0 0%,#c9c9c9 100%); 885 | background: linear-gradient(top, #f0f0f0 0%,#c9c9c9 100%); 886 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f0f0', endColorstr='#c9c9c9',GradientType=0 ); 887 | } 888 | 889 | a.balance_area:hover { 890 | text-decoration: none; 891 | color: #333; 892 | color: #000; 893 | 894 | background: #f9f9f9; 895 | background: -moz-linear-gradient(top, #f9f9f9 0%, #f0f0f0 100%); 896 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f9f9f9), color-stop(100%,#f0f0f0)); 897 | background: -webkit-linear-gradient(top, #f9f9f9 0%,#f0f0f0 100%); 898 | background: -o-linear-gradient(top, #f9f9f9 0%,#f0f0f0 100%); 899 | background: -ms-linear-gradient(top, #f9f9f9 0%,#f0f0f0 100%); 900 | background: linear-gradient(top, #f9f9f9 0%,#f0f0f0 100%); 901 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f9f9f9', endColorstr='#f0f0f0',GradientType=0 ); 902 | } 903 | 904 | /* DATA */ 905 | 906 | table.data { 907 | 908 | } 909 | 910 | table.data td.h { 911 | text-align: left; 912 | font-size: 12px; 913 | font-weight: bold; 914 | border-right: 1px solid #ccc; 915 | border-bottom: 2px solid #ccc; 916 | text-shadow: 0px 1px 0px #fff; 917 | 918 | background: #f5f5f5; 919 | background: -moz-linear-gradient(top, #f5f5f5 0%, #e2e2e2 100%); 920 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(100%,#e2e2e2)); 921 | background: -webkit-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 922 | background: -o-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 923 | background: -ms-linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 924 | background: linear-gradient(top, #f5f5f5 0%,#e2e2e2 100%); 925 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#e2e2e2',GradientType=0 ); 926 | } 927 | 928 | table.data td.d { 929 | text-align: left; 930 | font-size: 12px; 931 | font-weight: normal; 932 | border-right: 1px solid #ccc; 933 | border-bottom: 1px solid #ccc; 934 | } 935 | 936 | .positive { 937 | color: #0aa31c; 938 | } 939 | 940 | .negative { 941 | color: #ff3c00; 942 | } 943 | 944 | .note { 945 | font-size: 15px; 946 | line-height: 150%; 947 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Luxi Mono", "Courier New", Monaco, "Hiragino Sans GB", STHeiti !important; 948 | } 949 | 950 | .gist { 951 | font-size: 12px; 952 | font-family: "Panic Sans", "Menlo", "DejaVu Sans Mono", "Luxi Mono", "Courier New", Monaco, "Hiragino Sans GB", STHeiti !important; 953 | } 954 | 955 | .gist-file { 956 | -moz-border-radius: 3px; 957 | -webkit-border-radius: 3px; 958 | border: 2px solid #999; 959 | -moz-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1); 960 | -webkit-box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1); 961 | } 962 | 963 | .gist-file .gist-data { 964 | -moz-border-radius: 3px 3px 0px 0px; 965 | -webkit-border-radius: 3px; 966 | } 967 | 968 | #editor { 969 | position: relative; 970 | width: auto; 971 | height: 600px; 972 | font-size: 16px; 973 | line-height: 130%; 974 | } 975 | 976 | .event_badge { 977 | float: left; 978 | display: block; 979 | width: 40px; 980 | text-align: center; 981 | padding: 0px 10px 0px 10px; 982 | border-left: 4px solid #e2e2e2; 983 | vertical-align: top; 984 | } 985 | 986 | .event_day { 987 | font-size: 24px; 988 | line-height: 24px; 989 | font-weight: bold; 990 | color: #000; 991 | margin: 5px 0px 5px 0px; 992 | } 993 | 994 | .event_month { 995 | font-size: 14px; 996 | line-height: 14px; 997 | font-weight: bold; 998 | color: #999; 999 | } 1000 | 1001 | .event_body { 1002 | display: block; 1003 | margin-left: 74px; 1004 | } 1005 | 1006 | .event_title { 1007 | font-size: 15px; 1008 | line-height: 20px; 1009 | font-weight: 500; 1010 | margin-bottom: 5px; 1011 | } 1012 | 1013 | .event_brief { 1014 | font-size: 13px; 1015 | line-height: 20px; 1016 | color: #666; 1017 | } 1018 | 1019 | .event_location { 1020 | font-size: 11px; 1021 | line-height: 20px; 1022 | color: #999; 1023 | } 1024 | 1025 | .event_ops { 1026 | padding-top: 5px; 1027 | font-size: 12px; 1028 | line-height: 12px; 1029 | color: #ccc; 1030 | } 1031 | 1032 | img.avatar { 1033 | -moz-border-radius: 4px; 1034 | border-radius: 4px; 1035 | } --------------------------------------------------------------------------------