├── .bowerrc ├── .gitignore ├── README.md ├── README_CN.md ├── app ├── __init__.py ├── base │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── appbase.html │ │ ├── base.html │ │ └── modals.html │ └── urlmap.py ├── chat │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── chat.html │ │ ├── message-chat.html │ │ ├── message-mine.html │ │ └── message-others.html │ └── urlmap.py ├── community │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── community.html │ │ ├── node-avatar-edit-template.html │ │ ├── node-description-edit-template.html │ │ ├── node-description.html │ │ ├── node-one.html │ │ ├── topic-comment-list-item.html │ │ ├── topic-comment-list.html │ │ ├── topic-list-item.html │ │ ├── topic-list.html │ │ ├── topic-new.html │ │ └── topic-one.html │ └── urlmap.py ├── home │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── friend-recommend.html │ │ ├── friend │ │ │ └── friend.html │ │ ├── home.html │ │ ├── league │ │ │ ├── league-member-list.html │ │ │ └── league-member.html │ │ ├── message │ │ │ ├── message-chat-message-list-item.html │ │ │ ├── message-chat-message-list.html │ │ │ ├── message-comment-and-reply-list-item.html │ │ │ ├── message-comment-and-reply-list.html │ │ │ ├── message-friend-request-list-item.html │ │ │ ├── message-friend-request-list.html │ │ │ ├── message-friends-dynamic-list-item.html │ │ │ ├── message-friends-dynamic-list.html │ │ │ ├── message-like-list-item.html │ │ │ ├── message-like-list.html │ │ │ └── message.html │ │ ├── sidebar-left.html │ │ ├── sidebar-right.html │ │ └── status │ │ │ ├── status-comment-list-item.html │ │ │ ├── status-comment-list.html │ │ │ ├── status-list-item.html │ │ │ ├── status-list.html │ │ │ └── status.html │ └── urlmap.py ├── message │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── message-header.html │ │ └── message.html │ └── urlmap.py ├── profile │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler │ │ ├── __init__.py │ │ ├── leavemessage.py │ │ ├── profile.py │ │ ├── status.py │ │ └── topic.py │ ├── setting.py │ ├── template │ │ ├── leavemessage │ │ │ ├── leavemessage-list-item.html │ │ │ ├── leavemessage-list.html │ │ │ └── leavemessage.html │ │ ├── profile-comment-list.html │ │ ├── profile-league-member.html │ │ ├── profile-visitor.html │ │ ├── profile.html │ │ ├── status │ │ │ ├── friend-recommend.html │ │ │ ├── status-list-item.html │ │ │ ├── status-list.html │ │ │ ├── status-sidebar.html │ │ │ └── status.html │ │ └── topic │ │ │ ├── topic-main.html │ │ │ ├── topic-sidebar.html │ │ │ └── topic.html │ └── urlmap.py ├── search │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ └── urlmap.py ├── setting │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── setting-avatar.html │ │ ├── setting-notification.html │ │ ├── setting-password.html │ │ ├── setting-private.html │ │ ├── setting-profile-cover.html │ │ ├── setting-profile.html │ │ ├── setting-theme.html │ │ └── setting.html │ └── urlmap.py ├── share │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ │ ├── share-category.html │ │ ├── share-comment-list-item.html │ │ ├── share-comment-list.html │ │ ├── share-new.html │ │ ├── share-one.html │ │ ├── share-sidebar.html │ │ └── share.html │ └── urlmap.py └── user │ ├── __init__.py │ ├── document.py │ ├── form.py │ ├── handler.py │ ├── setting.py │ ├── template │ ├── feedback.html │ ├── login.html │ ├── password-reset.html │ └── register.html │ └── urlmap.py ├── bower.json ├── fabfile.py ├── gulpfile.js ├── lib ├── __init__.py ├── message │ ├── __init__.py │ ├── browser.py │ ├── handler.py │ ├── topic.py │ └── writer.py └── xmpp │ ├── __init__.py │ ├── browser.py │ ├── client.py │ └── ejabberd.py ├── package.json ├── requirements.txt ├── scripts ├── init_db.py ├── install.sh └── setup_mail.sh ├── server.py ├── static ├── app │ ├── base │ │ ├── css │ │ │ ├── base.css │ │ │ └── theme │ │ │ │ ├── black.css │ │ │ │ └── default.css │ │ └── js │ │ │ ├── appbase.js │ │ │ └── base.js │ ├── chat │ │ ├── css │ │ │ └── chat.css │ │ ├── img │ │ │ ├── chat-header-bg.png │ │ │ └── spinner.gif │ │ └── js │ │ │ └── chat-panel.js │ ├── community │ │ ├── css │ │ │ ├── community.css │ │ │ ├── node-one.css │ │ │ ├── topic-new.css │ │ │ └── topic-one.css │ │ └── js │ │ │ ├── community.js │ │ │ ├── community_base.js │ │ │ ├── node-one.js │ │ │ ├── topic-new.js │ │ │ └── topic-one.js │ ├── home │ │ ├── css │ │ │ ├── friends.css │ │ │ ├── home.css │ │ │ ├── message.css │ │ │ └── status.css │ │ └── js │ │ │ ├── friends.js │ │ │ ├── home.js │ │ │ ├── league.js │ │ │ ├── message.js │ │ │ ├── status.js │ │ │ └── status_.js │ ├── message │ │ └── js │ │ │ └── message.js │ ├── profile │ │ ├── css │ │ │ ├── profile.css │ │ │ ├── status.css │ │ │ └── topic.css │ │ └── js │ │ │ ├── leave_message.js │ │ │ ├── profile.js │ │ │ ├── status.js │ │ │ └── topic.js │ ├── search │ │ ├── css │ │ │ └── search.css │ │ └── js │ │ │ └── search.js │ ├── setting │ │ ├── css │ │ │ ├── setting-avatar.css │ │ │ ├── setting-notification.css │ │ │ ├── setting-password.css │ │ │ ├── setting-private.css │ │ │ ├── setting-profile-cover.css │ │ │ ├── setting-profile.css │ │ │ ├── setting-theme.css │ │ │ └── setting.css │ │ └── js │ │ │ ├── setting-avatar.js │ │ │ ├── setting-notification.js │ │ │ ├── setting-password.js │ │ │ ├── setting-private.js │ │ │ ├── setting-profile-cover.js │ │ │ ├── setting-profile.js │ │ │ └── setting-theme.js │ ├── share │ │ ├── css │ │ │ ├── share-base.css │ │ │ ├── share-category.css │ │ │ ├── share-one.css │ │ │ └── share.css │ │ ├── img │ │ │ ├── category │ │ │ │ ├── bittorrent.png │ │ │ │ ├── book.png │ │ │ │ ├── course.png │ │ │ │ ├── document.png │ │ │ │ ├── homework.png │ │ │ │ ├── music.png │ │ │ │ ├── music1.png │ │ │ │ ├── music2.png │ │ │ │ ├── note.png │ │ │ │ ├── notebook.png │ │ │ │ ├── paper.png │ │ │ │ ├── picture.png │ │ │ │ ├── software.png │ │ │ │ └── video.png │ │ │ └── icons │ │ │ │ ├── accdb.png │ │ │ │ ├── avi.png │ │ │ │ ├── bmp.png │ │ │ │ ├── css.png │ │ │ │ ├── doc.png │ │ │ │ ├── docx.png │ │ │ │ ├── eml.png │ │ │ │ ├── eps.png │ │ │ │ ├── fla.png │ │ │ │ ├── gif.png │ │ │ │ ├── html.png │ │ │ │ ├── ind.png │ │ │ │ ├── ini.png │ │ │ │ ├── jpg.png │ │ │ │ ├── jsf.png │ │ │ │ ├── midi.png │ │ │ │ ├── mov.png │ │ │ │ ├── mp3.png │ │ │ │ ├── mpeg.png │ │ │ │ ├── pdf.png │ │ │ │ ├── png.png │ │ │ │ ├── ppt.png │ │ │ │ ├── pptx.png │ │ │ │ ├── proj.png │ │ │ │ ├── psd.png │ │ │ │ ├── pst.png │ │ │ │ ├── pub.png │ │ │ │ ├── rar.png │ │ │ │ ├── readme.png │ │ │ │ ├── settings.png │ │ │ │ ├── tiff.png │ │ │ │ ├── txt.png │ │ │ │ ├── url.png │ │ │ │ ├── vsd.png │ │ │ │ ├── wav.png │ │ │ │ ├── wma.png │ │ │ │ ├── wmv.png │ │ │ │ └── zip.png │ │ └── js │ │ │ ├── share-base.js │ │ │ ├── share-category.js │ │ │ ├── share-new.js │ │ │ ├── share-one.js │ │ │ ├── share.js │ │ │ └── share_.js │ └── user │ │ ├── css │ │ ├── login.css │ │ └── password-reset.css │ │ ├── img │ │ ├── 1.png │ │ └── 2.png │ │ └── js │ │ ├── login.js │ │ └── password-reset.js ├── favicon.ico ├── icons │ ├── girl.png │ ├── loading.gif │ ├── man.png │ └── right.png └── img │ ├── black-bg.gif │ ├── black-bg.jpg │ ├── coin.png │ ├── default.jpg │ ├── landing.jpg │ ├── logo.png │ ├── node.png │ ├── profile-cover │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 20.jpg │ ├── 21.jpg │ ├── 22.jpg │ ├── 23.jpg │ ├── 24.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg │ └── white-bg.png └── young ├── __init__.py ├── handler.py ├── setting.py └── urlmap.py /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "static/plugin" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.dat 3 | *.rdb 4 | *.diskqueue.meta.dat 5 | *.swp 6 | *.tmp 7 | *.log 8 | *.timestamp 9 | .DS_Store 10 | nohup.out 11 | chat-message* 12 | chat_message_new* 13 | node_modules/ 14 | static/plugin 15 | static/dist 16 | templates/ 17 | envs/ 18 | workspace/ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | A Full-featured forum software built with love by [Lime](http://lime66.com) in 4 | [Python](https://www.python.org/). 5 | 6 | [中文README](https://github.com/shiyanhui/Young/blob/master/README_CN.md) 7 | 8 | ## Features: 9 | 10 | - Classified topics 11 | - Anonymity Support 12 | - Social Network (tweet, friends etc.) 13 | - IM Chat 14 | - Real-time Notification 15 | - Resource Share 16 | 17 | ## Screenshots 18 | 19 | 20 | 21 | ## Installation 22 | 23 | On Unbuntu 16.04: 24 | 25 | git clone https://github.com/shiyanhui/Young.git 26 | cd Young && ./scripts/install.sh 27 | 28 | Then set your mongodb environment: 29 | 30 | 1. open /etc/mongod.conf, add 31 | 32 | replication: 33 | replSetName: rs0 34 | 35 | 2. restart mongodb 36 | 37 | service mongod restart 38 | 39 | 3. enter mongo client and execute 40 | 41 | mogno 42 | rs.initiate() 43 | 44 | The next step you should initialize the database. 45 | 46 | fab init 47 | 48 | If you want to set up your own mail server, execute **setup_mail.sh**, 49 | which will install postfix. 50 | 51 | ./scripts/setup_mail.sh 52 | 53 | **NOTE**: 54 | 55 | **scripts/install.sh** is only tested on Ubuntu-16.04, so on other 56 | platform you may install manually. Just do as **scripts/install.sh** do step 57 | by step. 58 | 59 | ## Requirements 60 | 61 | - Mongodb >= 2.6 62 | - Ejabberd >= 16.08 63 | - NSQ >= 0.3.8 64 | - Elasticsearch >= 2.3.5 65 | - NodeJS >= 4.0 66 | 67 | ## Development 68 | 69 | - you should start all required services before you run it. 70 | 71 | ```bash 72 | fab start_service 73 | ``` 74 | 75 | - build the resource. 76 | 77 | ```bash 78 | fab build 79 | ``` 80 | 81 | - run it locally. 82 | 83 | ```bash 84 | # debug mode is close by default, it will automatically build before run 85 | fab run 86 | 87 | # run it in debug mode 88 | fab run:debug=true 89 | ``` 90 | 91 | ## License 92 | 93 | Young is licensed under the [GNU Affero General Public License v3 (AGPL-3)](https://www.gnu.org/licenses/agpl-3.0.html). 94 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Young是一个用Python写的功能丰富的、界面小清新的类似NodeBB的社区软件。 4 | 5 | ## Features: 6 | 7 | - 话题按主题分类,你可以查看某一主题的话题 8 | - 支持匿名发帖,匿名评论 9 | - 社交功能(朋友圈,发状态) 10 | - 像QQ一样即时聊天 11 | - 实时提醒 12 | - 资源分享 13 | 14 | ## Screenshots 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ## Installation 25 | 26 | 在Unbuntu 16.04上 27 | 28 | git clone https://github.com/shiyanhui/Young.git 29 | cd Young && ./scripts/install.sh 30 | 31 | 然后设置你的Mongodb环境 32 | 33 | 1. 修改/etc/mongod.conf,添加 34 | 35 | replication: 36 | replSetName: rs0 37 | 38 | 2. 重启Mongodb服务 39 | 40 | service mongod restart 41 | 42 | 3. 启动mongo,执行initiate 43 | 44 | mongo 45 | rs.initiate() 46 | 47 | 下一步需要做的是初始化Mongodb数据库 48 | 49 | fab init 50 | 51 | 如果你想自己搭建Email服务器,运行setup_mail.sh脚本 52 | 53 | ./scripts/setup_mail.sh 54 | 55 | **注意**: 56 | 57 | **scripts/install.sh** 只在Ubuntu16.04上面测试过,如果你是Ubuntu其他版本或者其他 58 | 操作系统,你需要手动安装。**scripts/install.sh** 稍微修改一下,一步一步安装即可。 59 | 60 | ## Requirements 61 | 62 | - Mongodb >= 2.6 63 | - Ejabberd >= 16.08 64 | - NSQ >= 0.3.8 65 | - Elasticsearch >= 2.3.5 66 | - NodeJS >= 4.0 67 | 68 | ## Development 69 | 70 | - 在运行之前必须启动所有依赖的服务 71 | 72 | ```bash 73 | fab start_service 74 | ``` 75 | 76 | - 在非debug模式中,每次修改后,需要重建资源 77 | 78 | ```bash 79 | fab build 80 | ``` 81 | 82 | - 本地运行 83 | 84 | ```bash 85 | # 默认为非debug模式,run之前会自动地build 86 | fab run 87 | 88 | # 启用debug模式 89 | fab run:debug=true 90 | ``` 91 | 92 | ## License 93 | 94 | [GNU Affero General Public License v3 (AGPL-3)](https://www.gnu.org/licenses/agpl-3.0.html). 95 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/__init__.py -------------------------------------------------------------------------------- /app/base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/base/__init__.py -------------------------------------------------------------------------------- /app/base/form.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/base/form.py -------------------------------------------------------------------------------- /app/base/handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | from bson.dbref import DBRef 6 | from bson.objectid import ObjectId 7 | from tornado import gen 8 | from tornado.web import authenticated, HTTPError 9 | 10 | from young.handler import BaseHandler 11 | from app.user.document import UserDocument 12 | from app.base.document import ImageDocument 13 | 14 | __all__ = ['ImageUploadHandler', 'ImageStaticFileHandler'] 15 | 16 | 17 | class ImageUploadHandler(BaseHandler): 18 | @authenticated 19 | @gen.coroutine 20 | def post(self): 21 | if 'files[]' not in self.request.files: 22 | raise HTTPError(404) 23 | 24 | uploaded_file = self.request.files['files[]'][0] 25 | uploader = DBRef( 26 | UserDocument.meta['collection'], ObjectId(self.current_user['_id']) 27 | ) 28 | upload_time = datetime.now() 29 | 30 | image_id = yield ImageDocument.insert_one( 31 | uploaded_file, uploader=uploader, upload_time=upload_time 32 | ) 33 | 34 | response_data = { 35 | 'files': [{ 36 | 'name': "", 37 | 'size': 10, 38 | 'url': '/image/%s' % image_id, 39 | 'thumbnail_url': '/image/%s' % image_id, 40 | 'delete_url': '/image/%s' % image_id, 41 | 'delete_type': 'POST' 42 | }] 43 | } 44 | 45 | self.write_json(response_data) 46 | 47 | 48 | class ImageStaticFileHandler(BaseHandler): 49 | @gen.coroutine 50 | def get(self, image_id, thumbnail=None): 51 | image = yield ImageDocument.find_one({'_id': ObjectId(image_id)}) 52 | self.set_header( 53 | 'Content-Type', ('image/%s' % image['content_type']).lower()) 54 | 55 | content = image['body'] 56 | if thumbnail and 'thumbnail' in image: 57 | content = image['thumbnail'] 58 | 59 | self.finish(str(content)) 60 | -------------------------------------------------------------------------------- /app/base/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | WEALTH_SETTINGS = { 4 | 'like': 1, 5 | 'topic_new': 3 6 | } 7 | -------------------------------------------------------------------------------- /app/base/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.base.handler import ImageUploadHandler, ImageStaticFileHandler 4 | 5 | urlpattern = ( 6 | (r'/image/upload/?', ImageUploadHandler), 7 | (r'/image/([a-f0-9]{24})/?(thumbnail)?/?', ImageStaticFileHandler) 8 | ) 9 | -------------------------------------------------------------------------------- /app/chat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/chat/__init__.py -------------------------------------------------------------------------------- /app/chat/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms_tornado import Form 4 | from wtforms.fields import StringField, FloatField 5 | from wtforms.validators import InputRequired, Length, Regexp 6 | 7 | __all__ = ['MessageNewForm', 'MessageUpdateForm', 'MessageHistoryForm'] 8 | 9 | 10 | class MessageNewForm(Form): 11 | body = StringField(validators=[ 12 | InputRequired(message='消息不能为空!'), 13 | Length(max=1000, message='消息太长了!') 14 | ]) 15 | 16 | chat_with = StringField(validators=[ 17 | InputRequired(message='请设置交谈对象!'), 18 | Regexp('^[a-f0-9]{24}$', message='交谈对象错误!') 19 | ]) 20 | 21 | 22 | class MessageUpdateForm(Form): 23 | chat_with = StringField(validators=[ 24 | InputRequired(), Regexp('^[a-f0-9]{24}$') 25 | ]) 26 | 27 | 28 | class MessageHistoryForm(Form): 29 | chat_with = StringField(validators=[ 30 | InputRequired(), Regexp('^[a-f0-9]{24}$') 31 | ]) 32 | since = FloatField(validators=[InputRequired()]) 33 | -------------------------------------------------------------------------------- /app/chat/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 获取历史消息, 每次获取多少条 4 | history_messages_number_per_time = 20 5 | -------------------------------------------------------------------------------- /app/chat/template/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |   11 | 12 | 13 |
14 |
15 |
16 | 下拉查看历史消息 17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/chat/template/message-chat.html: -------------------------------------------------------------------------------- 1 | {% set date_show={} %} 2 | {% for message in history_messages %} 3 | {% set date = message['send_time'].strftime('%Y-%m-%d') %} 4 | {% if not date_show.get(date, False) %} 5 | {{ date_show.update({date: True}) }} 6 | 7 | 8 |
9 | {{ date }} 10 |
11 | 12 | 13 | {% end %} 14 | {% if str(message['sender'].id) == str(current_user['_id']) %} 15 | {% include 'message-mine.html' %} 16 | {% else %} 17 | {% include 'message-others.html' %} 18 | {% end %} 19 | {% end %} 20 | -------------------------------------------------------------------------------- /app/chat/template/message-mine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ message['body'] }} 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/chat/template/message-others.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ message['body'] }} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/chat/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.chat.handler import ( 4 | ChatWithHandler, MessageNewHandler, MessageUpdateHandler, 5 | MessageHistoryHandler 6 | ) 7 | 8 | urlpattern = ( 9 | (r'/chat/with/?', ChatWithHandler), 10 | (r'/chat/message/new/?', MessageNewHandler), 11 | (r'/chat/message/update/?', MessageUpdateHandler), 12 | (r'/chat/message/history/?', MessageHistoryHandler), 13 | ) 14 | -------------------------------------------------------------------------------- /app/community/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/community/__init__.py -------------------------------------------------------------------------------- /app/community/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | COMMUNITY_SETTINGS = { 4 | 'topic_number_per_page': 20, 5 | 'topic_comment_number_per_page': 20, 6 | 'hot_topic_number': 10, 7 | 'hot_node_number': 100 8 | } 9 | -------------------------------------------------------------------------------- /app/community/template/node-avatar-edit-template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 修改节点照片 5 |
6 |
7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /app/community/template/node-description-edit-template.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 修改节点描述 5 |
6 |
7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 | 你的修改操作将会被记录 18 |
19 |
20 | 23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /app/community/template/node-description.html: -------------------------------------------------------------------------------- 1 | {% if 'description' in current_node and current_node['description'] %} 2 | {{ current_node['description'] }} 3 | 4 | 5 | 修改 6 | 7 | {% if 'last_modified_by' in current_node %} 8 |
9 | 10 | 最后修改来自: 11 | 12 | {{ current_node['last_modified_by']['name'] }} 13 | 14 | 15 |
16 | {% end %} 17 | {% else %} 18 | 19 | 添加描述 20 | 21 | {% end %} 22 | -------------------------------------------------------------------------------- /app/community/template/topic-comment-list-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {% if not comment['anonymous'] %} 5 | 6 | 7 | 8 | {% else %} 9 | 10 | {% end %} 11 |
12 |
13 |
14 |
15 |
16 | 17 | {% if not comment['anonymous'] %} 18 | {{ comment['author']['name'] }}{% if 'replyeder' in comment and comment['replyeder'] %} 20 | 回复{{ comment['replyeder']['name'] }}{% end %}: 21 | {% else %} 22 | 匿名: 23 | {% end %} 24 | 25 | 26 | {{ handler.translate_time(comment['comment_time']) }} 27 | 28 |
29 |
30 |
31 | 32 | 33 | {% if not comment['anonymous'] and current_user and current_user['_id'] != comment['author']['_id'] %} 34 | 35 | 回复 36 | 37 | {% end %} 38 | 39 | 40 | {{ comment['floor'] }}楼 41 |
42 |
43 |
44 |
45 |
46 | {% raw comment['content'] %} 47 |
48 |
49 |
50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /app/community/template/topic-comment-list.html: -------------------------------------------------------------------------------- 1 |
2 | {% for comment in comment_list %} 3 | {% include 'topic-comment-list-item.html' %} 4 | {% end %} 5 |
6 | 7 | {% if comment_list and len(comment_list) >= COMMUNITY_SETTINGS['topic_comment_number_per_page'] %} 8 |
9 | 12 |
13 | {% end %} 14 | -------------------------------------------------------------------------------- /app/community/template/topic-list.html: -------------------------------------------------------------------------------- 1 | {% if topic_list %} 2 |
3 | {% for topic in topic_list %} 4 | {% include 'topic-list-item.html' %} 5 | {% end %} 6 |
7 | {% else %} 8 |
9 |
10 | 尚未有话题 11 |
12 |
13 | {% end %} 14 | -------------------------------------------------------------------------------- /app/community/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.community.handler import ( 4 | CommunityHandler, TopicNewHandler, 5 | TopicCommentNewHandler, TopicCommentMoreHandler, TopicLikeHandler, 6 | TopicEditHandler, NodeHandler, NodeAvatarSetHandler, 7 | NodeAvatarEditTemplateHandler, NodeDescriptionEditHandler, 8 | NodeDescriptionEditTemplateHandler, NodeSuggestionHandler, TopicHandler 9 | ) 10 | 11 | urlpattern = ( 12 | (r'/?', CommunityHandler), 13 | (r'/community/?', CommunityHandler), 14 | (r'/community/topic/new/?', TopicNewHandler), 15 | (r'/community/topic/([a-f0-9]{24})/?', TopicHandler), 16 | (r'/community/topic/comment/new/?', TopicCommentNewHandler), 17 | (r'/community/topic/comment/more/?', TopicCommentMoreHandler), 18 | (r'/community/topic/like/?', TopicLikeHandler), 19 | (r'/community/topic/edit/?', TopicEditHandler), 20 | (r'/community/node/([a-f0-9]{24})/?', NodeHandler), 21 | (r'/community/node/avatar/edit/template/?', NodeAvatarEditTemplateHandler), 22 | (r'/community/node/avatar/set/?', NodeAvatarSetHandler), 23 | (r'/community/node/description/edit/template/?', NodeDescriptionEditTemplateHandler), 24 | (r'/community/node/description/edit/?', NodeDescriptionEditHandler), 25 | (r'/community/node/suggestion/?', NodeSuggestionHandler), 26 | ) 27 | -------------------------------------------------------------------------------- /app/home/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/home/__init__.py -------------------------------------------------------------------------------- /app/home/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import IntegerField, StringField 4 | from wtforms.validators import InputRequired, Length, AnyOf, Regexp 5 | from wtforms_tornado import Form 6 | 7 | from lib.message import MessageTopic 8 | 9 | 10 | __all__ = ['StatusMoreForm', 'StatusNewForm', 'StatusCommentsForm', 11 | 'StatusCommentNewForm', 'FriendActionForm', 12 | 'StatusLikeForm', 'MessageForm', 'MessageMoreForm'] 13 | 14 | 15 | class StatusMoreForm(Form): 16 | ''' 17 | :Variables: 18 | - `page`: 当前页数 19 | ''' 20 | 21 | page = IntegerField(validators=[InputRequired()]) 22 | 23 | 24 | class StatusNewForm(Form): 25 | ''' 26 | :Variables: 27 | - `content`: 内容 28 | ''' 29 | 30 | content = StringField(validators=[ 31 | Length(max=1000, message='状态内容不能超过140字!') 32 | ]) 33 | 34 | 35 | class StatusCommentsForm(Form): 36 | ''' 37 | :Variables: 38 | - `status_id`: 状态id 39 | ''' 40 | 41 | status_id = StringField(validators=[ 42 | InputRequired(message='请指定状态'), 43 | Regexp('[0-9a-f]{24}', message='状态不存在') 44 | ]) 45 | 46 | 47 | class StatusCommentNewForm(Form): 48 | ''' 49 | :Variables: 50 | - `status_id`: 状态id 51 | - `content`: 内容 52 | ''' 53 | 54 | status_id = StringField(validators=[ 55 | InputRequired(message='请指定状态'), 56 | Regexp('[0-9a-f]{24}', message='状态不存在') 57 | ]) 58 | 59 | content = StringField(validators=[ 60 | InputRequired(message='评论内容不能为空'), 61 | Length(max=200, message="评论内容不能操作200字!") 62 | ]) 63 | 64 | replyeder_id = StringField() 65 | 66 | 67 | class StatusLikeForm(Form): 68 | status_id = StringField(validators=[ 69 | InputRequired(), 70 | Regexp('[a-f0-9]{24}') 71 | ]) 72 | 73 | 74 | class FriendActionForm(Form): 75 | friend_id = StringField(validators=[ 76 | InputRequired(message='朋友不存在'), 77 | Regexp('[0-9a-f]{24}', message='朋友不存在') 78 | ]) 79 | 80 | 81 | class MessageForm(Form): 82 | category = StringField(validators=[ 83 | InputRequired(), 84 | AnyOf([ 85 | MessageTopic._FRIENDS_DYNAMIC, 86 | MessageTopic._COMMENT_AND_REPLY, 87 | MessageTopic.LIKE, 88 | MessageTopic.AT, 89 | MessageTopic.CHAT_MESSAGE_NEW, 90 | MessageTopic.FRIEND_REQUEST_NEW 91 | ]) 92 | ]) 93 | 94 | 95 | class MessageMoreForm(Form): 96 | ''' 97 | :Variables: 98 | - `page`: 当前页数 99 | - `category`: 消息种类 100 | ''' 101 | 102 | page = IntegerField(validators=[InputRequired()]) 103 | category = StringField(validators=[ 104 | InputRequired(), 105 | AnyOf([ 106 | MessageTopic._FRIENDS_DYNAMIC, 107 | MessageTopic._COMMENT_AND_REPLY, 108 | MessageTopic.LIKE, 109 | MessageTopic.AT, 110 | MessageTopic.CHAT_MESSAGE_NEW, 111 | MessageTopic.FRIEND_REQUEST_NEW 112 | ]) 113 | ]) 114 | -------------------------------------------------------------------------------- /app/home/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | HOME_SETTINGS = { 4 | 'status_max_length': 140, 5 | 'status_number_per_page': 20, 6 | 'league_member_num_per_page': 20, 7 | 'message_number_per_page': 30, 8 | } 9 | -------------------------------------------------------------------------------- /app/home/template/friend-recommend.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 随机推荐 5 |
6 |
7 | 换一批 8 |
9 |
10 |
11 | {% if not random_user_list %} 12 |
13 | 暂无推荐 14 |
15 | {% else %} 16 |
17 | 18 | {% for user in random_user_list %} 19 | 20 | 25 | 29 | 34 | 35 | {% end %} 36 |
21 | 22 | 23 | 24 | 26 | {{ user['name'] }} 28 | 30 | 31 | 朋友 32 | 33 |
37 |
38 | {% end %} 39 |
40 | -------------------------------------------------------------------------------- /app/home/template/home.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base/template/appbase.html" %} 2 | 3 | {% block nav_collapse %} 4 | 16 | {% end %} 17 | 18 | {% block body %} 19 |
20 | {% include 'sidebar-left.html' %} 21 | {% include 'sidebar-right.html' %} 22 | 23 | {% block main_content %}{% end %} 24 |
25 | {% end %} 26 | -------------------------------------------------------------------------------- /app/home/template/league/league-member.html: -------------------------------------------------------------------------------- 1 | {% extends "../home.html" %} 2 | 3 | {% block body %} 4 |
5 | {% include "home-sidebar-left.html" %} 6 | 16 | {% include "league_member_list.html" %} 17 |
18 | {% end %} 19 | -------------------------------------------------------------------------------- /app/home/template/message/message-chat-message-list-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% if message['sender']['_id'] == current_user['_id'] %} 4 | 我对 5 | 6 | {{ message['recipient']['name'] }} 说: 7 | {% elif message['recipient']['_id'] == current_user['_id'] %} 8 | 9 | {{ message['sender']['name'] }} 对我说: 10 | {% end %} 11 | {{ message['body'] }} 12 | {{ handler.translate_time(message['send_time']) }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/home/template/message/message-chat-message-list.html: -------------------------------------------------------------------------------- 1 | {% if message_list %} 2 |
3 |
4 | 私聊信息 5 |
6 |
7 | 8 | {% for message in message_list %} 9 | {% include 'message-chat-message-list-item.html' %} 10 | {% end %} 11 |
12 |
13 |
14 | {% else %} 15 |
16 |
17 | 私聊信息 18 |
19 |
20 | 没有私聊信息 21 |
22 |
23 | {% end %} -------------------------------------------------------------------------------- /app/home/template/message/message-comment-and-reply-list.html: -------------------------------------------------------------------------------- 1 | {% if message_list %} 2 |
3 |
4 | 评论和回复 5 |
6 |
7 | 8 | {% for message in message_list %} 9 | {% include 'message-comment-and-reply-list-item.html' %} 10 | {% end %} 11 |
12 |
13 |
14 | {% else %} 15 |
16 |
17 | 评论和回复 18 |
19 |
20 | 没有评论和回复 21 |
22 |
23 | {% end %} -------------------------------------------------------------------------------- /app/home/template/message/message-friend-request-list-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ message['sender']['name'] }} 5 | 请求添加你为好友 6 | {{ handler.translate_time(message['time']) }} 7 | 8 | 9 | 同意 10 | 拒绝 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/home/template/message/message-friend-request-list.html: -------------------------------------------------------------------------------- 1 | {% if message_list %} 2 |
3 |
4 | 好友请求 5 |
6 |
7 | 8 | {% for message in message_list %} 9 | {% include 'message-friend-request-list-item.html' %} 10 | {% end %} 11 |
12 |
13 |
14 | {% else %} 15 |
16 |
17 | 好友请求 18 |
19 |
20 | 没有好友请求 21 |
22 |
23 | {% end %} -------------------------------------------------------------------------------- /app/home/template/message/message-friends-dynamic-list-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if message['message_type'] == MessageTopic.STATUS_NEW %} 5 | 6 | {{ message['sender']['name'] }} 7 | 发表了新状态: 8 | {% if message['data']['content'] %} 9 | 10 | {{ message['data']['content'] }} 11 | 12 | {% end %} 13 | {% if 'photo' in message['data'] %} 14 |
15 | 16 | 17 | 18 |
19 | {% end %} 20 | {% elif message['message_type'] == MessageTopic.TOPIC_NEW %} 21 | 22 | {{ message['sender']['name'] }} 23 | 发布了话题: 24 | 25 | {{ message['data']['title'][:20] }}{% if message['data']['title'] > 20 %}...{% end %} 26 | 27 | {% elif message['message_type'] == MessageTopic.SHARE_NEW %} 28 | 29 | {{ message['sender']['name'] }} 30 | 发布了新分享: 31 | 32 | {{ message['data']['title'] }} 33 | 34 | {% end %} 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/home/template/message/message-friends-dynamic-list.html: -------------------------------------------------------------------------------- 1 | {% if message_list %} 2 |
3 |
4 | 朋友动态 5 |
6 |
7 | 8 | {% for message in message_list %} 9 | {% include 'message-friends-dynamic-list-item.html' %} 10 | {% end %} 11 |
12 |
13 |
14 | {% else %} 15 |
16 |
17 | 朋友动态 18 |
19 |
20 | 没有朋友动态 21 |
22 |
23 | {% end %} -------------------------------------------------------------------------------- /app/home/template/message/message-like-list-item.html: -------------------------------------------------------------------------------- 1 | {% set app = message['message_type'].split(':')[-1] %} 2 | 3 | 4 | 5 | {% if app == 'status' %} 6 | 7 | 8 | {{ message['sender']['name'] }} 9 | 赞了你的状态 10 | {% if message['data']['status']['content'] %} 11 | 12 | {{ message['data']['status']['content'] }} 13 | 14 | {% end %} 15 | {% if 'photo' in message['data']['status'] %} 16 |
17 | 18 | 19 | 20 |
21 | {% end %} 22 | 23 | {% elif app == 'topic' %} 24 | 25 | 26 | {{ message['sender']['name'] }} 27 | 赞了你的话题 28 | 29 | {{ message['data']['topic']['title'][:20] }}{% if len(message['data']['topic']['title']) > 20 %}...{% end %} 30 | 31 | 32 | {% elif app == 'share' %} 33 | 34 | 35 | {{ message['sender']['name'] }} 36 | 赞了你的分享 37 | 38 | {{ message['data']['share']['title'][:20] }}{% if len(message['data']['share']['title']) > 20 %}...{% end %} 39 | 40 | 41 | {% end %} 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/home/template/message/message-like-list.html: -------------------------------------------------------------------------------- 1 | {% if message_list %} 2 |
3 |
4 | 赞 5 |
6 |
7 | 8 | {% for message in message_list %} 9 | {% include 'message-like-list-item.html' %} 10 | {% end %} 11 |
12 |
13 |
14 | {% else %} 15 |
16 |
17 | 赞 18 |
19 |
20 | 没有收到赞 21 |
22 |
23 | {% end %} -------------------------------------------------------------------------------- /app/home/template/sidebar-left.html: -------------------------------------------------------------------------------- 1 | 35 | -------------------------------------------------------------------------------- /app/home/template/sidebar-right.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/home/template/status/status-comment-list-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 6 |
7 | 8 | 9 |
10 | 11 | 12 | {{ status_comment['author']['name'] }}{% if 'replyeder' in status_comment %} 13 | 回复 14 | 15 | {{ status_comment['replyeder']['name'] }}{% end %}: 16 | 17 | {{ status_comment['content'] }} 18 | {{ handler.translate_time(status_comment['comment_time'])}} 19 |
20 | 21 | 22 | {% if status_comment['author']['_id'] != current_user['_id'] %} 23 | 30 | {% end %} 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/home/template/status/status-comment-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {% for status_comment in status_comment_list %} 5 | {% include 'status-comment-list-item.html' %} 6 | {% end %} 7 |
8 |
9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /app/home/template/status/status-list-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
8 |
9 | 10 | 11 | {{ status['author']['name'] }} 12 | 13 | 14 |
15 | {{ handler.translate_time(status['publish_time']) }} 16 |
17 |
18 |
19 |
20 |
21 | {{ status['content'] }} 22 |
23 | {% if 'photo' in status %} 24 |
25 | 26 | 27 | 28 |
29 | {% end %} 30 |
31 |
32 | 33 | {% if status['liked'] %} 34 | 36 | 赞{% if status['like_times'] > 0 %}{{ status['like_times'] }}{% end %} 37 | 38 | {% else %} 39 | 43 | 赞{% if status['like_times'] > 0 %}{{ status['like_times'] }}{% end %} 44 | 45 | {% end %} 46 | • 47 | 48 | 评论{% if status['comment_times'] > 0 %}{{ status['comment_times'] }}{% end %} 49 | 50 | 51 |
52 |
53 |
54 |
55 |
56 |
57 | -------------------------------------------------------------------------------- /app/home/template/status/status-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% if not status_list %} 4 |
5 | 你还没有发布状态 6 |
7 | {% else %} 8 | {% for status in status_list %} 9 | {% include 'status-list-item.html' %} 10 | {% end %} 11 | {% end %} 12 |
13 |
14 | 15 | {% if status_list and len(status_list) >= HOME_SETTINGS['status_number_per_page'] %} 16 |
17 | 18 |
19 | {% end %} 20 | -------------------------------------------------------------------------------- /app/home/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.home.handler import ( 4 | HomeHandler, StatusMoreHandler, FriendRecommendHandler, StatusNewHandler, 5 | StatusCommentsHandler, StatusCommentNewHandler, StatusLikeHandler, 6 | FriendsHandler, MessageHandler, MessageMoreHandler, FriendActionHandler, 7 | StatusPhotoStaticFileHandler 8 | ) 9 | 10 | urlpattern = ( 11 | (r'/home/?', HomeHandler), 12 | (r'/home/status/more/?', StatusMoreHandler), 13 | (r'/home/friend/recommend/?', FriendRecommendHandler), 14 | (r'/home/status/new/?', StatusNewHandler), 15 | (r'/home/status/comments/?', StatusCommentsHandler), 16 | (r'/home/status/comment/new/?', StatusCommentNewHandler), 17 | (r'/home/status/like/?', StatusLikeHandler), 18 | (r'/home/friends/?', FriendsHandler), 19 | (r'/home/message/?', MessageHandler), 20 | (r'/home/message/more?', MessageMoreHandler), 21 | (r'/friend/(shield|block|delete)/?', FriendActionHandler), 22 | (r'/status/photo/([a-f0-9]{24})/?(thumbnail)?/?', StatusPhotoStaticFileHandler), 23 | ) 24 | -------------------------------------------------------------------------------- /app/message/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/message/__init__.py -------------------------------------------------------------------------------- /app/message/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms_tornado import Form 4 | from wtforms.fields import IntegerField 5 | from wtforms.validators import InputRequired, NumberRange 6 | 7 | __all__ = ['MessageUpdaterForm'] 8 | 9 | 10 | class MessageUpdaterForm(Form): 11 | n = IntegerField(validators=[InputRequired(), NumberRange(min=1)]) 12 | -------------------------------------------------------------------------------- /app/message/setting.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/message/setting.py -------------------------------------------------------------------------------- /app/message/template/message-header.html: -------------------------------------------------------------------------------- 1 | {% set m_sum = sum(unread_message_numbers.values()) %} 2 | 3 | 4 | 0 %}class="red-color"{% end %}> 5 | {% if m_sum > 0 %}{% end %} 6 | 7 | 8 | 76 | -------------------------------------------------------------------------------- /app/message/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.message.handler import MessageUpdaterHandler 4 | 5 | urlpattern = ( 6 | (r'/message/update/?', MessageUpdaterHandler), 7 | ) 8 | -------------------------------------------------------------------------------- /app/profile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/profile/__init__.py -------------------------------------------------------------------------------- /app/profile/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import IntegerField, StringField, BooleanField 4 | from wtforms.validators import InputRequired, Regexp, Length 5 | from wtforms_tornado import Form 6 | 7 | __all__ = ['ProfileCardForm', 'StatusMoreForm', 8 | 'LeaveMessageNewForm', 'LeaveMessageMoreForm', 'FriendRequestForm', 9 | 'LeagueBulletinSaveForm'] 10 | 11 | 12 | class StatusMoreForm(Form): 13 | ''' 14 | :Variables: 15 | - `page`: 当前页数 16 | ''' 17 | 18 | page = IntegerField(validators=[InputRequired()]) 19 | 20 | 21 | class LeaveMessageNewForm(Form): 22 | '''新留言''' 23 | 24 | user_id = StringField(validators=[ 25 | InputRequired(), Regexp('[0-9a-f]{24}') 26 | ]) 27 | private = BooleanField(validators=[ 28 | InputRequired() 29 | ]) 30 | content = StringField(validators=[ 31 | InputRequired(), Length(max=5000) 32 | ]) 33 | replyeder_id = StringField() 34 | 35 | 36 | class LeaveMessageMoreForm(Form): 37 | '''陌生人访问界面更多留言''' 38 | 39 | user_id = StringField(validators=[ 40 | InputRequired(), Regexp('[0-9a-f]{24}') 41 | ]) 42 | page = IntegerField(validators=[InputRequired()]) 43 | 44 | 45 | class ProfileCardForm(Form): 46 | '''用户名片''' 47 | 48 | user_id = StringField(validators=[ 49 | InputRequired(), Regexp('[0-9a-f]{24}') 50 | ]) 51 | 52 | 53 | class FriendRequestForm(Form): 54 | '''添加好友请求''' 55 | 56 | user_id = StringField(validators=[ 57 | InputRequired(), Regexp('[0-9a-f]{24}') 58 | ]) 59 | 60 | 61 | class LeagueBulletinSaveForm(Form): 62 | '''修改社团公告''' 63 | 64 | league_bulletin = StringField(validators=[ 65 | Length(max=300) 66 | ]) 67 | -------------------------------------------------------------------------------- /app/profile/handler/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.profile.handler.leavemessage import * 4 | from app.profile.handler.profile import * 5 | from app.profile.handler.status import * 6 | from app.profile.handler.topic import * 7 | -------------------------------------------------------------------------------- /app/profile/handler/leavemessage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from tornado import gen 4 | from tornado.web import authenticated 5 | from bson.objectid import ObjectId 6 | 7 | from app.profile.handler.profile import ProfileBaseHandler 8 | from app.profile.setting import PROFILE_SETTINGS 9 | from app.profile.document import LeaveMessageDocument 10 | from app.message.document import MessageDocument, MessageTopic 11 | 12 | __all__ = ['LeaveMessageHandler'] 13 | 14 | 15 | class LeaveMessageHandler(ProfileBaseHandler): 16 | @authenticated 17 | @gen.coroutine 18 | def get(self, user_id=None): 19 | user_id = user_id or self.current_user["_id"] 20 | 21 | kwargs = yield self.get_header_arguments(user_id) 22 | if not kwargs['can_seen']: 23 | self.render('profile/template/profile-visitor.html', **kwargs) 24 | else: 25 | messages_func = LeaveMessageDocument.get_leave_message_list 26 | leave_message_list = yield messages_func( 27 | user_id, self.current_user['_id'], 28 | limit=PROFILE_SETTINGS['leave_message_number_per_page'] 29 | ) 30 | 31 | kwargs.update({ 32 | 'leave_message_list': leave_message_list 33 | }) 34 | 35 | if ObjectId(user_id) == ObjectId(self.current_user['_id']): 36 | yield MessageDocument.set_read( 37 | user_id, MessageTopic.LEAVE_MESSAGE_NEW 38 | ) 39 | 40 | self.render( 41 | 'profile/template/leavemessage/leavemessage.html', 42 | **kwargs 43 | ) 44 | -------------------------------------------------------------------------------- /app/profile/handler/topic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import random 4 | 5 | from tornado import gen 6 | from tornado.web import authenticated 7 | from app.community.document import TopicDocument 8 | from app.profile.handler.profile import ProfileBaseHandler 9 | 10 | __all__ = ['TopicHandler'] 11 | 12 | 13 | class TopicHandler(ProfileBaseHandler): 14 | @authenticated 15 | @gen.coroutine 16 | def get(self, user_id=None): 17 | if user_id is None: 18 | user_id = self.current_user['_id'] 19 | 20 | kwargs = yield self.get_header_arguments(user_id) 21 | if not kwargs['can_seen']: 22 | self.render('profile/template/profile-visitor.html', **kwargs) 23 | else: 24 | recommend_topic_list = [] 25 | 26 | topic_list = yield TopicDocument.get_topic_list_by_someone(user_id) 27 | if topic_list: 28 | index = random.randint(0, len(topic_list) - 1) 29 | 30 | topic_list_func = TopicDocument.get_recommend_topic_list 31 | recommend_topic_list = yield topic_list_func( 32 | topic_list[index]['_id'] 33 | ) 34 | 35 | kwargs.update({ 36 | 'topic_list': topic_list, 37 | 'recommend_topic_list': recommend_topic_list 38 | }) 39 | self.render('profile/template/topic/topic.html', **kwargs) 40 | -------------------------------------------------------------------------------- /app/profile/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | PROFILE_SETTINGS = { 4 | 'status_number_per_page': 20, 5 | 'leave_message_number_per_page': 20 6 | } 7 | -------------------------------------------------------------------------------- /app/profile/template/leavemessage/leavemessage-list-item.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | {{ leave_message['author']['name'] }} 12 | 13 | {% if 'replyeder' in leave_message %} 14 | 回复 15 | 16 | {{ leave_message['replyeder']['name'] }} 17 | 18 | {% end %}: 19 | 20 | {{ handler.translate_time(leave_message['leave_time']) }} 21 | {% if leave_message['author']['_id'] == current_user['_id'] and leave_message['private'] %} 22 | (私密的) 23 | {% end %} 24 |
25 |
26 | {% raw leave_message['content'] %} 27 |
28 |
29 | 30 | 31 |
32 | 33 | {% if user['_id'] == current_user['_id'] and leave_message['author']['_id'] != current_user['_id'] %} 34 | 36 | 回复 37 | 38 | {% end %} 39 | 40 |
41 |
42 | {{ leave_message['floor'] }}楼 43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /app/profile/template/leavemessage/leavemessage-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | {% for leave_message in leave_message_list %} 6 | {% include 'leavemessage-list-item.html' %} 7 | {% end %} 8 | 9 |
10 |
11 |
12 | 13 | {% if leave_message_list and len(leave_message_list) >= PROFILE_SETTINGS['leave_message_number_per_page'] %} 14 |
15 | 18 |
19 | {% end %} 20 | -------------------------------------------------------------------------------- /app/profile/template/leavemessage/leavemessage.html: -------------------------------------------------------------------------------- 1 | {% extends ../profile.html %} 2 | 3 | {% block link %} 4 | 5 | 6 | 7 | {% end %} 8 | 9 | {% block javascript_in_body %} 10 | 11 | 12 | 13 | 14 | 15 | {% end %} 16 | 17 | {% block app %} 18 |
19 |
20 | 21 | 状态 22 | 23 | 24 | 话题 25 | 26 | 27 | 留言 28 | 29 |
30 |
31 | 32 |
33 | {% include 'leavemessage-list.html' %} 34 |
35 | 36 | {% if user_setting['enable_leaving_message'] %} 37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 | 48 | 51 | 52 | 53 |
46 | 47 | 49 | 私密留言{% if current_user['_id'] != user['_id'] %}(只有自己和{{ user['name'] }}可见){% end %} 50 |
54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 |
62 | {% else %} 63 |
64 |
65 | 该用户禁止了留言 66 |
67 |
68 | {% end %} 69 | 70 | {% end %} 71 | -------------------------------------------------------------------------------- /app/profile/template/profile-comment-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {% for reply in reply_list %} 5 |
6 |
7 | 8 |
9 |
10 |
11 | 12 | {{ reply['replyer']['name'] }} 13 | 14 |
15 | {{ handler.translate_time( reply['reply_time'] ) }} 16 |
17 |
18 |
19 |
20 | {% raw reply['content'] %} 21 |
22 | 27 |
28 | {% end %} 29 |
30 |
31 | 32 | {% block pagination %}{% end %} 33 | -------------------------------------------------------------------------------- /app/profile/template/profile-visitor.html: -------------------------------------------------------------------------------- 1 | {% extends "profile.html" %} 2 | 3 | {% block link %} 4 | 5 | 6 | 7 | {% end %} 8 | 9 | {% block javascript_in_body %} 10 | 11 | 12 | 13 | 14 | 15 | {% end %} 16 | 17 | {% block app %} 18 |
19 |
20 | {% set call = '他' %} 21 | {% if user['user_type'] != 'league' and user['sex'] == 'female' %} 22 | {% set call = '她' %} 23 | {% end %} 24 | {{ call }}设置了空间权限, 你无权查看 25 | 30 |
31 |
32 | 33 | {% if user_setting['enable_leaving_message'] %} 34 | 35 | {% include 'leavemessage/leavemessage-list.html' %} 36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 47 | 50 | 51 | 52 |
45 | 46 | 48 | 私密留言{% if current_user['_id'] != user['_id'] %}(只有自己和{{ user['name'] }}可见){% end %} 49 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |
61 | {% else %} 62 |
63 |
64 | 该用户禁止了留言 65 |
66 |
67 | {% end %} 68 | {% end %} 69 | -------------------------------------------------------------------------------- /app/profile/template/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base/template/appbase.html" %} 2 | 3 | {% block body %} 4 |
5 |
6 |
7 |
8 | 9 | 29 |
30 | 31 |
32 |
33 | {{ user['name'] }} 34 | 35 | {% if 'relationship_status' in user and user['relationship_status'] == 'single' %} 36 | (单身中) 37 | {% elif 'relationship_status' in user and user['relationship_status'] == 'in_love' %} 38 | (恋爱中) 39 | {% end %} 40 | 41 | {% if current_user['_id'] != user['_id'] %} 42 | {% if can_seen and not is_friend %} 43 | 44 | 朋友 45 | 46 | {% end %} 47 | 48 | {% if is_friend or user_setting['allow_stranger_chat_with_me'] %} 49 | 50 | 私聊 51 | 52 | {% end %} 53 | {% end %} 54 | 55 | {% if 'signature' in user and user['signature'] %} 56 |
57 | {{ user['signature'] }} 58 |
59 | {% end %} 60 |
61 |
62 |
63 |
64 | {% block app %}{% end %} 65 | {% end %} 66 | -------------------------------------------------------------------------------- /app/profile/template/status/friend-recommend.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 你可能感兴趣 5 |
6 |
7 | 换一批 8 |
9 |
10 |
11 | {% if recommend_friend_list %} 12 |
13 | 14 | {% for recommend_friend in recommend_friend_list %} 15 | 16 | 21 | 25 | 30 | 31 | {% end %} 32 |
17 | 18 | 19 | 20 | 22 | {{ recommend_friend['name'] }} 24 | 26 | 27 | 朋友 28 | 29 |
33 |
34 | {% else %} 35 |
36 |
37 | 暂无推荐 38 |
39 |
40 | {% end %} 41 |
42 | -------------------------------------------------------------------------------- /app/profile/template/status/status-list-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
8 |
9 | 10 | 11 | {{ status['author']['name'] }} 12 | 13 | 14 |
15 | {{ handler.translate_time(status['publish_time']) }} 16 |
17 |
18 |
19 |
20 |
21 | {{ status['content'] }} 22 |
23 | {% if 'photo' in status %} 24 |
25 | 26 | 27 | 28 |
29 | {% end %} 30 |
31 |
32 | 33 | {% if status['liked'] %} 34 | 36 | 赞{% if status['like_times'] > 0 %}{{ status['like_times'] }}{% end %} 37 | 38 | {% else %} 39 | 43 | 赞{% if status['like_times'] > 0 %}{{ status['like_times'] }}{% end %} 44 | 45 | {% end %} 46 | • 47 | 48 | 评论{% if status['comment_times'] > 0 %}{{ status['comment_times'] }}{% end %} 49 | 50 | 51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /app/profile/template/status/status-list.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {% if not status_list %} 4 |
5 |
6 | {% if current_user['_id'] == user['_id'] %}我{% elif user['user_type']=='league' or user['sex']=='male' %}他{% else %}她{% end %}还没有发布状态 7 |
8 |
9 | {% else %} 10 | {% for status in status_list %} 11 | {% include "status-list-item.html" %} 12 | {% end %} 13 | {% end %} 14 |
15 |
16 | 17 | {% if status_list and len(status_list) >= PROFILE_SETTINGS['status_number_per_page'] %} 18 |
19 | 20 |
21 | {% end %} 22 | -------------------------------------------------------------------------------- /app/profile/template/status/status.html: -------------------------------------------------------------------------------- 1 | {% extends ../profile.html %} 2 | 3 | {% block link %} 4 | 5 | 6 | {% end %} 7 | 8 | {% block javascript_in_body %} 9 | 10 | 11 | {% end %} 12 | 13 | {% block app %} 14 |
15 |
16 | 17 | 状态 18 | 19 | 20 | 话题 21 | 22 | 23 | 留言 24 | 25 |
26 |
27 | 28 |
29 |
30 | {% include 'status-list.html' %} 31 |
32 |
33 | {% include 'status-sidebar.html' %} 34 |
35 |
36 | {% end %} 37 | -------------------------------------------------------------------------------- /app/profile/template/topic/topic-sidebar.html: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /app/profile/template/topic/topic.html: -------------------------------------------------------------------------------- 1 | {% extends ../profile.html %} 2 | 3 | {% block link %} 4 | 5 | 6 | {% end %} 7 | 8 | {% block app %} 9 |
10 |
11 | 12 | 状态 13 | 14 | 15 | 话题 16 | 17 | 18 | 留言 19 | 20 |
21 |
22 |
23 |
24 | {% include 'topic-main.html' %} 25 |
26 | {% include 'topic-sidebar.html' %} 27 |
28 | {% end %} 29 | -------------------------------------------------------------------------------- /app/profile/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.profile.handler import ( 4 | StatusHandler, StatusMoreHandler, FriendRecommendHandler, 5 | FriendRequestNewHandler, FriendRequestAgreeHandler, 6 | FriendRequestRefuseHandler, TopicHandler, LeaveMessageHandler, 7 | LeaveMessageNewHandler, LeaveMessageMoreHandler, 8 | LeagueBulletinSaveHandler 9 | ) 10 | 11 | urlpattern = ( 12 | (r'/profile/?([a-f0-9]{24})?', StatusHandler), 13 | (r'/profile/status/?([a-f0-9]{24})?/?', StatusHandler), 14 | (r'/profile/status/more/?', StatusMoreHandler), 15 | (r'/profile/friend/recommend/?', FriendRecommendHandler), 16 | (r'/profile/friend/request/new?', FriendRequestNewHandler), 17 | (r'/profile/friend/request/agree/?', FriendRequestAgreeHandler), 18 | (r'/profile/friend/request/refuse/?', FriendRequestRefuseHandler), 19 | (r'/profile/topic/?([a-f0-9]{24})?/?', TopicHandler), 20 | (r'/profile/leavemessage/?([a-f0-9]{24})?/?', LeaveMessageHandler), 21 | (r'/profile/leavemessage/new/?', LeaveMessageNewHandler), 22 | (r'/profile/leavemessage/more/?', LeaveMessageMoreHandler), 23 | (r'/profile/league/bulletin/save/?', LeagueBulletinSaveHandler) 24 | ) 25 | -------------------------------------------------------------------------------- /app/search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/search/__init__.py -------------------------------------------------------------------------------- /app/search/document.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/search/document.py -------------------------------------------------------------------------------- /app/search/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import StringField 4 | from wtforms.validators import InputRequired, AnyOf 5 | from wtforms_tornado import Form 6 | 7 | __all__ = ['SearchForm'] 8 | 9 | 10 | class SearchForm(Form): 11 | '''搜索 12 | 13 | :Variables: 14 | - `category`: 查询的类型 15 | - `query`: 查询的内容 16 | ''' 17 | category = StringField(validators=[ 18 | InputRequired(), AnyOf(['user', 'topic', 'share']) 19 | ]) 20 | query = StringField() 21 | -------------------------------------------------------------------------------- /app/search/handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from tornado import gen 4 | from tornado.web import HTTPError 5 | 6 | from young.handler import BaseHandler 7 | from app.user.document import UserDocument 8 | from app.community.document import TopicDocument 9 | from app.share.document import ShareDocument 10 | from app.search.form import SearchForm 11 | 12 | __all__ = ['SearchHandler'] 13 | 14 | 15 | class SearchHandler(BaseHandler): 16 | @gen.coroutine 17 | def get(self): 18 | form = SearchForm(self.request.arguments) 19 | if not form.validate(): 20 | raise HTTPError(404) 21 | 22 | category = form.category.data 23 | query = form.query.data 24 | 25 | response_data = [] 26 | if category == 'user': 27 | res = self.es.search( 28 | index="young", 29 | doc_type=UserDocument.meta['collection'], 30 | body={ 31 | "query": { 32 | "match": { 33 | "name": query 34 | } 35 | } 36 | } 37 | ) 38 | 39 | response_data = [ 40 | {'name': r["_source"]['name'], '_id': r["_id"]} 41 | for r in res["hits"]["hits"] 42 | ] 43 | 44 | elif category == 'topic': 45 | res = self.es.search( 46 | index="young", 47 | doc_type=TopicDocument.meta['collection'], 48 | body={ 49 | "query": { 50 | "match": { 51 | "title": query 52 | } 53 | } 54 | } 55 | ) 56 | 57 | response_data = [ 58 | {'title': r["_source"]['title'], '_id': r["_id"]} 59 | for r in res["hits"]["hits"] 60 | ] 61 | 62 | elif category == 'share': 63 | res = self.es.search( 64 | index="young", 65 | doc_type=ShareDocument.meta['collection'], 66 | body={ 67 | "query": { 68 | "match": { 69 | "title": query 70 | } 71 | } 72 | } 73 | ) 74 | 75 | response_data = [ 76 | {'title': r["_source"]['title'], '_id': r["_id"]} 77 | for r in res["hits"]["hits"] 78 | ] 79 | 80 | self.write_json(response_data) 81 | -------------------------------------------------------------------------------- /app/search/setting.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/search/setting.py -------------------------------------------------------------------------------- /app/search/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.search.handler import SearchHandler 4 | 5 | urlpattern = ( 6 | (r'/search', SearchHandler), 7 | ) 8 | -------------------------------------------------------------------------------- /app/setting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/setting/__init__.py -------------------------------------------------------------------------------- /app/setting/document.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/setting/document.py -------------------------------------------------------------------------------- /app/setting/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import FloatField, BooleanField, StringField, IntegerField 4 | from wtforms.validators import InputRequired, AnyOf, Length, Regexp 5 | from wtforms_tornado import Form 6 | 7 | __all__ = ['AvatarSetForm', 'ProfileSetForm', 8 | 'PasswordSetForm', 'ProfileCoverSetForm', 'PrivateSetForm', 9 | 'NotificationSetForm', 'ThemeSetForm'] 10 | 11 | 12 | class AvatarSetForm(Form): 13 | ''' 14 | :Variables: 15 | - `x`: 头像剪裁的起始x坐标 16 | - `y`: 头像剪裁的起始y坐标 17 | - `w`: 头像剪裁的宽度 18 | - `h`: 头像剪裁的高度 19 | - `target_width`: 浏览器前端对比宽度 20 | ''' 21 | x = FloatField(validators=[InputRequired()]) 22 | y = FloatField(validators=[InputRequired()]) 23 | w = FloatField(validators=[InputRequired()]) 24 | h = FloatField(validators=[InputRequired()]) 25 | target_width = IntegerField(validators=[InputRequired()]) 26 | 27 | 28 | class ProfileSetForm(Form): 29 | sex = StringField(validators=[ 30 | AnyOf(['', 'male', 'female']) 31 | ]) 32 | birthday = StringField(validators=[ 33 | Regexp('|\d{4}-\d{1,2}-\d{1,2}') 34 | ]) 35 | relationship_status = StringField(validators=[ 36 | AnyOf(['', 'single', 'in_love']) 37 | ]) 38 | province = StringField() 39 | city = StringField() 40 | phone = StringField() 41 | qq = StringField() 42 | signature = StringField() 43 | 44 | 45 | class PasswordSetForm(Form): 46 | current_password = StringField(validators=[ 47 | InputRequired(message='请输入当前密码!') 48 | ]) 49 | 50 | new_password = StringField(validators=[ 51 | InputRequired(message='请输入新密码!'), 52 | Regexp(r'[-\da-zA-Z`=\\\[\];\',\./~!@#$%^&*()_+|{}:"<>?]{6,20}', 53 | message='新密码格式不正确!') 54 | ]) 55 | 56 | repeat_password = StringField(validators=[ 57 | InputRequired(message='请重复输入新密码!') 58 | ]) 59 | 60 | 61 | class ProfileCoverSetForm(Form): 62 | profile_cover_id = StringField(validators=[ 63 | InputRequired(), Regexp('[0-9a-f]{24}') 64 | ]) 65 | 66 | 67 | class PrivateSetForm(Form): 68 | require_verify_when_add_friend = BooleanField(validators=[ 69 | InputRequired() 70 | ]) 71 | allow_stranger_visiting_profile = BooleanField(validators=[ 72 | InputRequired() 73 | ]) 74 | allow_stranger_chat_with_me = BooleanField(validators=[ 75 | InputRequired() 76 | ]) 77 | enable_leaving_message = BooleanField(validators=[ 78 | InputRequired() 79 | ]) 80 | 81 | 82 | class NotificationSetForm(Form): 83 | email_notify_when_offline = BooleanField(validators=[ 84 | InputRequired() 85 | ]) 86 | 87 | 88 | class ThemeSetForm(Form): 89 | theme = StringField(validators=[ 90 | InputRequired(), AnyOf(['default', 'black']) 91 | ]) 92 | -------------------------------------------------------------------------------- /app/setting/setting.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/setting/setting.py -------------------------------------------------------------------------------- /app/setting/template/setting-notification.html: -------------------------------------------------------------------------------- 1 | {% extends "setting.html" %} 2 | 3 | {% block link %} 4 | 5 | 6 | 7 | {% end %} 8 | 9 | {% block javascript_in_body %} 10 | 11 | 12 | {% end %} 13 | 14 | {% block setting-navbar %} 15 | 38 | {% end %} 39 | 40 | {% block setting_content %} 41 |
42 |
43 |
44 | 提醒设置 45 |
46 |
47 | 48 |
49 | 51 |
52 |
53 |
54 |
55 |
56 |
57 | {% end %} 58 | -------------------------------------------------------------------------------- /app/setting/template/setting-password.html: -------------------------------------------------------------------------------- 1 | {% extends "setting.html" %} 2 | 3 | {% block link %} 4 | 5 | 6 | {% end %} 7 | 8 | {% block javascript_in_body %} 9 | 10 | {% end %} 11 | 12 | {% block setting-navbar %} 13 | 36 | {% end %} 37 | 38 | {% block setting_content %} 39 |
40 |
41 |
42 |
43 | 修改密码 44 |
45 |
46 | 47 |
48 | 49 | 请输入当前密码 50 |
51 |
52 |
53 | 54 |
55 | 56 | 由6-20位字符(字母、数字、符号)组成,区分大小写 57 |
58 |
59 |
60 | 61 |
62 | 63 | 请重复输入新密码 64 |
65 |
66 |
67 |
68 |
69 | 保存 70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | {% end %} 80 | -------------------------------------------------------------------------------- /app/setting/template/setting-theme.html: -------------------------------------------------------------------------------- 1 | {% extends "setting.html" %} 2 | 3 | {% block link %} 4 | 5 | 6 | {% end %} 7 | 8 | {% block javascript_in_body %} 9 | 10 | {% end %} 11 | 12 | {% block setting-navbar %} 13 | 36 | {% end %} 37 | 38 | {% block setting_content %} 39 |
40 |
41 |
42 | 主题设置 43 |
44 |
45 | 46 |
47 | 55 |    56 | 57 |
58 |
59 |
60 |
61 |
62 |
63 | {% end %} 64 | -------------------------------------------------------------------------------- /app/setting/template/setting.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base/template/appbase.html" %} 2 | 3 | {% block body %} 4 |
5 | 16 | 17 | {% block setting_content %}{% end %} 18 |
19 | {% end %} 20 | -------------------------------------------------------------------------------- /app/setting/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.setting.handler import ( 4 | SettingHandler, AvatarSetHandler, PasswordSetHandler, ProfileCoverSetHander, 5 | ProfileCoverCustomHandler, ProfileSetHandler, NotificationSetHandler, 6 | ThemeSetHandler, PrivateSetHandler 7 | ) 8 | 9 | urlpattern = ( 10 | (r'/setting/(profile|avatar|profile/cover|password|course|private|notification|theme)/?', SettingHandler), 11 | (r'/setting/avatar/set/?', AvatarSetHandler), 12 | (r'/setting/password/set/?', PasswordSetHandler), 13 | (r'/setting/profile/cover/set/?', ProfileCoverSetHander), 14 | (r'/setting/profile/cover/custom/?', ProfileCoverCustomHandler), 15 | (r'/setting/profile/set/?', ProfileSetHandler), 16 | (r'/setting/private/set/?', PrivateSetHandler), 17 | (r'/setting/notification/set/?', NotificationSetHandler), 18 | (r'/setting/theme/set/?', ThemeSetHandler), 19 | ) 20 | -------------------------------------------------------------------------------- /app/share/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/share/__init__.py -------------------------------------------------------------------------------- /app/share/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import StringField, IntegerField, BooleanField 4 | from wtforms.validators import InputRequired, NumberRange, Length, Regexp 5 | from wtforms_tornado import Form 6 | 7 | __all__ = ['ShareCategoryForm', 'ShareNewForm', 'ShareNewCancelForm', 8 | 'ShareCommentNewForm', 'ShareLikeForm', 'SharePreviewForm'] 9 | 10 | 11 | class ShareCategoryForm(Form): 12 | category = StringField(validators=[InputRequired()]) 13 | 14 | 15 | class ShareNewForm(Form): 16 | resumableChunkNumber = IntegerField(validators=[ 17 | InputRequired(), NumberRange(min=1) 18 | ]) 19 | resumableTotalChunks = IntegerField(validators=[ 20 | InputRequired(), NumberRange(min=1) 21 | ]) 22 | resumableChunkSize = IntegerField(validators=[ 23 | InputRequired(), NumberRange(min=1) 24 | ]) 25 | resumableTotalSize = IntegerField(validators=[ 26 | InputRequired(), NumberRange(min=1) 27 | ]) 28 | resumableIdentifier = StringField(validators=[ 29 | InputRequired() 30 | ]) 31 | resumableFilename = StringField(validators=[ 32 | InputRequired(), Length(max=500) 33 | ]) 34 | resumableRelativePath = StringField(validators=[ 35 | InputRequired() 36 | ]) 37 | upload_token = StringField(validators=[ 38 | InputRequired(), Length(max=100) 39 | ]) 40 | title = StringField(validators=[ 41 | InputRequired(), Length(max=100) 42 | ]) 43 | category = StringField(validators=[ 44 | InputRequired() 45 | ]) 46 | cost = IntegerField(validators=[ 47 | InputRequired(), NumberRange(min=0) 48 | ]) 49 | description = StringField(validators=[ 50 | Length(max=10 ** 5) 51 | ]) 52 | 53 | 54 | class ShareNewCancelForm(Form): 55 | upload_token = StringField(validators=[ 56 | InputRequired(), Length(max=100) 57 | ]) 58 | 59 | 60 | class ShareCommentNewForm(Form): 61 | ''' 62 | :Variables: 63 | - `share_id`: 被评论的分享 64 | - `content`: 回答内容 65 | - `anonymous`: 是否匿名 66 | ''' 67 | 68 | share_id = StringField(validators=[ 69 | InputRequired(message='请指定要评论的话题'), 70 | Regexp('[a-f0-9]{24}', message='要评论的话题不存在') 71 | ]) 72 | 73 | content = StringField(validators=[ 74 | InputRequired(message='评论内容不能为空'), 75 | Length(max=10 ** 5, message='评论的内容太长了') 76 | ]) 77 | 78 | anonymous = BooleanField() 79 | replyeder_id = StringField() 80 | 81 | 82 | class ShareLikeForm(Form): 83 | ''' 84 | :Variables: 85 | - `share_id`: 相关的分享 86 | ''' 87 | 88 | share_id = StringField(validators=[ 89 | InputRequired(), Regexp('[a-f0-9]{24}') 90 | ]) 91 | 92 | 93 | class SharePreviewForm(Form): 94 | share_id = StringField(validators=[ 95 | InputRequired(), Regexp('[a-f0-9]{24}') 96 | ]) 97 | -------------------------------------------------------------------------------- /app/share/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | SHARE_SETTINGS = { 4 | 'share_number_per_page': 20, 5 | 'share_comment_number_per_page': 20, 6 | } 7 | -------------------------------------------------------------------------------- /app/share/template/share-comment-list-item.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 12 | 50 |
51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /app/share/template/share-comment-list.html: -------------------------------------------------------------------------------- 1 |
2 | {% for comment in comment_list %} 3 | {% include 'share-comment-list-item.html' %} 4 | {% end %} 5 |
6 | 7 | {% if comment_list and len(comment_list) >= SHARE_SETTINGS['share_comment_number_per_page'] %} 8 |
9 | 12 |
13 | {% end %} -------------------------------------------------------------------------------- /app/share/template/share-sidebar.html: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /app/share/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from app.share.handler import ( 4 | ShareHandler, ShareNewTemplateHandler, ShareNewHandler, 5 | ShareNewCancelHandler, ShareCategoryHandler, ShareOneHandler, 6 | ShareDownloadHandler, ShareCommentNewHandler, ShareLikeHandler 7 | ) 8 | 9 | urlpattern = ( 10 | (r'/share/?', ShareHandler), 11 | (r'/share/new/template/?', ShareNewTemplateHandler), 12 | (r'/share/new/?', ShareNewHandler), 13 | (r'/share/new/cancel/?', ShareNewCancelHandler), 14 | (r'/share/category/?', ShareCategoryHandler), 15 | (r'/share/([a-f0-9]{24})/?', ShareOneHandler), 16 | (r'/share/download/([a-f0-9]{24})/?', ShareDownloadHandler), 17 | (r'/share/comment/new/?', ShareCommentNewHandler), 18 | (r'/share/like/?', ShareLikeHandler), 19 | ) 20 | -------------------------------------------------------------------------------- /app/user/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/app/user/__init__.py -------------------------------------------------------------------------------- /app/user/form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from wtforms.fields import StringField 4 | from wtforms.validators import InputRequired, Regexp 5 | from wtforms_tornado import Form 6 | 7 | __all__ = ['LoginForm', 'RegisterForm', 'ServerTicketForm', 8 | 'AccountActiveForm', 'PasswordResetSendmailForm', 9 | 'PasswordResetGetForm', 'PasswordResetPostForm', 10 | 'AccountActiveSendmailForm'] 11 | 12 | email_regex = ( 13 | '^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|' 14 | '(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' 15 | '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$' 16 | ) 17 | 18 | password_regex = '''[-\da-zA-Z`=\\\[\];',./~!@#$%^&*()_+|{}:"<>?]{6,20}''' 19 | 20 | 21 | class LoginForm(Form): 22 | ''' 23 | :Variables: 24 | - `email`: 邮箱 25 | - `password`: 密码 26 | ''' 27 | 28 | email = StringField(validators=[ 29 | InputRequired(message='请输入邮箱') 30 | ]) 31 | password = StringField(validators=[ 32 | InputRequired(message='请输入密码') 33 | ]) 34 | 35 | 36 | class RegisterForm(Form): 37 | name = StringField(validators=[ 38 | InputRequired(message='请填写用户名!'), 39 | Regexp('^[0-9A-Za-z]{1,30}$', message='用户名格式不正确! 用户名必须为' 40 | '字母或数字, 且长度不超过30') 41 | ]) 42 | email = StringField(validators=[ 43 | InputRequired(message='请填写你的邮箱!'), 44 | Regexp(email_regex, message='邮箱格式不正确!') 45 | ]) 46 | 47 | password = StringField( 48 | validators=[ 49 | InputRequired(message='请输入密码!'), 50 | Regexp(password_regex, message='密码格式不正确! 密码必须为长度至' 51 | '少为6的字母、数字或者非空白字符的组合!') 52 | ] 53 | ) 54 | 55 | 56 | class ServerTicketForm(Form): 57 | ticket = StringField(validators=[ 58 | InputRequired() 59 | ]) 60 | 61 | 62 | class AccountActiveSendmailForm(Form): 63 | email = StringField(validators=[ 64 | InputRequired(), Regexp(email_regex) 65 | ]) 66 | 67 | 68 | class AccountActiveForm(Form): 69 | uid = StringField(validators=[ 70 | InputRequired() 71 | ]) 72 | code = StringField(validators=[ 73 | InputRequired() 74 | ]) 75 | 76 | 77 | class PasswordResetSendmailForm(Form): 78 | email = StringField(validators=[ 79 | InputRequired(), Regexp(email_regex) 80 | ]) 81 | 82 | 83 | class PasswordResetGetForm(Form): 84 | uid = StringField(validators=[ 85 | InputRequired(), Regexp(email_regex) 86 | ]) 87 | code = StringField(validators=[ 88 | InputRequired() 89 | ]) 90 | 91 | 92 | class PasswordResetPostForm(Form): 93 | password = StringField(validators=[ 94 | InputRequired(), Regexp(password_regex) 95 | ]) 96 | -------------------------------------------------------------------------------- /app/user/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | USER_SETTINGS = { 4 | # 单位day 5 | 'code_expired_after': 2 6 | } 7 | 8 | WEALTH_SETTINHS = { 9 | 'login_reward': 1 10 | } 11 | -------------------------------------------------------------------------------- /app/user/template/feedback.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base/template/base.html" %} 2 | 3 | {% block javascript %} 4 | 44 | {% end %} 45 | -------------------------------------------------------------------------------- /app/user/template/password-reset.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base/template/base.html" %} 2 | 3 | {% block link %} 4 | 5 | {% end %} 6 | 7 | {% block javascript_in_body %} 8 | 9 | {% end %} 10 | 11 | {% block body%} 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 | 请重设你的密码, 该验证链接仅能使用一次 20 |
21 |
22 | 提交 23 |
24 |
25 |
26 |
27 |
28 |
29 | {% end %} 30 | -------------------------------------------------------------------------------- /app/user/template/register.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 | 注册 15 |
16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /app/user/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from handler import ( 4 | LoginHandler, LogoutHandler, AvatarStaticFileHandler, 5 | RegisterTemplateHandler, RegisterHandler, AccountActiveSendmailHandler, 6 | AccountActiveHandler, PasswordResetSendmailHandler, PasswordResetHandler, 7 | FetchLoginRewardHandler 8 | ) 9 | 10 | urlpattern = ( 11 | (r'/login/?', LoginHandler), 12 | (r'/logout/?', LogoutHandler), 13 | (r'/avatar/([a-f0-9]{24})/?(thumbnail50x50|thumbnail180x180)?', AvatarStaticFileHandler), 14 | (r'/register/template/?', RegisterTemplateHandler), 15 | (r'/register/?', RegisterHandler), 16 | (r'/account/active/sendmail/?', AccountActiveSendmailHandler), 17 | (r'/account/active/?', AccountActiveHandler), 18 | (r'/password/reset/sendmail/?', PasswordResetSendmailHandler), 19 | (r'/password/reset/?', PasswordResetHandler), 20 | (r'/reward/login/fetch/?', FetchLoginRewardHandler), 21 | ) 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Young", 3 | "homepage": "https://github.com/shiyanhui/Young", 4 | "authors": [ 5 | "Lime " 6 | ], 7 | "description": "", 8 | "main": "", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "tests" 16 | ], 17 | "dependencies": { 18 | "alertify.js": "0.3.11", 19 | "Buttons": "1.1.1", 20 | "sprintf": "^1.0.3", 21 | "bootstrap-datepicker": "1.6.4", 22 | "fancybox": "2.1.5", 23 | "font-awesome": "4.6.3", 24 | "highlightjs": "9.6.0", 25 | "iCheck": "1.0.2", 26 | "blueimp-load-image": "2.6.1", 27 | "Jcrop": "0.9.12", 28 | "blueimp-file-upload": "9.5.7", 29 | "jquery-textext": "1.3.1", 30 | "jquery.pin": "1.0.3", 31 | "jquery.scrollTo": "2.1.2", 32 | "MathJax": "2.6.1", 33 | "messenger": "1.5.0", 34 | "jquery-mousewheel": "3.1.13", 35 | "node-uuid": "1.4.7", 36 | "resumable.js": "Resumable.js#1.0.2", 37 | "store-js": "1.3.20", 38 | "switchery": "0.8.1", 39 | "typeahead.js": "0.10.2", 40 | "handlebars": "4.0.5", 41 | "cityselector": "0.1", 42 | "bootstrap-young": "0.1", 43 | "wysiwyg-young": "0.1", 44 | "select2": "3.4.5", 45 | "jquery": "1.8.3" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import os 4 | 5 | from fabric.api import local, cd 6 | 7 | 8 | def init(): 9 | start_service() 10 | local("/bin/bash -l -c 'source envs/main/bin/activate && python scripts/init_db.py'") 11 | 12 | 13 | # supervisor is better 14 | def start_service(): 15 | path = os.path.join(os.path.dirname(__file__), "workspace") 16 | if not os.path.exists(path): 17 | os.mkdir(path) 18 | 19 | with cd(path): 20 | try: 21 | local("ejabberdctl start") 22 | except: 23 | pass 24 | 25 | local("service elasticsearch start") 26 | local("service mongod start") 27 | local("nohup nsqd -data-path=%s &" % path) 28 | 29 | local("/bin/bash -l -c 'source envs/mongo/bin/activate && nohup " 30 | "mongo-connector -m localhost:27017 -t localhost:9200 -d " 31 | "elastic2_doc_manager --continue-on-error &'") 32 | 33 | 34 | def build(): 35 | local("npm install") 36 | local("bower install --allow-root") 37 | local("gulp") 38 | 39 | 40 | def run(debug=False): 41 | if debug: 42 | args = "-debug=true" 43 | else: 44 | args = "" 45 | build() 46 | 47 | local("source envs/main/bin/activate && python server.py %s" % args, shell="/bin/bash") 48 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /lib/message/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from lib.message.writer import * 4 | from lib.message.browser import * 5 | from lib.message.handler import * 6 | from lib.message.topic import * 7 | -------------------------------------------------------------------------------- /lib/message/browser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ['BrowserCallbackManager'] 4 | 5 | 6 | class BrowserCallbackManager(object): 7 | '''管理服务器挂起的浏览器端请求. 8 | 9 | _callbacks的结构为: 10 | { 11 | user_id: callback, 12 | } 13 | ''' 14 | 15 | _callbacks = {} 16 | 17 | @classmethod 18 | def add(cls, user_id, callback): 19 | cls._callbacks.update({str(user_id): callback}) 20 | 21 | @classmethod 22 | def remove(cls, user_id): 23 | try: 24 | cls._callbacks.pop(str(user_id)) 25 | except: 26 | pass 27 | 28 | @classmethod 29 | def get(cls, user_id): 30 | return cls._callbacks.get(str(user_id), None) 31 | -------------------------------------------------------------------------------- /lib/message/topic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ['MessageTopic'] 4 | 5 | 6 | class MessageTopic(object): 7 | '''消息类型 8 | 9 | 注意: 在此处添加新的类型后别忘了在`MessageDocument`的`message_type`变量里添加 10 | 11 | :Variables: 12 | - `CHAT_MESSAGE_NEW`: 新私聊信息 13 | - `COMMENT`: 评论 14 | - `REPLY`: 回复 15 | - `LIKE`: 赞 16 | - `AT`: @ 17 | - `STATUS_NEW`: 朋友发表新状态时 18 | - `TOPIC_NEW`: 朋友发表新话题时 19 | - `FRIEND_REQUEST_NEW`: 新好友请求 20 | - `LEAVE_MESSAGE_NEW`: 新留言 21 | - `SHARE_NEW`: 朋友发布了新的分享 22 | 23 | - `SEND_ACTIVATION_EMAIL`: 给用户发送激活邮件 24 | - `SEND_RESET_PASSWORD_EMAIL`: 给用户发送重置密码邮件 25 | - `SEND_HAS_UNREAD_MESSAGE_EMAIL`: 给用户发送有未读消息邮件 26 | 27 | - `_COMMENT_AND_REPLY`: 评论和回复, 该项是为了在查询历史消息时的方便而拼凑出的, 28 | 实际上不会存在该类型的消息 29 | - `_FRIENDS_DYNAMIC`: 朋友动态, 同上. 30 | ''' 31 | 32 | # 用户发给用户的消息 33 | CHAT_MESSAGE_NEW = 'chat_message_new' 34 | COMMENT = 'comment' 35 | REPLY = 'reply' 36 | LIKE = 'like' 37 | AT = 'at' 38 | STATUS_NEW = 'status_new' 39 | TOPIC_NEW = 'topic_new' 40 | FRIEND_REQUEST_NEW = 'friend_request_new' 41 | LEAVE_MESSAGE_NEW = 'leave_message_new' 42 | SHARE_NEW = 'share_new' 43 | 44 | # 系统自己发给自己的消息, 不会保存到MessageDocument里边 45 | SEND_ACTIVATION_EMAIL = 'send_activation_email' 46 | SEND_RESET_PASSWORD_EMAIL = 'send_reset_password_email' 47 | SEND_HAS_UNREAD_MESSAGE_EMAIL = 'send_has_unread_message_email' 48 | 49 | _COMMENT_AND_REPLY = 'comment_and_reply' 50 | _FRIENDS_DYNAMIC = 'friends_dynamic' 51 | 52 | all_topic = [ 53 | CHAT_MESSAGE_NEW, COMMENT, REPLY, LIKE, AT, STATUS_NEW, TOPIC_NEW, 54 | FRIEND_REQUEST_NEW, LEAVE_MESSAGE_NEW, SHARE_NEW, SEND_ACTIVATION_EMAIL, 55 | SEND_RESET_PASSWORD_EMAIL, SEND_HAS_UNREAD_MESSAGE_EMAIL 56 | ] 57 | 58 | @classmethod 59 | def message_type2topic(cls, message_type): 60 | for topic in cls.all_topic: 61 | if message_type.startswith(topic): 62 | return topic 63 | 64 | return None 65 | -------------------------------------------------------------------------------- /lib/message/writer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __all__ = ['WriterManager'] 4 | 5 | 6 | class WriterManager(object): 7 | writer = None 8 | 9 | @classmethod 10 | def pub(cls, topic, msg, callback=None): 11 | cls.writer.pub(topic, str(msg), callback) 12 | 13 | @classmethod 14 | def mpub(cls, topic, msg, callback=None): 15 | cls.writer.mpub(topic, map(str, msg), callback) 16 | -------------------------------------------------------------------------------- /lib/xmpp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from lib.xmpp.browser import * 4 | from lib.xmpp.client import * 5 | from lib.xmpp.ejabberd import * 6 | -------------------------------------------------------------------------------- /lib/xmpp/browser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | from collections import defaultdict 5 | 6 | from bson.objectid import ObjectId 7 | 8 | from app.chat.document import ChatMessageDocument 9 | from lib.message import WriterManager, MessageTopic 10 | 11 | __all__ = ['BrowserClientManager'] 12 | 13 | 14 | class BrowserClientManager(object): 15 | # {'current_user_id': {'chat_with_user_jid': callback}} 16 | _callbacks = defaultdict(dict) 17 | 18 | @classmethod 19 | def add(cls, current_user_id, chat_with_id, callback): 20 | cls._callbacks[current_user_id][chat_with_id] = callback 21 | 22 | @classmethod 23 | def remove(cls, current_user_id, chat_with_id): 24 | try: 25 | cls._callbacks[current_user_id].pop(chat_with_id) 26 | except: 27 | pass 28 | 29 | @classmethod 30 | def get(cls, current_user_id, chat_with_id): 31 | callback = None 32 | try: 33 | callback = cls._callbacks[current_user_id][chat_with_id] 34 | except: 35 | pass 36 | 37 | return callback 38 | 39 | @classmethod 40 | def new_message(cls, msg): 41 | '''收到XMPPClient端发来的消息''' 42 | 43 | current_user_id = str(msg['to']).split('@')[0] 44 | chat_with_id = str(msg['from']).split('@')[0] 45 | _id = ObjectId(str(msg['subject'])) 46 | 47 | callback = cls.get(current_user_id, chat_with_id) 48 | 49 | # 在线, 并且打开了聊天窗口 50 | if callback: 51 | try: 52 | callback(msg) 53 | except: 54 | logging.error("Error in browser client callback", exc_info=True) 55 | 56 | ChatMessageDocument.get_collection(pymongo=True).update( 57 | {'_id': ObjectId(_id)}, {'$set': {'read': True}}) 58 | 59 | # 在线但是没有打开聊天窗口. 这种情况下, 交给nsq消息系统处理. 60 | else: 61 | WriterManager.pub(MessageTopic.CHAT_MESSAGE_NEW, str(msg['subject'])) 62 | -------------------------------------------------------------------------------- /lib/xmpp/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from bson.objectid import ObjectId 4 | from sleekxmpp import ClientXMPP 5 | 6 | from lib.xmpp.browser import BrowserClientManager 7 | from app.chat.document import ChatMessageDocument 8 | 9 | __all__ = ['XMPPClient', 'XMPPClientManager'] 10 | 11 | 12 | class XMPPClient(ClientXMPP): 13 | def __init__(self, jid, password): 14 | ClientXMPP.__init__(self, jid, password) 15 | 16 | self.add_event_handler('session_start', self.session_start) 17 | self.add_event_handler('message', self.message) 18 | 19 | self._jid = jid 20 | self._password = password 21 | 22 | def session_start(self, event): 23 | self.send_presence() 24 | self.get_roster() 25 | 26 | def message(self, msg): 27 | '''收到Ejabberd服务器发来的信息, 判断后发到browser端''' 28 | 29 | if msg['type'] == 'chat': 30 | # 只有上线后才能收到信息 31 | ChatMessageDocument.get_collection(True).update( 32 | {'_id': ObjectId(str(msg['subject']))}, 33 | {'$set': {'received': True}}) 34 | 35 | BrowserClientManager.new_message(msg) 36 | 37 | def start(self): 38 | self.register_plugin('xep_0030') # Service Discovery 39 | self.register_plugin('xep_0004') # Data Forms 40 | self.register_plugin('xep_0060') # PubSub 41 | self.register_plugin('xep_0199') # XMPP Ping 42 | 43 | if self.connect(): 44 | self.process(block=False) 45 | 46 | @classmethod 47 | def make_jid(cls, _id, host='localhost'): 48 | return "%s@%s" % (_id, host) 49 | 50 | @classmethod 51 | def jid2objectid(cls, jid): 52 | objectid = None 53 | try: 54 | objectid = str(jid).split('@')[0] 55 | except: 56 | pass 57 | 58 | return objectid 59 | 60 | 61 | class XMPPClientManager(object): 62 | _clients = {} 63 | 64 | @classmethod 65 | def has(cls, jid): 66 | return str(jid) in cls._clients 67 | 68 | @classmethod 69 | def add(cls, jid, client): 70 | c = cls.get(jid) 71 | if c is not None: 72 | c.disconnect() 73 | 74 | cls._clients[str(jid)] = client 75 | 76 | @classmethod 77 | def remove(cls, jid): 78 | try: 79 | client = cls._clients.pop(str(jid)) 80 | client.disconnect() 81 | except: 82 | pass 83 | 84 | @classmethod 85 | def get(cls, jid): 86 | return cls._clients.get(str(jid), None) 87 | 88 | @classmethod 89 | def get_xmppclient(cls, user_id, password): 90 | jid = XMPPClient.make_jid(user_id) 91 | client = XMPPClientManager.get(jid) 92 | 93 | if client is None: 94 | client = XMPPClient(jid, password) 95 | XMPPClientManager.add(jid, client) 96 | client.start() 97 | 98 | return client 99 | 100 | @classmethod 101 | def logout(cls, user_id): 102 | jid = XMPPClient.make_jid(user_id) 103 | cls.remove(jid) 104 | -------------------------------------------------------------------------------- /lib/xmpp/ejabberd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import commands 4 | 5 | __all__ = ['Ejabberd'] 6 | 7 | 8 | class Ejabberd(object): 9 | @classmethod 10 | def registered(cls, user, host='localhost'): 11 | ''' 12 | :Parm: 13 | - `user`: @前面的部分 14 | - `host`: @后面的部分 15 | ''' 16 | status, output = commands.getstatusoutput( 17 | 'ejabberdctl registered_users %s' % host) 18 | 19 | if status == 0: 20 | return str(user) in output.split('\n') 21 | 22 | raise Exception(output) 23 | 24 | @classmethod 25 | def register(cls, user, password, host='localhost'): 26 | ''' 27 | :Parm: 28 | - `user`: @前面的部分 29 | - `host`: @后面的部分 30 | - `password`: 密码 31 | ''' 32 | status, output = commands.getstatusoutput( 33 | 'ejabberdctl register %s %s %s' % (user, host, password)) 34 | 35 | if status != 0: 36 | raise Exception(output) 37 | 38 | @classmethod 39 | def unregister(cls, user, host='localhost'): 40 | ''' 41 | :Parm: 42 | - `user`: @前面的部分 43 | - `host`: @后面的部分 44 | ''' 45 | status, output = commands.getstatusoutput( 46 | 'ejabberdctl unregister %s %s' % (user, host)) 47 | 48 | if status != 0: 49 | raise Exception(output) 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Young", 3 | "version": "0.1.0", 4 | "description": "Young", 5 | "main": "index.js", 6 | "dependencies": { 7 | "del": "~2.2.2", 8 | "gulp": "^3.9.1", 9 | "gulp-clean-css": "~2.0.12", 10 | "gulp-concat": "~2.6.0", 11 | "gulp-html-replace": "~1.6.1", 12 | "gulp-htmlmin": "~2.0.0", 13 | "gulp-rev": "^7.1.2", 14 | "gulp-rev-collector": "^1.0.5", 15 | "gulp-sourcemaps": "~1.6.0", 16 | "gulp-uglify": "~2.0.0" 17 | }, 18 | "devDependencies": {}, 19 | "scripts": { 20 | "test": "echo \"Error: no test specified\" && exit 1" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/shiyanhui/Young.git" 25 | }, 26 | "keywords": [ 27 | "Young" 28 | ], 29 | "author": "Lime", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/shiyanhui/Young/issues" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | backports-abc==0.4 2 | beautifulsoup4==4.5.1 3 | bs4==0.0.1 4 | certifi==2016.8.8 5 | elasticsearch==2.4.0 6 | greenlet==0.4.10 7 | motor==0.6.2 8 | pyasn1==0.1.9 9 | pyasn1-modules==0.0.8 10 | pymongo==2.8 11 | pynsq==0.7.0 12 | simplejson==3.8.2 13 | singledispatch==3.4.0.3 14 | six==1.10.0 15 | sleekxmpp==1.3.1 16 | tornado==4.4.1 17 | torsession==0.2.9 18 | urllib3==1.26.5 19 | WTForms==2.1 20 | wtforms-tornado==0.0.2 21 | -------------------------------------------------------------------------------- /scripts/setup_mail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # We use iRedMail as our mail server. 4 | # See more here http://www.iredmail.org/index.html 5 | 6 | # fqdn is something like mail.young.io 7 | if [ -z $1 ]; then 8 | echo "Usage: install_mail.sh [your fqdn]" 9 | exit 10 | fi 11 | 12 | hostnamectl set-hostname $1 13 | echo "127.0.0.1 $1" >> /etc/hosts 14 | echo "::1 $1" >> /etc/hosts 15 | 16 | # set the timezone 17 | dpkg-reconfigure tzdata 18 | 19 | apt-get update 20 | apt-get install -y postfix -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | import os 5 | from signal import signal, SIGINT 6 | 7 | import nsq 8 | from monguo.connection import Connection 9 | from tornado.ioloop import IOLoop 10 | from tornado.web import Application 11 | from tornado.options import define, options, parse_command_line 12 | from tornado.httpserver import HTTPServer 13 | from elasticsearch import Elasticsearch 14 | from torsession.sync import SessionManager 15 | 16 | from young import setting, urlmap 17 | from lib.message import ( 18 | WriterManager, MessageTopic, chat_message_handler, 19 | send_activation_email_handler, send_reset_password_email_handler, 20 | send_has_unread_message_email_handler, message_handler 21 | ) 22 | 23 | 24 | def register_message_writers(): 25 | '''为nsq消息系统建立生产者''' 26 | 27 | WriterManager.writer = nsq.Writer(['127.0.0.1:4150']) 28 | 29 | 30 | def register_message_readers(): 31 | '''为nsq消息系统建立消费者''' 32 | 33 | handlers = { 34 | MessageTopic.CHAT_MESSAGE_NEW: 35 | chat_message_handler, 36 | 37 | MessageTopic.SEND_ACTIVATION_EMAIL: 38 | send_activation_email_handler, 39 | 40 | MessageTopic.SEND_RESET_PASSWORD_EMAIL: 41 | send_reset_password_email_handler, 42 | 43 | MessageTopic.SEND_HAS_UNREAD_MESSAGE_EMAIL: 44 | send_has_unread_message_email_handler, 45 | } 46 | 47 | for topic in MessageTopic.all_topic: 48 | nsq.Reader( 49 | message_handler=handlers.get(topic, message_handler), 50 | nsqd_tcp_addresses=['127.0.0.1:4150'], 51 | topic=topic, 52 | channel=topic, 53 | lookupd_poll_interval=15 54 | ) 55 | 56 | 57 | # controlling is in c, Crtl^C won't exit, so we should kill self 58 | def suicide(signum, frame): 59 | os.system("kill -9 %d" % os.getpid()) 60 | 61 | 62 | def runserver(): 63 | '''启动服务器''' 64 | 65 | signal(SIGINT, suicide) 66 | 67 | # for sleekxmpp 68 | reload(sys) 69 | sys.setdefaultencoding('utf8') 70 | 71 | define('port', default=8000, help='run on the given port', type=int) 72 | define("database", default="Young", help="database to use", type=str) 73 | define("debug", default=False, help="whether is debug mode", type=bool) 74 | 75 | parse_command_line() 76 | 77 | register_message_writers() 78 | register_message_readers() 79 | 80 | Connection.connect(options.database) 81 | 82 | setting.APPLICATION_SETTINGS.update({ 83 | "debug": options.debug, 84 | "template_path": os.path.join( 85 | setting.ROOT_LOCATION, 86 | "app" if options.debug else "templates" 87 | ), 88 | "es": Elasticsearch(), 89 | "session_manager": SessionManager( 90 | Connection.get_database(pymongo=True).session 91 | ) 92 | }) 93 | application = Application( 94 | handlers=urlmap.urlpattern, 95 | **setting.APPLICATION_SETTINGS 96 | ) 97 | 98 | http_server = HTTPServer(application, xheaders=True) 99 | http_server.listen(options.port) 100 | 101 | IOLoop.instance().start() 102 | 103 | 104 | if __name__ == '__main__': 105 | runserver() 106 | -------------------------------------------------------------------------------- /static/app/base/js/appbase.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | addFriend: function() { 6 | var user_id = $(this).data("user-id"); 7 | 8 | $.ajax({ 9 | type: "post", 10 | url: "/profile/friend/request/new", 11 | data: { 12 | user_id: user_id, 13 | _xsrf: getCookie("_xsrf") 14 | }, 15 | dataType: "json", 16 | success: function(data, textStatus, jqXHR) { 17 | if (data.ok) { 18 | Messenger().post({ 19 | message: "好友添加成功!", 20 | type: "success", 21 | showCloseButton: true, 22 | id: 0 23 | }); 24 | } else if (data.error) { 25 | Messenger().post({ 26 | message: data.error, 27 | type: "error", 28 | showCloseButton: true, 29 | id: 0 30 | }); 31 | } else { 32 | Messenger().post({ 33 | message: "好友请求发送成功!", 34 | type: "success", 35 | showCloseButton: true, 36 | id: 0 37 | }); 38 | } 39 | }, 40 | error: function(jqXHR, textStatus, errorThrown) { 41 | Messenger().post({ 42 | message: "好友请求发送失败!", 43 | type: "error", 44 | showCloseButton: true, 45 | id: 0 46 | }); 47 | } 48 | }); 49 | } 50 | }; 51 | 52 | $(document).ajaxComplete(function(ev, jqXHR, options) { 53 | if (jqXHR.status == 403 && options.url != "/message/update") { 54 | window.location.href = "/login"; 55 | } 56 | return true; 57 | }); 58 | 59 | $(document).ready(function() { 60 | $(".fancybox-close-button").live("click", function() { 61 | $.fancybox.close(); 62 | }); 63 | 64 | $(".fancybox").fancybox({ 65 | padding: 5, 66 | maxWidth: 1000, 67 | prevEffect: 'none', 68 | nextEffect: 'none', 69 | helpers: { 70 | thumbs: { 71 | width: 50, 72 | height: 50 73 | } 74 | } 75 | }); 76 | 77 | Messenger({ 78 | extraClasses: 'messenger-fixed messenger-on-right messenger-on-top', 79 | messageDefaults: { 80 | hideAfter: 3 81 | } 82 | }); 83 | 84 | $(".add-friend-button").live("click", Controller.addFriend); 85 | }); 86 | }(jQuery)); 87 | -------------------------------------------------------------------------------- /static/app/base/js/base.js: -------------------------------------------------------------------------------- 1 | function is_null(str) { 2 | return !str || str.replace(/(^\s*)|(\s*$)/g, "").length == 0; 3 | } 4 | 5 | function cleanHTML(html) { 6 | return html && html.replace( 7 | /(
|\s|
|<\/div>| |
|<\/pre>||<\/code>||<\/span>)/g,
  8 |         ''
  9 |     );
 10 | }
 11 | 
 12 | function array_unique(arr) {
 13 |     var a = [], o = {}, i, v, len = arr.length;
 14 | 
 15 |     if (len < 2) {
 16 |         return arr;
 17 |     }
 18 | 
 19 |     for (i = len - 1; i >= 0; i--) {
 20 |         v = arr[i];
 21 |         if (o[v] !== 1) {
 22 |             a.unshift(v);
 23 |             o[v] = 1;
 24 |         }
 25 |     }
 26 |     return a;
 27 | }
 28 | 
 29 | function array_remove_all(arr, value) {
 30 |     var a = [];
 31 |     for (var i = 0; i < arr.length; i++) {
 32 |         if (arr[i] != value) {
 33 |             a.push(arr[i]);
 34 |         }
 35 |     }
 36 |     return a;
 37 | }
 38 | 
 39 | function validateEmail(email) {
 40 |     var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 41 |     return re.test(email);
 42 | }
 43 | 
 44 | 
 45 | function getCookie(name) {
 46 |     var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
 47 |     return r ? r[1] : undefined;
 48 | }
 49 | 
 50 | function unicode_to_utf8(str) {
 51 |     return unescape(str.replace(/\\u/gi, '%u'));
 52 | };
 53 | 
 54 | function font_number(str) {
 55 |     var regex = /\w+/g;
 56 |     if (!!str) {
 57 |         var words = str.match(regex);
 58 |         for(var w in words) {
 59 |             if (w.length > 50) {
 60 |                 return str.length;
 61 |             }
 62 |         }
 63 | 
 64 |         str = str.replace(regex, "");
 65 | 
 66 |         var _words_cnt = 0;
 67 |         if(words){
 68 |             _words_cnt = words.length;
 69 |         }
 70 |         return _words_cnt + str.length;
 71 |     }
 72 |     return 0;
 73 | }
 74 | 
 75 | // http://blog.csdn.net/coder_andy/article/details/6202231.
 76 | jQuery.fn.extend({
 77 |     getCurPos: function() {
 78 |         var e = $(this).get(0);
 79 |         e.focus();
 80 |         if (e.selectionStart) {
 81 |             //FF
 82 |             return e.selectionStart;
 83 |         }
 84 | 
 85 |         if (document.selection) {
 86 |             //IE
 87 |             var r = document.selection.createRange();
 88 |             if (r == null) {
 89 |                 return e.value.length;
 90 |             }
 91 | 
 92 |             var re = e.createTextRange();
 93 |             var rc = re.duplicate();
 94 | 
 95 |             re.moveToBookmark(r.getBookmark());
 96 |             rc.setEndPoint('EndToStart', re);
 97 |             return rc.text.length;
 98 |         }
 99 |         return e.value.length;
100 |     },
101 |     setCurPos: function(pos) {
102 |         var e = $(this).get(0);
103 |         e.focus();
104 | 
105 |         if (e.setSelectionRange) {
106 |             e.setSelectionRange(pos, pos);
107 |         } else if (e.createTextRange) {
108 |             var range = e.createTextRange();
109 | 
110 |             range.collapse(true);
111 |             range.moveEnd('character', pos);
112 |             range.moveStart('character', pos);
113 |             range.select();
114 |         }
115 |     }
116 | });
117 | 


