├── .gitignore ├── .jshintrc ├── .travis.yml ├── History.md ├── Makefile ├── README.md ├── api └── v1 │ ├── message.js │ ├── middleware.js │ ├── reply.js │ ├── tools.js │ ├── topic.js │ └── user.js ├── api_router_v1.js ├── app.js ├── assets.json ├── bdunion.txt ├── bin └── generate_accesstoken.js ├── common ├── at.js ├── cache.js ├── mail.js ├── message.js ├── push.js ├── render_helper.js ├── store.js ├── store_local.js ├── store_qn.js └── tools.js ├── config.default.js ├── controllers ├── github.js ├── message.js ├── reply.js ├── rss.js ├── search.js ├── sign.js ├── site.js ├── static.js ├── topic.js └── user.js ├── middlewares ├── auth.js ├── conf.js ├── github_strategy.js └── limit.js ├── models ├── index.js ├── message.js ├── reply.js ├── topic.js ├── topic_collect.js └── user.js ├── nav.html ├── nav.js ├── newrelic.js ├── package.json ├── proxy ├── index.js ├── message.js ├── reply.js ├── topic.js ├── topic_collect.js └── user.js ├── public ├── bdunion.txt ├── editor.min.1ab56128.debug.js ├── editor.min.1ab56128.min.js ├── favicon.ico ├── favicon.ico.bak ├── github-card.html ├── images │ ├── 75team-favicon.png │ ├── 75team-qrcode.png │ ├── aliued-favicon.ico │ ├── alloyteam-favicon.jpg │ ├── alloyteam-qrcode.png │ ├── angular-small.png │ ├── aotu-favicon.png │ ├── appdownload.png │ ├── babel-favicon.png │ ├── behance-favicon.png │ ├── bootstrap-favicon.png │ ├── browserify.png │ ├── byvoid.png │ ├── caniuse.png │ ├── charles-favicon.png │ ├── checkmark_icon&16.png │ ├── cnode_icon_32.png │ ├── cnode_icon_64.png │ ├── cnode_logo_128.png │ ├── cnode_logo_32.png │ ├── cnodejs-favicon.png │ ├── cnodejs.png │ ├── cnodejs.svg │ ├── cnodejs_light.svg │ ├── cocos2dx.png │ ├── code.png │ ├── codepen.png │ ├── coding.png │ ├── css-conf.png │ ├── css-tricks-favicon.png │ ├── css-wizardry-favicon.png │ ├── d2.png │ ├── d3-favicon.png │ ├── daqianduan-favicon.png │ ├── define-guild.jpg │ ├── digitalocean.png │ ├── dntzhang.jpeg │ ├── dribbble-favicon.png │ ├── echarts-favicon.png │ ├── efe-favicon.png │ ├── egret.png │ ├── express.png │ ├── f2e-logo.png │ ├── fe-zaoduke-qrcode.png │ ├── fed-favicon.png │ ├── feday.png │ ├── fex-favicon.png │ ├── fex-qrcode.png │ ├── fiddler-favicon.png │ ├── fis-favicon.png │ ├── font-awesome-favicon.png │ ├── front-dev-qrcode.png │ ├── front-end-magazine-qrcode.png │ ├── front-show.png │ ├── github-favicon.png │ ├── golangtc-logo.png │ ├── golangtc-logo2.png │ ├── gold-favicon.png │ ├── grunt-favicon.png │ ├── gulp-favicon.png │ ├── highcharts-favicon.png │ ├── html5dw-favicon.png │ ├── http-guide.jpg │ ├── huaban-favicon.png │ ├── hubwiz-logo.png │ ├── icomoon-favicon.png │ ├── iconfont-favicon.jpg │ ├── iconfont.css │ ├── index.css │ ├── index.js │ ├── iojs-logo-w150h50.png │ ├── iojs-logo.png │ ├── ionic-china-icon.png │ ├── ionic-logo-white.svg │ ├── ionichina_icon_32.png │ ├── ionichina_icon_64.png │ ├── isux-favicon.jpg │ ├── iweb.png │ ├── jpush-logo.png │ ├── jpush_150616.jpg │ ├── jpush_150629.jpg │ ├── jpush_150702.jpg │ ├── jpush_150729.jpg │ ├── jpush_150815.gif │ ├── jquery.png │ ├── js-conf.png │ ├── js-design-pattern.jpg │ ├── jstips-favicon.png │ ├── kinvix.png │ ├── koa.png │ ├── less-favicon.png │ ├── lifesinger.png │ ├── logo.png │ ├── logo_bak.png │ ├── node-subway-qrcode.png │ ├── nodejs_black.png │ ├── o2-qrcode.png │ ├── phaserjs.png │ ├── phphub-logo.png │ ├── pinterest-favicon.png │ ├── professional-javascript.jpg │ ├── proginn-logo.png │ ├── qdjhu-logo.png │ ├── qiniu.png │ ├── qqlog.png │ ├── qrcode-weixin.jpg │ ├── react-favicon.png │ ├── react-native.png │ ├── ruby-china-logo2.png │ ├── rxjs-favicon.png │ ├── sass-favicon.png │ ├── search.png │ ├── segmentfault-favicon.png │ ├── sf-logo.png │ ├── showcase │ │ ├── app_mask.png │ │ ├── app_mask@2x.png │ │ ├── cnodejs.png │ │ ├── common.png │ │ ├── gudongjiankong.jpg │ │ ├── hipo.png │ │ ├── tuchong.png │ │ ├── yibeiban.png │ │ └── zhangshangyiyuan.png │ ├── smashingmagazine-favicon.png │ ├── stackoverflow-favicon.png │ ├── stats │ ├── stylus-favicon.png │ ├── sublime-favicon.png │ ├── testerhome-logo.png │ ├── thefwa-favicon.png │ ├── threejs-favicon.png │ ├── tj.png │ ├── ucloud-150515.jpg │ ├── ucloud-151226.jpg │ ├── ucloud.png │ ├── uigreat-favicon.jpg │ ├── upyun_logo.png │ ├── v2ex-favicon.png │ ├── vendor.js │ ├── vue-favicon.png │ ├── vue.png │ ├── vueconf.png │ ├── w3cplus-favicon.png │ ├── w3cplus-qrcode.png │ ├── w3cteach-logo.png │ ├── w3ctech-favicon.jpg │ ├── w3ctech-qrcode.png │ ├── webpack-favicon.png │ ├── webstorm-favicon.png │ ├── winter.png │ ├── yeoman-favicon.png │ ├── yyx990803.png │ └── zcool-favicon.jpg ├── img │ ├── Golang.png │ ├── PoweredMongoDBbeige50.png │ ├── accept.png │ ├── bg-footer.gif │ ├── bg.png │ ├── exclamation.png │ ├── golang-china.gif │ ├── icon06.gif │ ├── icon07.gif │ ├── icon08.gif │ ├── icon09.gif │ ├── line04.gif │ ├── logo.jpg │ ├── qrcode.jpg │ ├── ricepaper.png │ ├── weibo.png │ └── wmd-buttons.png ├── index.min.d24523a2.debug.js ├── index.min.d24523a2.min.js ├── index.min.d24523a2.min.js.gz ├── javascripts │ ├── history.js │ ├── main.js │ └── responsive.js ├── libs │ ├── bootstrap │ │ ├── css │ │ │ ├── bootstrap-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ ├── img │ │ │ ├── glyphicons-halflings-white.png │ │ │ └── glyphicons-halflings.png │ │ └── js │ │ │ ├── bootstrap.js │ │ │ └── bootstrap.min.js │ ├── code-prettify │ │ ├── highlight.css │ │ ├── highlight.js │ │ ├── lang-apollo.js │ │ ├── lang-clj.js │ │ ├── lang-css.js │ │ ├── lang-go.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-lua.js │ │ ├── lang-ml.js │ │ ├── lang-n.js │ │ ├── lang-proto.js │ │ ├── lang-scala.js │ │ ├── lang-sql.js │ │ ├── lang-tex.js │ │ ├── lang-vb.js │ │ ├── lang-vhdl.js │ │ ├── lang-wiki.js │ │ ├── lang-xq.js │ │ ├── lang-yaml.js │ │ ├── prettify.css │ │ └── prettify.js │ ├── editor │ │ ├── editor.css │ │ ├── editor.js │ │ ├── ext.js │ │ └── fonts │ │ │ ├── icomoon.dev.svg │ │ │ ├── icomoon.eot │ │ │ ├── icomoon.svg │ │ │ ├── icomoon.ttf │ │ │ └── icomoon.woff │ ├── font-awesome │ │ ├── css │ │ │ └── font-awesome.css │ │ └── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ ├── jquery-2.1.0.js │ ├── jquery-ujs.js │ ├── jquery.atwho.js │ ├── jquery.caret.js │ ├── lodash.compat.js │ ├── markdownit.js │ ├── qrcode.js │ └── webuploader │ │ ├── Uploader.swf │ │ ├── webuploader.css │ │ └── webuploader.withoutimage.js ├── stylesheets │ ├── common.css │ ├── index.min.ae2207d9.debug.css │ ├── index.min.ae2207d9.min.css │ ├── jquery.atwho.css │ ├── responsive.css │ └── style.less └── vue.png ├── t ├── test ├── api │ └── v1 │ │ ├── message.test.js │ │ ├── reply.test.js │ │ ├── tools.test.js │ │ ├── topic.test.js │ │ └── user.test.js ├── app.test.js ├── common │ ├── at.test.js │ ├── mail.test.js │ ├── message.test.js │ ├── render_helper.test.js │ ├── store_local.test.js │ └── tools.test.js ├── controllers │ ├── github.test.js │ ├── message.test.js │ ├── reply.test.js │ ├── rss.test.js │ ├── search.test.js │ ├── sign.test.js │ ├── site.test.js │ ├── static.test.js │ ├── topic.test.js │ └── user.test.js ├── middlewares │ └── conf.test.js ├── proxy │ ├── message.test.js │ ├── reply.test.js │ ├── topic.test.js │ └── user.test.js └── support │ └── support.js ├── views ├── _ads.html ├── _sponsors.html ├── bdunion.txt ├── editor_sidebar.html ├── index.html ├── layout.html ├── message │ ├── index.html │ └── message.html ├── notify │ └── notify.html ├── reply │ ├── edit.html │ └── reply.html ├── sidebar.html ├── sign │ ├── new_oauth.html │ ├── no_github_email.html │ ├── reset.html │ ├── search_pass.html │ ├── sidebar.html │ ├── signin.html │ └── signup.html ├── static │ ├── about.html │ ├── api.html │ ├── faq.html │ ├── getstart.html │ ├── showcase.html │ └── timeline.html ├── topic │ ├── _top_good.html │ ├── abstract.html │ ├── edit.html │ ├── index.html │ ├── list.html │ └── small.html └── user │ ├── card.html │ ├── collect_topics.html │ ├── followers.html │ ├── followings.html │ ├── index.html │ ├── replies.html │ ├── setting.html │ ├── star.html │ ├── stars.html │ ├── top.html │ ├── top100.html │ ├── top100_user.html │ ├── topics.html │ └── user.html └── web_router.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | config.js 3 | .cov 4 | coverage 5 | node_modules 6 | .naeindex 7 | coverage.html 8 | .monitor 9 | 10 | *.min.*.js 11 | *.min.*.css 12 | assets.json 13 | 14 | # Ignore Mac OS desktop services store 15 | .DS_Store 16 | 17 | # Ignore Windows desktop setting file 18 | desktop.ini 19 | 20 | *.log 21 | 22 | .idea 23 | public/upload/* 24 | 25 | *.sublime-project 26 | *.sublime-workspace 27 | *.swp 28 | 29 | data/* 30 | data 31 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "phantom", 4 | "module", 5 | "require", 6 | "__dirname", 7 | "process", 8 | "console", 9 | "it", 10 | "describe", 11 | "before", 12 | "beforeEach", 13 | "after", 14 | "afterEach", 15 | "ace", 16 | "$" 17 | ], 18 | 19 | "browser": true, 20 | "node": true, 21 | "es5": true, 22 | "bitwise": true, 23 | "curly": true, 24 | "eqeqeq": true, 25 | "forin": false, 26 | "immed": true, 27 | "latedef": true, 28 | "newcap": true, 29 | "noarg": true, 30 | "noempty": true, 31 | "nonew": true, 32 | "plusplus": false, 33 | "undef": true, 34 | "strict": false, 35 | "trailing": false, 36 | "globalstrict": true, 37 | "nonstandard": true, 38 | "white": true, 39 | "indent": 2, 40 | "expr": true, 41 | "multistr": true, 42 | "onevar": false, 43 | "unused": "vars" 44 | } 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - 'iojs' 6 | services: 7 | - mongodb 8 | 9 | script: make test-cov 10 | after_success: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = $(shell find test -type f -name "*.test.js") 2 | TEST_TIMEOUT = 5000 3 | MOCHA_REPORTER = spec 4 | # NPM_REGISTRY = "--registry=http://registry.npm.taobao.org" 5 | NPM_REGISTRY = "" 6 | 7 | 8 | all: test 9 | 10 | install: 11 | @npm install $(NPM_REGISTRY) 12 | 13 | pretest: 14 | @if ! test -f config.js; then \ 15 | cp config.default.js config.js; \ 16 | fi 17 | @if ! test -d public/upload; then \ 18 | mkdir public/upload; \ 19 | fi 20 | 21 | test: install pretest 22 | @NODE_ENV=test ./node_modules/mocha/bin/mocha \ 23 | --reporter $(MOCHA_REPORTER) \ 24 | -r should \ 25 | --timeout $(TEST_TIMEOUT) \ 26 | $(TESTS) 27 | 28 | test-cov cov: install pretest 29 | @NODE_ENV=test node \ 30 | node_modules/.bin/istanbul cover --preserve-comments \ 31 | ./node_modules/.bin/_mocha \ 32 | -- \ 33 | -r should \ 34 | --reporter $(MOCHA_REPORTER) \ 35 | --timeout $(TEST_TIMEOUT) \ 36 | $(TESTS) 37 | 38 | build: 39 | @./node_modules/loader/bin/build views . 40 | 41 | start: install build 42 | @nohup ./node_modules/.bin/pm2 start app.js -i max --name "cnode" --max-memory-restart 400M >> cnode.log 2>&1 & 43 | 44 | restart: install build 45 | @nohup ./node_modules/.bin/pm2 restart "cnode" >> cnode.log 2>&1 & 46 | 47 | .PHONY: install test cov test-cov build start restart 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Nodeclub 2 | = 3 | 4 | [![build status][travis-image]][travis-url] 5 | [![Coverage Status][coverage-image]][coverage-url] 6 | [![David deps][david-image]][david-url] 7 | [![node version][node-image]][node-url] 8 | 9 | [travis-image]: https://img.shields.io/travis/cnodejs/nodeclub.svg?style=flat-square 10 | [travis-url]: https://travis-ci.org/cnodejs/nodeclub 11 | [coverage-image]: https://img.shields.io/coveralls/cnodejs/nodeclub.svg?style=flat-square 12 | [coverage-url]: https://coveralls.io/r/cnodejs/nodeclub?branch=master 13 | [david-image]: https://img.shields.io/david/cnodejs/nodeclub.svg?style=flat-square 14 | [david-url]: https://david-dm.org/cnodejs/nodeclub 15 | [node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square 16 | [node-url]: http://nodejs.org/download/ 17 | 18 | ## 介绍 19 | 20 | Nodeclub 是使用 **Node.js** 和 **MongoDB** 开发的社区系统,界面优雅,功能丰富,小巧迅速, 21 | 已在Node.js 中文技术社区 [CNode(http://cnodejs.org)](http://cnodejs.org) 得到应用,但你完全可以用它搭建自己的社区。 22 | 23 | ## 安装部署 24 | 25 | *不保证 Windows 系统的兼容性* 26 | 27 | 线上跑的是 Node.js v1.5,MongoDB 是 v2.6。 28 | 29 | ``` 30 | 1. install `node.js[必须]` `mongodb[必须]` 31 | 2. run mongod 32 | 3. `$ make install` 安装 Nodeclub 的依赖包 33 | 4. `cp config.default.js config.js` 请根据需要修改配置文件 34 | 5. `$ make test` 确保各项服务都正常 35 | 6. `$ node app.js` 36 | 7. visit `localhost:3000` 37 | 8. done! 38 | ``` 39 | 40 | ## 其他 41 | 42 | 跑测试 43 | 44 | ```bash 45 | $ make test 46 | ``` 47 | 48 | 跑覆盖率测试 49 | 50 | ```bash 51 | $ make test-cov 52 | ``` 53 | 54 | ## License 55 | 56 | MIT 57 | -------------------------------------------------------------------------------- /api/v1/message.js: -------------------------------------------------------------------------------- 1 | var eventproxy = require('eventproxy'); 2 | var Message = require('../../proxy').Message; 3 | var _ = require('lodash'); 4 | 5 | var index = function (req, res, next) { 6 | var user_id = req.user._id; 7 | var ep = new eventproxy(); 8 | ep.fail(next); 9 | 10 | ep.all('has_read_messages', 'hasnot_read_messages', function (has_read_messages, hasnot_read_messages) { 11 | res.send({ 12 | data: { 13 | has_read_messages: has_read_messages, 14 | hasnot_read_messages: hasnot_read_messages 15 | } 16 | }); 17 | }); 18 | 19 | ep.all('has_read', 'unread', function (has_read, unread) { 20 | [has_read, unread].forEach(function (msgs, idx) { 21 | var epfill = new eventproxy(); 22 | epfill.fail(next); 23 | epfill.after('message_ready', msgs.length, function (docs) { 24 | docs = docs.filter(function (doc) { 25 | return !doc.is_invalid; 26 | }); 27 | docs = docs.map(function (doc) { 28 | doc.author = _.pick(doc.author, ['loginname', 'avatar_url']); 29 | doc.topic = _.pick(doc.topic, ['id', 'author', 'title', 'last_reply_at']); 30 | doc.reply = _.pick(doc.reply, ['id', 'content', 'ups', 'create_at']); 31 | doc = _.pick(doc, ['id', 'type', 'has_read', 'author', 'topic', 'reply']); 32 | return doc; 33 | }); 34 | ep.emit(idx === 0 ? 'has_read_messages' : 'hasnot_read_messages', docs); 35 | }); 36 | msgs.forEach(function (doc) { 37 | Message.getMessageById(doc._id, epfill.group('message_ready')); 38 | }); 39 | }); 40 | }); 41 | 42 | Message.getReadMessagesByUserId(user_id, ep.done('has_read')); 43 | 44 | Message.getUnreadMessageByUserId(user_id, ep.done('unread')); 45 | }; 46 | 47 | exports.index = index; 48 | 49 | var markAll = function (req, res, next) { 50 | var user_id = req.user._id; 51 | var ep = new eventproxy(); 52 | ep.fail(next); 53 | Message.getUnreadMessageByUserId(user_id, ep.done('unread', function (docs) { 54 | docs.forEach(function (doc) { 55 | doc.has_read = true; 56 | doc.save(); 57 | }); 58 | return docs; 59 | })); 60 | 61 | ep.all('unread', function (unread) { 62 | unread = unread.map(function (doc) { 63 | doc = _.pick(doc, ['id']); 64 | return doc; 65 | }); 66 | res.send({ 67 | success: true, 68 | marked_msgs: unread, 69 | }); 70 | }); 71 | }; 72 | 73 | exports.markAll = markAll; 74 | 75 | var count = function (req, res, next) { 76 | var userId = req.user.id; 77 | 78 | var ep = new eventproxy(); 79 | ep.fail(next); 80 | 81 | Message.getMessagesCount(userId, ep.done(function (count) { 82 | res.send({data: count}); 83 | })); 84 | }; 85 | 86 | exports.count = count; 87 | -------------------------------------------------------------------------------- /api/v1/middleware.js: -------------------------------------------------------------------------------- 1 | 2 | var UserModel = require('../../models').User; 3 | var eventproxy = require('eventproxy'); 4 | var validator = require('validator'); 5 | 6 | var auth = function (req, res, next) { 7 | var ep = new eventproxy(); 8 | ep.fail(next); 9 | 10 | var accessToken = req.body.accesstoken || req.query.accesstoken; 11 | accessToken = validator.trim(accessToken); 12 | 13 | UserModel.findOne({accessToken: accessToken}, ep.done(function (user) { 14 | if (!user) { 15 | res.status(403); 16 | return res.send({error_msg: 'wrong accessToken'}); 17 | } 18 | req.user = user; 19 | next(); 20 | })); 21 | 22 | }; 23 | 24 | exports.auth = auth; 25 | -------------------------------------------------------------------------------- /api/v1/reply.js: -------------------------------------------------------------------------------- 1 | 2 | var eventproxy = require('eventproxy'); 3 | var validator = require('validator'); 4 | var Topic = require('../../proxy').Topic; 5 | var User = require('../../proxy').User; 6 | var Reply = require('../../proxy').Reply; 7 | var at = require('../../common/at'); 8 | var message = require('../../common/message'); 9 | var config = require('../../config'); 10 | 11 | var create = function (req, res, next) { 12 | var topic_id = req.params.topic_id; 13 | var content = req.body.content; 14 | var reply_id = req.body.reply_id; 15 | 16 | var ep = new eventproxy(); 17 | ep.fail(next); 18 | 19 | var str = validator.trim(content); 20 | if (str === '') { 21 | res.status(422); 22 | res.send({error_msg: '回复内容不能为空!'}); 23 | return; 24 | } 25 | 26 | Topic.getTopic(topic_id, ep.done(function (topic) { 27 | if (!topic) { 28 | res.status(404); 29 | res.send({error_msg: 'topic `' + topic_id + '` not found'}); 30 | return; 31 | } 32 | if (topic.lock) { 33 | res.status(403); 34 | return res.send({error_msg: 'topic is locked'}); 35 | } 36 | ep.emit('topic', topic); 37 | })); 38 | 39 | ep.all('topic', function (topic) { 40 | User.getUserById(topic.author_id, ep.done('topic_author')); 41 | }); 42 | 43 | ep.all('topic', 'topic_author', function (topic, topicAuthor) { 44 | Reply.newAndSave(content, topic_id, req.user.id, reply_id, ep.done(function (reply) { 45 | Topic.updateLastReply(topic_id, reply._id, ep.done(function () { 46 | ep.emit('reply_saved', reply); 47 | //发送at消息,并防止重复 at 作者 48 | var newContent = content.replace('@' + topicAuthor.loginname + ' ', ''); 49 | at.sendMessageToMentionUsers(newContent, topic_id, req.user.id, reply._id); 50 | })); 51 | })); 52 | 53 | User.getUserById(req.user.id, ep.done(function (user) { 54 | user.score += 5; 55 | user.reply_count += 1; 56 | user.save(); 57 | ep.emit('score_saved'); 58 | })); 59 | }); 60 | 61 | ep.all('reply_saved', 'topic', function (reply, topic) { 62 | if (topic.author_id.toString() !== req.user.id.toString()) { 63 | message.sendReplyMessage(topic.author_id, req.user.id, topic._id, reply._id); 64 | } 65 | ep.emit('message_saved'); 66 | }); 67 | 68 | ep.all('reply_saved', 'message_saved', 'score_saved', function (reply) { 69 | res.send({ 70 | success: true, 71 | reply_id: reply._id, 72 | }); 73 | }); 74 | }; 75 | 76 | exports.create = create; 77 | 78 | var ups = function (req, res, next) { 79 | var replyId = req.params.reply_id; 80 | var userId = req.user.id; 81 | Reply.getReplyById(replyId, function (err, reply) { 82 | if (err) { 83 | return next(err); 84 | } 85 | if (!reply) { 86 | res.status(404); 87 | return res.send({error_msg: 'reply `' + replyId + '` not found'}); 88 | } 89 | if (reply.author_id.equals(userId) && !config.debug) { 90 | // 不能帮自己点赞 91 | res.send({ 92 | error_msg: '呵呵,不能帮自己点赞。', 93 | }); 94 | } else { 95 | var action; 96 | reply.ups = reply.ups || []; 97 | var upIndex = reply.ups.indexOf(userId); 98 | if (upIndex === -1) { 99 | reply.ups.push(userId); 100 | action = 'up'; 101 | } else { 102 | reply.ups.splice(upIndex, 1); 103 | action = 'down'; 104 | } 105 | reply.save(function () { 106 | res.send({ 107 | success: true, 108 | action: action 109 | }); 110 | }); 111 | } 112 | }); 113 | }; 114 | 115 | exports.ups = ups; 116 | -------------------------------------------------------------------------------- /api/v1/tools.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var eventproxy = require('eventproxy'); 4 | 5 | var accesstoken = function (req, res, next) { 6 | var ep = new eventproxy(); 7 | ep.fail(next); 8 | 9 | res.send({ 10 | success: true, 11 | loginname: req.user.loginname, 12 | avatar_url: req.user.avatar_url, 13 | id: req.user.id, 14 | }); 15 | }; 16 | exports.accesstoken = accesstoken; 17 | -------------------------------------------------------------------------------- /api/v1/user.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var eventproxy = require('eventproxy'); 3 | var UserProxy = require('../../proxy').User; 4 | var TopicProxy = require('../../proxy').Topic; 5 | var ReplyProxy = require('../../proxy').Reply; 6 | var TopicCollect = require('../../proxy').TopicCollect; 7 | 8 | var show = function (req, res, next) { 9 | var loginname = req.params.loginname; 10 | 11 | var ep = new eventproxy(); 12 | ep.fail(next); 13 | 14 | UserProxy.getUserByLoginName(loginname, ep.done(function (user) { 15 | if (!user) { 16 | return res.send({error_msg: 'user `' + loginname + '` is not exists'}); 17 | } 18 | var query = {author_id: user._id}; 19 | var opt = {limit: 5, sort: '-create_at'}; 20 | TopicProxy.getTopicsByQuery(query, opt, ep.done('recent_topics')); 21 | 22 | ReplyProxy.getRepliesByAuthorId(user._id, {limit: 20, sort: '-create_at'}, 23 | ep.done(function (replies) { 24 | var topic_ids = []; 25 | for (var i = 0; i < replies.length; i++) { 26 | if (topic_ids.indexOf(replies[i].topic_id.toString()) < 0) { 27 | topic_ids.push(replies[i].topic_id.toString()); 28 | } 29 | } 30 | var query = {_id: {'$in': topic_ids}}; 31 | var opt = {limit: 5, sort: '-create_at'}; 32 | TopicProxy.getTopicsByQuery(query, opt, ep.done('recent_replies')); 33 | })); 34 | 35 | TopicCollect.getTopicCollectsByUserId(user._id, 36 | ep.done(function (collections) { 37 | var topic_ids = []; 38 | for (var i = 0; i < collections.length; i++) { 39 | if (topic_ids.indexOf(collections[i].topic_id.toString()) < 0) { 40 | topic_ids.push(collections[i].topic_id.toString()); 41 | } 42 | } 43 | var query = {_id: {'$in': topic_ids}}; 44 | var opt = {sort: '-create_at'}; 45 | TopicProxy.getTopicsByQuery(query, opt, ep.done('collect_topics')); 46 | })); 47 | ep.all('recent_topics', 'recent_replies', 'collect_topics', 48 | function (recent_topics, recent_replies, collect_topics) { 49 | 50 | user = _.pick(user, ['loginname', 'avatar_url', 'githubUsername', 51 | 'create_at', 'score']); 52 | 53 | user.recent_topics = recent_topics.map(function (topic) { 54 | topic.author = _.pick(topic.author, ['loginname', 'avatar_url']); 55 | topic = _.pick(topic, ['id', 'author', 'title', 'last_reply_at']); 56 | return topic; 57 | }); 58 | user.recent_replies = recent_replies.map(function (topic) { 59 | topic.author = _.pick(topic.author, ['loginname', 'avatar_url']); 60 | topic = _.pick(topic, ['id', 'author', 'title', 'last_reply_at']); 61 | return topic; 62 | }); 63 | user.collect_topics = collect_topics.map(function (topic) { 64 | topic.author = _.pick(topic.author, ['loginname', 'avatar_url']); 65 | topic = _.pick(topic, ['id', 'author', 'title', 'last_reply_at']); 66 | return topic; 67 | }); 68 | 69 | res.send({data: user}); 70 | }); 71 | })); 72 | }; 73 | 74 | exports.show = show; 75 | -------------------------------------------------------------------------------- /api_router_v1.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var topicController = require('./api/v1/topic'); 4 | var userController = require('./api/v1/user'); 5 | var toolsController = require('./api/v1/tools'); 6 | var replyController = require('./api/v1/reply'); 7 | var messageController = require('./api/v1/message'); 8 | var middleware = require('./api/v1/middleware'); 9 | 10 | var router = express.Router(); 11 | 12 | // 主题 13 | router.get('/topics', topicController.index); 14 | router.get('/topic/:id', topicController.show); 15 | router.post('/topics', middleware.auth, topicController.create); 16 | router.post('/topic/collect', middleware.auth, topicController.collect); // 关注某话题 17 | router.post('/topic/de_collect', middleware.auth, topicController.de_collect); // 取消关注某话题 18 | 19 | // 用户 20 | router.get('/user/:loginname', userController.show); 21 | 22 | // accessToken 测试 23 | router.post('/accesstoken', middleware.auth, toolsController.accesstoken); 24 | 25 | // 评论 26 | router.post('/topic/:topic_id/replies', middleware.auth, replyController.create); 27 | router.post('/reply/:reply_id/ups', middleware.auth, replyController.ups); 28 | 29 | // 通知 30 | router.get('/messages', middleware.auth, messageController.index); 31 | router.get('/message/count', middleware.auth, messageController.count); 32 | router.post('/message/mark_all', middleware.auth, messageController.markAll); 33 | 34 | module.exports = router; 35 | -------------------------------------------------------------------------------- /assets.json: -------------------------------------------------------------------------------- 1 | {"/public/stylesheets/index.min.css":"/public/stylesheets/index.min.ae2207d9.min.css","/public/index.min.js":"/public/index.min.d24523a2.min.js","/public/editor.min.js":"/public/editor.min.1ab56128.min.js"} -------------------------------------------------------------------------------- /bdunion.txt: -------------------------------------------------------------------------------- 1 | a49a794726776a7d1700d152d88cc208 -------------------------------------------------------------------------------- /bin/generate_accesstoken.js: -------------------------------------------------------------------------------- 1 | // 为所有老用户生成 accessToken 2 | 3 | var uuid = require('node-uuid'); 4 | var mongoose = require('mongoose'); 5 | var config = require('../config'); 6 | var async = require('async'); 7 | require('../models/user'); 8 | 9 | mongoose.connect(config.db, function (err) { 10 | if (err) { 11 | console.error('connect to %s error: ', config.db, err.message); 12 | process.exit(1); 13 | } 14 | }); 15 | 16 | var UserModel = mongoose.model('User'); 17 | 18 | var hasRemain = true; 19 | async.whilst( 20 | function () { 21 | return hasRemain; 22 | }, 23 | function (callback) { 24 | UserModel.findOne({accessToken: {$exists: false}}, function (err, user) { 25 | if (!user) { 26 | hasRemain = false; 27 | callback(); 28 | return; 29 | } 30 | user.accessToken = uuid.v4(); 31 | user.save(function () { 32 | console.log(user.loginname + ' done!'); 33 | callback(); 34 | }); 35 | }); 36 | }, 37 | function (err) { 38 | mongoose.disconnect(); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /common/at.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nodeclub - topic mention user controller. 3 | * Copyright(c) 2012 fengmk2 4 | * Copyright(c) 2012 muyuan 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var User = require('../proxy').User; 13 | var Message = require('./message'); 14 | var EventProxy = require('eventproxy'); 15 | var _ = require('lodash'); 16 | 17 | /** 18 | * 从文本中提取出@username 标记的用户名数组 19 | * @param {String} text 文本内容 20 | * @return {Array} 用户名数组 21 | */ 22 | var fetchUsers = function (text) { 23 | var ignoreRegexs = [ 24 | /```.+?```/g, // 去除单行的 ``` 25 | /^```[\s\S]+?^```/gm, // ``` 里面的是 pre 标签内容 26 | /`[\s\S]+?`/g, // 同一行中,`some code` 中内容也不该被解析 27 | /^ .*/gm, // 4个空格也是 pre 标签,在这里 . 不会匹配换行 28 | /\b\S*?@[^\s]*?\..+?\b/g, // somebody@gmail.com 会被去除 29 | /\[@.+?\]\(\/.+?\)/g, // 已经被 link 的 username 30 | ]; 31 | 32 | ignoreRegexs.forEach(function (ignore_regex) { 33 | text = text.replace(ignore_regex, ''); 34 | }); 35 | 36 | var results = text.match(/@[a-z0-9\-_]+\b/igm); 37 | var names = []; 38 | if (results) { 39 | for (var i = 0, l = results.length; i < l; i++) { 40 | var s = results[i]; 41 | //remove leading char @ 42 | s = s.slice(1); 43 | names.push(s); 44 | } 45 | } 46 | names = _.uniq(names); 47 | return names; 48 | }; 49 | exports.fetchUsers = fetchUsers; 50 | 51 | /** 52 | * 根据文本内容中读取用户,并发送消息给提到的用户 53 | * Callback: 54 | * - err, 数据库异常 55 | * @param {String} text 文本内容 56 | * @param {String} topicId 主题ID 57 | * @param {String} authorId 作者ID 58 | * @param {String} reply_id 回复ID 59 | * @param {Function} callback 回调函数 60 | */ 61 | exports.sendMessageToMentionUsers = function (text, topicId, authorId, reply_id, callback) { 62 | if (typeof reply_id === 'function') { 63 | callback = reply_id; 64 | reply_id = null; 65 | } 66 | callback = callback || _.noop; 67 | 68 | User.getUsersByNames(fetchUsers(text), function (err, users) { 69 | if (err || !users) { 70 | return callback(err); 71 | } 72 | var ep = new EventProxy(); 73 | ep.fail(callback); 74 | 75 | users = users.filter(function (user) { 76 | return !user._id.equals(authorId); 77 | }); 78 | 79 | ep.after('sent', users.length, function () { 80 | callback(); 81 | }); 82 | 83 | users.forEach(function (user) { 84 | Message.sendAtMessage(user._id, authorId, topicId, reply_id, ep.done('sent')); 85 | }); 86 | }); 87 | }; 88 | 89 | /** 90 | * 根据文本内容,替换为数据库中的数据 91 | * Callback: 92 | * - err, 数据库异常 93 | * - text, 替换后的文本内容 94 | * @param {String} text 文本内容 95 | * @param {Function} callback 回调函数 96 | */ 97 | exports.linkUsers = function (text, callback) { 98 | var users = fetchUsers(text); 99 | for (var i = 0, l = users.length; i < l; i++) { 100 | var name = users[i]; 101 | text = text.replace(new RegExp('@' + name + '\\b', 'g'), '[@' + name + '](/user/' + name + ')'); 102 | } 103 | if (!callback) { 104 | return text; 105 | } 106 | return callback(null, text); 107 | }; 108 | -------------------------------------------------------------------------------- /common/cache.js: -------------------------------------------------------------------------------- 1 | 2 | var mcache = require('memory-cache'); 3 | 4 | var get = function (key, callback) { 5 | setImmediate(function () { 6 | callback(null, mcache.get(key)); 7 | }); 8 | }; 9 | 10 | exports.get = get; 11 | 12 | // time 参数可选,毫秒为单位 13 | var set = function (key, value, time, callback) { 14 | if (typeof time === 'function') { 15 | callback = time; 16 | time = null; 17 | } 18 | mcache.put(key, value, time); 19 | setImmediate(function () { 20 | callback && callback(null); 21 | }); 22 | }; 23 | 24 | exports.set = set; 25 | -------------------------------------------------------------------------------- /common/mail.js: -------------------------------------------------------------------------------- 1 | var mailer = require('nodemailer'); 2 | var config = require('../config'); 3 | var util = require('util'); 4 | 5 | var smtpTransport = require('nodemailer-smtp-transport'); 6 | var transporter = mailer.createTransport(smtpTransport(config.mail_opts)); 7 | var SITE_ROOT_URL = 'http://' + config.host; 8 | 9 | /** 10 | * Send an email 11 | * @param {Object} data 邮件对象 12 | */ 13 | var sendMail = function (data) { 14 | if (config.debug) { 15 | return; 16 | } 17 | // 遍历邮件数组,发送每一封邮件,如果有发送失败的,就再压入数组,同时触发mailEvent事件 18 | transporter.sendMail(data, function (err) { 19 | if (err) { 20 | // 写为日志 21 | console.log(err); 22 | } 23 | }); 24 | }; 25 | exports.sendMail = sendMail; 26 | 27 | /** 28 | * 发送激活通知邮件 29 | * @param {String} who 接收人的邮件地址 30 | * @param {String} token 重置用的token字符串 31 | * @param {String} name 接收人的用户名 32 | */ 33 | exports.sendActiveMail = function (who, token, name) { 34 | var from = util.format('%s <%s>', config.name, config.mail_opts.auth.user); 35 | var to = who; 36 | var subject = config.name + '社区帐号激活'; 37 | var html = '