--------------------------------------------------------------------------------
/static/app/chat/css/chat.css:
--------------------------------------------------------------------------------
 1 | .chat-panel{
 2 |     width: 500px;
 3 |     min-height: 500px;
 4 | }
 5 | .chat-panel .header{
 6 |     background: url(/static/app/chat/img/chat-header-bg.png);
 7 |     padding: 8px;
 8 |     border-bottom: 1px solid #cccccc;
 9 | }
10 | .chat-panel .header .minimize-panel{
11 |     position: absolute;
12 |     top: 8px;
13 |     right: 28px;
14 |     display: block;
15 |     color: #666;
16 |     width: 14px;
17 |     height: 14px;
18 |     z-index: 2;
19 | }
20 | .chat-panel .header .close-panel{
21 |     position: absolute;
22 |     top: 8px;
23 |     right: 8px;
24 |     display: block;
25 |     color: #666;
26 |     width: 14px;
27 |     height: 14px;
28 |     z-index: 2;
29 | }
30 | .chat-panel .body{
31 |     padding: 10px 8px;
32 |     height: 400px;
33 |     overflow-y: scroll;
34 | }
35 | .chat-panel .body .chat-label{
36 |     text-align: center;
37 |     color: gray;
38 | }
39 | .chat-panel .body table th,
40 | .chat-panel .body table td{
41 |     padding: 5px;
42 | }
43 | .chat-panel .chat-input-field{
44 |     min-width: 100%;
45 |     max-width: 100%;
46 |     height: 80px;
47 |     border-width: 0px;
48 |     border-top: 1px solid #eee;
49 | }
50 | .message .left-part,
51 | .message .right-part {
52 |     width: 50px;
53 | }
54 | .message .middle-part {
55 |     max-width: 384px;
56 |     min-width: 384px;
57 | }
58 | 


--------------------------------------------------------------------------------
/static/app/chat/img/chat-header-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/chat/img/chat-header-bg.png


--------------------------------------------------------------------------------
/static/app/chat/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/chat/img/spinner.gif


--------------------------------------------------------------------------------
/static/app/community/css/topic-new.css:
--------------------------------------------------------------------------------
 1 | .wysiwyg-editor {
 2 |     min-height: 330px;
 3 | }
 4 | .text-wrap {
 5 |     width: 100%!important;
 6 | }
 7 | #question-title {
 8 |     width: 686px;
 9 | }
10 | #node-list-input {
11 |     width: 100%;
12 | }
13 | .block .text-content {
14 |     padding: 40px!important;
15 | }


--------------------------------------------------------------------------------
/static/app/community/css/topic-one.css:
--------------------------------------------------------------------------------
 1 | .topic-comment-list{
 2 |     margin-top: 40px;
 3 |     padding: 0 15px;
 4 | }
 5 | .topic-comment-list .topic-comment-item{
 6 |     padding: 10px 0;
 7 | }
 8 | .topic-comment-list .topic-comment-item:first-child{
 9 |     padding-top: 15px;
10 | }
11 | .topic-comment-list .topic-comment-item:last-child{
12 |     padding-bottom: 15px;
13 |     border-bottom-width: 0
14 | }
15 | .topic-comment-list .topic-comment-item .topic-author-avatar{
16 |     width: 40px
17 | }
18 | .topic-comment-list .topic-comment-item .topic-main{
19 |     width: 605px
20 | }
21 | .topic-comment-list .topic-comment-item .topic-publish-time{
22 |     margin-left: 5px
23 | }
24 | .topic-comment-list .topic-comment-item .topic-main .topic-comment-action{
25 |     display: none;
26 |     margin-right: 10px
27 | }
28 | .topic-comment-list .topic-comment-item .topic-main .topic-comment-content{
29 |     padding: 5px 30px 0 0
30 | }
31 | 
32 | .topic{
33 |     padding: 15px;
34 | }
35 | .topic .topic-title{
36 |     font-size: 18px;
37 |     margin-top: 10px;
38 | }
39 | .topic .topic-content{
40 |     margin-top: 15px;
41 |     /*font-size: 14px!important;*/
42 | }
43 | .topic .topic-info{
44 |     margin-top: 20px
45 | }
46 | 
47 | .submit-comment-part{
48 |     margin-top: 10px
49 | }
50 | .submit-comment-part .anonymous-checkbox{
51 |     width: 200px
52 | }
53 | 
54 | .similar-topic-list{
55 |     min-height: 200px;
56 | }
57 | .similar-topic-list li{
58 |     border-bottom: 1px solid #eeeeee;
59 |     padding: 5px 0;
60 | }
61 | .similar-topic-list li:last-child{
62 |     border-bottom-width: 0;
63 | }
64 | .similar-topic-list tr td:first-child {
65 |     min-width: 40px;
66 |     max-width: 40px;
67 | }
68 | 
69 | .recommend-topic-list{
70 |     padding: 0 10px!important
71 | }
72 | .no-recommend-topic-list{
73 |     min-height: 150px;
74 | }
75 | .no-recommend-topic-list .no-recommend-topic-list-inner{
76 |     padding-top: 50px;
77 | }
78 | 
79 | 
80 | .like-list-part{
81 |     margin-bottom: 15px
82 | }
83 | .like-list{
84 |     min-height: 100px;
85 |     padding: 5px
86 | }
87 | .like-list table th,
88 | .like-list table td{
89 |     padding: 5px 0;
90 | }
91 | .like-list-none{
92 |     text-align: center;
93 |     margin-top: 20px
94 | }
95 | 