您好:' + name + '

' + 38 | '

我们收到您在' + config.name + '社区的注册信息,请点击下面的链接来激活帐户:

' + 39 | '激活链接' + 40 | '

若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。

' + 41 | '

' + config.name + '社区 谨上。

'; 42 | 43 | exports.sendMail({ 44 | from: from, 45 | to: to, 46 | subject: subject, 47 | html: html 48 | }); 49 | }; 50 | 51 | /** 52 | * 发送密码重置通知邮件 53 | * @param {String} who 接收人的邮件地址 54 | * @param {String} token 重置用的token字符串 55 | * @param {String} name 接收人的用户名 56 | */ 57 | exports.sendResetPassMail = function (who, token, name) { 58 | var from = util.format('%s <%s>', config.name, config.mail_opts.auth.user); 59 | var to = who; 60 | var subject = config.name + '社区密码重置'; 61 | var html = '

您好:' + name + '

' + 62 | '

我们收到您在' + config.name + '社区重置密码的请求,请在24小时内单击下面的链接来重置密码:

' + 63 | '重置密码链接' + 64 | '

若您没有在' + config.name + '社区填写过注册信息,说明有人滥用了您的电子邮箱,请删除此邮件,我们对给您造成的打扰感到抱歉。

' + 65 | '

' + config.name + '社区 谨上。