--------------------------------------------------------------------------------
/static/app/community/js/community.js:
--------------------------------------------------------------------------------
 1 | ;(function($) {
 2 |     'use strict';
 3 | 
 4 |     var Controller = {
 5 |         getRecommendFriends: function() {
 6 |             $.ajax({
 7 |                 type: "post",
 8 |                 url: "/profile/friend/recommend",
 9 |                 data: {
10 |                     _xsrf: getCookie("_xsrf")
11 |                 },
12 |                 dataType: "json",
13 |                 success: function(data, textStatus, jqXHR) {
14 |                     $("#recommend-friend-block").replaceWith(data.html);
15 |                 },
16 |                 error: function(jqXHR, textStatus, errorThrown) {
17 |                     Messenger().post({
18 |                         id: 0,
19 |                         message: "加载失败!",
20 |                         showCloseButton: true,
21 |                         type: "error"
22 |                     })
23 |                 }
24 |             });
25 |         },
26 |         fetchReward: function() {
27 |             $.ajax({
28 |                 type: "post",
29 |                 url: "/reward/login/fetch",
30 |                 data: {
31 |                     _xsrf: getCookie("_xsrf")
32 |                 },
33 |                 dataType: "json",
34 |                 success: function(data, textStatus, jqXHR) {
35 |                     if(data.error){
36 |                         Messenger().post({
37 |                             id: 0,
38 |                             message: data.error,
39 |                             showCloseButton: true,
40 |                             type: "error"
41 |                         })
42 |                     }
43 |                     else{
44 |                         $("#fetch-login-reward-label").html(sprintf(
45 |                             '连续 %s 天',
46 |                             data.continuous_login_days
47 |                         ));
48 |                         $("#wealth-quantity-label").html(data.wealth);
49 |                     }
50 |                 },
51 |                 error: function(jqXHR, textStatus, errorThrown) {
52 |                     Messenger().post({
53 |                         id: 0,
54 |                         message: "领取失败!",
55 |                         showCloseButton: true,
56 |                         type: "error"
57 |                     })
58 |                 }
59 |             });
60 |         }
61 |     };
62 | 
63 |     $(document).ready(function() {
64 |         $("#friend-recommend-link").live(
65 |             "click", Controller.getRecommendFriends
66 |         );
67 |         $("#fetch-login-reward-link").click(Controller.fetchReward);
68 |     });
69 | }(jQuery));
70 | 


--------------------------------------------------------------------------------
/static/app/community/js/community_base.js:
--------------------------------------------------------------------------------
 1 | ;(function($) {
 2 |     'use strict';
 3 | 
 4 |     var Controller = {
 5 |         like: function(event) {
 6 |             var _this = $(this);
 7 |             var topic_id = _this.data("topic-id");
 8 | 
 9 |             $.ajax({
10 |                 url: '/community/topic/like',
11 |                 type: 'POST',
12 |                 dataType: 'json',
13 |                 data: {
14 |                     topic_id: topic_id,
15 |                     _xsrf: getCookie("_xsrf"),
16 |                 },
17 |                 success: function(data, textStatus, jqXHR){
18 |                     if(data.error){
19 |                         Messenger().post({
20 |                             id: 0,
21 |                             message: data.error,
22 |                             showCloseButton: true,
23 |                             type: "error"
24 |                         })
25 |                     }
26 |                     else{
27 |                         var t = '\
28 |                              赞%s\
30 |                             ';
31 |                         var html = sprintf(t, data.like_times)
32 |                         _this.replaceWith(html);
33 |                     }
34 |                 },
35 |                 error: function(jqXHR , textStatus , errorThrown){
36 |                     Messenger().post({
37 |                         id: 0,
38 |                         message: "点赞失败!",
39 |                         showCloseButton: true,
40 |                         type: "error"
41 |                     })
42 |                 }
43 |             });
44 |         }
45 |     };
46 | 
47 |     $(document).ready(function(){
48 |         $(".topic-image").fancybox({
49 |             padding: 5,
50 |             maxWidth: 1000,
51 |             prevEffect: 'none',
52 |             nextEffect: 'none',
53 |             helpers: {
54 |                 thumbs: {
55 |                     width: 50,
56 |                     height: 50
57 |                 }
58 |             }
59 |         });
60 | 
61 |         $(".topic-like-link").live("click", Controller.like);
62 |     });
63 | }(jQuery));
64 | 