'; 66 | 67 | exports.sendMail({ 68 | from: from, 69 | to: to, 70 | subject: subject, 71 | html: html 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /common/message.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var eventproxy = require('eventproxy'); 3 | var Message = models.Message; 4 | var User = require('../proxy').User; 5 | var push = require('../common/push'); 6 | var messageProxy = require('../proxy/message'); 7 | var _ = require('lodash'); 8 | 9 | exports.sendReplyMessage = function (master_id, author_id, topic_id, reply_id, callback) { 10 | callback = callback || _.noop; 11 | var ep = new eventproxy(); 12 | ep.fail(callback); 13 | var message = new Message(); 14 | message.type = 'reply'; 15 | message.master_id = master_id; 16 | message.author_id = author_id; 17 | message.topic_id = topic_id; 18 | message.reply_id = reply_id; 19 | message.save(ep.done('message_saved')); 20 | ep.all('message_saved', function (msg) { 21 | push.send(message.type, author_id, master_id, topic_id); 22 | callback(null, msg); 23 | }); 24 | }; 25 | 26 | exports.sendAtMessage = function (master_id, author_id, topic_id, reply_id, callback) { 27 | callback = callback || _.noop; 28 | var ep = new eventproxy(); 29 | ep.fail(callback); 30 | var message = new Message(); 31 | message.type = 'at'; 32 | message.master_id = master_id; 33 | message.author_id = author_id; 34 | message.topic_id = topic_id; 35 | message.reply_id = reply_id; 36 | message.save(ep.done('message_saved')); 37 | ep.all('message_saved', function (msg) { 38 | push.send(message.type, author_id, master_id, topic_id); 39 | callback(null, msg); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /common/push.js: -------------------------------------------------------------------------------- 1 | var User = require('../proxy/user'); 2 | var Message = require('../proxy/message'); 3 | var JPush = require("jpush-sdk"); 4 | var eventproxy = require('eventproxy'); 5 | var config = require('../config'); 6 | var client = null; 7 | if (config.jpush && config.jpush.masterSecret !== 'YourSecretKeyyyyyyyyyyyyy') { 8 | client = JPush.buildClient(config.jpush); 9 | } 10 | 11 | /** 12 | * 通过极光推送发生消息通知 13 | * @param {String} type 消息类型 14 | * @param {String} author_id 消息作者ID 15 | * @param {String} master_id 被通知者ID 16 | * @param {String} topic_id 相关主题ID 17 | */ 18 | exports.send = function (type, author_id, master_id, topic_id) { 19 | if (client !== null) { 20 | var ep = new eventproxy(); 21 | User.getUserById(author_id, ep.done('author')); 22 | Message.getMessagesCount(master_id, ep.done('count')); 23 | ep.all('author', 'count', function (author, count) { 24 | var msg = author.loginname + ' '; 25 | var extras = { 26 | topicId: topic_id 27 | }; 28 | switch (type) { 29 | case 'at': 30 | msg += '@了你'; 31 | break; 32 | case 'reply': 33 | msg += '回复了你的主题'; 34 | break; 35 | default: 36 | break; 37 | } 38 | client.push() 39 | .setPlatform(JPush.ALL) 40 | .setAudience(JPush.alias(master_id.toString())) 41 | .setNotification(msg, 42 | JPush.ios(msg, 'default', count, null, extras), 43 | JPush.android(msg, null, null, extras) 44 | ) 45 | .setOptions(null, null, null, !config.jpush.isDebug) 46 | .send(function (err, res) { 47 | if (config.debug) { 48 | if (err) { 49 | console.log(err.message); 50 | } else { 51 | console.log('Sendno: ' + res.sendno); 52 | console.log('Msg_id: ' + res.msg_id); 53 | } 54 | } 55 | }); 56 | }); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /common/render_helper.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nodeclub - common/render_helpers.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var MarkdownIt = require('markdown-it'); 14 | var _ = require('lodash'); 15 | var config = require('../config'); 16 | var validator = require('validator'); 17 | var multiline = require('multiline'); 18 | var jsxss = require('xss'); 19 | 20 | // Set default options 21 | var md = new MarkdownIt(); 22 | 23 | md.set({ 24 | html: true, // Enable HTML tags in source 25 | xhtmlOut: false, // Use '/' to close single tags (
) 26 | breaks: false, // Convert '\n' in paragraphs into
27 | linkify: true, // Autoconvert URL-like text to links 28 | typographer: true, // Enable smartypants and other sweet transforms 29 | }); 30 | 31 | md.renderer.rules.fence = function (tokens, idx) { 32 | var token = tokens[idx]; 33 | 34 | var language = token.params && ('language-' + token.params) || ''; 35 | language = validator.escape(language); 36 | 37 | return '
'
38 |     + '' + validator.escape(token.content) + ''
39 |     + '
'; 40 | }; 41 | 42 | md.renderer.rules.code_block = function (tokens, idx /*, options*/) { 43 | var token = tokens[idx]; 44 | var language = token.params && ('language-' + token.params) || ''; 45 | language = validator.escape(language); 46 | return '
'
47 |     + '' + validator.escape(token.content) + ''
48 |     + '
'; 49 | }; 50 | 51 | md.renderer.rules.code_inline = function (tokens, idx /*, options*/) { 52 | return '' + validator.escape(tokens[idx].content) + ''; 53 | }; 54 | 55 | var myxss = new jsxss.FilterXSS({ 56 | onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) { 57 | // 让 prettyprint 可以工作 58 | if (tag === 'pre' && name === 'class') { 59 | return name + '="' + jsxss.escapeAttrValue(value) + '"'; 60 | } 61 | } 62 | }); 63 | 64 | exports.markdown = function (text) { 65 | return '
' + myxss.process(md.render(text || '')) + '
'; 66 | }; 67 | 68 | exports.multiline = multiline; 69 | 70 | exports.escapeSignature = function (signature) { 71 | return signature.split('\n').map(function (p) { 72 | return _.escape(p); 73 | }).join('
'); 74 | }; 75 | 76 | exports.staticFile = function (filePath) { 77 | if (filePath.indexOf('http') === 0 || filePath.indexOf('//') === 0) { 78 | return filePath; 79 | } 80 | return config.site_static_host + filePath; 81 | }; 82 | 83 | exports.tabName = function (tab) { 84 | var pair = _.find(config.tabs, function (pair) { 85 | return pair[0] === tab; 86 | }); 87 | if (pair) { 88 | return pair[1]; 89 | } 90 | }; 91 | 92 | exports._ = _; 93 | -------------------------------------------------------------------------------- /common/store.js: -------------------------------------------------------------------------------- 1 | var qn = require('./store_qn'); 2 | var local = require('./store_local'); 3 | 4 | module.exports = qn || local; 5 | -------------------------------------------------------------------------------- /common/store_local.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'); 2 | var utility = require('utility'); 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | 6 | exports.upload = function (file, options, callback) { 7 | var filename = options.filename; 8 | 9 | var newFilename = utility.md5(filename + String((new Date()).getTime())) + 10 | path.extname(filename); 11 | 12 | var upload_path = config.upload.path; 13 | var base_url = config.upload.url; 14 | var filePath = path.join(upload_path, newFilename); 15 | var fileUrl = base_url + newFilename; 16 | 17 | file.on('end', function () { 18 | callback(null, { 19 | url: fileUrl 20 | }); 21 | }); 22 | 23 | file.pipe(fs.createWriteStream(filePath)); 24 | }; 25 | -------------------------------------------------------------------------------- /common/store_qn.js: -------------------------------------------------------------------------------- 1 | var qn = require('qn'); 2 | var config = require('../config'); 3 | 4 | //7牛 client 5 | var qnClient = null; 6 | if (config.qn_access && config.qn_access.secretKey !== 'your secret key') { 7 | qnClient = qn.create(config.qn_access); 8 | } 9 | 10 | module.exports = qnClient; 11 | -------------------------------------------------------------------------------- /common/tools.js: -------------------------------------------------------------------------------- 1 | 2 | var bcrypt = require('bcryptjs'); 3 | var moment = require('moment'); 4 | moment.locale('zh-cn'); // 使用中文 5 | 6 | // 格式化时间 7 | exports.formatDate = function (date, friendly) { 8 | date = moment(date); 9 | 10 | if (friendly) { 11 | return date.fromNow(); 12 | } else { 13 | return date.format('YYYY-MM-DD HH:mm'); 14 | } 15 | 16 | }; 17 | 18 | exports.validateId = function (str) { 19 | return (/^[a-zA-Z0-9\-_]+$/i).test(str); 20 | }; 21 | 22 | exports.bhash = function (str, callback) { 23 | bcrypt.hash(str, 10, callback); 24 | }; 25 | 26 | exports.bcompare = function (str, hash, callback) { 27 | bcrypt.compare(str, hash, callback); 28 | }; 29 | -------------------------------------------------------------------------------- /config.default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * config 3 | */ 4 | 5 | var path = require('path'); 6 | 7 | var config = { 8 | // debug 为 true 时,用于本地调试 9 | debug: true, 10 | 11 | get mini_assets() { return !this.debug; }, // 是否启用静态文件的合并压缩,详见视图中的Loader 12 | 13 | name: 'Nodeclub', // 社区名字 14 | description: 'CNode:Node.js专业中文社区', // 社区的描述 15 | keywords: 'nodejs, node, express, connect, socket.io', 16 | 17 | // 添加到 html head 中的信息 18 | site_headers: [ 19 | '' 20 | ], 21 | site_logo: '/public/images/cnodejs_light.svg', // default is `name` 22 | site_icon: '/public/images/cnode_icon_32.png', // 默认没有 favicon, 这里填写网址 23 | // 右上角的导航区 24 | site_navs: [ 25 | // 格式 [ path, title, [target=''] ] 26 | [ '/about', '关于' ] 27 | ], 28 | // cdn host,如 http://cnodejs.qiniudn.com 29 | site_static_host: '', // 静态文件存储域名 30 | // 社区的域名 31 | host: 'localhost', 32 | // 默认的Google tracker ID,自有站点请修改,申请地址:http://www.google.com/analytics/ 33 | google_tracker_id: '', 34 | // 默认的cnzz tracker ID,自有站点请修改 35 | cnzz_tracker_id: '', 36 | 37 | // mongodb 配置 38 | db: 'mongodb://127.0.0.1/node_club_dev', 39 | db_name: 'node_club_dev', 40 | 41 | 42 | session_secret: 'node_club_secret', // 务必修改 43 | auth_cookie_name: 'node_club', 44 | 45 | // 程序运行的端口 46 | port: 3000, 47 | 48 | // 话题列表显示的话题数量 49 | list_topic_count: 20, 50 | 51 | // 限制发帖时间间隔,单位:毫秒 52 | post_interval: 2000, 53 | 54 | // RSS配置 55 | rss: { 56 | title: 'CNode:Node.js专业中文社区', 57 | link: 'http://cnodejs.org', 58 | language: 'zh-cn', 59 | description: 'CNode:Node.js专业中文社区', 60 | //最多获取的RSS Item数量 61 | max_rss_items: 50 62 | }, 63 | 64 | // 邮箱配置 65 | mail_opts: { 66 | host: 'smtp.126.com', 67 | port: 25, 68 | auth: { 69 | user: 'club@126.com', 70 | pass: 'club' 71 | } 72 | }, 73 | 74 | //weibo app key 75 | weibo_key: 10000000, 76 | weibo_id: 'your_weibo_id', 77 | 78 | // admin 可删除话题,编辑标签,设某人为达人 79 | admins: { user_login_name: true }, 80 | 81 | // github 登陆的配置 82 | GITHUB_OAUTH: { 83 | clientID: 'your GITHUB_CLIENT_ID', 84 | clientSecret: 'your GITHUB_CLIENT_SECRET', 85 | callbackURL: 'http://cnodejs.org/auth/github/callback' 86 | }, 87 | // 是否允许直接注册(否则只能走 github 的方式) 88 | allow_sign_up: true, 89 | 90 | // newrelic 是个用来监控网站性能的服务 91 | newrelic_key: 'yourkey', 92 | 93 | //7牛的access信息,用于文件上传 94 | qn_access: { 95 | accessKey: 'your access key', 96 | secretKey: 'your secret key', 97 | bucket: 'your bucket name', 98 | domain: 'http://{bucket}.qiniudn.com' 99 | }, 100 | 101 | //文件上传配置 102 | //注:如果填写 qn_access,则会上传到 7牛,以下配置无效 103 | upload: { 104 | path: path.join(__dirname, 'public/upload/'), 105 | url: '/public/upload/' 106 | }, 107 | 108 | // 版块 109 | tabs: [ 110 | ['share', '分享'], 111 | ['ask', '问答'], 112 | ['job', '招聘'], 113 | ], 114 | 115 | // 极光推送 116 | jpush: { 117 | appKey: 'YourAccessKeyyyyyyyyyyyy', 118 | masterSecret: 'YourSecretKeyyyyyyyyyyyyy', 119 | isDebug: false, 120 | } 121 | }; 122 | 123 | module.exports = config; 124 | -------------------------------------------------------------------------------- /controllers/message.js: -------------------------------------------------------------------------------- 1 | var Message = require('../proxy').Message; 2 | var eventproxy = require('eventproxy'); 3 | 4 | exports.index = function (req, res, next) { 5 | var user_id = req.session.user._id; 6 | var ep = new eventproxy(); 7 | ep.fail(next); 8 | 9 | ep.all('has_read_messages', 'hasnot_read_messages', function (has_read_messages, hasnot_read_messages) { 10 | res.render('message/index', {has_read_messages: has_read_messages, hasnot_read_messages: hasnot_read_messages}); 11 | }); 12 | 13 | ep.all('has_read', 'unread', function (has_read, unread) { 14 | [has_read, unread].forEach(function (msgs, idx) { 15 | var epfill = new eventproxy(); 16 | epfill.fail(next); 17 | epfill.after('message_ready', msgs.length, function (docs) { 18 | docs = docs.filter(function (doc) { 19 | return !doc.is_invalid; 20 | }); 21 | ep.emit(idx === 0 ? 'has_read_messages' : 'hasnot_read_messages', docs); 22 | }); 23 | msgs.forEach(function (doc) { 24 | Message.getMessageById(doc._id, epfill.group('message_ready')); 25 | }); 26 | }); 27 | }); 28 | 29 | Message.getReadMessagesByUserId(user_id, ep.done('has_read')); 30 | 31 | Message.getUnreadMessageByUserId(user_id, ep.done('unread', function (docs) { 32 | docs.forEach(function (doc) { 33 | doc.has_read = true; 34 | doc.save(); 35 | }); 36 | return docs; 37 | })); 38 | }; 39 | -------------------------------------------------------------------------------- /controllers/rss.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'); 2 | var convert = require('data2xml')(); 3 | var Topic = require('../proxy').Topic; 4 | var cache = require('../common/cache'); 5 | var renderHelper = require('../common/render_helper'); 6 | var eventproxy = require('eventproxy'); 7 | 8 | exports.index = function (req, res, next) { 9 | if (!config.rss) { 10 | res.statusCode = 404; 11 | return res.send('Please set `rss` in config.js'); 12 | } 13 | res.contentType('application/xml'); 14 | 15 | var ep = new eventproxy(); 16 | ep.fail(next); 17 | 18 | cache.get('rss', ep.done(function (rss) { 19 | if (!config.debug && rss) { 20 | res.send(rss); 21 | } else { 22 | var opt = { limit: config.rss.max_rss_items, sort: '-create_at'}; 23 | Topic.getTopicsByQuery({}, opt, function (err, topics) { 24 | if (err) { 25 | return next(err); 26 | } 27 | var rss_obj = { 28 | _attr: { version: '2.0' }, 29 | channel: { 30 | title: config.rss.title, 31 | link: config.rss.link, 32 | language: config.rss.language, 33 | description: config.rss.description, 34 | item: [] 35 | } 36 | }; 37 | 38 | topics.forEach(function (topic) { 39 | rss_obj.channel.item.push({ 40 | title: topic.title, 41 | link: config.rss.link + '/topic/' + topic._id, 42 | guid: config.rss.link + '/topic/' + topic._id, 43 | description: renderHelper.markdown(topic.content), 44 | author: topic.author.loginname, 45 | pubDate: topic.create_at.toUTCString() 46 | }); 47 | }); 48 | 49 | var rssContent = convert('rss', rss_obj); 50 | 51 | cache.set('rss', rssContent, 1000 * 60 * 5); // 五分钟 52 | res.send(rssContent); 53 | }); 54 | } 55 | })); 56 | }; 57 | -------------------------------------------------------------------------------- /controllers/search.js: -------------------------------------------------------------------------------- 1 | exports.index = function (req, res, next) { 2 | var q = req.query.q; 3 | q = encodeURIComponent(q); 4 | res.redirect('https://www.google.com.hk/#hl=zh-CN&q=site:vue-js.com+' + q); 5 | }; 6 | -------------------------------------------------------------------------------- /controllers/static.js: -------------------------------------------------------------------------------- 1 | var multiline = require('multiline'); 2 | // static page 3 | 4 | // showcase 5 | exports.showcase = function (req, res, next) { 6 | res.render('static/showcase', { 7 | pageTitle: 'Ionic app showcase' 8 | }); 9 | }; 10 | 11 | // About 12 | exports.about = function (req, res, next) { 13 | res.render('static/about', { 14 | pageTitle: '关于我们' 15 | }); 16 | }; 17 | 18 | 19 | // timeline 20 | exports.timeline = function (req, res, next) { 21 | res.render('static/timeline', { 22 | pageTitle: '时间线' 23 | }); 24 | }; 25 | 26 | // FAQ 27 | exports.faq = function (req, res, next) { 28 | res.render('static/faq'); 29 | }; 30 | 31 | exports.getstart = function (req, res) { 32 | res.render('static/getstart'); 33 | }; 34 | 35 | exports.robots = function (req, res, next) { 36 | res.type('text/plain'); 37 | res.send(multiline(function () { 38 | /* 39 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 40 | # 41 | # To ban all spiders from the entire site uncomment the next two lines: 42 | # User-Agent: * 43 | # Disallow: / 44 | User-agent: * 45 | Disallow: /404.htm 46 | Sitemap: http://ionichina.com/sitemap.xml 47 | */ 48 | })); 49 | }; 50 | 51 | exports.api = function (req, res, next) { 52 | res.render('static/api'); 53 | }; 54 | -------------------------------------------------------------------------------- /middlewares/auth.js: -------------------------------------------------------------------------------- 1 | 2 | var mongoose = require('mongoose'); 3 | var UserModel = mongoose.model('User'); 4 | var Message = require('../proxy').Message; 5 | var config = require('../config'); 6 | var eventproxy = require('eventproxy'); 7 | var UserProxy = require('../proxy').User; 8 | 9 | /** 10 | * 需要管理员权限 11 | */ 12 | exports.adminRequired = function (req, res, next) { 13 | if (!req.session.user) { 14 | return res.render('notify/notify', {error: '你还没有登录。'}); 15 | } 16 | if (!req.session.user.is_admin) { 17 | return res.render('notify/notify', {error: '需要管理员权限。'}); 18 | } 19 | next(); 20 | }; 21 | 22 | /** 23 | * 需要登录 24 | */ 25 | exports.userRequired = function (req, res, next) { 26 | if (!req.session || !req.session.user) { 27 | return res.status(403).send('forbidden!'); 28 | } 29 | next(); 30 | }; 31 | 32 | exports.blockUser = function () { 33 | return function (req, res, next) { 34 | if (req.path === '/signout') { 35 | return next(); 36 | } 37 | if (req.session.user && req.session.user.is_block && req.method !== 'GET') { 38 | return res.status(403).send('您已被管理员屏蔽了。有疑问请联系 @V。'); 39 | } 40 | next(); 41 | }; 42 | }; 43 | 44 | 45 | function gen_session(user, res) { 46 | var auth_token = user._id + '$$$$'; // 以后可能会存储更多信息,用 $$$$ 来分隔 47 | res.cookie(config.auth_cookie_name, auth_token, 48 | {path: '/', maxAge: 1000 * 60 * 60 * 24 * 30, signed: true, httpOnly: true}); //cookie 有效期30天 49 | } 50 | 51 | exports.gen_session = gen_session; 52 | 53 | // 验证用户是否登录 54 | exports.authUser = function (req, res, next) { 55 | var ep = new eventproxy(); 56 | ep.fail(next); 57 | 58 | if (config.debug && req.cookies['mock_user']) { 59 | var mockUser = JSON.parse(req.cookies['mock_user']); 60 | req.session.user = new UserModel(mockUser); 61 | if (mockUser.is_admin) { 62 | req.session.user.is_admin = true; 63 | } 64 | return next(); 65 | } 66 | 67 | ep.all('get_user', function (user) { 68 | if (!user) { 69 | return next(); 70 | } 71 | user = res.locals.current_user = req.session.user = new UserModel(user); 72 | 73 | if (config.admins.hasOwnProperty(user.loginname)) { 74 | user.is_admin = true; 75 | } 76 | Message.getMessagesCount(user._id, ep.done(function (count) { 77 | user.messages_count = count; 78 | next(); 79 | })); 80 | 81 | }); 82 | 83 | if (req.session.user) { 84 | ep.emit('get_user', req.session.user); 85 | } else { 86 | var auth_token = req.signedCookies[config.auth_cookie_name]; 87 | if (!auth_token) { 88 | return next(); 89 | } 90 | 91 | var auth = auth_token.split('$$$$'); 92 | var user_id = auth[0]; 93 | UserProxy.getUserById(user_id, ep.done('get_user')); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /middlewares/conf.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'); 2 | 3 | exports.github = function (req, res, next) { 4 | if (config.GITHUB_OAUTH.clientID === 'your GITHUB_CLIENT_ID') { 5 | return res.send('call the admin to set github oauth.'); 6 | } 7 | next(); 8 | }; 9 | -------------------------------------------------------------------------------- /middlewares/github_strategy.js: -------------------------------------------------------------------------------- 1 | module.exports = function (accessToken, refreshToken, profile, done) { 2 | profile.accessToken = accessToken; 3 | done(null, profile); 4 | }; 5 | -------------------------------------------------------------------------------- /middlewares/limit.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'); 2 | 3 | // 发帖时间间隔,为毫秒 4 | var POST_INTERVAL = config.post_interval; 5 | if (!(POST_INTERVAL > 0)) POST_INTERVAL = 0; 6 | var DISABLE_POST_INTERVAL = POST_INTERVAL > 0 ? false : true; 7 | 8 | /** 9 | * 发帖/评论时间间隔限制 10 | */ 11 | exports.postInterval = function (req, res, next) { 12 | if (DISABLE_POST_INTERVAL) return next(); 13 | if (isNaN(req.session.lastPostTimestamp)) { 14 | req.session.lastPostTimestamp = Date.now(); 15 | return next(); 16 | } 17 | if (Date.now() - req.session.lastPostTimestamp < POST_INTERVAL) { 18 | var ERROR_MSG = '您的回复速度太快。'; 19 | res.render('notify/notify', {error: ERROR_MSG}); 20 | return; 21 | } 22 | 23 | req.session.lastPostTimestamp = Date.now(); 24 | next(); 25 | }; 26 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var config = require('../config'); 3 | 4 | mongoose.connect(config.db, function (err) { 5 | if (err) { 6 | console.error('connect to %s error: ', config.db, err.message); 7 | process.exit(1); 8 | } 9 | }); 10 | 11 | // models 12 | require('./user'); 13 | require('./topic'); 14 | require('./reply'); 15 | require('./topic_collect'); 16 | require('./message'); 17 | 18 | exports.User = mongoose.model('User'); 19 | exports.Topic = mongoose.model('Topic'); 20 | exports.Reply = mongoose.model('Reply'); 21 | exports.TopicCollect = mongoose.model('TopicCollect'); 22 | exports.Message = mongoose.model('Message'); 23 | -------------------------------------------------------------------------------- /models/message.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var ObjectId = Schema.ObjectId; 4 | 5 | /* 6 | * type: 7 | * reply: xx 回复了你的话题 8 | * reply2: xx 在话题中回复了你 9 | * follow: xx 关注了你 10 | * at: xx @了你 11 | */ 12 | 13 | var MessageSchema = new Schema({ 14 | type: { type: String }, 15 | master_id: { type: ObjectId}, 16 | author_id: { type: ObjectId }, 17 | topic_id: { type: ObjectId }, 18 | reply_id: { type: ObjectId }, 19 | has_read: { type: Boolean, default: false }, 20 | create_at: { type: Date, default: Date.now } 21 | }); 22 | 23 | MessageSchema.index({master_id: 1, has_read: -1, create_at: -1}); 24 | 25 | mongoose.model('Message', MessageSchema); 26 | -------------------------------------------------------------------------------- /models/reply.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var ObjectId = Schema.ObjectId; 4 | 5 | var ReplySchema = new Schema({ 6 | content: { type: String }, 7 | topic_id: { type: ObjectId}, 8 | author_id: { type: ObjectId }, 9 | reply_id: { type: ObjectId }, 10 | create_at: { type: Date, default: Date.now }, 11 | update_at: { type: Date, default: Date.now }, 12 | content_is_html: { type: Boolean }, 13 | ups: [Schema.Types.ObjectId] 14 | }); 15 | 16 | ReplySchema.index({topic_id: 1}); 17 | ReplySchema.index({author_id: 1, create_at: -1}); 18 | 19 | mongoose.model('Reply', ReplySchema); 20 | -------------------------------------------------------------------------------- /models/topic.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var ObjectId = Schema.ObjectId; 4 | var config = require('../config'); 5 | var _ = require('lodash'); 6 | 7 | var TopicSchema = new Schema({ 8 | title: { type: String }, 9 | content: { type: String }, 10 | author_id: { type: ObjectId }, 11 | top: { type: Boolean, default: false }, // 置顶帖 12 | good: {type: Boolean, default: false}, // 精华帖 13 | lock: {type: Boolean, default: false}, // 被锁定主题 14 | reply_count: { type: Number, default: 0 }, 15 | visit_count: { type: Number, default: 0 }, 16 | collect_count: { type: Number, default: 0 }, 17 | create_at: { type: Date, default: Date.now }, 18 | update_at: { type: Date, default: Date.now }, 19 | last_reply: { type: ObjectId }, 20 | last_reply_at: { type: Date, default: Date.now }, 21 | content_is_html: { type: Boolean }, 22 | tab: {type: String}, 23 | }); 24 | 25 | TopicSchema.index({create_at: -1}); 26 | TopicSchema.index({top: -1, last_reply_at: -1}); 27 | TopicSchema.index({last_reply_at: -1}); 28 | TopicSchema.index({author_id: 1, create_at: -1}); 29 | 30 | TopicSchema.virtual('tabName').get(function () { 31 | var tab = this.tab; 32 | var pair = _.find(config.tabs, function (_pair) { 33 | return _pair[0] === tab; 34 | }); 35 | if (pair) { 36 | return pair[1]; 37 | } else { 38 | return ''; 39 | } 40 | }); 41 | 42 | mongoose.model('Topic', TopicSchema); 43 | -------------------------------------------------------------------------------- /models/topic_collect.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var ObjectId = Schema.ObjectId; 4 | 5 | var TopicCollectSchema = new Schema({ 6 | user_id: { type: ObjectId }, 7 | topic_id: { type: ObjectId }, 8 | create_at: { type: Date, default: Date.now } 9 | }); 10 | 11 | mongoose.model('TopicCollect', TopicCollectSchema); 12 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var utility = require('utility'); 4 | 5 | var UserSchema = new Schema({ 6 | name: { type: String}, 7 | loginname: { type: String}, 8 | pass: { type: String }, 9 | email: { type: String}, 10 | url: { type: String }, 11 | profile_image_url: {type: String}, 12 | location: { type: String }, 13 | signature: { type: String }, 14 | profile: { type: String }, 15 | weibo: { type: String }, 16 | avatar: { type: String }, 17 | githubId: { type: String}, 18 | githubUsername: {type: String}, 19 | githubAccessToken: {type: String}, 20 | is_block: {type: Boolean, default: false}, 21 | 22 | score: { type: Number, default: 0 }, 23 | topic_count: { type: Number, default: 0 }, 24 | reply_count: { type: Number, default: 0 }, 25 | follower_count: { type: Number, default: 0 }, 26 | following_count: { type: Number, default: 0 }, 27 | collect_tag_count: { type: Number, default: 0 }, 28 | collect_topic_count: { type: Number, default: 0 }, 29 | create_at: { type: Date, default: Date.now }, 30 | update_at: { type: Date, default: Date.now }, 31 | is_star: { type: Boolean }, 32 | level: { type: String }, 33 | active: { type: Boolean, default: false }, 34 | 35 | receive_reply_mail: {type: Boolean, default: false }, 36 | receive_at_mail: { type: Boolean, default: false }, 37 | from_wp: { type: Boolean }, 38 | 39 | retrieve_time: {type: Number}, 40 | retrieve_key: {type: String}, 41 | 42 | accessToken: {type: String}, 43 | }); 44 | 45 | UserSchema.virtual('avatar_url').get(function () { 46 | var url = this.avatar || ('//gravatar.com/avatar/' + utility.md5(this.email.toLowerCase()) + '?size=48'); 47 | 48 | // www.gravatar.com 被墙 49 | url = url.replace('//www.gravatar.com', '//gravatar.com'); 50 | 51 | // 让协议自适应 protocol 52 | if (url.indexOf('http:') === 0) { 53 | url = url.slice(5); 54 | } 55 | 56 | // 如果是 github 的头像,则限制大小 57 | if (url.indexOf('githubusercontent') !== -1) { 58 | url += '&s=120'; 59 | } 60 | return url; 61 | }); 62 | 63 | UserSchema.virtual('isAdvanced').get(function () { 64 | // 积分高于 700 则认为是高级用户 65 | return this.score > 700 || this.is_star; 66 | }); 67 | 68 | UserSchema.index({loginname: 1}, {unique: true}); 69 | UserSchema.index({email: 1}, {unique: true}); 70 | UserSchema.index({score: -1}); 71 | UserSchema.index({githubId: 1}); 72 | UserSchema.index({accessToken: 1}); 73 | 74 | mongoose.model('User', UserSchema); 75 | -------------------------------------------------------------------------------- /newrelic.js: -------------------------------------------------------------------------------- 1 | var config = require('./config'); 2 | /** 3 | * New Relic agent configuration. 4 | * 5 | * See lib/config.defaults.js in the agent distribution for a more complete 6 | * description of configuration variables and their potential values. 7 | */ 8 | exports.config = { 9 | /** 10 | * Array of application names. 11 | */ 12 | app_name: [config.name], 13 | /** 14 | * Your New Relic license key. 15 | */ 16 | license_key: config.newrelic_key, 17 | logging: { 18 | /** 19 | * Level at which to log. 'trace' is most useful to New Relic when diagnosing 20 | * issues with the agent, 'info' and higher will impose the least overhead on 21 | * production applications. 22 | */ 23 | level: 'info' 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeclub", 3 | "version": "2.0.0", 4 | "private": true, 5 | "main": "app.js", 6 | "description": "A Node.js bbs using MongoDB", 7 | "repository": "https://github.com/cnodejs/nodeclub", 8 | "customHost": [ 9 | "cnodejs.org", 10 | "club.cnodejs.org" 11 | ], 12 | "engines": { 13 | "node": "0.12.x" 14 | }, 15 | "dependencies": { 16 | "async": "0.9.0", 17 | "bcrypt": "0.8.1", 18 | "body-parser": "1.9.2", 19 | "compression": "1.2.0", 20 | "connect-busboy": "0.0.1", 21 | "connect-mongo": "0.8.2", 22 | "cookie-parser": "1.3.3", 23 | "cors": "2.5.0", 24 | "csurf": "1.6.2", 25 | "data2xml": "0.8.0", 26 | "ejs-mate": "2.0.0", 27 | "eventproxy": "0.3.1", 28 | "express": "4.9.5", 29 | "express-session": "1.9.1", 30 | "loader": "0.1.4", 31 | "lodash": "2.4.1", 32 | "markdown-it": "3.0.3", 33 | "memory-cache": "0.0.5", 34 | "method-override": "1.0.2", 35 | "moment": "2.9.0", 36 | "mongoose": "3.8.23", 37 | "multiline": "1.0.1", 38 | "newrelic": "1.16.1", 39 | "node-uuid": "1.4.1", 40 | "nodemailer": "1.8.0", 41 | "nodemailer-smtp-transport": "1.0.3", 42 | "passport": "0.1.18", 43 | "passport-github": "0.1.5", 44 | "pm2": "0.12.7", 45 | "qn": "1.0.1", 46 | "ready": "0.1.1", 47 | "response-time": "2.2.0", 48 | "utility": "1.0.0", 49 | "validator": "3.22.1", 50 | "xmlbuilder": "2.5.0", 51 | "xss": "0.1.15", 52 | "jpush-sdk": "3.2.0" 53 | }, 54 | "devDependencies": { 55 | "coveralls": "2.11.2", 56 | "errorhandler": "1.2.2", 57 | "istanbul": "0.3.2", 58 | "mm": "0.2.1", 59 | "mocha": "2.0.1", 60 | "nock": "0.48.1", 61 | "pedding": "1.0.0", 62 | "should": "4.1.0", 63 | "supertest": "0.14.0" 64 | }, 65 | "scripts": { 66 | "test": "make test" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /proxy/index.js: -------------------------------------------------------------------------------- 1 | exports.User = require('./user'); 2 | exports.Message = require('./message'); 3 | exports.Topic = require('./topic'); 4 | exports.Reply = require('./reply'); 5 | exports.TopicCollect = require('./topic_collect'); 6 | -------------------------------------------------------------------------------- /proxy/message.js: -------------------------------------------------------------------------------- 1 | var EventProxy = require('eventproxy'); 2 | 3 | var Message = require('../models').Message; 4 | 5 | var User = require('./user'); 6 | var Topic = require('./topic'); 7 | var Reply = require('./reply'); 8 | 9 | /** 10 | * 根据用户ID,获取未读消息的数量 11 | * Callback: 12 | * 回调函数参数列表: 13 | * - err, 数据库错误 14 | * - count, 未读消息数量 15 | * @param {String} id 用户ID 16 | * @param {Function} callback 获取消息数量 17 | */ 18 | exports.getMessagesCount = function (id, callback) { 19 | Message.count({master_id: id, has_read: false}, callback); 20 | }; 21 | 22 | 23 | /** 24 | * 根据消息Id获取消息 25 | * Callback: 26 | * - err, 数据库错误 27 | * - message, 消息对象 28 | * @param {String} id 消息ID 29 | * @param {Function} callback 回调函数 30 | */ 31 | exports.getMessageById = function (id, callback) { 32 | Message.findOne({_id: id}, function (err, message) { 33 | if (err) { 34 | return callback(err); 35 | } 36 | if (message.type === 'reply' || message.type === 'reply2' || message.type === 'at') { 37 | var proxy = new EventProxy(); 38 | proxy.fail(callback); 39 | proxy.assign('author_found', 'topic_found', 'reply_found', function (author, topic, reply) { 40 | message.author = author; 41 | message.topic = topic; 42 | message.reply = reply; 43 | if (!author || !topic) { 44 | message.is_invalid = true; 45 | } 46 | return callback(null, message); 47 | }); // 接收异常 48 | User.getUserById(message.author_id, proxy.done('author_found')); 49 | Topic.getTopicById(message.topic_id, proxy.done('topic_found')); 50 | Reply.getReplyById(message.reply_id, proxy.done('reply_found')); 51 | } 52 | 53 | if (message.type === 'follow') { 54 | User.getUserById(message.author_id, function (err, author) { 55 | if (err) { 56 | return callback(err); 57 | } 58 | message.author = author; 59 | if (!author) { 60 | message.is_invalid = true; 61 | } 62 | return callback(null, message); 63 | }); 64 | } 65 | }); 66 | }; 67 | 68 | /** 69 | * 根据用户ID,获取已读消息列表 70 | * Callback: 71 | * - err, 数据库异常 72 | * - messages, 消息列表 73 | * @param {String} userId 用户ID 74 | * @param {Function} callback 回调函数 75 | */ 76 | exports.getReadMessagesByUserId = function (userId, callback) { 77 | Message.find({master_id: userId, has_read: true}, null, 78 | {sort: '-create_at', limit: 20}, callback); 79 | }; 80 | 81 | /** 82 | * 根据用户ID,获取未读消息列表 83 | * Callback: 84 | * - err, 数据库异常 85 | * - messages, 未读消息列表 86 | * @param {String} userId 用户ID 87 | * @param {Function} callback 回调函数 88 | */ 89 | exports.getUnreadMessageByUserId = function (userId, callback) { 90 | Message.find({master_id: userId, has_read: false}, null, 91 | {sort: '-create_at'}, callback); 92 | }; 93 | -------------------------------------------------------------------------------- /proxy/topic_collect.js: -------------------------------------------------------------------------------- 1 | var TopicCollect = require('../models').TopicCollect; 2 | 3 | exports.getTopicCollect = function (userId, topicId, callback) { 4 | TopicCollect.findOne({user_id: userId, topic_id: topicId}, callback); 5 | }; 6 | 7 | exports.getTopicCollectsByUserId = function (userId, callback) { 8 | TopicCollect.find({user_id: userId}, callback); 9 | }; 10 | 11 | exports.newAndSave = function (userId, topicId, callback) { 12 | var topic_collect = new TopicCollect(); 13 | topic_collect.user_id = userId; 14 | topic_collect.topic_id = topicId; 15 | topic_collect.save(callback); 16 | }; 17 | 18 | exports.remove = function (userId, topicId, callback) { 19 | TopicCollect.remove({user_id: userId, topic_id: topicId}, callback); 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /proxy/user.js: -------------------------------------------------------------------------------- 1 | var models = require('../models'); 2 | var User = models.User; 3 | var utility = require('utility'); 4 | var uuid = require('node-uuid'); 5 | 6 | /** 7 | * 根据用户名列表查找用户列表 8 | * Callback: 9 | * - err, 数据库异常 10 | * - users, 用户列表 11 | * @param {Array} names 用户名列表 12 | * @param {Function} callback 回调函数 13 | */ 14 | exports.getUsersByNames = function (names, callback) { 15 | if (names.length === 0) { 16 | return callback(null, []); 17 | } 18 | User.find({ loginname: { $in: names } }, callback); 19 | }; 20 | 21 | /** 22 | * 根据登录名查找用户 23 | * Callback: 24 | * - err, 数据库异常 25 | * - user, 用户 26 | * @param {String} loginName 登录名 27 | * @param {Function} callback 回调函数 28 | */ 29 | exports.getUserByLoginName = function (loginName, callback) { 30 | User.findOne({'loginname': loginName}, callback); 31 | }; 32 | 33 | /** 34 | * 根据用户ID,查找用户 35 | * Callback: 36 | * - err, 数据库异常 37 | * - user, 用户 38 | * @param {String} id 用户ID 39 | * @param {Function} callback 回调函数 40 | */ 41 | exports.getUserById = function (id, callback) { 42 | User.findOne({_id: id}, callback); 43 | }; 44 | 45 | /** 46 | * 根据邮箱,查找用户 47 | * Callback: 48 | * - err, 数据库异常 49 | * - user, 用户 50 | * @param {String} email 邮箱地址 51 | * @param {Function} callback 回调函数 52 | */ 53 | exports.getUserByMail = function (email, callback) { 54 | User.findOne({email: email}, callback); 55 | }; 56 | 57 | /** 58 | * 根据用户ID列表,获取一组用户 59 | * Callback: 60 | * - err, 数据库异常 61 | * - users, 用户列表 62 | * @param {Array} ids 用户ID列表 63 | * @param {Function} callback 回调函数 64 | */ 65 | exports.getUsersByIds = function (ids, callback) { 66 | User.find({'_id': {'$in': ids}}, callback); 67 | }; 68 | 69 | /** 70 | * 根据关键字,获取一组用户 71 | * Callback: 72 | * - err, 数据库异常 73 | * - users, 用户列表 74 | * @param {String} query 关键字 75 | * @param {Object} opt 选项 76 | * @param {Function} callback 回调函数 77 | */ 78 | exports.getUsersByQuery = function (query, opt, callback) { 79 | User.find(query, '', opt, callback); 80 | }; 81 | 82 | /** 83 | * 根据查询条件,获取一个用户 84 | * Callback: 85 | * - err, 数据库异常 86 | * - user, 用户 87 | * @param {String} name 用户名 88 | * @param {String} key 激活码 89 | * @param {Function} callback 回调函数 90 | */ 91 | exports.getUserByNameAndKey = function (loginname, key, callback) { 92 | User.findOne({loginname: loginname, retrieve_key: key}, callback); 93 | }; 94 | 95 | exports.newAndSave = function (name, loginname, pass, email, avatar_url, active, callback) { 96 | var user = new User(); 97 | user.name = loginname; 98 | user.loginname = loginname; 99 | user.pass = pass; 100 | user.email = email; 101 | user.avatar = avatar_url; 102 | user.active = active || false; 103 | user.accessToken = uuid.v4(); 104 | user.save(callback); 105 | }; 106 | 107 | var makeGravatar = function (email) { 108 | return 'http://www.gravatar.com/avatar/' + utility.md5(email.toLowerCase()) + '?size=48'; 109 | }; 110 | exports.makeGravatar = makeGravatar; 111 | 112 | exports.getGravatar = function (user) { 113 | return user.avatar || makeGravatar(user); 114 | }; 115 | -------------------------------------------------------------------------------- /public/bdunion.txt: -------------------------------------------------------------------------------- 1 | a49a794726776a7d1700d152d88cc208 -------------------------------------------------------------------------------- /public/editor.min.1ab56128.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/editor.min.1ab56128.min.js -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/favicon.ico.bak -------------------------------------------------------------------------------- /public/images/75team-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/75team-favicon.png -------------------------------------------------------------------------------- /public/images/75team-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/75team-qrcode.png -------------------------------------------------------------------------------- /public/images/aliued-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/aliued-favicon.ico -------------------------------------------------------------------------------- /public/images/alloyteam-favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/alloyteam-favicon.jpg -------------------------------------------------------------------------------- /public/images/alloyteam-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/alloyteam-qrcode.png -------------------------------------------------------------------------------- /public/images/angular-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/angular-small.png -------------------------------------------------------------------------------- /public/images/aotu-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/aotu-favicon.png -------------------------------------------------------------------------------- /public/images/appdownload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/appdownload.png -------------------------------------------------------------------------------- /public/images/babel-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/babel-favicon.png -------------------------------------------------------------------------------- /public/images/behance-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/behance-favicon.png -------------------------------------------------------------------------------- /public/images/bootstrap-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/bootstrap-favicon.png -------------------------------------------------------------------------------- /public/images/browserify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/browserify.png -------------------------------------------------------------------------------- /public/images/byvoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/byvoid.png -------------------------------------------------------------------------------- /public/images/caniuse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/caniuse.png -------------------------------------------------------------------------------- /public/images/charles-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/charles-favicon.png -------------------------------------------------------------------------------- /public/images/checkmark_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/checkmark_icon&16.png -------------------------------------------------------------------------------- /public/images/cnode_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnode_icon_32.png -------------------------------------------------------------------------------- /public/images/cnode_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnode_icon_64.png -------------------------------------------------------------------------------- /public/images/cnode_logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnode_logo_128.png -------------------------------------------------------------------------------- /public/images/cnode_logo_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnode_logo_32.png -------------------------------------------------------------------------------- /public/images/cnodejs-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnodejs-favicon.png -------------------------------------------------------------------------------- /public/images/cnodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cnodejs.png -------------------------------------------------------------------------------- /public/images/cocos2dx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/cocos2dx.png -------------------------------------------------------------------------------- /public/images/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/code.png -------------------------------------------------------------------------------- /public/images/codepen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/codepen.png -------------------------------------------------------------------------------- /public/images/coding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/coding.png -------------------------------------------------------------------------------- /public/images/css-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/css-conf.png -------------------------------------------------------------------------------- /public/images/css-tricks-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/css-tricks-favicon.png -------------------------------------------------------------------------------- /public/images/css-wizardry-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/css-wizardry-favicon.png -------------------------------------------------------------------------------- /public/images/d2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/d2.png -------------------------------------------------------------------------------- /public/images/d3-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/d3-favicon.png -------------------------------------------------------------------------------- /public/images/daqianduan-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/daqianduan-favicon.png -------------------------------------------------------------------------------- /public/images/define-guild.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/define-guild.jpg -------------------------------------------------------------------------------- /public/images/digitalocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/digitalocean.png -------------------------------------------------------------------------------- /public/images/dntzhang.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/dntzhang.jpeg -------------------------------------------------------------------------------- /public/images/dribbble-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/dribbble-favicon.png -------------------------------------------------------------------------------- /public/images/echarts-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/echarts-favicon.png -------------------------------------------------------------------------------- /public/images/efe-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/efe-favicon.png -------------------------------------------------------------------------------- /public/images/egret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/egret.png -------------------------------------------------------------------------------- /public/images/express.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/express.png -------------------------------------------------------------------------------- /public/images/f2e-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/f2e-logo.png -------------------------------------------------------------------------------- /public/images/fe-zaoduke-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fe-zaoduke-qrcode.png -------------------------------------------------------------------------------- /public/images/fed-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fed-favicon.png -------------------------------------------------------------------------------- /public/images/feday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/feday.png -------------------------------------------------------------------------------- /public/images/fex-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fex-favicon.png -------------------------------------------------------------------------------- /public/images/fex-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fex-qrcode.png -------------------------------------------------------------------------------- /public/images/fiddler-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fiddler-favicon.png -------------------------------------------------------------------------------- /public/images/fis-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/fis-favicon.png -------------------------------------------------------------------------------- /public/images/font-awesome-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/font-awesome-favicon.png -------------------------------------------------------------------------------- /public/images/front-dev-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/front-dev-qrcode.png -------------------------------------------------------------------------------- /public/images/front-end-magazine-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/front-end-magazine-qrcode.png -------------------------------------------------------------------------------- /public/images/front-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/front-show.png -------------------------------------------------------------------------------- /public/images/github-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/github-favicon.png -------------------------------------------------------------------------------- /public/images/golangtc-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/golangtc-logo.png -------------------------------------------------------------------------------- /public/images/golangtc-logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/golangtc-logo2.png -------------------------------------------------------------------------------- /public/images/gold-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/gold-favicon.png -------------------------------------------------------------------------------- /public/images/grunt-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/grunt-favicon.png -------------------------------------------------------------------------------- /public/images/gulp-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/gulp-favicon.png -------------------------------------------------------------------------------- /public/images/highcharts-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/highcharts-favicon.png -------------------------------------------------------------------------------- /public/images/html5dw-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/html5dw-favicon.png -------------------------------------------------------------------------------- /public/images/http-guide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/http-guide.jpg -------------------------------------------------------------------------------- /public/images/huaban-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/huaban-favicon.png -------------------------------------------------------------------------------- /public/images/hubwiz-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/hubwiz-logo.png -------------------------------------------------------------------------------- /public/images/icomoon-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/icomoon-favicon.png -------------------------------------------------------------------------------- /public/images/iconfont-favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/iconfont-favicon.jpg -------------------------------------------------------------------------------- /public/images/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1491529839522'); 4 | /* IE9*/ 5 | src: url('iconfont.eot?t=1491529839522#iefix') format('embedded-opentype'), /* IE6-IE8 */ 6 | url('iconfont.woff?t=1491529839522') format('woff'), /* chrome, firefox */ 7 | url('iconfont.ttf?t=1491529839522') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 8 | url('iconfont.svg?t=1491529839522#iconfont') format('svg'); 9 | /* iOS 4.1- */ 10 | } 11 | 12 | .iconfont { 13 | font-family: "iconfont" !important; 14 | font-size: 16px; 15 | font-style: normal; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | } 19 | 20 | .icon-application:before { 21 | content: "\e60b"; 22 | } 23 | 24 | .icon-iconfontcolor54:before { 25 | content: "\e6aa"; 26 | } 27 | 28 | .icon-nodejs:before { 29 | content: "\e6ba"; 30 | } 31 | 32 | .icon-zuzhi:before { 33 | content: "\e619"; 34 | } 35 | 36 | .icon-sheji:before { 37 | content: "\e60c"; 38 | } 39 | 40 | .icon-ziti:before { 41 | content: "\e623"; 42 | } 43 | 44 | .icon-shequ:before { 45 | content: "\e6a0"; 46 | } 47 | 48 | .icon-importedlayerscopy3:before { 49 | content: "\e656"; 50 | } 51 | 52 | .icon-shu-copy:before { 53 | content: "\e600"; 54 | } 55 | 56 | .icon-gongju:before { 57 | content: "\e605"; 58 | } 59 | 60 | .icon-daohang:before { 61 | content: "\e65b"; 62 | } 63 | 64 | .icon-debug:before { 65 | content: "\e67b"; 66 | } 67 | 68 | .icon-youxi:before { 69 | content: "\e66f"; 70 | } 71 | 72 | .icon-usersecret:before { 73 | content: "\e865"; 74 | } 75 | 76 | .icon-tool:before { 77 | content: "\e601"; 78 | } 79 | 80 | .icon-ui-copy:before { 81 | content: "\e602"; 82 | } 83 | 84 | .icon-weibiaoti-1-03:before { 85 | content: "\e604"; 86 | } 87 | 88 | .icon-leimu:before { 89 | content: "\e683"; 90 | } 91 | 92 | .icon-lecloud_daimagoujian:before { 93 | content: "\e684"; 94 | } 95 | 96 | .icon-editor:before { 97 | content: "\e642"; 98 | } 99 | 100 | .icon-liuyan:before { 101 | content: "\e638"; 102 | } 103 | 104 | .icon-conference:before { 105 | content: "\e603"; 106 | } 107 | 108 | .icon-jiantou:before { 109 | content: "\e6a9"; 110 | } -------------------------------------------------------------------------------- /public/images/iojs-logo-w150h50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/iojs-logo-w150h50.png -------------------------------------------------------------------------------- /public/images/iojs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/iojs-logo.png -------------------------------------------------------------------------------- /public/images/ionic-china-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ionic-china-icon.png -------------------------------------------------------------------------------- /public/images/ionic-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 13 | 16 | 17 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/images/ionichina_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ionichina_icon_32.png -------------------------------------------------------------------------------- /public/images/ionichina_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ionichina_icon_64.png -------------------------------------------------------------------------------- /public/images/isux-favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/isux-favicon.jpg -------------------------------------------------------------------------------- /public/images/iweb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/iweb.png -------------------------------------------------------------------------------- /public/images/jpush-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush-logo.png -------------------------------------------------------------------------------- /public/images/jpush_150616.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush_150616.jpg -------------------------------------------------------------------------------- /public/images/jpush_150629.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush_150629.jpg -------------------------------------------------------------------------------- /public/images/jpush_150702.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush_150702.jpg -------------------------------------------------------------------------------- /public/images/jpush_150729.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush_150729.jpg -------------------------------------------------------------------------------- /public/images/jpush_150815.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jpush_150815.gif -------------------------------------------------------------------------------- /public/images/jquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jquery.png -------------------------------------------------------------------------------- /public/images/js-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/js-conf.png -------------------------------------------------------------------------------- /public/images/js-design-pattern.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/js-design-pattern.jpg -------------------------------------------------------------------------------- /public/images/jstips-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/jstips-favicon.png -------------------------------------------------------------------------------- /public/images/kinvix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/kinvix.png -------------------------------------------------------------------------------- /public/images/koa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/koa.png -------------------------------------------------------------------------------- /public/images/less-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/less-favicon.png -------------------------------------------------------------------------------- /public/images/lifesinger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/lifesinger.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/logo.png -------------------------------------------------------------------------------- /public/images/logo_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/logo_bak.png -------------------------------------------------------------------------------- /public/images/node-subway-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/node-subway-qrcode.png -------------------------------------------------------------------------------- /public/images/nodejs_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/nodejs_black.png -------------------------------------------------------------------------------- /public/images/o2-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/o2-qrcode.png -------------------------------------------------------------------------------- /public/images/phaserjs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/phaserjs.png -------------------------------------------------------------------------------- /public/images/phphub-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/phphub-logo.png -------------------------------------------------------------------------------- /public/images/pinterest-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/pinterest-favicon.png -------------------------------------------------------------------------------- /public/images/professional-javascript.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/professional-javascript.jpg -------------------------------------------------------------------------------- /public/images/proginn-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/proginn-logo.png -------------------------------------------------------------------------------- /public/images/qdjhu-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/qdjhu-logo.png -------------------------------------------------------------------------------- /public/images/qiniu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/qiniu.png -------------------------------------------------------------------------------- /public/images/qqlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/qqlog.png -------------------------------------------------------------------------------- /public/images/qrcode-weixin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/qrcode-weixin.jpg -------------------------------------------------------------------------------- /public/images/react-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/react-favicon.png -------------------------------------------------------------------------------- /public/images/react-native.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/react-native.png -------------------------------------------------------------------------------- /public/images/ruby-china-logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ruby-china-logo2.png -------------------------------------------------------------------------------- /public/images/rxjs-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/rxjs-favicon.png -------------------------------------------------------------------------------- /public/images/sass-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/sass-favicon.png -------------------------------------------------------------------------------- /public/images/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/search.png -------------------------------------------------------------------------------- /public/images/segmentfault-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/segmentfault-favicon.png -------------------------------------------------------------------------------- /public/images/sf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/sf-logo.png -------------------------------------------------------------------------------- /public/images/showcase/app_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/app_mask.png -------------------------------------------------------------------------------- /public/images/showcase/app_mask@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/app_mask@2x.png -------------------------------------------------------------------------------- /public/images/showcase/cnodejs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/cnodejs.png -------------------------------------------------------------------------------- /public/images/showcase/common.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/common.png -------------------------------------------------------------------------------- /public/images/showcase/gudongjiankong.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/gudongjiankong.jpg -------------------------------------------------------------------------------- /public/images/showcase/hipo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/hipo.png -------------------------------------------------------------------------------- /public/images/showcase/tuchong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/tuchong.png -------------------------------------------------------------------------------- /public/images/showcase/yibeiban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/yibeiban.png -------------------------------------------------------------------------------- /public/images/showcase/zhangshangyiyuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/showcase/zhangshangyiyuan.png -------------------------------------------------------------------------------- /public/images/smashingmagazine-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/smashingmagazine-favicon.png -------------------------------------------------------------------------------- /public/images/stackoverflow-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/stackoverflow-favicon.png -------------------------------------------------------------------------------- /public/images/stylus-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/stylus-favicon.png -------------------------------------------------------------------------------- /public/images/sublime-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/sublime-favicon.png -------------------------------------------------------------------------------- /public/images/testerhome-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/testerhome-logo.png -------------------------------------------------------------------------------- /public/images/thefwa-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/thefwa-favicon.png -------------------------------------------------------------------------------- /public/images/threejs-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/threejs-favicon.png -------------------------------------------------------------------------------- /public/images/tj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/tj.png -------------------------------------------------------------------------------- /public/images/ucloud-150515.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ucloud-150515.jpg -------------------------------------------------------------------------------- /public/images/ucloud-151226.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ucloud-151226.jpg -------------------------------------------------------------------------------- /public/images/ucloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/ucloud.png -------------------------------------------------------------------------------- /public/images/uigreat-favicon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/uigreat-favicon.jpg -------------------------------------------------------------------------------- /public/images/upyun_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/upyun_logo.png -------------------------------------------------------------------------------- /public/images/v2ex-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/images/v2ex-favicon.png -------------------------------------------------------------------------------- /public/images/vendor.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(n){if(r[n])return r[n].exports;var a=r[n]={exports:{},id:n,loaded:!1};return e[n].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n=window.webpackJsonp;window.webpackJsonp=function(o,p){for(var l,c,i=0,s=[];i 200) { 16 | $backtotop.fadeIn(); 17 | } else { 18 | $backtotop.fadeOut(); 19 | } 20 | }); 21 | 22 | moveBacktotop(); 23 | $(window).resize(moveBacktotop); 24 | 25 | $('.topic_content a,.reply_content a').attr('target', '_blank'); 26 | 27 | // pretty code 28 | prettyPrint(); 29 | 30 | // data-loading-text="提交中" 31 | $('.submit_btn').click(function () { 32 | $(this).button('loading'); 33 | }); 34 | 35 | // 广告的统计信息 36 | $('.sponsor_outlink').click(function () { 37 | var $this = $(this); 38 | var label = $this.data('label'); 39 | ga('send', 'event', 'banner', 'click', label, 1.00, {'nonInteraction': 1}); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /public/javascripts/responsive.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | var $responsiveBtn = $('#responsive-sidebar-trigger'), 3 | $sidebarMask = $('#sidebar-mask'), 4 | $sidebar = $('#sidebar'), 5 | $main = $('#main'), 6 | winWidth = $(window).width(), 7 | startX = 0, 8 | startY = 0, 9 | delta = { 10 | x: 0, 11 | y: 0 12 | }, 13 | swipeThreshold = winWidth / 3, 14 | toggleSideBar = function () { 15 | var isShow = $responsiveBtn.data('is-show'), 16 | mainHeight = $main.height(), 17 | sidebarHeight = $sidebar.outerHeight(); 18 | $sidebar.css({right: isShow ? -300 : 0}); 19 | $responsiveBtn.data('is-show', !isShow); 20 | if (!isShow && mainHeight < sidebarHeight) { 21 | $main.height(sidebarHeight); 22 | } 23 | $sidebarMask[isShow ? 'fadeOut' : 'fadeIn']().height($('body').height()); 24 | }, 25 | touchstart = function (e) { 26 | var touchs = e.targetTouches; 27 | startX = +touchs[0].pageX; 28 | startY = +touchs[0].pageY; 29 | delta.x = delta.y = 0; 30 | document.body.addEventListener('touchmove', touchmove, false); 31 | document.body.addEventListener('touchend', touchend, false); 32 | }, 33 | touchmove = function (e) { 34 | var touchs = e.changedTouches; 35 | delta.x = +touchs[0].pageX - startX; 36 | delta.y = +touchs[0].pageY - startY; 37 | //当水平距离大于垂直距离时,才认为是用户想滑动打开右侧栏 38 | if (Math.abs(delta.x) > Math.abs(delta.y)) { 39 | e.preventDefault(); 40 | } 41 | }, 42 | touchend = function (e) { 43 | var touchs = e.changedTouches, 44 | isShow = $responsiveBtn.data('is-show'); 45 | delta.x = +touchs[0].pageX - startX; 46 | //右侧栏未显示&&用户touch点在屏幕右侧1/4区域内&&move距离大于阀值时,打开右侧栏 47 | if (!isShow && (startX > winWidth * 3 / 4) && Math.abs(delta.x) > swipeThreshold) { 48 | $responsiveBtn.trigger('click'); 49 | } 50 | //右侧栏显示中&&用户touch点在屏幕左侧侧1/4区域内&&move距离大于阀值时,关闭右侧栏 51 | if (isShow && (startX < winWidth * 1 / 4) && Math.abs(delta.x) > swipeThreshold) { 52 | $responsiveBtn.trigger('click'); 53 | } 54 | startX = startY = 0; 55 | delta.x = delta.y = 0; 56 | document.body.removeEventListener('touchmove', touchmove, false); 57 | document.body.removeEventListener('touchend', touchend, false); 58 | }; 59 | 60 | if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 61 | document.body.addEventListener('touchstart', touchstart); 62 | } 63 | 64 | $responsiveBtn.on('click', toggleSideBar); 65 | 66 | $sidebarMask.on('click', function () { 67 | $responsiveBtn.trigger('click'); 68 | }); 69 | 70 | }); -------------------------------------------------------------------------------- /public/libs/bootstrap/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/bootstrap/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/libs/bootstrap/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/bootstrap/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/libs/code-prettify/highlight.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:bold}.hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable,.hljs-tag .hljs-attr{color:#008080}.hljs-string,.hljs-doctag{color:#d14}.hljs-title,.hljs-section,.hljs-selector-id{color:#900;font-weight:bold}.hljs-subst{font-weight:normal}.hljs-type,.hljs-class .hljs-title{color:#458;font-weight:bold}.hljs-tag,.hljs-name,.hljs-attribute{color:#000080;font-weight:normal}.hljs-regexp,.hljs-link{color:#009926}.hljs-symbol,.hljs-bullet{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-apollo.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["com", /^#[^\n\r]*/, null, "#"], 3 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 4 | ["str", /^"(?:[^"\\]|\\[\S\s])*(?:"|$)/, null, '"'] 5 | ], [ 6 | ["kwd", /^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/, 7 | null], 8 | ["typ", /^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[ES]?BANK=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/, null], 9 | ["lit", /^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/], 10 | ["pln", /^-*(?:[!-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/], 11 | ["pun", /^[^\w\t\n\r "'-);\\\xa0]+/] 12 | ]), ["apollo", "agc", "aea"]); 13 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a = null; 17 | PR.registerLangHandler(PR.createSimpleLexer([ 18 | ["opn", /^[([{]+/, a, "([{"], 19 | ["clo", /^[)\]}]+/, a, ")]}"], 20 | ["com", /^;[^\n\r]*/, a, ";"], 21 | ["pln", /^[\t\n\r \xa0]+/, a, "\t\n\r \xa0"], 22 | ["str", /^"(?:[^"\\]|\\[\S\s])*(?:"|$)/, a, '"'] 23 | ], [ 24 | ["kwd", /^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/, a], 25 | ["typ", /^:[\dA-Za-z-]+/] 26 | ]), ["clj"]); 27 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\f\r ]+/, null, " \t\r\n "] 3 | ], [ 4 | ["str", /^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/, null], 5 | ["str", /^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/, null], 6 | ["lang-css-str", /^url\(([^"')]*)\)/i], 7 | ["kwd", /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i, null], 8 | ["lang-css-kw", /^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i], 9 | ["com", /^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//], 10 | ["com", 11 | /^(?:<\!--|--\>)/], 12 | ["lit", /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], 13 | ["lit", /^#[\da-f]{3,6}/i], 14 | ["pln", /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i], 15 | ["pun", /^[^\s\w"']+/] 16 | ]), ["css"]); 17 | PR.registerLangHandler(PR.createSimpleLexer([], [ 18 | ["kwd", /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i] 19 | ]), ["css-kw"]); 20 | PR.registerLangHandler(PR.createSimpleLexer([], [ 21 | ["str", /^[^"')]+/] 22 | ]), ["css-str"]); 23 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-go.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["pln", /^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])+(?:'|$)|`[^`]*(?:`|$))/, null, "\"'"] 4 | ], [ 5 | ["com", /^(?:\/\/[^\n\r]*|\/\*[\S\s]*?\*\/)/], 6 | ["pln", /^(?:[^"'/`]|\/(?![*/]))+/] 7 | ]), ["go"]); 8 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t-\r ]+/, null, "\t\n \r "], 3 | ["str", /^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/, null, '"'], 4 | ["str", /^'(?:[^\n\f\r'\\]|\\[^&])'?/, null, "'"], 5 | ["lit", /^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i, null, "0123456789"] 6 | ], [ 7 | ["com", /^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/], 8 | ["kwd", /^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 9 | null], 10 | ["pln", /^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/], 11 | ["pun", /^[^\d\t-\r "'A-Za-z]+/] 12 | ]), ["hs"]); 13 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a = null; 2 | PR.registerLangHandler(PR.createSimpleLexer([ 3 | ["opn", /^\(+/, a, "("], 4 | ["clo", /^\)+/, a, ")"], 5 | ["com", /^;[^\n\r]*/, a, ";"], 6 | ["pln", /^[\t\n\r \xa0]+/, a, "\t\n\r \xa0"], 7 | ["str", /^"(?:[^"\\]|\\[\S\s])*(?:"|$)/, a, '"'] 8 | ], [ 9 | ["kwd", /^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/, a], 10 | ["lit", /^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i], 11 | ["lit", /^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/], 12 | ["pln", /^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i], 13 | ["pun", /^[^\w\t\n\r "'-);\\\xa0]+/] 14 | ]), ["cl", "el", "lisp", "scm"]); 15 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-lua.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["str", /^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$))/, null, "\"'"] 4 | ], [ 5 | ["com", /^--(?:\[(=*)\[[\S\s]*?(?:]\1]|$)|[^\n\r]*)/], 6 | ["str", /^\[(=*)\[[\S\s]*?(?:]\1]|$)/], 7 | ["kwd", /^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/, null], 8 | ["lit", /^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i], 9 | ["pln", /^[_a-z]\w*/i], 10 | ["pun", /^[^\w\t\n\r \xa0][^\w\t\n\r "'+=\xa0-]*/] 11 | ]), ["lua"]); 12 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-ml.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["com", /^#(?:if[\t\n\r \xa0]+(?:[$_a-z][\w']*|``[^\t\n\r`]*(?:``|$))|else|endif|light)/i, null, "#"], 4 | ["str", /^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])(?:'|$))/, null, "\"'"] 5 | ], [ 6 | ["com", /^(?:\/\/[^\n\r]*|\(\*[\S\s]*?\*\))/], 7 | ["kwd", /^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/], 8 | ["lit", /^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i], 9 | ["pln", /^(?:[_a-z][\w']*[!#?]?|``[^\t\n\r`]*(?:``|$))/i], 10 | ["pun", /^[^\w\t\n\r "'\xa0]+/] 11 | ]), ["fs", "ml"]); 12 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-n.js: -------------------------------------------------------------------------------- 1 | var a = null; 2 | PR.registerLangHandler(PR.createSimpleLexer([ 3 | ["str", /^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/, a, '"'], 4 | ["com", /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/, a, "#"], 5 | ["pln", /^\s+/, a, " \r\n\t\xa0"] 6 | ], [ 7 | ["str", /^@"(?:[^"]|"")*(?:"|$)/, a], 8 | ["str", /^<#[^#>]*(?:#>|$)/, a], 9 | ["str", /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, a], 10 | ["com", /^\/\/[^\n\r]*/, a], 11 | ["com", /^\/\*[\S\s]*?(?:\*\/|$)/, 12 | a], 13 | ["kwd", /^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 14 | a], 15 | ["typ", /^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/, a], 16 | ["lit", /^@[$_a-z][\w$@]*/i, a], 17 | ["typ", /^@[A-Z]+[a-z][\w$@]*/, a], 18 | ["pln", /^'?[$_a-z][\w$@]*/i, a], 19 | ["lit", /^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i, a, "0123456789"], 20 | ["pun", /^.[^\s\w"-$'./@`]*/, a] 21 | ]), ["n", "nemerle"]); 22 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-proto.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.sourceDecorator({keywords: "bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true", types: /^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/, cStyleComments: !0}), ["proto"]); 2 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-scala.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["str", /^"(?:""(?:""?(?!")|[^"\\]|\\.)*"{0,3}|(?:[^\n\r"\\]|\\.)*"?)/, null, '"'], 4 | ["lit", /^`(?:[^\n\r\\`]|\\.)*`?/, null, "`"], 5 | ["pun", /^[!#%&(--:-@[-^{-~]+/, null, "!#%&()*+,-:;<=>?@[\\]^{|}~"] 6 | ], [ 7 | ["str", /^'(?:[^\n\r'\\]|\\(?:'|[^\n\r']+))'/], 8 | ["lit", /^'[$A-Z_a-z][\w$]*(?![\w$'])/], 9 | ["kwd", /^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\b/], 10 | ["lit", /^(?:true|false|null|this)\b/], 11 | ["lit", /^(?:0(?:[0-7]+|x[\da-f]+)l?|(?:0|[1-9]\d*)(?:(?:\.\d+)?(?:e[+-]?\d+)?f?|l?)|\\.\d+(?:e[+-]?\d+)?f?)/i], 12 | ["typ", /^[$_]*[A-Z][\d$A-Z_]*[a-z][\w$]*/], 13 | ["pln", /^[$A-Z_a-z][\w$]*/], 14 | ["com", /^\/(?:\/.*|\*(?:\/|\**[^*/])*(?:\*+\/?)?)/], 15 | ["pun", /^(?:\.+|\/)/] 16 | ]), ["scala"]); 17 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-sql.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["str", /^(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/, null, "\"'"] 4 | ], [ 5 | ["com", /^(?:--[^\n\r]*|\/\*[\S\s]*?(?:\*\/|$))/], 6 | ["kwd", /^(?:add|all|alter|and|any|as|asc|authorization|backup|begin|between|break|browse|bulk|by|cascade|case|check|checkpoint|close|clustered|coalesce|collate|column|commit|compute|constraint|contains|containstable|continue|convert|create|cross|current|current_date|current_time|current_timestamp|current_user|cursor|database|dbcc|deallocate|declare|default|delete|deny|desc|disk|distinct|distributed|double|drop|dummy|dump|else|end|errlvl|escape|except|exec|execute|exists|exit|fetch|file|fillfactor|for|foreign|freetext|freetexttable|from|full|function|goto|grant|group|having|holdlock|identity|identitycol|identity_insert|if|in|index|inner|insert|intersect|into|is|join|key|kill|left|like|lineno|load|match|merge|national|nocheck|nonclustered|not|null|nullif|of|off|offsets|on|open|opendatasource|openquery|openrowset|openxml|option|or|order|outer|over|percent|plan|precision|primary|print|proc|procedure|public|raiserror|read|readtext|reconfigure|references|replication|restore|restrict|return|revoke|right|rollback|rowcount|rowguidcol|rule|save|schema|select|session_user|set|setuser|shutdown|some|statistics|system_user|table|textsize|then|to|top|tran|transaction|trigger|truncate|tsequal|union|unique|update|updatetext|use|user|using|values|varying|view|waitfor|when|where|while|with|writetext)(?=[^\w-]|$)/i, 7 | null], 8 | ["lit", /^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i], 9 | ["pln", /^[_a-z][\w-]*/i], 10 | ["pun", /^[^\w\t\n\r "'\xa0][^\w\t\n\r "'+\xa0-]*/] 11 | ]), ["sql"]); 12 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-tex.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"], 3 | ["com", /^%[^\n\r]*/, null, "%"] 4 | ], [ 5 | ["kwd", /^\\[@-Za-z]+/], 6 | ["kwd", /^\\./], 7 | ["typ", /^[$&]/], 8 | ["lit", /[+-]?(?:\.\d+|\d+(?:\.\d*)?)(cm|em|ex|in|pc|pt|bp|mm)/i], 9 | ["pun", /^[()=[\]{}]+/] 10 | ]), ["latex", "tex"]); 11 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-vb.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0\u2028\u2029]+/, null, "\t\n\r �\xa0