--------------------------------------------------------------------------------
/static/app/home/css/friends.css:
--------------------------------------------------------------------------------
 1 | .friends-list{
 2 |     padding: 0 10px
 3 | }
 4 | .friends-list .flat-block{
 5 |     min-height: 640px
 6 | }
 7 | .friends-list .friends-list-inner{
 8 |     padding: 8px
 9 | }
10 | .friends-list .friends-list-inner .action-list{
11 |     display: none;
12 | }
13 | .friends-list .friends-list-inner .action-list li{
14 |     display: inline-block;
15 |     padding: 0 5px
16 | }
17 | .no-friends{
18 |     text-align: center;
19 |     margin-top: 20%
20 | }
21 | 


--------------------------------------------------------------------------------
/static/app/home/css/home.css:
--------------------------------------------------------------------------------
 1 | .sidebar-left{
 2 |     float: left;
 3 |     width: 180px;
 4 | }
 5 | .sidebar-left .summary-numbers{
 6 |     min-height: 130px;
 7 | }
 8 | .sidebar-left .summary-numbers .info{
 9 |     margin-top: 10px;
10 | }
11 | .sidebar-left .summary-numbers .info .number{
12 |     font-size: 16px;
13 | }
14 | .sidebar-left .summary-numbers .info table tr td{
15 |     border-right: 1px solid #eeeeee;
16 | }
17 | .sidebar-left .summary-numbers .info table tr td:last-child{
18 |     border-right-width: 0;
19 | }
20 | 
21 | .sidebar-left .home-navbar{
22 |     margin-top: 10px;
23 |     min-height: 500px;
24 | }
25 | .sidebar-left .home-navbar .home-navbar-item{
26 |     padding: 8px 10px 8px 15px;
27 |     border-bottom: 1px solid #EEE;
28 |     cursor: pointer;
29 | }
30 | .sidebar-left .home-navbar .active{
31 |     border-left: 3px solid #DD4B39;
32 | }
33 | .sidebar-left .home-navbar .message-collapse{
34 |     font-size: 12px;
35 |     padding: 5px 0 0 10px
36 | }
37 | .sidebar-left .home-navbar .message-collapse li:first-child{
38 |     border-top: 1px solid #EEE;
39 |     padding-top: 5px;
40 | }
41 | 
42 | .sidebar-right{
43 |     float: right;;
44 |     width: 180px;
45 | }
46 | 
47 | .main-content{
48 |     width: 600px;
49 |     margin: 0 180px;
50 | }
51 | .main-content .main-content-inner{
52 |     margin:0 10px;
53 | }
54 | 
55 | .recommand-friend-list-part{
56 |     margin-bottom: 15px
57 | }
58 | .recommand-friend-list{
59 |     padding: 8px
60 | }
61 | .recommand-friend-list table tr th,
62 | .recommand-friend-list table tr td{
63 |     padding: 5px 0;
64 |     border-bottom: 1px dashed #ccc;
65 | }
66 | .recommand-friend-list table tr:first-child th,
67 | .recommand-friend-list table tr:first-child td{
68 |     padding-top: 0;
69 | }
70 | .recommand-friend-list table tr:last-child th,
71 | .recommand-friend-list table tr:last-child td{
72 |     padding-bottom: 0;
73 |     border-bottom-width: 0
74 | }
75 | .recommand-friend-list img{
76 |     width: 40px;
77 |     height: 40px;
78 | }
79 | .no-recommand-friend-list{
80 |     min-height: 150px;
81 |     color: #999999;
82 |     text-align: center;
83 |     padding-top: 50px;
84 | }
85 | 


--------------------------------------------------------------------------------
/static/app/home/css/message.css:
--------------------------------------------------------------------------------
 1 | .message-list{
 2 |     padding: 10px;
 3 |     min-height: 589px;
 4 | }
 5 | .message-list .message-list-item td{
 6 |     padding: 10px 0;
 7 | }
 8 | .message-list .message-list-item:first-child td{
 9 |     padding-top: 0;
10 | }
11 | .message-list .message-list-item:last-child td{
12 |     border-bottom-width: 0
13 | }
14 | .message-list .message-list-item img{
15 |     max-width: 200px
16 | }
17 | 
18 | .no-message-list{
19 |     min-height: 640px
20 | }
21 | .no-message-list .no-message-list-inner{
22 |     padding-top: 160px
23 | }
24 | .load-more-message-part{
25 |     margin-top: 10px;
26 | }
27 | 


--------------------------------------------------------------------------------
/static/app/home/js/home.js:
--------------------------------------------------------------------------------
 1 | ;(function($) {
 2 |     "use strict";
 3 | 
 4 |     var Controller = {
 5 |         recommendFriend: function() {
 6 |             $.ajax({
 7 |                 type: "post",
 8 |                 url: "/home/friend/recommend",
 9 |                 data: {
10 |                     _xsrf: getCookie("_xsrf")
11 |                 },
12 |                 dataType: "json",
13 |                 success: function(data, textStatus, jqXHR) {
14 |                     $("#random-recommend-block").replaceWith(data.html);
15 |                 },
16 |                 error: function(jqXHR, textStatus, errorThrown) {}
17 |             });
18 |         }
19 |     };
20 | 
21 |     $(document).ready(function() {
22 |         $(".home-navbar-item").live({
23 |             mouseenter: function() {
24 |                 $($(this).children()[0]).addClass('red-color-force');
25 |             },
26 |             mouseleave: function() {
27 |                 $($(this).children()[0]).removeClass('red-color-force');
28 |             }
29 |         });
30 | 
31 |         $(".message-collapse li").live({
32 |             mouseenter: function() {
33 |                 $($(this).children()[0]).addClass('red-color-force');
34 |             },
35 |             mouseleave: function() {
36 |                 $($(this).children()[0]).removeClass('red-color-force');
37 |             }
38 |         });
39 | 
40 |         $(".home-navbar-item").click(function() {
41 |             window.location.href=$(this).children()[0].href;
42 |         });
43 | 
44 |         $(".message-collapse li").click(function(e){
45 |             window.location.href=$(this).children()[0].href;
46 |             return false;
47 |         });
48 |         $("#friend-recommend-link").live("click", Controller.recommendFriend);
49 |     });
50 | 
51 | }(jQuery));
52 | 