"], 3 | ["str", /^(?:["\u201c\u201d](?:[^"\u201c\u201d]|["\u201c\u201d]{2})(?:["\u201c\u201d]c|$)|["\u201c\u201d](?:[^"\u201c\u201d]|["\u201c\u201d]{2})*(?:["\u201c\u201d]|$))/i, null, '"“”'], 4 | ["com", /^['\u2018\u2019].*/, null, "'‘’"] 5 | ], [ 6 | ["kwd", /^(?:addhandler|addressof|alias|and|andalso|ansi|as|assembly|auto|boolean|byref|byte|byval|call|case|catch|cbool|cbyte|cchar|cdate|cdbl|cdec|char|cint|class|clng|cobj|const|cshort|csng|cstr|ctype|date|decimal|declare|default|delegate|dim|directcast|do|double|each|else|elseif|end|endif|enum|erase|error|event|exit|finally|for|friend|function|get|gettype|gosub|goto|handles|if|implements|imports|in|inherits|integer|interface|is|let|lib|like|long|loop|me|mod|module|mustinherit|mustoverride|mybase|myclass|namespace|new|next|not|notinheritable|notoverridable|object|on|option|optional|or|orelse|overloads|overridable|overrides|paramarray|preserve|private|property|protected|public|raiseevent|readonly|redim|removehandler|resume|return|select|set|shadows|shared|short|single|static|step|stop|string|structure|sub|synclock|then|throw|to|try|typeof|unicode|until|variant|wend|when|while|with|withevents|writeonly|xor|endif|gosub|let|variant|wend)\b/i, 7 | null], 8 | ["com", /^rem.*/i], 9 | ["lit", /^(?:true\b|false\b|nothing\b|\d+(?:e[+-]?\d+[dfr]?|[dfilrs])?|(?:&h[\da-f]+|&o[0-7]+)[ils]?|\d*\.\d+(?:e[+-]?\d+)?[dfr]?|#\s+(?:\d+[/-]\d+[/-]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:am|pm))?)?|\d+:\d+(?::\d+)?(\s*(?:am|pm))?)\s+#)/i], 10 | ["pln", /^(?:(?:[a-z]|_\w)\w*|\[(?:[a-z]|_\w)\w*])/i], 11 | ["pun", /^[^\w\t\n\r "'[\]\xa0\u2018\u2019\u201c\u201d\u2028\u2029]+/], 12 | ["pun", /^(?:\[|])/] 13 | ]), ["vb", "vbs"]); 14 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-vhdl.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\t\n\r \xa0]+/, null, "\t\n\r �\xa0"] 3 | ], [ 4 | ["str", /^(?:[box]?"(?:[^"]|"")*"|'.')/i], 5 | ["com", /^--[^\n\r]*/], 6 | ["kwd", /^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\w-]|$)/i, 7 | null], 8 | ["typ", /^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\w-]|$)/i, null], 9 | ["typ", /^'(?:active|ascending|base|delayed|driving|driving_value|event|high|image|instance_name|last_active|last_event|last_value|left|leftof|length|low|path_name|pos|pred|quiet|range|reverse_range|right|rightof|simple_name|stable|succ|transaction|val|value)(?=[^\w-]|$)/i, null], 10 | ["lit", /^\d+(?:_\d+)*(?:#[\w.\\]+#(?:[+-]?\d+(?:_\d+)*)?|(?:\.\d+(?:_\d+)*)?(?:e[+-]?\d+(?:_\d+)*)?)/i], 11 | ["pln", /^(?:[a-z]\w*|\\[^\\]*\\)/i], 12 | ["pun", /^[^\w\t\n\r "'\xa0][^\w\t\n\r "'\xa0-]*/] 13 | ]), ["vhdl", "vhd"]); 14 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-wiki.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([ 2 | ["pln", /^[\d\t a-gi-z\xa0]+/, null, "\t �\xa0abcdefgijklmnopqrstuvwxyz0123456789"], 3 | ["pun", /^[*=[\]^~]+/, null, "=*~^[]"] 4 | ], [ 5 | ["lang-wiki.meta", /(?:^^|\r\n?|\n)(#[a-z]+)\b/], 6 | ["lit", /^[A-Z][a-z][\da-z]+[A-Z][a-z][^\W_]+\b/], 7 | ["lang-", /^{{{([\S\s]+?)}}}/], 8 | ["lang-", /^`([^\n\r`]+)`/], 9 | ["str", /^https?:\/\/[^\s#/?]*(?:\/[^\s#?]*)?(?:\?[^\s#]*)?(?:#\S*)?/i], 10 | ["pln", /^(?:\r\n|[\S\s])[^\n\r#*=A-[^`h{~]*/] 11 | ]), ["wiki"]); 12 | PR.registerLangHandler(PR.createSimpleLexer([ 13 | ["kwd", /^#[a-z]+/i, null, "#"] 14 | ], []), ["wiki.meta"]); 15 | -------------------------------------------------------------------------------- /public/libs/code-prettify/lang-yaml.js: -------------------------------------------------------------------------------- 1 | var a = null; 2 | PR.registerLangHandler(PR.createSimpleLexer([ 3 | ["pun", /^[:>?|]+/, a, ":|>?"], 4 | ["dec", /^%(?:YAML|TAG)[^\n\r#]+/, a, "%"], 5 | ["typ", /^&\S+/, a, "&"], 6 | ["typ", /^!\S*/, a, "!"], 7 | ["str", /^"(?:[^"\\]|\\.)*(?:"|$)/, a, '"'], 8 | ["str", /^'(?:[^']|'')*(?:'|$)/, a, "'"], 9 | ["com", /^#[^\n\r]*/, a, "#"], 10 | ["pln", /^\s+/, a, " \t\r\n"] 11 | ], [ 12 | ["dec", /^(?:---|\.\.\.)(?:[\n\r]|$)/], 13 | ["pun", /^-/], 14 | ["kwd", /^\w+:[\n\r ]/], 15 | ["pln", /^\w+/] 16 | ]), ["yaml", "yml"]); 17 | -------------------------------------------------------------------------------- /public/libs/code-prettify/prettify.css: -------------------------------------------------------------------------------- 1 | /* Pretty printing styles. Used with prettify.js. */ 2 | 3 | /* SPAN elements with the classes below are added by prettyprint. */ 4 | .pln { 5 | color: #000 6 | } 7 | 8 | /* plain text */ 9 | 10 | @media screen { 11 | .str { 12 | color: #080 13 | } 14 | 15 | /* string content */ 16 | .kwd { 17 | color: #008 18 | } 19 | 20 | /* a keyword */ 21 | .com { 22 | color: #800 23 | } 24 | 25 | /* a comment */ 26 | .typ { 27 | color: #606 28 | } 29 | 30 | /* a type name */ 31 | .lit { 32 | color: #066 33 | } 34 | 35 | /* a literal value */ 36 | /* punctuation, lisp open bracket, lisp close bracket */ 37 | .pun, .opn, .clo { 38 | color: #660 39 | } 40 | 41 | .tag { 42 | color: #008 43 | } 44 | 45 | /* a markup tag name */ 46 | .atn { 47 | color: #606 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atv { 52 | color: #080 53 | } 54 | 55 | /* a markup attribute value */ 56 | .dec, .var { 57 | color: #606 58 | } 59 | 60 | /* a declaration; a variable name */ 61 | .fun { 62 | color: red 63 | } 64 | 65 | /* a function name */ 66 | } 67 | 68 | /* Use higher contrast and text-weight for printable form. */ 69 | @media print, projection { 70 | .str { 71 | color: #060 72 | } 73 | 74 | .kwd { 75 | color: #006; 76 | font-weight: bold 77 | } 78 | 79 | .com { 80 | color: #600; 81 | font-style: italic 82 | } 83 | 84 | .typ { 85 | color: #404; 86 | font-weight: bold 87 | } 88 | 89 | .lit { 90 | color: #044 91 | } 92 | 93 | .pun, .opn, .clo { 94 | color: #440 95 | } 96 | 97 | .tag { 98 | color: #006; 99 | font-weight: bold 100 | } 101 | 102 | .atn { 103 | color: #404 104 | } 105 | 106 | .atv { 107 | color: #060 108 | } 109 | } 110 | 111 | /* Put a border around prettyprinted code snippets. */ 112 | pre.prettyprint { 113 | padding: 2px; 114 | border: 1px solid #888 115 | } 116 | 117 | /* Specify class=linenums on a pre to get line numbering */ 118 | ol.linenums { 119 | margin-top: 0; 120 | margin-bottom: 0 121 | } 122 | 123 | /* IE indents via margin-left */ 124 | li.L0, 125 | li.L1, 126 | li.L2, 127 | li.L3, 128 | li.L5, 129 | li.L6, 130 | li.L7, 131 | li.L8 { 132 | list-style-type: none 133 | } 134 | 135 | /* Alternate shading for lines */ 136 | li.L1, 137 | li.L3, 138 | li.L5, 139 | li.L7, 140 | li.L9 { 141 | background: #eee 142 | } -------------------------------------------------------------------------------- /public/libs/editor/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/editor/fonts/icomoon.eot -------------------------------------------------------------------------------- /public/libs/editor/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/editor/fonts/icomoon.ttf -------------------------------------------------------------------------------- /public/libs/editor/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/editor/fonts/icomoon.woff -------------------------------------------------------------------------------- /public/libs/font-awesome/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/font-awesome/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/libs/font-awesome/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/font-awesome/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/libs/font-awesome/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/font-awesome/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/libs/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/libs/webuploader/Uploader.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/libs/webuploader/Uploader.swf -------------------------------------------------------------------------------- /public/libs/webuploader/webuploader.css: -------------------------------------------------------------------------------- 1 | .webuploader-container { 2 | position: relative; 3 | } 4 | .webuploader-element-invisible { 5 | position: absolute !important; 6 | clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ 7 | clip: rect(1px,1px,1px,1px); 8 | } 9 | .webuploader-pick { 10 | position: relative; 11 | display: inline-block; 12 | cursor: pointer; 13 | background: #00b7ee; 14 | padding: 10px 15px; 15 | color: #fff; 16 | text-align: center; 17 | border-radius: 3px; 18 | overflow: hidden; 19 | } 20 | .webuploader-pick-hover { 21 | background: #00a2d4; 22 | } 23 | 24 | .webuploader-pick-disable { 25 | opacity: 0.6; 26 | pointer-events:none; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /public/stylesheets/common.css: -------------------------------------------------------------------------------- 1 | body, p, input, textarea { 2 | font-size: 14px; 3 | word-break: break-word; 4 | } 5 | 6 | textarea, input[type="text"], 7 | input[type="password"], 8 | input[type="datetime"], 9 | input[type="datetime-local"], 10 | input[type="date"], 11 | input[type="month"], 12 | input[type="time"], 13 | input[type="week"], 14 | input[type="number"], 15 | input[type="email"], 16 | input[type="url"], 17 | input[type="search"], 18 | input[type="tel"], 19 | input[type="color"], 20 | .uneditable-input { 21 | background: hsla(0, 0%, 0%, 0) 22 | } 23 | 24 | pre { 25 | /* background: #fee9cc; */ 26 | /* border: 1px dashed #ccc; */ 27 | line-height: 22px; 28 | /* box-shadow: 9px 12px 27px #c8c8c8; */ 29 | } 30 | 31 | code { 32 | padding: 0; 33 | border: none; 34 | } 35 | 36 | p code { 37 | background: none; 38 | color: hsl(0, 0%, 50%); 39 | margin: 0 1px; 40 | padding: 1px 4px; 41 | border-radius: 1px; 42 | } 43 | 44 | div pre.prettyprint { 45 | font-size: 14px; 46 | border-radius: 0px; 47 | padding: 0 15px; 48 | border: none; 49 | margin: 20px -10px; 50 | border-width: 1px 0px; 51 | background: #f7f7f7; 52 | } 53 | 54 | form { 55 | margin-bottom: 0; 56 | } 57 | 58 | textarea { 59 | margin-bottom: 0; 60 | } 61 | 62 | input, textarea { 63 | background: hsla(0, 0%, 0%, 0); 64 | } 65 | -------------------------------------------------------------------------------- /public/stylesheets/jquery.atwho.css: -------------------------------------------------------------------------------- 1 | .atwho-view { 2 | position:absolute; 3 | top: 0; 4 | left: 0; 5 | display: none; 6 | margin-top: 18px; 7 | background: white; 8 | color: black; 9 | border: 1px solid #DDD; 10 | border-radius: 3px; 11 | box-shadow: 0 0 5px rgba(0,0,0,0.1); 12 | min-width: 120px; 13 | z-index: 11110 !important; 14 | } 15 | 16 | .atwho-view .cur { 17 | background: #3366FF; 18 | color: white; 19 | } 20 | .atwho-view .cur small { 21 | color: white; 22 | } 23 | .atwho-view strong { 24 | color: #3366FF; 25 | } 26 | .atwho-view .cur strong { 27 | color: white; 28 | font:bold; 29 | } 30 | .atwho-view ul { 31 | /* width: 100px; */ 32 | list-style:none; 33 | padding:0; 34 | margin:auto; 35 | } 36 | .atwho-view ul li { 37 | display: block; 38 | padding: 5px 10px; 39 | border-bottom: 1px solid #DDD; 40 | cursor: pointer; 41 | /* border-top: 1px solid #C8C8C8; */ 42 | } 43 | .atwho-view small { 44 | font-size: smaller; 45 | color: #777; 46 | font-weight: normal; 47 | } 48 | -------------------------------------------------------------------------------- /public/stylesheets/responsive.css: -------------------------------------------------------------------------------- 1 | @-ms-viewport { 2 | width: device-width; 3 | } 4 | 5 | #sidebar-mask { 6 | background-color: #333; 7 | width: 100%; 8 | height: 100%; 9 | filter: alpha(opacity=60); 10 | opacity: .6; 11 | z-index: 99; 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | display: none; 16 | } 17 | 18 | @media (max-width: 400px) { 19 | .navbar .brand { 20 | float: none; 21 | margin: 0 auto; 22 | } 23 | 24 | .navbar .navbar-search { 25 | clear: both; 26 | margin: 0 auto; 27 | float: none; 28 | } 29 | 30 | .navbar .search-query { 31 | display: block; 32 | margin: 0 auto; 33 | } 34 | } 35 | 36 | @media (max-width: 979px) { 37 | 38 | .navbar { 39 | margin: 0 5px; 40 | z-index: 999; 41 | width: auto !important; 42 | } 43 | 44 | .navbar .container, #main, 45 | #content, #footer_main { 46 | width: 100%; 47 | min-width: 0; 48 | } 49 | 50 | .navbar .nav.pull-right { 51 | float: none; 52 | clear: both; 53 | } 54 | 55 | #responsive-sidebar-trigger { 56 | display: none; 57 | } 58 | 59 | #main { 60 | overflow: hidden; 61 | margin: 20px auto; 62 | min-height: 0; 63 | } 64 | 65 | #content .panel { 66 | margin: 0 5px; 67 | } 68 | 69 | #sidebar { 70 | float: none; 71 | position: absolute; 72 | right: -100%; 73 | top: 0; 74 | background-color: #fff; 75 | z-index: 999; 76 | border: 5px solid #ccc; 77 | border-right: 0; 78 | -webkit-transition: .3s right; 79 | -moz-transition: .3s right; 80 | -ms-transition: .3s right; 81 | -o-transition: .3s right; 82 | transition: .3s right; 83 | } 84 | 85 | #content .topic_title { 86 | font-size: 1em; 87 | width: 100%; 88 | } 89 | 90 | #content .last_time { 91 | position: absolute; 92 | bottom: 0; 93 | right: 10px; 94 | font-size: .8em; 95 | } 96 | 97 | #content .last_time img { 98 | display: none; 99 | } 100 | 101 | #content .reply_count { 102 | position: absolute; 103 | bottom: 0; 104 | left: 85px; 105 | text-align: left; 106 | line-height: 2em; 107 | font-size: 10px; 108 | } 109 | 110 | .topic_title_wrapper { 111 | padding-left: 40px; 112 | } 113 | 114 | #main .topic_content p a.content_img, 115 | #main .reply_content p a.content_img { 116 | width: 100%; 117 | } 118 | 119 | #footer { 120 | margin: 0 5px 5px; 121 | } 122 | 123 | #footer_main { 124 | display: none; 125 | } 126 | 127 | #backtotop { 128 | background-color: #f5f5f5; 129 | border: 1px solid #ccc; 130 | border-right: 0; 131 | } 132 | 133 | .form-horizontal .control-label { 134 | float: none; 135 | width: auto; 136 | padding-top: 0; 137 | text-align: left; 138 | } 139 | 140 | .form-horizontal .controls { 141 | margin-left: 0; 142 | } 143 | 144 | .form-horizontal .control-list { 145 | padding-top: 0; 146 | } 147 | 148 | .form-horizontal .form-actions { 149 | padding-right: 10px; 150 | padding-left: 10px; 151 | } 152 | 153 | #content .reply_content { 154 | clear: both; 155 | padding-left: 0; 156 | padding-top: 5px; 157 | } 158 | 159 | #content .action { 160 | display: none; 161 | } 162 | 163 | .user_profile { 164 | margin-top: 0; 165 | } 166 | } 167 | 168 | -------------------------------------------------------------------------------- /public/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/febobo/vueClub/c6efa60ed5affda2202ff11e5a74a8c97322a648/public/vue.png -------------------------------------------------------------------------------- /test/api/v1/message.test.js: -------------------------------------------------------------------------------- 1 | var support = require('../../support/support'); 2 | var message = require('../../../common/message'); 3 | var MessageProxy = require('../../../proxy').Message; 4 | var app = require('../../../app'); 5 | var request = require('supertest')(app); 6 | var mm = require('mm'); 7 | var should = require('should'); 8 | 9 | describe('test/api/v1/message.test.js', function () { 10 | var mockUser; 11 | before(function (done) { 12 | support.ready(function () { 13 | support.createUser(function (err, user) { 14 | mockUser = user; 15 | done(); 16 | }); 17 | }); 18 | }); 19 | 20 | afterEach(function () { 21 | mm.restore(); 22 | }); 23 | 24 | it('should get unread messages', function (done) { 25 | mm(MessageProxy, 'getMessageById', function (id, callback) { 26 | callback(null, {reply: {author: {}}}); 27 | }); 28 | message.sendReplyMessage(mockUser.id, mockUser.id, mockUser.id, mockUser.id, 29 | function (err) { 30 | should.not.exists(err); 31 | request.get('/api/v1/messages') 32 | .query({ 33 | accesstoken: mockUser.accessToken, 34 | }) 35 | .end(function (err, res) { 36 | res.body.data.hasnot_read_messages.length.should.above(0); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | it('should get unread messages count', function (done) { 43 | mm(MessageProxy, 'getMessageById', function (id, callback) { 44 | callback(null, {reply: {author: {}}}); 45 | }); 46 | request.get('/api/v1/message/count') 47 | .query({ 48 | accesstoken: mockUser.accessToken, 49 | }) 50 | .end(function (err, res) { 51 | res.body.data.should.equal(1); 52 | done(); 53 | }); 54 | }); 55 | 56 | it('should mark all messages read', function (done) { 57 | request.post('/api/v1/message/mark_all') 58 | .send({ 59 | accesstoken: mockUser.accessToken, 60 | }) 61 | .end(function (err, res) { 62 | // 第一次查询有一个 63 | res.body.marked_msgs.length.should.equal(1); 64 | request.post('/api/v1/message/mark_all') 65 | .send({ 66 | accesstoken: mockUser.accessToken, 67 | }) 68 | .end(function (err, res) { 69 | // 第二次查询没了 70 | res.body.marked_msgs.length.should.equal(0); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/api/v1/tools.test.js: -------------------------------------------------------------------------------- 1 | 2 | var app = require('../../../app'); 3 | var request = require('supertest')(app); 4 | var support = require('../../support/support'); 5 | var should = require('should'); 6 | 7 | 8 | describe('test/api/v1/tools.test.js', function () { 9 | var mockUser; 10 | before(function (done) { 11 | support.createUser(function (err, user) { 12 | mockUser = user; 13 | done(); 14 | }) 15 | }) 16 | 17 | it('should response with loginname', function (done) { 18 | request.post('/api/v1/accesstoken') 19 | .send({ 20 | accesstoken: mockUser.accessToken 21 | }) 22 | .end(function (err, res) { 23 | should.not.exists(err); 24 | res.status.should.equal(200); 25 | res.body.loginname.should.equal(mockUser.loginname); 26 | done(); 27 | }) 28 | }) 29 | 30 | it('should 403 when accessToken is wrong', function (done) { 31 | request.post('/api/v1/accesstoken') 32 | .send({ 33 | accessToken: 'not_exists' 34 | }) 35 | .end(function (err, res) { 36 | should.not.exists(err); 37 | res.status.should.equal(403); 38 | res.body.error_msg.should.containEql('wrong accessToken'); 39 | done(); 40 | }) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/api/v1/user.test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var app = require('../../../app'); 4 | var request = require('supertest')(app); 5 | var support = require('../../support/support'); 6 | var should = require('should'); 7 | 8 | describe('test/api/v1/user.test.js', function () { 9 | it('should return user info', function (done) { 10 | support.createUser(function (err, user) { 11 | should.not.exists(err); 12 | request.get('/api/v1/user/' + user.loginname) 13 | .end(function (err, res) { 14 | should.not.exists(err); 15 | res.body.data.loginname.should.equal(user.loginname); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/app.test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var app = require('../app'); 3 | var config = require('../config'); 4 | 5 | describe('test/app.test.js', function () { 6 | it('should / status 200', function (done) { 7 | request(app).get('/').end(function (err, res) { 8 | res.status.should.equal(200); 9 | res.text.should.containEql(config.description); 10 | done(); 11 | }); 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /test/common/mail.test.js: -------------------------------------------------------------------------------- 1 | var mail = require('../../common/mail'); 2 | 3 | describe('test/common/mail.test.js', function () { 4 | describe('sendActiveMail', function () { 5 | it('should ok', function () { 6 | mail.sendActiveMail('shyvo1987@gmail.com', 'token', 'jacksontian'); 7 | }); 8 | }); 9 | 10 | describe('sendResetPassMail', function () { 11 | it('should ok', function () { 12 | mail.sendResetPassMail('shyvo1987@gmail.com', 'token', 'jacksontian'); 13 | }); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /test/common/message.test.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var app = require('../../app'); 3 | var request = require('supertest')(app); 4 | var mm = require('mm'); 5 | var support = require('../support/support'); 6 | var _ = require('lodash'); 7 | var pedding = require('pedding'); 8 | var multiline = require('multiline'); 9 | var MessageService = require('../../common/message'); 10 | var eventproxy = require('eventproxy'); 11 | var ReplyProxy = require('../../proxy').Reply; 12 | 13 | describe('test/common/message.test.js', function () { 14 | var atUser; 15 | var author; 16 | var topic; 17 | var reply; 18 | before(function (done) { 19 | var ep = new eventproxy(); 20 | 21 | ep.all('topic', function (_topic) { 22 | topic = _topic; 23 | done(); 24 | }); 25 | support.ready(function () { 26 | atUser = support.normalUser; 27 | author = atUser; 28 | reply = {}; 29 | support.createTopic(author._id, ep.done('topic')); 30 | }); 31 | }); 32 | 33 | afterEach(function () { 34 | mm.restore(); 35 | }); 36 | 37 | describe('#sendReplyMessage', function () { 38 | it('should send reply message', function (done) { 39 | mm(ReplyProxy, 'getReplyById', function (id, callback) { 40 | callback(null, {author: {}}); 41 | }); 42 | MessageService.sendReplyMessage(atUser._id, author._id, topic._id, reply._id, 43 | function (err, msg) { 44 | request.get('/my/messages') 45 | .set('Cookie', support.normalUserCookie) 46 | .expect(200, function (err, res) { 47 | var texts = [ 48 | author.loginname, 49 | '回复了你的话题', 50 | topic.title, 51 | ]; 52 | texts.forEach(function (text) { 53 | res.text.should.containEql(text) 54 | }) 55 | done(err); 56 | }); 57 | }); 58 | }); 59 | }); 60 | 61 | describe('#sendAtMessage', function () { 62 | it('should send at message', function (done) { 63 | mm(ReplyProxy, 'getReplyById', function (id, callback) { 64 | callback(null, {author: {}}); 65 | }); 66 | MessageService.sendAtMessage(atUser._id, author._id, topic._id, reply._id, 67 | function (err, msg) { 68 | request.get('/my/messages') 69 | .set('Cookie', support.normalUserCookie) 70 | .expect(200, function (err, res) { 71 | var texts = [ 72 | author.loginname, 73 | '在话题', 74 | topic.title, 75 | '中@了你', 76 | ]; 77 | texts.forEach(function (text) { 78 | res.text.should.containEql(text) 79 | }) 80 | done(err); 81 | }); 82 | }); 83 | }); 84 | }); 85 | }) 86 | -------------------------------------------------------------------------------- /test/common/render_helper.test.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var app = require('../../app'); 3 | var request = require('supertest')(app); 4 | var mm = require('mm'); 5 | var support = require('../support/support'); 6 | var _ = require('lodash'); 7 | var pedding = require('pedding'); 8 | var multiline = require('multiline'); 9 | var renderHelper = require('../../common/render_helper'); 10 | 11 | describe('test/common/render_helper.test.js', function () { 12 | describe('#markdown', function () { 13 | it('should render code', function () { 14 | var text = multiline(function () {; 15 | /* 16 | ```js 17 | var a = 1; 18 | ``` 19 | */ 20 | }); 21 | 22 | var rendered = renderHelper.markdown(text); 23 | rendered.should.equal('
var a = 1;\n
'); 24 | }); 25 | }); 26 | 27 | describe('#escapeSignature', function () { 28 | it('should escape content', function () { 29 | var signature = multiline(function () {; 30 | /* 31 | 我爱北京天安门 33 | */ 34 | }); 35 | var escaped = renderHelper.escapeSignature(signature); 36 | escaped.should.equal('我爱北京天安门<script>alert(1)
</script>'); 37 | }) 38 | }) 39 | 40 | describe('#tabName', function () { 41 | it('should translate', function () { 42 | renderHelper.tabName('share') 43 | .should.equal('分享') 44 | }) 45 | }) 46 | 47 | 48 | }); 49 | -------------------------------------------------------------------------------- /test/common/store_local.test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var storeLocal = require('../../common/store_local'); 4 | var config = require('../../config'); 5 | 6 | describe('test/common/store_local.test.js', function () { 7 | it('should upload a file', function (done) { 8 | var file = fs.createReadStream(path.join(__dirname, 'at.test.js')); 9 | var filename = 'at.test.js'; 10 | storeLocal.upload(file, {filename: filename}, function (err, data) { 11 | var newFilename = data.url.match(/([^\/]+\.js)$/)[1]; 12 | var newFilePath = path.join(config.upload.path, newFilename); 13 | setTimeout(function () { 14 | fs.existsSync(newFilePath) 15 | .should.ok; 16 | fs.unlinkSync(newFilePath); 17 | done(err); 18 | }, 1 * 1000); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/common/tools.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nodeclub - onehost plugins unit tests. 3 | * Copyright(c) 2012 dead-horse 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | var tools = require('../../common/tools'); 11 | 12 | describe('test/common/tools.test.js', function () { 13 | it('should format date', function () { 14 | tools.formatDate(new Date(0)).should.match(/1970\-01\-01 0\d:00/); 15 | }); 16 | it('should format date friendly', function () { 17 | tools.formatDate(new Date(), true).should.equal('几秒前'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/controllers/message.test.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var app = require('../../app'); 3 | var support = require('../support/support'); 4 | 5 | describe('test/controllers/message.test.js', function () { 6 | before(function (done) { 7 | support.ready(done); 8 | }); 9 | 10 | describe('index', function () { 11 | it('should 403 without session', function (done) { 12 | request(app).get('/my/messages').end(function (err, res) { 13 | res.statusCode.should.equal(403); 14 | res.type.should.equal('text/html'); 15 | res.text.should.containEql('forbidden!'); 16 | done(err); 17 | }); 18 | }); 19 | 20 | it('should 200', function (done) { 21 | request(app).get('/my/messages') 22 | .set('Cookie', support.normalUserCookie) 23 | .expect(200) 24 | .end(function (err, res) { 25 | res.text.should.containEql('新消息'); 26 | done(err); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/controllers/rss.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nodeclub - rss controller test 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var request = require('supertest'); 12 | var app = require('../../app'); 13 | var config = require('../../config'); 14 | 15 | describe('test/controllers/rss.test.js', function () { 16 | 17 | describe('/rss', function () { 18 | it('should return `application/xml` Content-Type', function (done) { 19 | request(app).get('/rss').end(function (err, res) { 20 | res.status.should.equal(200); 21 | res.headers.should.property('content-type', 'application/xml; charset=utf-8'); 22 | res.text.indexOf('').should.equal(0); 23 | res.text.should.containEql(''); 24 | res.text.should.containEql('' + config.rss.title + ''); 25 | done(err); 26 | }); 27 | }); 28 | 29 | describe('mock `config.rss` not set', function () { 30 | var rss = config.rss; 31 | before(function () { 32 | config.rss = null; 33 | }); 34 | after(function () { 35 | config.rss = rss; 36 | }); 37 | 38 | it('should return waring message', function (done) { 39 | request(app).get('/rss').end(function (err, res) { 40 | res.status.should.equal(404); 41 | res.text.should.equal('Please set `rss` in config.js'); 42 | done(err); 43 | }); 44 | }); 45 | }); 46 | 47 | describe('mock `topic.getTopicsByQuery()` error', function () { 48 | var topic = require('../../proxy').Topic; 49 | var getTopicsByQuery = topic.getTopicsByQuery; 50 | before(function () { 51 | topic.getTopicsByQuery = function () { 52 | var callback = arguments[arguments.length - 1]; 53 | process.nextTick(function () { 54 | callback(new Error('mock getTopicsByQuery() error')); 55 | }); 56 | }; 57 | }); 58 | after(function () { 59 | topic.getTopicsByQuery = getTopicsByQuery; 60 | }); 61 | 62 | it('should return error', function (done) { 63 | request(app).get('/rss').end(function (err, res) { 64 | res.status.should.equal(500); 65 | res.text.should.containEql('mock getTopicsByQuery() error'); 66 | done(err); 67 | }); 68 | }); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/controllers/search.test.js: -------------------------------------------------------------------------------- 1 | var app = require('../../app'); 2 | var request = require('supertest')(app); 3 | 4 | describe('test/controllers/search.test.js', function () { 5 | it('should redirect to google search', function (done) { 6 | request.get('/search').query({q: 'node 中文'}) 7 | .expect(302) 8 | .end(function (err, res) { 9 | res.headers['location'].should.equal('https://www.google.com.hk/#hl=zh-CN&q=site:ionichina.com+node%20%E4%B8%AD%E6%96%87'); 10 | done(err); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/controllers/site.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * nodeclub - site controller test 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var should = require('should'); 12 | var config = require('../../config'); 13 | var app = require('../../app'); 14 | var request = require('supertest')(app); 15 | 16 | 17 | describe('test/controllers/site.test.js', function () { 18 | 19 | it('should / 200', function (done) { 20 | request.get('/').end(function (err, res) { 21 | res.status.should.equal(200); 22 | res.text.should.containEql('积分榜'); 23 | res.text.should.containEql('友情社区'); 24 | done(err); 25 | }); 26 | }); 27 | 28 | it('should /?page=-1 200', function (done) { 29 | request.get('/?page=-1').end(function (err, res) { 30 | res.status.should.equal(200); 31 | res.text.should.containEql('积分榜'); 32 | res.text.should.containEql('友情社区'); 33 | done(err); 34 | }); 35 | }); 36 | 37 | it('should /sitemap.xml 200', function (done) { 38 | request.get('/sitemap.xml') 39 | .expect(200, function (err, res) { 40 | res.text.should.containEql(''); 41 | done(err); 42 | }); 43 | }); 44 | 45 | it('should /app/download', function (done) { 46 | request.get('/app/download') 47 | .expect(302, function (err, res) { 48 | done(err); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/controllers/static.test.js: -------------------------------------------------------------------------------- 1 | var app = require('../../app'); 2 | var request = require('supertest')(app); 3 | 4 | describe('test/controllers/static.test.js', function () { 5 | it('should get /about', function (done) { 6 | request.get('/about').expect(200) 7 | .end(function (err, res) { 8 | res.text.should.containEql('Ionichina 中文社区致力于 IonicFramework 在中国的推广、学习、研究工作。'); 9 | done(err); 10 | }); 11 | }); 12 | 13 | it('should get /timeline', function (done) { 14 | request.get('/timeline').expect(200) 15 | .end(function (err, res) { 16 | res.text.should.containEql('时间线'); 17 | done(err); 18 | }); 19 | }); 20 | 21 | it('should get /faq', function (done) { 22 | request.get('/faq').expect(200) 23 | .end(function (err, res) { 24 | res.text.should.containEql('这是为什么呢?'); 25 | done(err); 26 | }); 27 | }); 28 | 29 | it('should get /getstart', function (done) { 30 | request.get('/getstart').expect(200) 31 | .end(function (err, res) { 32 | res.text.should.containEql('IonicFramework 新手入门'); 33 | done(err); 34 | }); 35 | }); 36 | 37 | it('should get /robots.txt', function (done) { 38 | request.get('/robots.txt').expect(200) 39 | .end(function (err, res) { 40 | res.text.should.containEql('User-Agent'); 41 | done(err); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/middlewares/conf.test.js: -------------------------------------------------------------------------------- 1 | var conf = require('../../middlewares/conf'); 2 | var config = require('../../config'); 3 | 4 | describe('test/middlewares/conf.test.js', function () { 5 | it('should alert no github oauth', function (done) { 6 | var _clientID = config.GITHUB_OAUTH.clientID; 7 | config.GITHUB_OAUTH.clientID = 'your GITHUB_CLIENT_ID'; 8 | conf.github({}, {send: function (str) { 9 | str.should.equal('call the admin to set github oauth.'); 10 | config.GITHUB_OAUTH.clientID = _clientID; 11 | done(); 12 | }}); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/proxy/message.test.js: -------------------------------------------------------------------------------- 1 | var Message = require('../../proxy/message'); 2 | var should = require('should'); 3 | 4 | describe('test/proxy/message.test.js', function () { 5 | }); 6 | -------------------------------------------------------------------------------- /test/proxy/reply.test.js: -------------------------------------------------------------------------------- 1 | var Reply = require('../../proxy/reply'); 2 | var support = require('../support/support'); 3 | var should = require('should'); 4 | 5 | describe('test/proxy/reply.test.js', function () { 6 | }); 7 | -------------------------------------------------------------------------------- /test/proxy/topic.test.js: -------------------------------------------------------------------------------- 1 | var Topic = require('../../proxy/topic'); 2 | var support = require('../support/support'); 3 | var should = require('should'); 4 | 5 | describe('test/proxy/topic.test.js', function () { 6 | }); 7 | -------------------------------------------------------------------------------- /test/proxy/user.test.js: -------------------------------------------------------------------------------- 1 | var User = require('../../proxy/user'); 2 | var should = require('should'); 3 | var support = require('../support/support'); 4 | 5 | describe('test/proxy/user.test.js', function () { 6 | }); 7 | -------------------------------------------------------------------------------- /test/support/support.js: -------------------------------------------------------------------------------- 1 | var User = require('../../proxy/user'); 2 | var Topic = require('../../proxy/topic'); 3 | var Reply = require('../../proxy/reply'); 4 | var ready = require('ready'); 5 | var eventproxy = require('eventproxy'); 6 | var utility = require('utility'); 7 | var tools = require('../../common/tools'); 8 | 9 | function randomInt() { 10 | return (Math.random() * 10000).toFixed(0); 11 | } 12 | 13 | var createUser = exports.createUser = function (callback) { 14 | var key = new Date().getTime() + '_' + randomInt(); 15 | tools.bhash('pass', function (err, passhash) { 16 | User.newAndSave('alsotang' + key, 'alsotang' + key, passhash, 'alsotang' + key + '@gmail.com', '', false, callback); 17 | }); 18 | }; 19 | 20 | exports.createUserByNameAndPwd = function (loginname, pwd, callback) { 21 | tools.bhash(pwd, function (err, passhash) { 22 | User.newAndSave(loginname, loginname, passhash, loginname + +new Date() + '@gmail.com', '', true, callback); 23 | }); 24 | }; 25 | 26 | var createTopic = exports.createTopic = function (authorId, callback) { 27 | var key = new Date().getTime() + '_' + randomInt(); 28 | Topic.newAndSave('topic title' + key, 'test topic content' + key, 'share', authorId, callback); 29 | }; 30 | 31 | var createReply = exports.createReply = function (topicId, authorId, callback) { 32 | Reply.newAndSave('I am content', topicId, authorId, callback); 33 | }; 34 | 35 | function mockUser(user) { 36 | return 'mock_user=' + JSON.stringify(user) + ';'; 37 | } 38 | 39 | ready(exports); 40 | 41 | var ep = new eventproxy(); 42 | ep.fail(function (err) { 43 | console.error(err); 44 | }); 45 | 46 | ep.all('user', 'user2', 'admin', function (user, user2, admin) { 47 | exports.normalUser = user; 48 | exports.normalUserCookie = mockUser(user); 49 | 50 | exports.normalUser2 = user2; 51 | exports.normalUser2Cookie = mockUser(user2); 52 | 53 | var adminObj = JSON.parse(JSON.stringify(admin)); 54 | adminObj.is_admin = true; 55 | exports.adminUser = admin; 56 | exports.adminUserCookie = mockUser(adminObj); 57 | 58 | createTopic(user._id, ep.done('topic')); 59 | }); 60 | createUser(ep.done('user')); 61 | createUser(ep.done('user2')); 62 | createUser(ep.done('admin')); 63 | 64 | ep.all('topic', function (topic) { 65 | exports.testTopic = topic; 66 | createReply(topic._id, exports.normalUser._id, ep.done('reply')); 67 | }); 68 | 69 | ep.all('reply', function (reply) { 70 | exports.testReply = reply; 71 | exports.ready(true); 72 | }); 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /views/_ads.html: -------------------------------------------------------------------------------- 1 | <% 2 | var ads = [ 3 | { 4 | href: '/nav.html', 5 | label: 'web navigation', 6 | img_src: 'https://user-images.githubusercontent.com/9276376/27581837-70cf9944-5b61-11e7-8d76-72cfc3856dad.png', 7 | }, 8 | { 9 | href:'http://www.react-d.com/api/', 10 | label: 'fruit-ui', 11 | img_src: 'https://cloud.githubusercontent.com/assets/9276376/18077980/92747c60-6ebb-11e6-9a6c-e65bfe42d45e.png', 12 | }, 13 | { 14 | href:'https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=m5qa9mqc&utm_source=m5qa9mqc', 15 | label: 'fruit-ui', 16 | img_src: 'https://user-images.githubusercontent.com/9276376/30099311-fd5c02f6-9317-11e7-8103-abb77c02fb40.png', 17 | }, 18 | ]; 19 | 20 | %> 21 | 32 |
33 |
34 | 35 | <% ads.forEach(function (adObj, idx) { %> 36 | <% if (idx !== 0) { %> 37 |
38 | <% } %> 39 | 43 | <% }) %> 44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /views/_sponsors.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

4 | 5 | 服务器搭建在 6 | UCloud云计算 7 | , 8 | 存储赞助商为 9 | 14 |

15 |

16 | 声明:内容均来自于网络,如有侵权行为请发送邮件至vueclub@126.com,我们将在第一时间删除 17 |

18 |
19 | -------------------------------------------------------------------------------- /views/bdunion.txt: -------------------------------------------------------------------------------- 1 | a49a794726776a7d1700d152d88cc208 -------------------------------------------------------------------------------- /views/editor_sidebar.html: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | <%- partial('sidebar') %> 2 | 3 |
4 |
5 |
6 | <% [['all', '全部'], ['good', '精华']].concat(tabs).forEach(function (pair) { 7 | var value = pair[0]; 8 | var text = pair[1]; %> 9 | <%= text %> 11 | <% }) %> 12 |
13 | <% if (typeof(topics) !== 'undefined' && topics.length > 0) { %> 14 |
15 | <%- partial('topic/list', { 16 | topics: topics, 17 | pages: pages, 18 | current_page: current_page, 19 | base: '/' 20 | }) %> 21 |
22 | <% } else { %> 23 |
24 |

无话题

25 |
26 | <% } %> 27 |
28 |
29 | -------------------------------------------------------------------------------- /views/message/index.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 | <% if (typeof(hasnot_read_messages) !== 'undefined' && hasnot_read_messages.length > 0) { %> 12 | <%- partial('../message/message', { collection: hasnot_read_messages, as: 'message' }) %> 13 | <% } else { %> 14 |
15 |

无消息

16 |
17 | <% } %> 18 |
19 |
20 |
21 | 过往信息 22 |
23 | <% if (typeof(has_read_messages) !== 'undefined' && has_read_messages.length > 0) { %> 24 | <%- partial('../message/message', { collection: has_read_messages, as: 'message' }) %> 25 | <% } else { %> 26 |
27 |

无消息

28 |
29 | <% } %> 30 |
31 |
32 | -------------------------------------------------------------------------------- /views/message/message.html: -------------------------------------------------------------------------------- 1 | <% if (message.has_read) { %> 2 |
3 | <% } else { %> 4 |
5 | <% } %> 6 | <% if(message.type == 'reply'){ %> 7 | 8 | <%= message.author.loginname %> 9 | 回复了你的话题 10 | <%= 11 | message.topic.title %> 12 | 13 | <% } %> 14 | <% if(message.type == 'reply2'){ %> 15 | 16 | <%= message.author.loginname %> 17 | 在话题 18 | <%= 19 | message.topic.title %> 20 | 中回复了你的回复 21 | 22 | <% } %> 23 | <% if(message.type == 'follow'){ %> 24 | 25 | <%= message.author.loginname %> 26 | 关注了你 27 | 28 | <% } %> 29 | <% if (message.type == 'at'){ %> 30 | 31 | <%= message.author.loginname %> 32 | 在话题 33 | <%= 34 | message.topic.title %> 35 | 中@了你 36 | 37 | <% } %> 38 | 39 | <% if (message.has_read) { %> 40 | 42 | <% } else { %> 43 | 44 | 48 | 49 | <% } %> 50 |
51 | -------------------------------------------------------------------------------- /views/notify/notify.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if(typeof(error) !== 'undefined' && error){ %> 13 |
14 | <%= error %> 15 |
16 | <% } %> 17 | <% if(typeof(success) !== 'undefined' && success){ %> 18 |
19 | <%= success %> 20 |
21 | <% } %> 22 | 23 | 返回 24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /views/reply/edit.html: -------------------------------------------------------------------------------- 1 | <%- partial('../editor_sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if(typeof(edit_error) !== 'undefined' && edit_error){ %> 13 |
14 | × 15 | <%= edit_error %> 16 |
17 | <% } %> 18 | <% if(typeof(error) !== 'undefined' && error){ %> 19 |
20 | <%= error %> 21 |
22 | <% }else{ %> 23 |
24 |
25 |
26 |
27 | 31 | 32 |
33 | 35 |
36 |
37 | 38 |
39 | 40 | 41 |
42 |
43 |
44 | <% } %> 45 |
46 |
47 | 48 | 49 | <%- Loader('/public/editor.min.js') 50 | .js('/public/libs/editor/editor.js') 51 | .js('/public/libs/webuploader/webuploader.withoutimage.js') 52 | .js('/public/libs/editor/ext.js') 53 | .done(assets, config.site_static_host, config.mini_assets) 54 | %> 55 | 61 | -------------------------------------------------------------------------------- /views/reply/reply.html: -------------------------------------------------------------------------------- 1 |
' 3 | id="reply<%= indexInCollection+1 %>" reply_id="<%= reply._id %>" reply_to_id="<%= reply.reply_id || '' %>"> 4 | 5 | 6 |
7 | 8 | 9 | 10 | 16 |
17 | 18 | 21 | 22 | <%= reply.ups && reply.ups.length ? reply.ups.length : '' %> 23 | 24 | 25 | <% if ( typeof(current_user) !== 'undefined' && current_user.is_admin || 26 | (typeof(current_user) !== 'undefined' && current_user._id.toString() == reply.author._id.toString()) 27 | ) { %> 28 | 29 | 30 | 31 | 32 | 33 | 34 | <% } %> 35 | 36 | <% if(typeof(current_user) !== 'undefined'){ %> 37 | 38 | <% } %> 39 | 40 |
41 |
42 |
43 | <%- markdown(reply.content) %> 44 |
45 |
46 |
47 | <% if (typeof(current_user) !== 'undefined') { %> 48 |
49 | 50 | 51 | 52 |
53 |
54 | 56 | 57 |
58 | 60 |
61 |
62 | 63 |
64 | 65 |
66 | <% } %> 67 |
68 |
69 |
70 | -------------------------------------------------------------------------------- /views/sign/new_oauth.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 8 |
9 |
10 |
method='post'> 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
method='post'> 26 |
27 | 28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 | 38 | 39 |
40 | 41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /views/sign/no_github_email.html: -------------------------------------------------------------------------------- 1 | GitHub 登陆出错 2 | 3 |
    4 |
  • 您 GitHub 账号的 Email 与之前在 Vueclub 注册的 Email 重复了。
  • 5 |
  • 6 |

    也可能是您的 GitHub 没有提供公开的 Profile Email 导致注册失败。

    7 | 8 |

    请访问:https://github.com/settings/profile 设置您的公开 Email 地址。请确保您 GitHub 的个人主页有可见的 Email。 9 | 10 |

    11 |
  • 12 |
13 | -------------------------------------------------------------------------------- /views/sign/reset.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sign/sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if(typeof(error) !== 'undefined' && error){ %> 13 |
14 | × 15 | <%= error %> 16 |
17 | <% } %> 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 | 29 |
30 | 31 |
32 |
33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /views/sign/search_pass.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sign/sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if (typeof(error) !== 'undefined' && error) { %> 13 |
14 | × 15 | <%= error %> 16 |
17 | <% } %> 18 |
19 |
20 | 21 | 22 |
23 | <% if (typeof(email) !== 'undefined') { %> 24 | 25 | <% } else { %> 26 | 27 | <% } %> 28 |

请输入您注册帐户时使用的电子邮箱

29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /views/sign/sidebar.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/sign/signin.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sign/sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if(typeof(error) !== 'undefined' && error){ %> 13 |
14 | × 15 | <%= error %> 16 |
17 | <% } %> 18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 | 29 |
30 | 31 |
32 |
33 | 34 | 35 | 44 |
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /views/sign/signup.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sign/sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 | <% if (typeof(error) !== 'undefined' && error) { %> 13 |
14 | × 15 | <%= error %> 16 |
17 | <% } %> 18 | <% if (typeof(success) !== 'undefined' && success) { %> 19 |
20 | <%= success %> 21 |
22 | <% } else { %> 23 |
24 |
25 | 26 | 27 |
28 | <% if (typeof(loginname) !== 'undefined') { %> 29 | 30 | <% } else { %> 31 | 32 | <% } %> 33 |
34 |
35 |
36 | 37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 | 51 | 52 |
53 | <% if (typeof(email) !== 'undefined') { %> 54 | 55 | <% } else { %> 56 | 57 | <% } %> 58 |
59 |
60 | 61 | 62 | 70 |
71 | <% } %> 72 |
73 |
74 |
75 | -------------------------------------------------------------------------------- /views/static/about.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 |
13 | <%- markdown(multiline(function () { 14 | /* 15 | ### 关于 16 | 17 | Vue 中文社区是全球最大的 Vueframework 中文开源技术社区,致力于 Vue 在中国的学习、推广、研究工作。 18 | 19 | Vue 中文社区正在努力建设中,如果你有什么想法,欢迎[吐槽](http://www.vue-js.com/?tab=bb)。 20 | 21 | 22 | vueJs社区交流讨论2 23 | 24 | 群二维码 25 | 26 | 尽管建了QQ群,但是还是希望大家尽量有问题在社区讨论,这样才可以有一些沉淀和积累,不至于每天都有人在问同样的问题,通过对问题的总结和用文字表达的过程,自己也可以对知识进行一个梳理。 27 | 28 | 29 | 30 | ### 移动客户端 31 | 32 | 客户端目前正在加急开发中...静请期待 33 | 34 | 35 | */ 36 | })) %> 37 |
38 |
39 |
40 |
41 | -------------------------------------------------------------------------------- /views/static/faq.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
4 |
5 |
6 | 10 |
11 |
12 |

FAQ

13 |
14 |
15 |

A:Ionichina 社区和 Node Club 是什么关系?

16 | 17 |

Q:Node Club 是一个用 Node.js 和 MongoDB 开发的开源社区软件,Ionichina 是基于 Node Club 的 Node.js 中文技术社区。

18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /views/static/timeline.html: -------------------------------------------------------------------------------- 1 | <%- partial( '../sidebar') %> 2 | 3 | 4 |
5 |
6 |
7 | 13 |
14 | 15 |
16 | 17 |

Ionichina中文社区成长时间线

18 |
19 |
20 |
    21 |

    22 | 2015年 23 |

    24 | 25 |
  • 26 |

    04.11 27 | 2015 28 |

    29 |
    30 |
    服务器及数据迁移到 Ucloud 云服务器上 31 |
    32 |
    33 |
  • 34 | 35 |
  • 36 |

    03.27 37 | 2015 38 |

    39 |
    40 |
    得到Ucloud云服务器赞助 41 |
    42 |
    43 |
  • 44 | 45 | 46 |
  • 47 |

    03.02 48 | 2015 49 |

    50 |
    51 |
    得到时尚时尚最时尚的七牛云存储赞助 52 | 将会把静态文件全部迁移到七牛,来加快访问速度 53 |
    54 |
    55 |
  • 56 | 57 |
  • 58 |

    02.27 59 | 2015 60 |

    61 |
    62 |
    63 | 1.启用正式域名http://ionichina.com 64 |
    65 |
    2.增加 Ionichina 中文社区成长时间线 66 | 67 | 说明:直接拿得 Golang社区 的时间线,求一前端大神给做一个自己的 68 | 69 |
    3.新建了一个QQ交流群: 127395584 70 | 请将得到解决的问题在社区中分享给更多地人 71 |
    72 |
    73 |
  • 74 | 75 | 76 | 77 |
  • 78 |

    02.14 79 | 2015 80 |

    81 |
    82 |
    Ionichina中文社区启动 83 | 网址:http://openionic.com 84 | 85 | 说明:直接拿nodeclub搭建的社区,fork了一份儿放在了这里 86 | 87 |
    88 |
    89 |
  • 90 | 91 | 92 | 93 | 94 | 95 |
96 |
97 |
98 | 99 |
100 | 101 | 102 |
103 |
104 | -------------------------------------------------------------------------------- /views/topic/_top_good.html: -------------------------------------------------------------------------------- 1 | <% if (topic.top) { %> 2 | 置顶 3 | <% } else if (topic.good) { %> 4 | 精华 5 | <% } else if (typeof(tab) !== 'undefined' && tab === 'all' && topic.tabName) { %> 6 | <%= topic.tabName %> 7 | <% } %> 8 | -------------------------------------------------------------------------------- /views/topic/abstract.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | <%= topic.reply_count %> 12 | 13 | / 14 | 15 | <%= topic.visit_count %> 16 | 17 | 18 | 19 | <% if (topic.reply) {%> 20 | 21 | 22 | <%= topic.reply.friendly_create_at %> 23 | 24 | <% } %> 25 | <% if (!topic.reply) {%> 26 | 27 | <%= topic.friendly_create_at %> 28 | 29 | <% } %> 30 | 31 |
32 | 33 | <%- partial('./_top_good', {topic: topic}) %> 34 | 35 | 36 | <%= topic.title %> 37 | 38 |
39 |
40 | -------------------------------------------------------------------------------- /views/topic/list.html: -------------------------------------------------------------------------------- 1 |
2 | <%- partial('../topic/abstract', {collection:topics, as:'topic'}) %> 3 |
4 | 42 | 58 | -------------------------------------------------------------------------------- /views/topic/small.html: -------------------------------------------------------------------------------- 1 |
  • 2 | 4 |
  • 5 | -------------------------------------------------------------------------------- /views/user/card.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 | 6 | <%= user.loginname %> 7 | 8 |
    9 |
    10 | 积分: <%= user.score %> 11 |
    12 |
    13 |
    14 | 15 | “ 16 | <% if (user.signature) {%> 17 | <%-escapeSignature(user.signature)%> 18 | <%} else {%> 19 | 这家伙很懒,什么个性签名都没有留下。 20 | <%}%> 21 | ” 22 | 23 |
    24 |
    25 | 26 | <% if (typeof(current_user) !== 'undefined') { %> 27 | 52 | <% } %> 53 | -------------------------------------------------------------------------------- /views/user/collect_topics.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 | <% if (topics.length > 0) { %> 13 | <%- partial('../topic/list', { topics: topics, pages: pages, current_pages: current_page, base: '/user/' + 14 | user.loginname + '/collections' }) %> 15 | <% } else { %> 16 |

    找不到话题 (T_T)

    17 | <% } %> 18 |
    19 |
    20 |
    21 | -------------------------------------------------------------------------------- /views/user/followers.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 | <% if (users.length > 0) { %> 13 | <%- partial('../user/user', { collection: users, as: 'user' }) %> 14 | <% } else { %> 15 |

    还没有任何人关注他

    16 | <% } %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /views/user/followings.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 | <% if (users.length > 0) { %> 13 | <%- partial('../user/user', { collection: users, as: 'user' }) %> 14 | <% } else { %> 15 |

    没有关注任何人

    16 | <% } %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /views/user/replies.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 |
    13 |
    <%= user.loginname %> 参与的话题
    14 |
    15 | <% if(typeof(topics) !== 'undefined' && topics.length > 0){ %> 16 | <%- partial('../topic/list', 17 | {topics:topics,pages:pages,current_pages:current_page,base:'/user/'+user.loginname+'/replies'}) %> 18 | <% }else{ %> 19 |
    20 |

    无话题

    21 |
    22 | <% } %> 23 |
    24 |
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /views/user/star.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 | 4 | <%= user.loginname %> 5 | <%= user.follower_count %> 粉丝 6 | <%= user.following_count %> 关注 7 |
    8 |
  • 9 | -------------------------------------------------------------------------------- /views/user/stars.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 | <% if(typeof(stars) !== 'undefined' && stars.length > 0){ %> 13 | <%- partial('../user/user',{collection:stars,as:'user'}) %> 14 | <% }else{ %> 15 |

    还没有社区达人

    16 | <% } %> 17 |
    18 |
    19 |
    20 | -------------------------------------------------------------------------------- /views/user/top.html: -------------------------------------------------------------------------------- 1 |
  • 2 | <%= user.score %> 3 | <%= user.loginname %> 4 |
  • 5 | -------------------------------------------------------------------------------- /views/user/top100.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 | <% if(typeof(users) !== 'undefined' && users.length > 0){ %> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | <%- partial('../user/top100_user',{collection:users,as:'user'}) %> 23 | 24 |
    #用户名积分主题数评论数
    25 | <% }else{ %> 26 |

    还没有用户

    27 | <% } %> 28 |
    29 |
    30 |
    31 | -------------------------------------------------------------------------------- /views/user/top100_user.html: -------------------------------------------------------------------------------- 1 | 2 | <%= indexInCollection+1 %> 3 | 4 | 5 | 6 | 7 | 8 | <%= user.loginname %> 9 | <%= user.score %> 10 | <%= user.topic_count %> 11 | <%= user.reply_count %> 12 | 13 | -------------------------------------------------------------------------------- /views/user/topics.html: -------------------------------------------------------------------------------- 1 | <%- partial('../sidebar') %> 2 | 3 |
    4 |
    5 |
    6 | 10 |
    11 |
    12 |
    13 |
    <%=user.loginname%> 创建的话题
    14 |
    15 | <% if(typeof(topics) !== 'undefined' && topics.length > 0 ){ %> 16 | <%- partial('../topic/list', 17 | {topics:topics,pages:pages,current_pages:current_page,base:'/user/'+user.loginname+'/topics'}) %> 18 | <% }else{ %> 19 |
    20 |

    无话题

    21 |
    22 | <% } %> 23 |
    24 |
    25 |
    26 | 27 | -------------------------------------------------------------------------------- /views/user/user.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 | 6 | <%= user.loginname %> 7 | 8 |
    9 | <%= user.follower_count %> 粉丝 10 | 11 | <%= user.following_count %> 关注 12 |
    13 |
    14 | 15 | 16 | 17 | 18 | 19 | <% if (user.url) { %> 20 | 21 | 22 | 23 | 24 | 25 | <% } %> 26 | <% if (user.weibo) { %> 27 | 28 | 29 | 30 | 31 | 32 | <% } %> 33 |
    34 |
    35 | --------------------------------------------------------------------------------