--------------------------------------------------------------------------------
/static/app/home/js/league.js:
--------------------------------------------------------------------------------
 1 | ;(function($) {
 2 |     "use strict";
 3 | 
 4 |     var Controller = {
 5 |         removeMember: function() {
 6 |             var user_id = $(this).data("extra-userid");
 7 |             $.ajax({
 8 |                 type: "post",
 9 |                 url: "/profile/remove-leauge-member",
10 |                 data: {
11 |                     user_id: user_id,
12 |                     _xsrf: getCookie("_xsrf")
13 |                 },
14 |                 dataType: "json",
15 |                 success: function(data, textStatus, jqXHR) {
16 |                     $('#member-' + user_id).remove();
17 |                     if (data.member_number == 0) {
18 |                         $("#member-list-table").html(
19 |                             '
还没有成员加入\ 21 |
' 22 | ); 23 | } 24 | } 25 | }); 26 | } 27 | }; 28 | 29 | $(document).ready(function(){ 30 | $(".remove-member-link").live("click", Controller.removeMember); 31 | }); 32 | }(jQuery)); 33 | -------------------------------------------------------------------------------- /static/app/profile/css/topic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-07 16:17:26 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-06-02 15:16:56 6 | */ 7 | 8 | .topic-list-part{ 9 | margin-bottom: 15px; 10 | } 11 | .topic-list{ 12 | min-height: 647px; 13 | padding: 10px 15px 14 | } 15 | .topic-list table tr td{ 16 | padding: 8px 0; 17 | } 18 | .topic-list table tr:first-child td{ 19 | padding-top: 0; 20 | } 21 | .topic-list table tr:last-child td{ 22 | padding-bottom: 0; 23 | border-bottom-width: 0; 24 | } 25 | .topic-list .topic-title{ 26 | 27 | font-size: 14px; 28 | } 29 | .no-topic-list .no-topic-list-inner{ 30 | padding-top: 100px; 31 | } 32 | 33 | .recommend-topic-list-part{ 34 | min-height: 200px; 35 | } 36 | .recommend-topic-list{ 37 | padding: 10px 38 | } 39 | .recommend-topic-list .recommend-topic-list-item{ 40 | padding: 5px 0; 41 | border-bottom: 1px dashed #ccc; 42 | } 43 | .recommend-topic-list .recommend-topic-list-item:first-child{ 44 | padding-top: 0; 45 | } 46 | .recommend-topic-list .recommend-topic-list-item:last-child{ 47 | padding-bottom: 0; 48 | border-bottom-width: 0 49 | } 50 | .no-recommend-topic-list{ 51 | padding-top: 50px; 52 | } -------------------------------------------------------------------------------- /static/app/profile/js/profile.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/profile/js/profile.js -------------------------------------------------------------------------------- /static/app/profile/js/topic.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/profile/js/topic.js -------------------------------------------------------------------------------- /static/app/search/css/search.css: -------------------------------------------------------------------------------- 1 | .tt-dropdown-menu { 2 | width: 300px; 3 | margin: 2px 0; 4 | list-style: none; 5 | background-color: #ffffff; 6 | -webkit-border-radius: 3px; 7 | -moz-border-radius: 3px; 8 | border-radius: 3px; 9 | -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 10 | -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 11 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 12 | } 13 | .tt-suggestion { 14 | padding: 8px 10px; 15 | } 16 | .tt-suggestion.tt-cursor { 17 | background: rgba(0,0,0,0.02); 18 | cursor: pointer; 19 | } 20 | 21 | .tt-header{ 22 | background: #f6f6f6; 23 | padding: 5px; 24 | border-top: 1px solid #ddd; 25 | border-bottom: 1px solid #ddd; 26 | } 27 | .tt-header-first{ 28 | background: #f6f6f6; 29 | padding: 5px; 30 | -webkit-border-radius: 3px 3px 0 0; 31 | -moz-border-radius: 3px 3px 0 0; 32 | border-radius: 3px 3px 0 0; 33 | border-bottom: 1px solid #ddd; 34 | } 35 | -------------------------------------------------------------------------------- /static/app/search/js/search.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | $(document).ready(function(){ 5 | var user = new Bloodhound({ 6 | datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), 7 | queryTokenizer: Bloodhound.tokenizers.whitespace, 8 | limit: 10, 9 | rateLimitWait: 0, 10 | remote: { 11 | url: "/search?category=user&query=%QUERY" 12 | } 13 | }); 14 | 15 | var topic = new Bloodhound({ 16 | datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), 17 | queryTokenizer: Bloodhound.tokenizers.whitespace, 18 | limit: 10, 19 | rateLimitWait: 0, 20 | remote: { 21 | url: "/search?category=topic&query=%QUERY" 22 | } 23 | }); 24 | 25 | var share = new Bloodhound({ 26 | datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), 27 | queryTokenizer: Bloodhound.tokenizers.whitespace, 28 | limit: 10, 29 | rateLimitWait: 0, 30 | remote: { 31 | url: "/search?category=share&query=%QUERY" 32 | } 33 | }); 34 | 35 | user.initialize(); 36 | topic.initialize(); 37 | share.initialize(); 38 | 39 | $('.search-query').typeahead(null, { 40 | name: 'user', 41 | displayKey: 'user', 42 | source: user.ttAdapter(), 43 | templates: { 44 | header: '
用户
', 45 | suggestion: Handlebars.compile( 46 | ' \ 47 | {{ name }}\ 49 | ' 50 | ) 51 | } 52 | }, { 53 | name: 'topic', 54 | displayKey: 'topic', 55 | source: topic.ttAdapter(), 56 | templates: { 57 | header: '
话题
', 58 | suggestion: Handlebars.compile( 59 | ' {{ title }}\ 61 | ' 62 | ) 63 | } 64 | }, { 65 | name: 'share', 66 | displayKey: 'share', 67 | source: share.ttAdapter(), 68 | templates: { 69 | header: '
分享
', 70 | suggestion: Handlebars.compile( 71 | ' \ 72 | {{ title }}\ 73 | ' 74 | ) 75 | } 76 | }); 77 | 78 | $(".tt-suggestion").live({ 79 | mouseenter: function() { 80 | $($(this).children()[0]).addClass('red-color-force'); 81 | }, 82 | mouseleave: function() { 83 | $($(this).children()[0]).removeClass('red-color-force'); 84 | } 85 | }); 86 | 87 | $(".tt-suggestion").live("click", function() { 88 | window.location.href=$(this).children()[0].href; 89 | }) 90 | }); 91 | }(jQuery)); 92 | -------------------------------------------------------------------------------- /static/app/setting/css/setting-avatar.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-02 09:49:55 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-03-02 10:38:33 6 | */ 7 | 8 | .setting-avatar{ 9 | padding: 10px 20px 20px 20px; 10 | min-height: 602px 11 | } 12 | .setting-avatar .setting-avatar-inner{ 13 | padding: 50px 0 0 110px; 14 | width: 500px 15 | } 16 | .setting-avatar-left{ 17 | width: 350px 18 | } 19 | .setting-avatar-left img{ 20 | width: 350px!important; 21 | max-width: none!important 22 | } 23 | .setting-avatar-actions{ 24 | margin-top: 30px 25 | } 26 | .setting-avatar-preview{ 27 | width:80px; 28 | height:80px; 29 | overflow:hidden; 30 | } 31 | .setting-avatar-preview img{ 32 | max-width: none!important 33 | } -------------------------------------------------------------------------------- /static/app/setting/css/setting-notification.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-05-13 09:06:36 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-05-13 09:06:58 6 | */ 7 | 8 | .setting-notification{ 9 | padding: 10px 20px 20px 20px; 10 | min-height: 602px 11 | } 12 | 13 | .setting-notification form{ 14 | padding: 70px 230px; 15 | width: 500px 16 | } 17 | -------------------------------------------------------------------------------- /static/app/setting/css/setting-password.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-01 17:25:08 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-03-01 17:30:02 6 | */ 7 | 8 | input{ 9 | width: 310px; 10 | } 11 | 12 | .setting-password{ 13 | padding: 10px 20px 20px 20px; 14 | min-height: 602px 15 | } 16 | .setting-password form{ 17 | padding: 70px 50px; 18 | width: 500px 19 | } 20 | -------------------------------------------------------------------------------- /static/app/setting/css/setting-private.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-21 09:13:50 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-03-21 12:11:57 6 | */ 7 | 8 | .setting-private{ 9 | padding: 10px 20px 20px 20px; 10 | min-height: 602px 11 | } 12 | 13 | .setting-private form{ 14 | padding: 70px 230px; 15 | width: 500px 16 | } 17 | -------------------------------------------------------------------------------- /static/app/setting/css/setting-profile-cover.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-01 17:59:29 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-03-01 18:05:02 6 | */ 7 | 8 | .setting-profile-cover-part{ 9 | padding: 10px 20px 20px 20px 10 | } 11 | 12 | .setting-profile-cover-part .form{ 13 | padding: 10px 50px 14 | } 15 | 16 | .setting-profile-cover-item{ 17 | padding: 5px; 18 | border: 1px solid #cccccc 19 | } 20 | 21 | .setting-profile-cover-selected{ 22 | position: relative; 23 | margin-top: -40px; 24 | margin-right: 20px; 25 | } -------------------------------------------------------------------------------- /static/app/setting/css/setting-profile.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-01 16:46:13 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-03-21 12:12:55 6 | */ 7 | 8 | input{ 9 | width: 310px; 10 | } 11 | textarea{ 12 | min-width: 310px; 13 | min-height: 80px; 14 | max-height: 80px; 15 | } 16 | .setting-profile{ 17 | padding: 10px 20px 20px 20px; 18 | min-height: 602px; 19 | } 20 | .setting-profile form{ 21 | padding: 20px 30px 22 | } 23 | -------------------------------------------------------------------------------- /static/app/setting/css/setting-theme.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-06-01 12:00:42 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-06-01 12:12:28 6 | */ 7 | 8 | .setting-theme{ 9 | padding: 10px 20px 20px 20px; 10 | min-height: 602px 11 | } 12 | 13 | .setting-theme form{ 14 | padding: 100px 60px; 15 | width: 500px 16 | } 17 | -------------------------------------------------------------------------------- /static/app/setting/css/setting.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: lime 3 | * @Date: 2014-03-01 08:48:56 4 | * @Last Modified by: lime 5 | * @Last Modified time: 2014-06-01 15:36:22 6 | */ 7 | .main-content{ 8 | position: relative; 9 | float: right; 10 | width: 780px; 11 | } 12 | .main-content .inner{ 13 | padding: 0px 0px 0px 10px; 14 | } 15 | .sidebar{ 16 | position: relative; 17 | float: left; 18 | width: 180px; 19 | } 20 | .setting-navbar{ 21 | min-height: 570px 22 | } 23 | .setting-navbar li{ 24 | font-size: 13px; 25 | padding: 8px 10px 8px 15px; 26 | border-left-width: 3px; 27 | } 28 | .setting-navbar .active{ 29 | border-left: 3px solid #D32; 30 | color: #D32!important; 31 | } 32 | .setting-navbar .active a{ 33 | color: #D32!important; 34 | } 35 | .select2-container{ 36 | margin-right: 10px; 37 | } -------------------------------------------------------------------------------- /static/app/setting/js/setting-notification.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | setNotification: function() { 6 | var email_notify_when_offline = $( 7 | "#email-notify-when-offline" 8 | ).get(0).checked; 9 | 10 | $.ajax({ 11 | type: "post", 12 | url: "/setting/notification/set", 13 | data: { 14 | email_notify_when_offline: email_notify_when_offline, 15 | _xsrf: getCookie("_xsrf") 16 | }, 17 | dataType: "json", 18 | error: function(jqXHR, textStatus, errorThrown) { 19 | Messenger().post({ 20 | id: 0, 21 | message: "设置失败!", 22 | showCloseButton: true, 23 | type: "error" 24 | }); 25 | } 26 | }); 27 | } 28 | }; 29 | 30 | $(document).ready(function() { 31 | var elem = $(".switchery"); 32 | for (var i = 0; i < elem.size(); i++) { 33 | var checkbox = elem.get(i); 34 | var init = new Switchery(checkbox); 35 | checkbox.onchange = Controller.setNotification; 36 | }; 37 | }); 38 | }(jQuery)); 39 | -------------------------------------------------------------------------------- /static/app/setting/js/setting-private.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | setPrivate: function() { 6 | var require_verify_when_add_friend = $( 7 | "#require-verify-when-add-friend").get(0).checked; 8 | var allow_stranger_visiting_profile = $( 9 | "#allow-stranger-visiting-profile").get(0).checked; 10 | var allow_stranger_chat_with_me = $( 11 | "#allow-stranger-chat-with-me").get(0).checked; 12 | var enable_leaving_message = $( 13 | "#enable-leaving-message").get(0).checked; 14 | 15 | $.ajax({ 16 | type: "post", 17 | url: "/setting/private/set", 18 | data: { 19 | require_verify_when_add_friend: require_verify_when_add_friend, 20 | allow_stranger_visiting_profile: allow_stranger_visiting_profile, 21 | allow_stranger_chat_with_me: allow_stranger_chat_with_me, 22 | enable_leaving_message: enable_leaving_message, 23 | _xsrf: getCookie("_xsrf") 24 | }, 25 | dataType: "json", 26 | error: function() { 27 | Messenger().post({ 28 | id: 0, 29 | message: "设置失败!", 30 | showCloseButton: true, 31 | type: "error" 32 | }); 33 | } 34 | }); 35 | } 36 | }; 37 | 38 | $(document).ready(function() { 39 | var elem = $(".switchery"); 40 | 41 | for (var i = 0; i < elem.size(); i++) { 42 | var checkbox = elem.get(i); 43 | var init = new Switchery(checkbox); 44 | checkbox.onchange = Controller.setPrivate; 45 | }; 46 | }); 47 | }(jQuery)); 48 | -------------------------------------------------------------------------------- /static/app/setting/js/setting-profile.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | saveProfile: function() { 6 | var sex = $.trim($('input[name="sex"]:checked').val()); 7 | var province = $.trim($('select[name="province"]').val()); 8 | var city = $.trim($('select[name="city"]').val()); 9 | var birthday = $.trim($('input[name="birthday"]').val()); 10 | var relationship_status = $.trim($("#relationship-status").val()); 11 | var phone = $.trim($('input[name="phone"]').val()); 12 | var qq = $.trim($('input[name="qq"]').val()); 13 | var signature = $.trim($('textarea[name="signature"]').val()); 14 | 15 | if(is_null(signature)){ 16 | signature = "" 17 | } 18 | 19 | $.ajax({ 20 | type: "post", 21 | url: "/setting/profile/set", 22 | data: { 23 | 'sex': sex, 24 | 'province': province, 25 | 'city': city, 26 | 'birthday': birthday, 27 | 'relationship_status': relationship_status, 28 | 'phone': phone, 29 | 'qq': qq, 30 | 'signature': signature, 31 | _xsrf: getCookie("_xsrf") 32 | }, 33 | dataType: "json", 34 | success: function(data, textStatus, jqXHR) { 35 | if(data.error){ 36 | Messenger().post({ 37 | id: 0, 38 | message: data.error, 39 | showCloseButton: true, 40 | type: "error" 41 | }); 42 | return; 43 | } 44 | Messenger().post({ 45 | id: 0, 46 | message: "保存成功!", 47 | showCloseButton: true, 48 | type: "success" 49 | }); 50 | }, 51 | error: function(jqXHR, textStatus, errorThrown) { 52 | Messenger().post({ 53 | id: 0, 54 | message: "保存失败!", 55 | showCloseButton: true, 56 | type: "error" 57 | }); 58 | } 59 | }); 60 | } 61 | }; 62 | 63 | $(document).ready(function() { 64 | $("input").iCheck({ 65 | checkboxClass: 'icheckbox_minimal-red', 66 | radioClass: 'iradio_minimal-red', 67 | }); 68 | 69 | $("#relationship-status").select2({ 70 | width: "324", 71 | placeholder: "请选择", 72 | }); 73 | 74 | $("#save-profile-button").click(Controller.saveProfile); 75 | }); 76 | }(jQuery)); 77 | -------------------------------------------------------------------------------- /static/app/setting/js/setting-theme.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | setTheme: function() { 6 | var theme = $('select[name="theme"]').val(); 7 | 8 | $.ajax({ 9 | type: "post", 10 | url: "/setting/theme/set", 11 | data: { 12 | theme: theme, 13 | _xsrf: getCookie("_xsrf"), 14 | }, 15 | dataType: "json", 16 | success: function(data, textStatus, jqXHR) { 17 | Messenger().post({ 18 | id: 0, 19 | message: "保存成功!", 20 | showCloseButton: true, 21 | type: "success" 22 | }); 23 | window.location.reload(); 24 | }, 25 | error: function(jqXHR, textStatus, errorThrown) { 26 | Messenger().post({ 27 | id: 0, 28 | message: "保存失败!", 29 | showCloseButton: true, 30 | type: "error" 31 | }); 32 | } 33 | }); 34 | } 35 | }; 36 | 37 | $(document).ready(function(){ 38 | $("#save-theme-button").click(Controller.setTheme); 39 | }); 40 | }(jQuery)); 41 | -------------------------------------------------------------------------------- /static/app/share/css/share-base.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | position: relative; 3 | float: right; 4 | width: 755px; 5 | } 6 | .main-content .main-content-inner{ 7 | padding-left: 10px; 8 | } 9 | .main-content-item{ 10 | padding: 15px; 11 | margin-bottom: 10px 12 | } 13 | .main-content-item .main-content-item-header{ 14 | padding-bottom: 8px 15 | } 16 | 17 | .sidebar{ 18 | position: relative; 19 | float: left; 20 | width: 205px; 21 | } 22 | .sidebar .sidebar-item{ 23 | margin-bottom: 10px; 24 | padding: 15px 25 | } 26 | .sidebar .sidebar-item .sidebar-item-header{ 27 | padding-bottom: 8px; 28 | } 29 | 30 | .share-navbar li{ 31 | padding: 8px 0 8px 2px; 32 | font-size: 14px; 33 | } 34 | .share-navbar li:first-child{ 35 | padding-top: 8px; 36 | } 37 | .share-navbar li:last-child{ 38 | padding-bottom: 0px; 39 | border-bottom-width: 0 40 | } 41 | .share-navbar li:hover{ 42 | background: rgba(0,0,0,0.04); 43 | cursor: pointer; 44 | } 45 | .tags li{ 46 | margin: 0 7px 6px 0; 47 | } 48 | 49 | .share-modal{ 50 | width: 415px; 51 | padding: 20px 52 | } 53 | 54 | .share-modal .share-field{ 55 | margin-top: 10px 56 | } 57 | 58 | input[name="title"]{ 59 | width: 400px 60 | } 61 | input[name="tags"]{ 62 | width: 415px 63 | } 64 | input[name="cost"]{ 65 | width: 220px 66 | } 67 | 68 | .download-button{ 69 | display: inline-block; 70 | font-size: 13px; 71 | color: #fff; 72 | font-family: inherit; 73 | padding: 4px 12px; 74 | margin-bottom: 0; 75 | line-height: 1.1; 76 | background-color: #825d5b; 77 | border: 1px solid #825d5b; 78 | -webkit-border-radius: 2px; 79 | border-radius: 2px; 80 | cursor: pointer; 81 | vertical-align: middle; 82 | text-decoration: none; 83 | } 84 | .download-button:hover{ 85 | color: #ddd; 86 | } 87 | -------------------------------------------------------------------------------- /static/app/share/css/share-category.css: -------------------------------------------------------------------------------- 1 | .share-table tr:nth-child(odd){ 2 | border-top: 1px solid #eee; 3 | } 4 | .share-table tr:first-child { 5 | border-top-width: 0; 6 | } 7 | .share-table tr td{ 8 | padding: 10px 0 9 | } 10 | .no-share-table{ 11 | min-height: 300px; 12 | padding-top: 200px; 13 | } 14 | -------------------------------------------------------------------------------- /static/app/share/css/share-one.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | position: relative; 3 | float: left; 4 | width: 680px; 5 | } 6 | .sidebar { 7 | position: relative; 8 | float: right; 9 | width: 280px; 10 | } 11 | .sidebar .sidebar-inner { 12 | padding: 0px 0px 0px 10px; 13 | } 14 | 15 | .share{ 16 | padding: 15px 17 | } 18 | .share .share-file-type{ 19 | width: 150px 20 | } 21 | .share .share-main{ 22 | width: 500px 23 | } 24 | .share .share-main .share-name{ 25 | width: 450px 26 | } 27 | .share .share-main .share-uploader{ 28 | width: 50px 29 | } 30 | .share .share-actions{ 31 | margin-top: 10px 32 | } 33 | 34 | .share-comment-list{ 35 | margin-top: 40px; 36 | padding: 0 15px; 37 | } 38 | .share-comment-list .share-comment-item{ 39 | padding: 10px 0; 40 | } 41 | .share-comment-list .share-comment-item:first-child{ 42 | padding-top: 15px; 43 | } 44 | .share-comment-list .share-comment-item:last-child{ 45 | padding-bottom: 15px; 46 | border-bottom-width: 0 47 | } 48 | .share-comment-list .share-comment-item .share-author-avatar{ 49 | width: 40px 50 | } 51 | .share-comment-list .share-comment-item .share-main{ 52 | width: 605px 53 | } 54 | .share-comment-list .share-comment-item .share-publish-time{ 55 | margin-left: 5px 56 | } 57 | .share-comment-list .share-comment-item .share-main .share-comment-action{ 58 | display: none; 59 | margin-right: 10px 60 | } 61 | .share-comment-list .share-comment-item .share-main .share-comment-content{ 62 | padding: 5px 30px 0 0 63 | } 64 | 65 | .submit-comment-part{ 66 | margin-top: 10px 67 | } 68 | .submit-comment-part .anonymous-checkbox{ 69 | width: 200px 70 | } 71 | 72 | .like-list-part{ 73 | margin-bottom: 15px 74 | } 75 | .like-list{ 76 | min-height: 100px; 77 | padding: 5px 78 | } 79 | .like-list table th, 80 | .like-list table td{ 81 | padding: 5px 0; 82 | } 83 | .like-list-none{ 84 | text-align: center; 85 | margin-top: 20px 86 | } 87 | 88 | .similar-share-list{ 89 | min-height: 200px; 90 | } 91 | .similar-share-list li{ 92 | border-bottom: 1px dashed #ccc; 93 | padding: 5px 0; 94 | } 95 | .similar-share-list li:last-child{ 96 | border-bottom-width: 0px; 97 | } 98 | .similar-share-list li:first-child{ 99 | padding-top: 10px; 100 | } 101 | .recommend-share-list{ 102 | padding: 0 10px!important 103 | } 104 | .no-recommend-share-list{ 105 | min-height: 150px; 106 | } 107 | .no-recommend-share-list .no-recommend-share-list-inner{ 108 | padding-top: 50px; 109 | } 110 | -------------------------------------------------------------------------------- /static/app/share/css/share.css: -------------------------------------------------------------------------------- 1 | .recommend-share-table tr{ 2 | } 3 | .recommend-share-table tr:last-child{ 4 | border-bottom-width: 0 5 | } 6 | .recommend-share-table tr td{ 7 | padding: 10px 0 8 | } 9 | .no-share-table{ 10 | min-height: 200px; 11 | padding-top: 100px 12 | } 13 | 14 | .uploader-table tr td{ 15 | padding: 5px 0 16 | } 17 | .uploader-table tr:first-child td{ 18 | padding-top: 10px 19 | } 20 | .uploader-table tr:last-child td{ 21 | padding-bottom: 10px 22 | } 23 | -------------------------------------------------------------------------------- /static/app/share/img/category/bittorrent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/bittorrent.png -------------------------------------------------------------------------------- /static/app/share/img/category/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/book.png -------------------------------------------------------------------------------- /static/app/share/img/category/course.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/course.png -------------------------------------------------------------------------------- /static/app/share/img/category/document.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/document.png -------------------------------------------------------------------------------- /static/app/share/img/category/homework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/homework.png -------------------------------------------------------------------------------- /static/app/share/img/category/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/music.png -------------------------------------------------------------------------------- /static/app/share/img/category/music1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/music1.png -------------------------------------------------------------------------------- /static/app/share/img/category/music2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/music2.png -------------------------------------------------------------------------------- /static/app/share/img/category/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/note.png -------------------------------------------------------------------------------- /static/app/share/img/category/notebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/notebook.png -------------------------------------------------------------------------------- /static/app/share/img/category/paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/paper.png -------------------------------------------------------------------------------- /static/app/share/img/category/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/picture.png -------------------------------------------------------------------------------- /static/app/share/img/category/software.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/software.png -------------------------------------------------------------------------------- /static/app/share/img/category/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/category/video.png -------------------------------------------------------------------------------- /static/app/share/img/icons/accdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/accdb.png -------------------------------------------------------------------------------- /static/app/share/img/icons/avi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/avi.png -------------------------------------------------------------------------------- /static/app/share/img/icons/bmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/bmp.png -------------------------------------------------------------------------------- /static/app/share/img/icons/css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/css.png -------------------------------------------------------------------------------- /static/app/share/img/icons/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/doc.png -------------------------------------------------------------------------------- /static/app/share/img/icons/docx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/docx.png -------------------------------------------------------------------------------- /static/app/share/img/icons/eml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/eml.png -------------------------------------------------------------------------------- /static/app/share/img/icons/eps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/eps.png -------------------------------------------------------------------------------- /static/app/share/img/icons/fla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/fla.png -------------------------------------------------------------------------------- /static/app/share/img/icons/gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/gif.png -------------------------------------------------------------------------------- /static/app/share/img/icons/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/html.png -------------------------------------------------------------------------------- /static/app/share/img/icons/ind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/ind.png -------------------------------------------------------------------------------- /static/app/share/img/icons/ini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/ini.png -------------------------------------------------------------------------------- /static/app/share/img/icons/jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/jpg.png -------------------------------------------------------------------------------- /static/app/share/img/icons/jsf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/jsf.png -------------------------------------------------------------------------------- /static/app/share/img/icons/midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/midi.png -------------------------------------------------------------------------------- /static/app/share/img/icons/mov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/mov.png -------------------------------------------------------------------------------- /static/app/share/img/icons/mp3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/mp3.png -------------------------------------------------------------------------------- /static/app/share/img/icons/mpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/mpeg.png -------------------------------------------------------------------------------- /static/app/share/img/icons/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/pdf.png -------------------------------------------------------------------------------- /static/app/share/img/icons/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/png.png -------------------------------------------------------------------------------- /static/app/share/img/icons/ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/ppt.png -------------------------------------------------------------------------------- /static/app/share/img/icons/pptx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/pptx.png -------------------------------------------------------------------------------- /static/app/share/img/icons/proj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/proj.png -------------------------------------------------------------------------------- /static/app/share/img/icons/psd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/psd.png -------------------------------------------------------------------------------- /static/app/share/img/icons/pst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/pst.png -------------------------------------------------------------------------------- /static/app/share/img/icons/pub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/pub.png -------------------------------------------------------------------------------- /static/app/share/img/icons/rar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/rar.png -------------------------------------------------------------------------------- /static/app/share/img/icons/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/readme.png -------------------------------------------------------------------------------- /static/app/share/img/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/settings.png -------------------------------------------------------------------------------- /static/app/share/img/icons/tiff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/tiff.png -------------------------------------------------------------------------------- /static/app/share/img/icons/txt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/txt.png -------------------------------------------------------------------------------- /static/app/share/img/icons/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/url.png -------------------------------------------------------------------------------- /static/app/share/img/icons/vsd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/vsd.png -------------------------------------------------------------------------------- /static/app/share/img/icons/wav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/wav.png -------------------------------------------------------------------------------- /static/app/share/img/icons/wma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/wma.png -------------------------------------------------------------------------------- /static/app/share/img/icons/wmv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/wmv.png -------------------------------------------------------------------------------- /static/app/share/img/icons/zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/img/icons/zip.png -------------------------------------------------------------------------------- /static/app/share/js/share-category.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/js/share-category.js -------------------------------------------------------------------------------- /static/app/share/js/share-new.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | $(document).ready(function(){ 5 | 6 | $("input").iCheck({ 7 | checkboxClass: 'icheckbox_minimal-red', 8 | radioClass: 'iradio_minimal', 9 | }); 10 | 11 | $(".wysiwyg-editor").wysiwyg({ 12 | hotKeys: {} 13 | }); 14 | 15 | $('select[name="category"]').select2({ 16 | width: 415, 17 | placeholder: '请选择类别' 18 | }); 19 | }); 20 | 21 | }(jQuery)); 22 | -------------------------------------------------------------------------------- /static/app/share/js/share.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/share/js/share.js -------------------------------------------------------------------------------- /static/app/share/js/share_.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | "use strict"; 3 | 4 | var Controller = { 5 | likeShare: function() { 6 | var _this = $(this); 7 | var share_id = _this.data("share-id"); 8 | 9 | $.ajax({ 10 | url: '/share/like', 11 | type: 'POST', 12 | dataType: 'json', 13 | data: { 14 | share_id: share_id, 15 | _xsrf: getCookie("_xsrf"), 16 | }, 17 | success: function(data){ 18 | if(data.error){ 19 | Messenger().post({ 20 | id: 0, 21 | message: data.error, 22 | showCloseButton: true, 23 | type: "error" 24 | }) 25 | return; 26 | } 27 | 28 | var html = sprintf( 29 | ' 赞%s', 31 | data.like_times 32 | ); 33 | _this.replaceWith(html); 34 | }, 35 | error: function() { 36 | Messenger().post({ 37 | id: 0, 38 | message: "点赞失败!", 39 | showCloseButton: true, 40 | type: "error" 41 | }) 42 | } 43 | }); 44 | }, 45 | downloadShare: function() { 46 | var _this = $(this); 47 | var cost = parseInt(_this.data("cost")); 48 | var share_id = _this.data("share-id"); 49 | 50 | if(cost && cost > 0){ 51 | alertify.set({labels: { 52 | ok: "确定", 53 | cancel : "取消" 54 | }}); 55 | 56 | alertify.confirm(sprintf( 57 | "下载将会花费你 %s 金币,你确定要下载?", cost 58 | ), function (e){ 59 | 60 | if(e) { 61 | window.location.href = sprintf( 62 | "/share/download/%s", share_id 63 | ); 64 | } 65 | }); 66 | } 67 | else { 68 | window.location.href=sprintf("/share/download/%s", share_id); 69 | } 70 | } 71 | }; 72 | 73 | $(document).ready(function(){ 74 | $(".share-like-link").live("click", Controller.likeShare); 75 | $(".share-download-button").click(Controller.downloadShare); 76 | }); 77 | 78 | }(jQuery)); 79 | -------------------------------------------------------------------------------- /static/app/user/css/login.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-image: url(""); 3 | } 4 | .main-content{ 5 | background-image: url("/static/img/landing.jpg"); 6 | background-size: cover; 7 | background-repeat: no-repeat; 8 | } 9 | .main-content .main-content-inner{ 10 | height: 450px; 11 | padding-top: 100px; 12 | } 13 | .main-content .main-content-inner .slogan{ 14 | -webkit-font-smoothing: antialiased; 15 | font-size: 48px; 16 | padding-top: 70px; 17 | color: #fff; 18 | } 19 | .main-content .main-content-inner .slogan .slogan-content{ 20 | margin-top: 50px; 21 | font-size: 20px; 22 | word-break: break-word; 23 | } 24 | .main-content .main-content-inner .login-form{ 25 | padding-top: 40px; 26 | color: #ffffff; 27 | text-align: center 28 | } 29 | .main-content .main-content-inner .login-form .login-item{ 30 | margin-top: 20px; 31 | } 32 | .main-content .main-content-inner .login-form .login-item:first-child{ 33 | margin-top: 0px; 34 | } 35 | .main-content .main-content-inner .login-form .login-item input { 36 | font-size: 14px; 37 | padding: 10px 12px; 38 | width: 275px; 39 | } 40 | 41 | .register-list{ 42 | list-style: none; 43 | } 44 | .register-list li{ 45 | display: inline-block; 46 | margin: 2px 0px!important; 47 | } 48 | 49 | .register-form{ 50 | width: 400px; 51 | min-height: 200px 52 | } 53 | .register-form .register-form-inner{ 54 | padding: 30px 55 | } 56 | .register-form .register-form-inner .register-field{ 57 | margin-top: 20px 58 | } 59 | .register-form .register-form-inner .register-field input { 60 | width: 325px; 61 | } 62 | .register-form .register-form-inner .register-actions{ 63 | margin-top: 50px 64 | } 65 | 66 | .hot-topic-table td{ 67 | padding: 4px 0; 68 | } 69 | .hot-topic-table tr td:first-child{ 70 | padding-right: 8px; 71 | } 72 | 73 | .white-color, 74 | .white-color:focus, 75 | .white-color:visited{ 76 | color: #f1f1f1; 77 | } 78 | .white-color:hover{ 79 | color: #ffffff; 80 | } 81 | -------------------------------------------------------------------------------- /static/app/user/css/password-reset.css: -------------------------------------------------------------------------------- 1 | .password-reset-form{ 2 | padding-top: 250px 3 | } 4 | .password-reset-form .password-reset-actions{ 5 | margin-top: 15px; 6 | } 7 | -------------------------------------------------------------------------------- /static/app/user/img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/user/img/1.png -------------------------------------------------------------------------------- /static/app/user/img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/app/user/img/2.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/favicon.ico -------------------------------------------------------------------------------- /static/icons/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/icons/girl.png -------------------------------------------------------------------------------- /static/icons/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/icons/loading.gif -------------------------------------------------------------------------------- /static/icons/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/icons/man.png -------------------------------------------------------------------------------- /static/icons/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/icons/right.png -------------------------------------------------------------------------------- /static/img/black-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/black-bg.gif -------------------------------------------------------------------------------- /static/img/black-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/black-bg.jpg -------------------------------------------------------------------------------- /static/img/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/coin.png -------------------------------------------------------------------------------- /static/img/default.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/default.jpg -------------------------------------------------------------------------------- /static/img/landing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/landing.jpg -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/logo.png -------------------------------------------------------------------------------- /static/img/node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/node.png -------------------------------------------------------------------------------- /static/img/profile-cover/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/1.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/10.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/11.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/12.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/13.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/14.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/15.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/16.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/17.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/18.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/19.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/2.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/20.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/21.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/22.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/23.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/24.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/3.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/4.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/5.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/6.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/7.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/8.jpg -------------------------------------------------------------------------------- /static/img/profile-cover/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/profile-cover/9.jpg -------------------------------------------------------------------------------- /static/img/white-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/static/img/white-bg.png -------------------------------------------------------------------------------- /young/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shiyanhui/Young/184d2d85831b0c54db1002fad9ac1bce98738bf6/young/__init__.py -------------------------------------------------------------------------------- /young/setting.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | 5 | ROOT_LOCATION = os.path.dirname(os.path.dirname(__file__)) 6 | 7 | APPLICATION_SETTINGS = { 8 | # NOTE: please use nginx to serve static file in production environment 9 | 'static_path': ROOT_LOCATION + '/static/', 10 | 'login_url': '/login', 11 | 'xsrf_cookies': True, 12 | # 生成方式 base64.b64encode(str(uuid.uuid5(uuid.NAMESPACE_DNS, str(uuid.uuid4())))) 13 | 'cookie_secret': 'Y2VjMTM4MzYtYWM4MC01Zjc3LWJiYmEtN2MxODQxNmIyMzky', 14 | } 15 | 16 | EMAIL_SETTINGS = { 17 | "host": "localhost", 18 | "port": 25, 19 | "robot": "root@mail.beyoung.io", 20 | 'url': 'http://beyoung.io', 21 | } 22 | -------------------------------------------------------------------------------- /young/urlmap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import app.base.urlmap 4 | import app.chat.urlmap 5 | import app.community.urlmap 6 | import app.home.urlmap 7 | import app.message.urlmap 8 | import app.profile.urlmap 9 | import app.search.urlmap 10 | import app.setting.urlmap 11 | import app.share.urlmap 12 | import app.user.urlmap 13 | 14 | from young.handler import BaseHandler 15 | 16 | urlpattern = () 17 | 18 | urlpattern += app.base.urlmap.urlpattern 19 | urlpattern += app.chat.urlmap.urlpattern 20 | urlpattern += app.community.urlmap.urlpattern 21 | urlpattern += app.home.urlmap.urlpattern 22 | urlpattern += app.message.urlmap.urlpattern 23 | urlpattern += app.profile.urlmap.urlpattern 24 | urlpattern += app.search.urlmap.urlpattern 25 | urlpattern += app.setting.urlmap.urlpattern 26 | urlpattern += app.share.urlmap.urlpattern 27 | urlpattern += app.user.urlmap.urlpattern 28 | 29 | urlpattern += ( 30 | (r'.*', BaseHandler), 31 | ) 32 | --------------------------------------------------------------------------------