├── demo.js ├── webpack ├── webpack.config.test.js ├── webpack.config.production.js ├── webpack.config.development.js └── webpack.config.base.js ├── sass ├── booklist.scss ├── app.scss ├── all.scss ├── fonts │ └── bootstrap │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 ├── bootstrap │ ├── bootstrap │ │ ├── mixins │ │ │ ├── _center-block.scss │ │ │ ├── _opacity.scss │ │ │ ├── _size.scss │ │ │ ├── _text-overflow.scss │ │ │ ├── _tab-focus.scss │ │ │ ├── _labels.scss │ │ │ ├── _resize.scss │ │ │ ├── _progress-bar.scss │ │ │ ├── _text-emphasis.scss │ │ │ ├── _reset-filter.scss │ │ │ ├── _nav-divider.scss │ │ │ ├── _background-variant.scss │ │ │ ├── _alerts.scss │ │ │ ├── _nav-vertical-align.scss │ │ │ ├── _reset-text.scss │ │ │ ├── _border-radius.scss │ │ │ ├── _pagination.scss │ │ │ ├── _responsive-visibility.scss │ │ │ ├── _panels.scss │ │ │ ├── _hide-text.scss │ │ │ ├── _clearfix.scss │ │ │ ├── _list-group.scss │ │ │ ├── _table-row.scss │ │ │ ├── _image.scss │ │ │ ├── _buttons.scss │ │ │ ├── _grid-framework.scss │ │ │ ├── _forms.scss │ │ │ ├── _grid.scss │ │ │ └── _gradients.scss │ │ ├── _wells.scss │ │ ├── _responsive-embed.scss │ │ ├── _breadcrumbs.scss │ │ ├── _close.scss │ │ ├── _component-animations.scss │ │ ├── _utilities.scss │ │ ├── _thumbnails.scss │ │ ├── _pager.scss │ │ ├── _mixins.scss │ │ ├── _media.scss │ │ ├── _jumbotron.scss │ │ ├── _labels.scss │ │ ├── _badges.scss │ │ ├── _code.scss │ │ ├── _grid.scss │ │ ├── _alerts.scss │ │ ├── _progress-bars.scss │ │ ├── _pagination.scss │ │ ├── _print.scss │ │ ├── _tooltip.scss │ │ ├── _list-group.scss │ │ ├── _scaffolding.scss │ │ ├── _popovers.scss │ │ └── _modals.scss │ ├── _bootstrap-sprockets.scss │ ├── _bootstrap-compass.scss │ ├── _bootstrap-mincer.scss │ └── _bootstrap.scss └── footer.scss ├── .gitignore ├── src ├── common │ ├── components │ │ ├── ui │ │ │ ├── tabs │ │ │ │ ├── helpers │ │ │ │ │ ├── styles.js │ │ │ │ │ ├── uuid.js │ │ │ │ │ └── childrenPropType.js │ │ │ │ ├── index.js │ │ │ │ └── components │ │ │ │ │ ├── TabPanel.js │ │ │ │ │ ├── TabList.js │ │ │ │ │ └── Tab.js │ │ │ ├── topicDetail │ │ │ │ ├── widgets │ │ │ │ │ ├── Avator.js │ │ │ │ │ ├── Answers.js │ │ │ │ │ ├── Button.js │ │ │ │ │ ├── Operation.js │ │ │ │ │ └── Vote.js │ │ │ │ ├── Tag.js │ │ │ │ ├── TagList.js │ │ │ │ ├── Question.js │ │ │ │ ├── Answers.js │ │ │ │ └── TopHeader.js │ │ │ ├── sidebar │ │ │ │ ├── HotTopic.js │ │ │ │ └── Sidebar.js │ │ │ └── common │ │ │ │ ├── FromNow.js │ │ │ │ ├── Button.js │ │ │ │ └── Loading.js │ │ ├── topic │ │ │ ├── NoTopic.js │ │ │ ├── CategoryItem.js │ │ │ ├── CategoryBar.js │ │ │ ├── Topics.js │ │ │ └── Topic.js │ │ ├── layout │ │ │ ├── Sidebar.js │ │ │ ├── MasterLayout.js │ │ │ ├── ExitHeader.js │ │ │ ├── Header.js │ │ │ └── Footer.js │ │ ├── appui │ │ │ └── ErrMsg.js │ │ ├── 404.js │ │ ├── booklist │ │ │ ├── BookList.js │ │ │ └── BookItem.js │ │ ├── Home.js │ │ └── Topic.js │ ├── reducers │ │ ├── version.js │ │ ├── layout.js │ │ ├── index.js │ │ ├── book.js │ │ ├── publish.js │ │ ├── login.js │ │ ├── topicDetail.js │ │ ├── user.js │ │ ├── reddit.js │ │ └── topic.js │ ├── constants │ │ └── topic.js │ ├── api │ │ ├── fetchComponentDataBeforeRender.js │ │ ├── immutifyState.js │ │ ├── promiseMiddleware.js │ │ └── makeRouteHooksSafe.js │ ├── tools │ │ └── DevTools.js │ ├── actions │ │ ├── book.js │ │ ├── layout.js │ │ ├── publish.js │ │ ├── login.js │ │ ├── user.js │ │ ├── topicDetail.js │ │ └── topic.js │ ├── containers │ │ ├── LoginPage.js │ │ ├── PublishPage.js │ │ ├── BookPage.js │ │ ├── TopicDetailPage.js │ │ ├── TopicPage.js │ │ └── App.js │ ├── routes.js │ └── store │ │ └── configureStore.js ├── server │ ├── constants │ │ ├── tag.js │ │ ├── comment.js │ │ └── index.js │ ├── config │ │ ├── credentials.js │ │ ├── email │ │ │ └── messagetpl.js │ │ ├── database.js │ │ ├── webpack.js │ │ ├── config.js │ │ └── express.js │ ├── index.js │ ├── controllers │ │ ├── logout.js │ │ ├── index.js │ │ ├── tag.js │ │ ├── login.js │ │ ├── user.js │ │ ├── ask.js │ │ ├── register.js │ │ └── topics.js │ ├── server.js │ ├── middlewares │ │ ├── loginAuth.js │ │ └── renderPage-middleware.js │ ├── lib │ │ ├── utils.js │ │ └── emailHelper.js │ └── models │ │ ├── User.js │ │ ├── Tag.js │ │ ├── TopicTagRelation.js │ │ └── Answers.js └── client │ └── index.js ├── .bowerrc ├── public ├── img │ ├── 404.jpg │ ├── 404.png │ ├── PDF.png │ ├── bd24.png │ ├── btn24.png │ ├── search.png │ ├── user-64.png │ ├── carousel.png │ ├── footer-bg.png │ ├── log-logo.png │ ├── vote-bg@2x.png │ ├── flash-bg-02.jpg │ ├── logo.c1968f51.png │ └── logo-w.svg └── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── .babelrc ├── README.md ├── bower.json ├── .editorconfig ├── webpack.config.js ├── LICENSE └── package.json /demo.js: -------------------------------------------------------------------------------- 1 | console.log("xxx"); 2 | -------------------------------------------------------------------------------- /webpack/webpack.config.test.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sass/booklist.scss: -------------------------------------------------------------------------------- 1 | /** BookList scss */ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/components 3 | .sass-cache -------------------------------------------------------------------------------- /sass/app.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | margin-bottom: 0; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/helpers/styles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "public/scripts", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /sass/all.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap/_bootstrap"; 2 | @import 'app.scss'; 3 | @import 'footer.scss'; 4 | -------------------------------------------------------------------------------- /public/img/404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/404.jpg -------------------------------------------------------------------------------- /public/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/404.png -------------------------------------------------------------------------------- /public/img/PDF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/PDF.png -------------------------------------------------------------------------------- /public/img/bd24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/bd24.png -------------------------------------------------------------------------------- /public/img/btn24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/btn24.png -------------------------------------------------------------------------------- /public/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/search.png -------------------------------------------------------------------------------- /public/img/user-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/user-64.png -------------------------------------------------------------------------------- /public/img/carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/carousel.png -------------------------------------------------------------------------------- /public/img/footer-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/footer-bg.png -------------------------------------------------------------------------------- /public/img/log-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/log-logo.png -------------------------------------------------------------------------------- /public/img/vote-bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/vote-bg@2x.png -------------------------------------------------------------------------------- /src/common/reducers/version.js: -------------------------------------------------------------------------------- 1 | export default function version(state = 0, action) { 2 | return state; 3 | } 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | stage:0, 3 | "optional": ["es7.decorators", "es7.classProperties", "es7.objectRestSpread"] 4 | } 5 | -------------------------------------------------------------------------------- /public/img/flash-bg-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/flash-bg-02.jpg -------------------------------------------------------------------------------- /public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/img/logo.c1968f51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/img/logo.c1968f51.png -------------------------------------------------------------------------------- /src/server/constants/tag.js: -------------------------------------------------------------------------------- 1 | export const TAG = { 2 | STATUS:{ 3 | DELETE:0, 4 | ENABLE:1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-bookstore 2 | React+Redux+Expressjs+MongoDB+Bootstrap 同构应用 3 | #启动 4 | ```npm install``` 5 | ```npm run dev``` 6 | -------------------------------------------------------------------------------- /src/server/constants/comment.js: -------------------------------------------------------------------------------- 1 | export const COMMENT = { 2 | STATUS:{ 3 | DELETE:0, 4 | ENABLE:1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /sass/fonts/bootstrap/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/sass/fonts/bootstrap/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /sass/fonts/bootstrap/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/sass/fonts/bootstrap/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /sass/fonts/bootstrap/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/sass/fonts/bootstrap/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /sass/fonts/bootstrap/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BSFullStack/isomorphic-redux-app/HEAD/sass/fonts/bootstrap/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/common/components/ui/tabs/helpers/uuid.js: -------------------------------------------------------------------------------- 1 | // Get a universally unique identifier 2 | let count = 0; 3 | module.exports = function uuid() { 4 | return count++; 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/constants/topic.js: -------------------------------------------------------------------------------- 1 | //topic的状态 2 | export const TOPIC = { 3 | STATUS:{ 4 | "0":"no-answer", 5 | "1":"answered", 6 | "2":"solved" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_center-block.scss: -------------------------------------------------------------------------------- 1 | // Center-align a block level element 2 | 3 | @mixin center-block() { 4 | display: block; 5 | margin-left: auto; 6 | margin-right: auto; 7 | } 8 | -------------------------------------------------------------------------------- /src/server/config/credentials.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 保存账号信息 3 | */ 4 | export default { 5 | gmail:{ 6 | user:"18301590621@163.com", 7 | password:"nktzsnqpnvurdtsl" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/server/constants/index.js: -------------------------------------------------------------------------------- 1 | export const USER = { 2 | ROLE:{ 3 | ADMIN:2, 4 | NORMAL:1 5 | }, 6 | STATUS:{ 7 | LOCK:0, 8 | ACTIVE:1 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_opacity.scss: -------------------------------------------------------------------------------- 1 | // Opacity 2 | 3 | @mixin opacity($opacity) { 4 | opacity: $opacity; 5 | // IE8 filter 6 | $opacity-ie: ($opacity * 100); 7 | filter: alpha(opacity=$opacity-ie); 8 | } 9 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webcy", 3 | "version": "0.0.1", 4 | "ignore": [ 5 | "**/.*", 6 | "node_modules", 7 | "components" 8 | ], 9 | "dependencies": { 10 | "simditor":"" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_size.scss: -------------------------------------------------------------------------------- 1 | // Sizing shortcuts 2 | 3 | @mixin size($width, $height) { 4 | width: $width; 5 | height: $height; 6 | } 7 | 8 | @mixin square($size) { 9 | @include size($size, $size); 10 | } 11 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | require('babel/register') 2 | var config = require('./config/config'); 3 | var server = require('./server')(config); 4 | server.listen(config.port, function () { 5 | console.log('服务器启动成功!端口号: ' + config.port); 6 | }); 7 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_text-overflow.scss: -------------------------------------------------------------------------------- 1 | // Text overflow 2 | // Requires inline-block or block for proper styling 3 | 4 | @mixin text-overflow() { 5 | overflow: hidden; 6 | text-overflow: ellipsis; 7 | white-space: nowrap; 8 | } 9 | -------------------------------------------------------------------------------- /sass/bootstrap/_bootstrap-sprockets.scss: -------------------------------------------------------------------------------- 1 | @function twbs-font-path($path) { 2 | @return font-path($path); 3 | } 4 | 5 | @function twbs-image-path($path) { 6 | @return image-path($path); 7 | } 8 | 9 | $bootstrap-sass-asset-helper: true; 10 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_tab-focus.scss: -------------------------------------------------------------------------------- 1 | // WebKit-style focus 2 | 3 | @mixin tab-focus() { 4 | // Default 5 | outline: thin dotted; 6 | // WebKit 7 | outline: 5px auto -webkit-focus-ring-color; 8 | outline-offset: -2px; 9 | } 10 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Tabs: require('./components/Tabs'), 3 | TabList: require('./components/TabList'), 4 | Tab: require('./components/Tab'), 5 | TabPanel: require('./components/TabPanel') 6 | }; 7 | -------------------------------------------------------------------------------- /sass/bootstrap/_bootstrap-compass.scss: -------------------------------------------------------------------------------- 1 | @function twbs-font-path($path) { 2 | @return font-url($path, true); 3 | } 4 | 5 | @function twbs-image-path($path) { 6 | @return image-url($path, true); 7 | } 8 | 9 | $bootstrap-sass-asset-helper: true; 10 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_labels.scss: -------------------------------------------------------------------------------- 1 | // Labels 2 | 3 | @mixin label-variant($color) { 4 | background-color: $color; 5 | 6 | &[href] { 7 | &:hover, 8 | &:focus { 9 | background-color: darken($color, 10%); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_resize.scss: -------------------------------------------------------------------------------- 1 | // Resize anything 2 | 3 | @mixin resizable($direction) { 4 | resize: $direction; // Options: horizontal, vertical, both 5 | overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible` 6 | } 7 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_progress-bar.scss: -------------------------------------------------------------------------------- 1 | // Progress bars 2 | 3 | @mixin progress-bar-variant($color) { 4 | background-color: $color; 5 | 6 | // Deprecated parent class requirement as of v3.2.0 7 | .progress-striped & { 8 | @include gradient-striped; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/server/config/email/messagetpl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通知类邮件模板 3 | */ 4 | export function sendMessage(metaData){ 5 | const { title ,link } = metaData; 6 | return ` 7 |

${title}

8 |

直达号:${link}

9 | 10 | `; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_text-emphasis.scss: -------------------------------------------------------------------------------- 1 | // Typography 2 | 3 | // [converter] $parent hack 4 | @mixin text-emphasis-variant($parent, $color) { 5 | #{$parent} { 6 | color: $color; 7 | } 8 | a#{$parent}:hover, 9 | a#{$parent}:focus { 10 | color: darken($color, 10%); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_reset-filter.scss: -------------------------------------------------------------------------------- 1 | // Reset filters for IE 2 | // 3 | // When you need to remove a gradient background, do not forget to use this to reset 4 | // the IE filter for IE9 and below. 5 | 6 | @mixin reset-filter() { 7 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 8 | } 9 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_nav-divider.scss: -------------------------------------------------------------------------------- 1 | // Horizontal dividers 2 | // 3 | // Dividers (basically an hr) within dropdowns and nav lists 4 | 5 | @mixin nav-divider($color: #e5e5e5) { 6 | height: 1px; 7 | margin: (($line-height-computed / 2) - 1) 0; 8 | overflow: hidden; 9 | background-color: $color; 10 | } 11 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_background-variant.scss: -------------------------------------------------------------------------------- 1 | // Contextual backgrounds 2 | 3 | // [converter] $parent hack 4 | @mixin bg-variant($parent, $color) { 5 | #{$parent} { 6 | background-color: $color; 7 | } 8 | a#{$parent}:hover, 9 | a#{$parent}:focus { 10 | background-color: darken($color, 10%); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_alerts.scss: -------------------------------------------------------------------------------- 1 | // Alerts 2 | 3 | @mixin alert-variant($background, $border, $text-color) { 4 | background-color: $background; 5 | border-color: $border; 6 | color: $text-color; 7 | 8 | hr { 9 | border-top-color: darken($border, 5%); 10 | } 11 | .alert-link { 12 | color: darken($text-color, 10%); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/common/reducers/layout.js: -------------------------------------------------------------------------------- 1 | import { TOGGLE_SIDEBAR } from '../actions/layout'; 2 | 3 | export default function layout(state = {sidebarOpen: false}, action) { 4 | switch (action.type) { 5 | case TOGGLE_SIDEBAR: 6 | return { 7 | sidebarOpen : action.value 8 | }; 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/common/api/fetchComponentDataBeforeRender.js: -------------------------------------------------------------------------------- 1 | export function fetchComponentDataBeforeRender(dispatch, components, params) { 2 | const needs = components.reduce( (prev, current) => { 3 | return current ? (current.need || []).concat(prev) : prev; 4 | }, []); 5 | 6 | const promises = needs.map(need => dispatch(need(params))); 7 | return Promise.all(promises); 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | require('babel/register'); 2 | const appEnv = process.env.NODE_ENV; 3 | if(appEnv === "development" ){ 4 | module.exports = require('./webpack/webpack.config.development'); 5 | }else if(appEnv === "production" ){ 6 | module.exports = require('./webpack/webpack.config.production'); 7 | }else{ 8 | module.exports = require('./webpack/webpack.config.development'); 9 | } 10 | -------------------------------------------------------------------------------- /src/server/config/database.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import glob from 'glob'; 3 | import mongoose from 'mongoose'; 4 | export default function(app,config){ 5 | 6 | mongoose.connect(config.db); 7 | 8 | const db = mongoose.connection; 9 | 10 | db.on('error', function () { 11 | throw new Error('unable to connect to database at ' + config.db); 12 | }); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/topic/NoTopic.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | 3 | /** 4 | * 无话题组件 5 | */ 6 | class NoTopic extends Component{ 7 | 8 | render(){ 9 | return ( 10 |
  • 11 |

    无话题组件!

    12 |
  • 13 | ); 14 | } 15 | _renderTopic(){ 16 | 17 | } 18 | } 19 | 20 | export default NoTopic; 21 | 22 | -------------------------------------------------------------------------------- /src/common/tools/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | export default createDevTools( 6 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/server/controllers/logout.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | 4 | const router = express.Router(); 5 | const User = mongoose.model('User'); 6 | 7 | module.exports= function (app) { 8 | app.use("/logout",router); 9 | }; 10 | //用户登录 11 | router.post('/api', function (req, res) { 12 | res.session.user = null; 13 | res.session.error = null; 14 | res.redirct("/"); 15 | }); 16 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' ; 2 | import path from 'path'; 3 | 4 | export default function(config){ 5 | 6 | //初始化express 应用 7 | var app = express(); 8 | 9 | //数据库以及后台MVC 10 | require('./config/database')(app, config); 11 | 12 | //初始化express配置 13 | require('./config/express')(app, config); 14 | //集成webpack运行环境 15 | require('./config/webpack')(app, config); 16 | return app; 17 | }; 18 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_nav-vertical-align.scss: -------------------------------------------------------------------------------- 1 | // Navbar vertical align 2 | // 3 | // Vertically center elements in the navbar. 4 | // Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin. 5 | 6 | @mixin navbar-vertical-align($element-height) { 7 | margin-top: (($navbar-height - $element-height) / 2); 8 | margin-bottom: (($navbar-height - $element-height) / 2); 9 | } 10 | -------------------------------------------------------------------------------- /sass/footer.scss: -------------------------------------------------------------------------------- 1 | .mainpart { 2 | 3 | min-width: 1220px; 4 | } 5 | .book_footer{ 6 | background: #4c4c4c url(/img/footer-bg.png) repeat-x; 7 | color: white; 8 | position: absolute; 9 | bottom: 0; 10 | width:100%; 11 | .mainpart{ 12 | min-width: 950px; 13 | 14 | padding: 10px 0; 15 | line-height: 25px; 16 | } 17 | .copyrught{ 18 | 19 | p{ 20 | margin: 0; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/widgets/Avator.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /** 4 | * 头像组件 5 | */ 6 | export default class Avator extends Component { 7 | render(){ 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/widgets/Answers.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /** 4 | * 回答条目数量 5 | */ 6 | export default class Answers extends Component { 7 | render(){ 8 | const { count } = this.props; 9 | return ( 10 |
    11 |

    {count} 个回答

    12 |
    13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/common/components/layout/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import classNames from 'classnames'; 4 | 5 | class Sidebar extends Component { 6 | 7 | constructor(props){ 8 | super(props); 9 | } 10 | 11 | render() { 12 | const {version,user} = this.props; 13 | return ( 14 |
    15 | 16 |
    17 | ); 18 | } 19 | } 20 | 21 | export default Sidebar; 22 | -------------------------------------------------------------------------------- /src/server/middlewares/loginAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 验证是否登录中间件 3 | * @param {[type]} req [description] 4 | * @param {[type]} res [description] 5 | * @param {Function} next [description] 6 | * @return {[type]} [description] 7 | */ 8 | export function isLoginAuth(req, res, next){ 9 | const { user } = req.session; 10 | if (user) { 11 | next(); 12 | } else { 13 | req.session.error = 'Access denied!'; 14 | res.redirect('/login'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/common/components/ui/sidebar/HotTopic.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | 4 | export default class HotTopic extends Component { 5 | 6 | render(){ 7 | 8 | return ( 9 |
  • 10 | {this.props.title} 11 | {this.props.answers}回答 | {this.props.isSolved?"已解决":"未解决"} 12 |
  • 13 | ); 14 | 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/actions/book.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 列表页Actions 3 | */ 4 | import request from 'axios'; 5 | export const BOOKS_GET = 'BOOKS_GET'; 6 | export const BOOKS_GET_REQUEST = 'BOOKS_GET_REQUEST'; 7 | export const BOOKS_GET_SUCCESS = 'BOOKS_GET_SUCCESS'; 8 | export const BOOKS_GET_FAILURE = 'BOOKS_GET_FAILURE'; 9 | 10 | export function fetchBooks(reddit = 'reactjs') { 11 | return { 12 | type: BOOKS_GET, 13 | reddit, 14 | promise: request.post(`http://localhost:3000/books/get`) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/Tag.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import cx from 'classnames'; 3 | export default class TagList extends Component { 4 | render(){ 5 | 6 | const { className , name , id } = this.props; 7 | return ( 8 |
  • 9 | 10 | {name} 11 | 12 | 13 |
  • 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/common/api/immutifyState.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | import assign from 'object-assign'; 3 | 4 | Object.assign = Object.assign || assign; 5 | 6 | // Abstraction to handle pre-composedstate received from server 7 | // (ie, leave top level keys untouched) 8 | export default function immutifyState(obj) { 9 | let objMut = Object.assign({}, obj); 10 | 11 | Object 12 | .keys(objMut) 13 | .forEach(key => { 14 | objMut[key] = fromJS(objMut[key]); 15 | }); 16 | 17 | return objMut; 18 | } 19 | -------------------------------------------------------------------------------- /src/common/components/ui/common/FromNow.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import cx from 'classnames'; 3 | import moment from 'moment'; 4 | /** 5 | * 时间显示ago 6 | */ 7 | export default class Answers extends Component { 8 | render(){ 9 | 10 | const { time , type } = this.props; 11 | let sAction = type == "1" ? "回答":"提问"; 12 | const leftTime = moment(time).fromNow(); 13 | return ( 14 | {leftTime}{' '}{sAction} 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/containers/LoginPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import React, { Component} from 'react'; 3 | import { connect } from 'react-redux'; 4 | import Login from '../components/Login'; 5 | import * as UserActions from '../actions/user'; 6 | 7 | function mapStateToProps(state) { 8 | return { 9 | userInfo:state.user 10 | }; 11 | } 12 | function mapDispatchToProps(dispatch) { 13 | return bindActionCreators(UserActions, dispatch); 14 | } 15 | 16 | export default connect(mapStateToProps,mapDispatchToProps)(Login); 17 | -------------------------------------------------------------------------------- /src/common/components/ui/common/Button.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /*关注-收藏按钮组件*/ 4 | 5 | export default class ButtonWiget extends Component { 6 | render(){ 7 | const {text,className,titpText}=this.props; 8 | return ( 9 |
    10 | 13 |
    14 | ); 15 | } 16 | } -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_reset-text.scss: -------------------------------------------------------------------------------- 1 | @mixin reset-text() { 2 | font-family: $font-family-base; 3 | // We deliberately do NOT reset font-size. 4 | font-style: normal; 5 | font-weight: normal; 6 | letter-spacing: normal; 7 | line-break: auto; 8 | line-height: $line-height-base; 9 | text-align: left; // Fallback for where `start` is not supported 10 | text-align: start; 11 | text-decoration: none; 12 | text-shadow: none; 13 | text-transform: none; 14 | white-space: normal; 15 | word-break: normal; 16 | word-spacing: normal; 17 | word-wrap: normal; 18 | } 19 | -------------------------------------------------------------------------------- /src/common/components/topic/CategoryItem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 分类栏条目 3 | */ 4 | import React, { Component, PropTypes } from 'react'; 5 | import classnames from 'classnames'; 6 | import { Link } from 'react-router'; 7 | export default class CategoryBar extends Component { 8 | 9 | render(){ 10 | const { text , active , link ,...other } = this.props; 11 | return ( 12 |
  • 13 | 14 | {text} 15 | 16 |
  • 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/widgets/Button.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /*关注-收藏按钮组件*/ 4 | 5 | export default class ButtonWiget extends Component { 6 | render(){ 7 | const {text,className,titpText}=this.props; 8 | return ( 9 |
    10 | 13 |
    14 | ); 15 | } 16 | } -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_border-radius.scss: -------------------------------------------------------------------------------- 1 | // Single side border-radius 2 | 3 | @mixin border-top-radius($radius) { 4 | border-top-right-radius: $radius; 5 | border-top-left-radius: $radius; 6 | } 7 | @mixin border-right-radius($radius) { 8 | border-bottom-right-radius: $radius; 9 | border-top-right-radius: $radius; 10 | } 11 | @mixin border-bottom-radius($radius) { 12 | border-bottom-right-radius: $radius; 13 | border-bottom-left-radius: $radius; 14 | } 15 | @mixin border-left-radius($radius) { 16 | border-bottom-left-radius: $radius; 17 | border-top-left-radius: $radius; 18 | } 19 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_pagination.scss: -------------------------------------------------------------------------------- 1 | // Pagination 2 | 3 | @mixin pagination-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { 4 | > li { 5 | > a, 6 | > span { 7 | padding: $padding-vertical $padding-horizontal; 8 | font-size: $font-size; 9 | line-height: $line-height; 10 | } 11 | &:first-child { 12 | > a, 13 | > span { 14 | @include border-left-radius($border-radius); 15 | } 16 | } 17 | &:last-child { 18 | > a, 19 | > span { 20 | @include border-right-radius($border-radius); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_responsive-visibility.scss: -------------------------------------------------------------------------------- 1 | // Responsive utilities 2 | 3 | // 4 | // More easily include all the states for responsive-utilities.less. 5 | // [converter] $parent hack 6 | @mixin responsive-visibility($parent) { 7 | #{$parent} { 8 | display: block !important; 9 | } 10 | table#{$parent} { display: table !important; } 11 | tr#{$parent} { display: table-row !important; } 12 | th#{$parent}, 13 | td#{$parent} { display: table-cell !important; } 14 | } 15 | 16 | // [converter] $parent hack 17 | @mixin responsive-invisibility($parent) { 18 | #{$parent} { 19 | display: none !important; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/components/appui/ErrMsg.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | 3 | 4 | class ErrMsg extends Component{ 5 | 6 | render(){ 7 | var ss='' 8 | debugger; 9 | switch(this.props.type){ 10 | case "name": 11 | ss = '请填写正确的名字'; 12 | 13 | case "email": 14 | ss = '请填写正确的E-Mail'; 15 | 16 | case "password": 17 | ss = '请填写正确的密码'; 18 | 19 | } 20 | 21 | return ( 22 | 23 | ); 24 | } 25 | 26 | 27 | } 28 | 29 | export default ErrMsg; 30 | 31 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_panels.scss: -------------------------------------------------------------------------------- 1 | // Panels 2 | 3 | @mixin panel-variant($border, $heading-text-color, $heading-bg-color, $heading-border) { 4 | border-color: $border; 5 | 6 | & > .panel-heading { 7 | color: $heading-text-color; 8 | background-color: $heading-bg-color; 9 | border-color: $heading-border; 10 | 11 | + .panel-collapse > .panel-body { 12 | border-top-color: $border; 13 | } 14 | .badge { 15 | color: $heading-bg-color; 16 | background-color: $heading-text-color; 17 | } 18 | } 19 | & > .panel-footer { 20 | + .panel-collapse > .panel-body { 21 | border-bottom-color: $border; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_wells.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: $well-bg; 12 | border: 1px solid $well-border; 13 | border-radius: $border-radius-base; 14 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 15 | blockquote { 16 | border-color: #ddd; 17 | border-color: rgba(0,0,0,.15); 18 | } 19 | } 20 | 21 | // Sizes 22 | .well-lg { 23 | padding: 24px; 24 | border-radius: $border-radius-large; 25 | } 26 | .well-sm { 27 | padding: 9px; 28 | border-radius: $border-radius-small; 29 | } 30 | -------------------------------------------------------------------------------- /src/common/api/promiseMiddleware.js: -------------------------------------------------------------------------------- 1 | export default function promiseMiddleware() { 2 | return next => action => { 3 | const { promise, type, ...rest } = action; 4 | 5 | if (!promise) return next(action); 6 | 7 | const SUCCESS = type + '_SUCCESS'; 8 | const REQUEST = type + '_REQUEST'; 9 | const FAILURE = type + '_FAILURE'; 10 | next({ ...rest, type: REQUEST }); 11 | return promise 12 | .then(req => { 13 | next({ ...rest, req, type: SUCCESS }); 14 | return true; 15 | }) 16 | .catch(error => { 17 | next({ ...rest, error, type: FAILURE }); 18 | console.log(error); 19 | return false; 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/TagList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import cx from 'classnames'; 3 | 4 | import Tag from './Tag'; 5 | 6 | export default class TagList extends Component { 7 | render(){ 8 | const { className , tags = [] , ...rest } = this.props; 9 | return ( 10 | 13 | ); 14 | } 15 | _getChildren(tags){ 16 | return tags.map((tag,index)=>{ 17 | const { id,name } = tag; 18 | return 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/components/404.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | class Error404 extends Component { 5 | 6 | render() { 7 | return ( 8 |
    9 | {/* 10 |

    404: Page not found

    11 |

    Sorry, we've misplaced that URL or it's pointing to something that does not exist.

    12 | */} 13 |

    14 |

    > Head back home

    15 |
    16 | ); 17 | } 18 | } 19 | 20 | export default Error404; 21 | 22 | -------------------------------------------------------------------------------- /src/common/containers/PublishPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import React, { Component} from 'react'; 3 | import { connect } from 'react-redux'; 4 | import Publish from '../components/Publish'; 5 | import * as PublishActions from '../actions/publish'; 6 | 7 | //获取所有标签。这里实现不是太好。以后再改进 8 | Publish.need = [ 9 | PublishActions.fetchTags 10 | ] 11 | 12 | function mapStateToProps(state) { 13 | 14 | return { 15 | publishInfo:state.publish, 16 | user:state.user 17 | }; 18 | } 19 | 20 | function mapDispatchToProps(dispatch) { 21 | return bindActionCreators(PublishActions, dispatch); 22 | } 23 | 24 | export default connect(mapStateToProps,mapDispatchToProps)(Publish); 25 | -------------------------------------------------------------------------------- /src/server/controllers/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import bodyParser from 'body-parser'; 4 | const User = mongoose.model('User'); 5 | const router = express.Router(); 6 | const jsonParser = bodyParser.json() 7 | 8 | 9 | module.exports= function (app) { 10 | 11 | app.use("/",router); 12 | }; 13 | 14 | router.post('/user/reg',jsonParser, function (req, res) { 15 | 16 | const { password ,email , confirmpassword } = req.body || {}; 17 | var user = new User({password,email}); 18 | user.save((err,user)=>{ 19 | if(!err){ 20 | res.status(200).json({"data":{ bl: 1,msg:"注册成功" }}); 21 | } 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_hide-text.scss: -------------------------------------------------------------------------------- 1 | // CSS image replacement 2 | // 3 | // Heads up! v3 launched with only `.hide-text()`, but per our pattern for 4 | // mixins being reused as classes with the same name, this doesn't hold up. As 5 | // of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. 6 | // 7 | // Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 8 | 9 | // Deprecated as of v3.0.1 (has been removed in v4) 10 | @mixin hide-text() { 11 | font: 0/0 a; 12 | color: transparent; 13 | text-shadow: none; 14 | background-color: transparent; 15 | border: 0; 16 | } 17 | 18 | // New mixin to use as of v3.0.1 19 | @mixin text-hide() { 20 | @include hide-text; 21 | } 22 | -------------------------------------------------------------------------------- /src/common/actions/layout.js: -------------------------------------------------------------------------------- 1 | 2 | import { ActionCreators } from 'redux-undo'; 3 | 4 | export const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'; 5 | 6 | export function toggleSidebar(value) { 7 | return { 8 | type: TOGGLE_SIDEBAR, 9 | value : value 10 | }; 11 | } 12 | 13 | export function undo() { 14 | return (dispatch, getState) => { 15 | dispatch(ActionCreators.undo()); 16 | }; 17 | } 18 | 19 | export function redo() { 20 | return (dispatch, getState) => { 21 | dispatch(ActionCreators.redo()); 22 | }; 23 | } 24 | 25 | /** 26 | * Bundle User into layout 27 | */ 28 | 29 | import { GET_USER, getUser} from './user'; 30 | export { getUser as getUser }; 31 | export { GET_USER as GET_USER }; 32 | 33 | -------------------------------------------------------------------------------- /src/common/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routeReducer as routing } from 'redux-simple-router'; 3 | import undoable from 'redux-undo'; 4 | import user from './user'; 5 | import layout from './layout'; 6 | 7 | import publish from './publish'; 8 | 9 | import { selectedCategory, topicsByCategory } from './topic'; 10 | import topicDetail from './topicDetail'; 11 | 12 | 13 | const rootReducer = combineReducers({ 14 | layout : undoable(layout), 15 | user:user, 16 | publish:publish, 17 | selectedCategory : undoable(selectedCategory), 18 | topicsByCategory : undoable(topicsByCategory), 19 | topicDetail:topicDetail, 20 | routing 21 | }); 22 | 23 | export default rootReducer; 24 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // Clearfix 2 | // 3 | // For modern browsers 4 | // 1. The space content is one way to avoid an Opera bug when the 5 | // contenteditable attribute is included anywhere else in the document. 6 | // Otherwise it causes space to appear at the top and bottom of elements 7 | // that are clearfixed. 8 | // 2. The use of `table` rather than `block` is only necessary if using 9 | // `:before` to contain the top-margins of child elements. 10 | // 11 | // Source: http://nicolasgallagher.com/micro-clearfix-hack/ 12 | 13 | @mixin clearfix() { 14 | &:before, 15 | &:after { 16 | content: " "; // 1 17 | display: table; // 2 18 | } 19 | &:after { 20 | clear: both; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_responsive-embed.scss: -------------------------------------------------------------------------------- 1 | // Embeds responsive 2 | // 3 | // Credit: Nicolas Gallagher and SUIT CSS. 4 | 5 | .embed-responsive { 6 | position: relative; 7 | display: block; 8 | height: 0; 9 | padding: 0; 10 | overflow: hidden; 11 | 12 | .embed-responsive-item, 13 | iframe, 14 | embed, 15 | object, 16 | video { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | bottom: 0; 21 | height: 100%; 22 | width: 100%; 23 | border: 0; 24 | } 25 | } 26 | 27 | // Modifier class for 16:9 aspect ratio 28 | .embed-responsive-16by9 { 29 | padding-bottom: 56.25%; 30 | } 31 | 32 | // Modifier class for 4:3 aspect ratio 33 | .embed-responsive-4by3 { 34 | padding-bottom: 75%; 35 | } 36 | -------------------------------------------------------------------------------- /src/server/controllers/tag.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import _ from 'lodash'; 4 | import { isLoginAuth } from '../middlewares/loginAuth'; 5 | import { setError , setOk } from '../lib/utils'; 6 | const router = express.Router(); 7 | const Tag = mongoose.model('Tag'); 8 | 9 | module.exports= function (app) { 10 | app.use("/tags",router); 11 | }; 12 | 13 | 14 | //查询所有Tags 15 | router.post('/getAll', function (req, res) { 16 | //获取tags 17 | Tag.getAll((err,tags)=>{ 18 | if(err){ 19 | res.status(200).send({ 20 | error:{ 21 | code:500, 22 | msg:"查询出错!" 23 | } 24 | }); 25 | } 26 | res.status(200).send({data:tags,user:req.session.user}); 27 | }); 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: $breadcrumb-padding-vertical $breadcrumb-padding-horizontal; 8 | margin-bottom: $line-height-computed; 9 | list-style: none; 10 | background-color: $breadcrumb-bg; 11 | border-radius: $border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | // [converter] Workaround for https://github.com/sass/libsass/issues/1115 18 | $nbsp: "\00a0"; 19 | content: "#{$breadcrumb-separator}#{$nbsp}"; // Unicode space added since inline-block means non-collapsing white-space 20 | padding: 0 5px; 21 | color: $breadcrumb-color; 22 | } 23 | } 24 | 25 | > .active { 26 | color: $breadcrumb-active-color; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_list-group.scss: -------------------------------------------------------------------------------- 1 | // List Groups 2 | 3 | @mixin list-group-item-variant($state, $background, $color) { 4 | .list-group-item-#{$state} { 5 | color: $color; 6 | background-color: $background; 7 | 8 | // [converter] extracted a&, button& to a.list-group-item-#{$state}, button.list-group-item-#{$state} 9 | } 10 | 11 | a.list-group-item-#{$state}, 12 | button.list-group-item-#{$state} { 13 | color: $color; 14 | 15 | .list-group-item-heading { 16 | color: inherit; 17 | } 18 | 19 | &:hover, 20 | &:focus { 21 | color: $color; 22 | background-color: darken($background, 5%); 23 | } 24 | &.active, 25 | &.active:hover, 26 | &.active:focus { 27 | color: #fff; 28 | background-color: $color; 29 | border-color: $color; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webpack/webpack.config.production.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import path from 'path'; 4 | import webpack from 'webpack'; 5 | import assign from 'object-assign'; 6 | import config from './webpack.config.base'; 7 | 8 | export default assign({},config,{ 9 | entry:[ 10 | 11 | path.join(__dirname, '../src/client/index.js') 12 | ], 13 | output: { 14 | path:path.join(__dirname, '../dest'), 15 | filename:"bundle.js", 16 | publicPath:path.join(__dirname, '../'), 17 | }, 18 | devtool: "source-map", 19 | 20 | plugins:[ 21 | new webpack.NoErrorsPlugin(), 22 | new webpack.DefinePlugin({ 23 | 'process.env': { 24 | NODE_ENV: JSON.stringify('production') 25 | } 26 | }), 27 | new webpack.optimize.UglifyJsPlugin({minimize: true}) 28 | ] 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /webpack/webpack.config.development.js: -------------------------------------------------------------------------------- 1 | //开发环境 2 | 3 | import path from 'path'; 4 | import webpack from 'webpack'; 5 | import assign from 'object-assign'; 6 | import config from './webpack.config.base'; 7 | 8 | export default assign({},config,{ 9 | entry:[ 10 | 'webpack-hot-middleware/client?reload=true', 11 | path.join(__dirname, '../src/client') 12 | ], 13 | plugins:[ 14 | new webpack.optimize.OccurenceOrderPlugin(), 15 | new webpack.HotModuleReplacementPlugin(), 16 | new webpack.NoErrorsPlugin(), 17 | 18 | new webpack.DefinePlugin({ 19 | 'process.env': { 20 | NODE_ENV: JSON.stringify('developmonent') 21 | } 22 | }) 23 | ], 24 | output: { 25 | path: path.join(__dirname, '../public'), 26 | publicPath:"/" 27 | 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /sass/bootstrap/_bootstrap-mincer.scss: -------------------------------------------------------------------------------- 1 | // Mincer asset helper functions 2 | // 3 | // This must be imported into a .css.ejs.scss file. 4 | // Then, <% %>-interpolations will be parsed as strings by Sass, and evaluated by EJS after Sass compilation. 5 | 6 | 7 | @function twbs-font-path($path) { 8 | // do something like following 9 | // from "path/to/font.ext#suffix" to "<%- asset_path(path/to/font.ext)) + #suffix %>" 10 | // from "path/to/font.ext?#suffix" to "<%- asset_path(path/to/font.ext)) + ?#suffix %>" 11 | // or from "path/to/font.ext" just "<%- asset_path(path/to/font.ext)) %>" 12 | @return "<%- asset_path("#{$path}".replace(/[#?].*$/, '')) + "#{$path}".replace(/(^[^#?]*)([#?]?.*$)/, '$2') %>"; 13 | } 14 | 15 | @function twbs-image-path($file) { 16 | @return "<%- asset_path("#{$file}") %>"; 17 | } 18 | 19 | $bootstrap-sass-asset-helper: true; 20 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_table-row.scss: -------------------------------------------------------------------------------- 1 | // Tables 2 | 3 | @mixin table-row-variant($state, $background) { 4 | // Exact selectors below required to override `.table-striped` and prevent 5 | // inheritance to nested tables. 6 | .table > thead > tr, 7 | .table > tbody > tr, 8 | .table > tfoot > tr { 9 | > td.#{$state}, 10 | > th.#{$state}, 11 | &.#{$state} > td, 12 | &.#{$state} > th { 13 | background-color: $background; 14 | } 15 | } 16 | 17 | // Hover states for `.table-hover` 18 | // Note: this is not available for cells or rows within `thead` or `tfoot`. 19 | .table-hover > tbody > tr { 20 | > td.#{$state}:hover, 21 | > th.#{$state}:hover, 22 | &.#{$state}:hover > td, 23 | &:hover > .#{$state}, 24 | &.#{$state}:hover > th { 25 | background-color: darken($background, 5%); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/server/config/webpack.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import webpackHotMiddleware from 'webpack-hot-middleware'; 3 | import webpackConfig from '../../../webpack.config'; 4 | import webpackDevMiddleware from "webpack-dev-middleware" ; 5 | 6 | import reactServerRenderMiddleware from '../middlewares/renderPage-middleware'; 7 | export default function(app,config){ 8 | //compiler实例 9 | const compiler = webpack(webpackConfig); 10 | const devMiddleware = webpackDevMiddleware(compiler, { 11 | publicPath: webpackConfig.output.publicPath, 12 | noInfo: true, 13 | stats: { 14 | colors: true 15 | } 16 | }); 17 | const hotMiddleware = webpackHotMiddleware(compiler); 18 | app.use(devMiddleware); 19 | app.use(hotMiddleware); 20 | //集成服务端渲染中间件 21 | app.get("*",reactServerRenderMiddleware); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/common/reducers/book.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 列表页rreducers 3 | */ 4 | import { 5 | BOOKS_GET_REQUEST, 6 | BOOKS_GET_SUCCESS, 7 | BOOKS_GET_FAILURE 8 | } from '../actions/book'; 9 | 10 | /*const initialState = { 11 | error: {}, 12 | isFetching: false, 13 | didInvalidate: false, 14 | items: [] 15 | }*/ 16 | const initialState = { 17 | items:[] 18 | } 19 | export default function books(state = initialState, action) { 20 | switch (action.type) { 21 | case BOOKS_GET_REQUEST: 22 | return state; 23 | case BOOKS_GET_SUCCESS: 24 | console.log("****************请求成功***********************"); 25 | return Object.assign({}, state, { 26 | items: [] 27 | }); 28 | case BOOKS_GET_FAILURE: 29 | return state; 30 | default: 31 | return state; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/common/components/booklist/BookList.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | import BookItem from './BookItem'; 3 | 4 | /** 5 | * 图书列表明细 6 | */ 7 | 8 | export default class BookList extends Component{ 9 | 10 | render(){ 11 | 12 | const { datas = [] , className } = this.props; 13 | 14 | let chdComponent ; 15 | 16 | datas.length > 0 ? chdComponent = this._renderChildren() : null; 17 | return ( 18 | 21 | ); 22 | } 23 | //渲染子组件 24 | _renderChildren(){ 25 | const { datas = []} = this.props; 26 | return datas.map( (item,index)=>{ 27 | const { id , ...other } = item; 28 | return ( 29 | 30 | ); 31 | }); 32 | } 33 | 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/server/config/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据库 3 | */ 4 | var path = require('path'), 5 | rootPath = path.normalize(__dirname + '/../../..'), 6 | env = process.env.NODE_ENV || 'development'; 7 | 8 | var config = { 9 | development: { 10 | root: rootPath, 11 | app: { 12 | name: 'webcy' 13 | }, 14 | cookieSecret: 'performanceapp', 15 | port: 8000, 16 | db: 'mongodb://localhost/webcy-development' 17 | }, 18 | 19 | test: { 20 | root: rootPath, 21 | app: { 22 | name: 'webcy' 23 | }, 24 | cookieSecret: 'performanceapp', 25 | port: 3000, 26 | db: 'mongodb://localhost/webcy-test' 27 | }, 28 | 29 | production: { 30 | root: rootPath, 31 | app: { 32 | name: 'webcy' 33 | }, 34 | cookieSecret: 'performanceapp', 35 | port: 3000, 36 | db: 'mongodb://localhost/webcy-production' 37 | } 38 | }; 39 | 40 | module.exports = config[env]; 41 | -------------------------------------------------------------------------------- /src/common/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route , IndexRoute} from "react-router"; 3 | import App from "./containers/App"; 4 | 5 | 6 | import NotFound from "./components/404"; 7 | import TopicPage from "./containers/TopicPage"; 8 | import PublishPage from './containers/PublishPage'; 9 | import LoginPage from './containers/LoginPage'; 10 | import TopicDetailPage from './containers/TopicDetailPage'; 11 | export default ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/server/controllers/login.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import { setError , setOk ,sendOk} from '../lib/utils'; 4 | import crypto from 'crypto'; 5 | import uuid from 'node-uuid'; 6 | 7 | const router = express.Router(); 8 | const User = mongoose.model('User'); 9 | 10 | module.exports= function (app) { 11 | app.use("/login",router); 12 | }; 13 | //用户登录 14 | router.post('/', function (req, res) { 15 | const { password , email } = req.body; 16 | 17 | const passHash = crypto.createHash('md5').update(password).digest('hex'); 18 | 19 | //根据用户名查询 20 | User.checkUser({email:email,password:passHash},(error,user)=>{ 21 | 22 | if(error || !user){ 23 | res.status(200).json(setError({msg:"账号或者密码不正确!"})); 24 | }else { 25 | req.session.user = user; 26 | res.status(200).json(setOk({msg:"登录成功!",user})); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/server/lib/utils.js: -------------------------------------------------------------------------------- 1 | import assign from 'object-assign'; 2 | export function setError (data){ 3 | const { bl = '0', msg = '操作失败!' , error = true , ...other } = data || {}; 4 | return { 5 | data:{ 6 | bl , 7 | msg , 8 | error , 9 | ...other 10 | } 11 | 12 | } 13 | 14 | } 15 | export function setOk (data){ 16 | const { bl = '1', msg = "操作成功!" , error=false,...other} = data || {}; 17 | return { 18 | data:{ 19 | bl, 20 | msg, 21 | error, 22 | ...other 23 | 24 | 25 | } 26 | 27 | } 28 | } 29 | export function sendOk(res){ 30 | return function(code){ 31 | return function(data){ 32 | const { bl , error = false , ...other } = data || {}; 33 | let res = bl == "1" ? setOk(data) : setError(data); 34 | res.status( code || 200 ).json(res); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/common/actions/publish.js: -------------------------------------------------------------------------------- 1 | import request from 'axios'; 2 | export const PUBLISH_GET = 'PUBLISH_GET'; 3 | export const PUBLISH_GET_REQUEST = 'PUBLISH_GET_REQUEST'; 4 | export const PUBLISH_GET_SUCCESS = 'PUBLISH_GET_SUCCESS'; 5 | export const PUBLISH_GET_FAILURE = 'PUBLISH_GET_FAILURE'; 6 | 7 | 8 | export const TAG_GET = 'TAG_GET'; 9 | export const TAG_GET_REQUEST = 'TAG_GET_REQUEST'; 10 | export const TAG_GET_SUCCESS = 'TAG_GET_SUCCESS'; 11 | export const TAG_GET_FAILURE = 'TAG_GET_FAILURE'; 12 | //发表问题 13 | export function doPublish({title,content,tagId}) { 14 | return { 15 | type:PUBLISH_GET, 16 | title, 17 | content, 18 | tagId, 19 | promise: request.post("http://localhost:8000/ask/publish",{title,content,tagId}) 20 | } 21 | } 22 | //获取所有标签 23 | export function fetchTags(){ 24 | return { 25 | type:TAG_GET, 26 | promise: request.post("http://localhost:8000/tags/getAll") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/components/TabPanel.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import cx from 'classnames'; 3 | 4 | module.exports = React.createClass({ 5 | displayName: 'TabPanel', 6 | 7 | 8 | 9 | contextTypes: { 10 | forceRenderTabPanel: PropTypes.bool 11 | }, 12 | 13 | getDefaultProps() { 14 | return { 15 | selected: false, 16 | id: null, 17 | tabId: null 18 | }; 19 | }, 20 | 21 | render() { 22 | const children = (this.context.forceRenderTabPanel || this.props.selected) ? 23 | this.props.children : 24 | null; 25 | 26 | return ( 27 |
    39 | {children} 40 |
    41 | ); 42 | } 43 | }); 44 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_close.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: ($font-size-base * 1.5); 9 | font-weight: $close-font-weight; 10 | line-height: 1; 11 | color: $close-color; 12 | text-shadow: $close-text-shadow; 13 | @include opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: $close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | @include opacity(.5); 21 | } 22 | 23 | // [converter] extracted button& to button.close 24 | } 25 | 26 | // Additional properties for button version 27 | // iOS requires the button element instead of an anchor tag. 28 | // If you want the anchor version, it requires `href="#"`. 29 | // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile 30 | button.close { 31 | padding: 0; 32 | cursor: pointer; 33 | background: transparent; 34 | border: 0; 35 | -webkit-appearance: none; 36 | } 37 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/widgets/Operation.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /** 4 | * 工具栏 5 | */ 6 | export default class Operation extends Component { 7 | render(){ 8 | return ( 9 |
    10 | 23 |
    24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/common/components/ui/common/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /*关注-收藏按钮组件*/ 4 | 5 | export default class LoadingWidget extends Component { 6 | render(){ 7 | return ( 8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/Question.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import VoteWidget from './widgets/Vote'; 3 | import TagList from './TagList'; 4 | import OperationWidget from './widgets/Operation'; 5 | import _ from 'lodash'; 6 | /** 7 | *问题详情 8 | */ 9 | export default class Question extends Component { 10 | render(){ 11 | const { title , content } = this.props; 12 | 13 | return ( 14 |
    15 |
    16 | 17 |
    18 |
    19 |
    20 |
    21 |
    22 | 23 |
    24 |
    25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_component-animations.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | @include transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | 21 | &.in { display: block; } 22 | // [converter] extracted tr&.in to tr.collapse.in 23 | // [converter] extracted tbody&.in to tbody.collapse.in 24 | } 25 | 26 | tr.collapse.in { display: table-row; } 27 | 28 | tbody.collapse.in { display: table-row-group; } 29 | 30 | .collapsing { 31 | position: relative; 32 | height: 0; 33 | overflow: hidden; 34 | @include transition-property(height, visibility); 35 | @include transition-duration(.35s); 36 | @include transition-timing-function(ease); 37 | } 38 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_utilities.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Floats 7 | // ------------------------- 8 | 9 | .clearfix { 10 | @include clearfix; 11 | } 12 | .center-block { 13 | @include center-block; 14 | } 15 | .pull-right { 16 | float: right !important; 17 | } 18 | .pull-left { 19 | float: left !important; 20 | } 21 | 22 | 23 | // Toggling content 24 | // ------------------------- 25 | 26 | // Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 27 | .hide { 28 | display: none !important; 29 | } 30 | .show { 31 | display: block !important; 32 | } 33 | .invisible { 34 | visibility: hidden; 35 | } 36 | .text-hide { 37 | @include text-hide; 38 | } 39 | 40 | 41 | // Hide from screenreaders and browsers 42 | // 43 | // Credit: HTML5 Boilerplate 44 | 45 | .hidden { 46 | display: none !important; 47 | } 48 | 49 | 50 | // For Affix plugin 51 | // ------------------------- 52 | 53 | .affix { 54 | position: fixed; 55 | } 56 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/widgets/Vote.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | /** 4 | *问题详情-对问题投票的组件 5 | */ 6 | export default class VoteWidget extends Component { 7 | render(){ 8 | const { likeText , hateText , star = 0 } = this.props; 9 | return ( 10 |
    11 | 16 | {star} 17 | 21 |
    22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/common/components/layout/MasterLayout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 布局类型: 3 | * header 4 | * main right 5 | * footer 6 | */ 7 | import React , { Component } from 'react'; 8 | import Footer from './Footer'; 9 | import Header from './Header'; 10 | class MasterLayout extends Component{ 11 | render(){ 12 | const { 13 | user , 14 | HeaderClass , 15 | CenterClass , 16 | RightClass 17 | } = this.props; 18 | 19 | let headerComponent = HeaderClass ? :
    ; 20 | 21 | return ( 22 |
    23 | { HeaderClass && } 24 |
    25 | { CenterClass && } 26 | { RightClass && } 27 |
    28 |
    29 |
    30 | 31 | ); 32 | } 33 | } 34 | export default MasterLayout; 35 | -------------------------------------------------------------------------------- /src/common/components/topic/CategoryBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 分类栏 3 | */ 4 | import React, { Component, PropTypes } from 'react'; 5 | 6 | export default class CategoryBar extends Component { 7 | constructor(props){ 8 | super(props); 9 | 10 | } 11 | render(){ 12 | return ( 13 |
      14 | {this.getCateGories()} 15 |
    16 | ); 17 | } 18 | getCateGories(){ 19 | const { 20 | children , 21 | activeKey , 22 | ...other 23 | } = this.props; 24 | 25 | return React.Children.map(children,(cateItem)=>{ 26 | 27 | let { cateKey } = cateItem.props; 28 | 29 | return React.cloneElement(cateItem,{ 30 | active: activeKey === cateKey, 31 | onClick:this.handlerClick.bind(this,cateKey) 32 | }); 33 | }) 34 | } 35 | handlerClick(cateKey){ 36 | 37 | this.props.onSelectCategory && this.props.onSelectCategory(cateKey); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/components/booklist/BookItem.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | 3 | /** 4 | * 图书列表明细 5 | */ 6 | class BookItem extends Component{ 7 | 8 | render(){ 9 | 10 | 11 | 12 | return ( 13 |
  • 14 |
    15 |
    16 | 17 | 18 | 住宅楼施工组织设计(作业) 19 | 20 |
    21 | 22 | 23 | 24 | zhanglaifa 25 | {' '} 26 | 上传于{' '}2015-12-21 21:16 27 | 28 |
    29 |
  • 30 | ); 31 | } 32 | 33 | 34 | } 35 | 36 | export default BookItem; 37 | 38 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_thumbnails.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Mixin and adjust the regular image class 7 | .thumbnail { 8 | display: block; 9 | padding: $thumbnail-padding; 10 | margin-bottom: $line-height-computed; 11 | line-height: $line-height-base; 12 | background-color: $thumbnail-bg; 13 | border: 1px solid $thumbnail-border; 14 | border-radius: $thumbnail-border-radius; 15 | @include transition(border .2s ease-in-out); 16 | 17 | > img, 18 | a > img { 19 | @include img-responsive; 20 | margin-left: auto; 21 | margin-right: auto; 22 | } 23 | 24 | // [converter] extracted a&:hover, a&:focus, a&.active to a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active 25 | 26 | // Image captions 27 | .caption { 28 | padding: $thumbnail-caption-padding; 29 | color: $thumbnail-caption-color; 30 | } 31 | } 32 | 33 | // Add a hover state for linked versions only 34 | a.thumbnail:hover, 35 | a.thumbnail:focus, 36 | a.thumbnail.active { 37 | border-color: $link-color; 38 | } 39 | -------------------------------------------------------------------------------- /src/server/models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose , { Schema } from 'mongoose'; 2 | import { USER } from '../constants/index'; 3 | const UserSchema = new Schema({ 4 | id:String, 5 | name:String, 6 | email:String, 7 | password:String, 8 | registerTime:{ 9 | type:Date, 10 | default:Date.now 11 | }, 12 | role:{ 13 | type:String, 14 | default:USER.ROLE.NORMAL 15 | }, 16 | status:{ 17 | type:String, 18 | default:USER.STATUS.ACTIVE 19 | }, 20 | score:{ 21 | type:Number, 22 | default:0 23 | }, 24 | tag:{ 25 | type:String, 26 | default:"" 27 | } 28 | 29 | }); 30 | UserSchema.statics.checkUser=function(params,cb){ 31 | return this.findOne(params,'id name',cb); 32 | } 33 | UserSchema.statics.getOne=function(params,cb){ 34 | return this.findOne(params,cb); 35 | } 36 | UserSchema.statics.getAllByIds=function(params,fieldsStr,ids,cb){ 37 | return this.find(params).select(fieldsStr).where('id').in(ids).exec(cb); 38 | } 39 | 40 | 41 | mongoose.model('User',UserSchema); 42 | -------------------------------------------------------------------------------- /src/common/reducers/publish.js: -------------------------------------------------------------------------------- 1 | import { 2 | PUBLISH_GET_REQUEST, 3 | PUBLISH_GET_SUCCESS, 4 | PUBLISH_GET_FAILURE, 5 | TAG_GET_REQUEST, 6 | TAG_GET_SUCCESS, 7 | TAG_GET_FAILURE 8 | } from '../actions/publish'; 9 | const initialState = { 10 | bl:0, 11 | isFetching:false, 12 | error:null, 13 | tags:[] 14 | 15 | }; 16 | 17 | export default function publish(state = initialState, action) { 18 | 19 | switch (action.type) { 20 | case TAG_GET_SUCCESS: 21 | const { data:tags,user } = action.req.data; 22 | 23 | return Object.assign( 24 | {}, 25 | state, 26 | { 27 | tags, 28 | user 29 | } 30 | ); 31 | case PUBLISH_GET_REQUEST: 32 | return state; 33 | case PUBLISH_GET_SUCCESS: 34 | 35 | return Object.assign({}, state.data, action.req.data.data); 36 | case PUBLISH_GET_FAILURE: 37 | return state; 38 | default: 39 | return state; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_pager.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | padding-left: 0; 8 | margin: $line-height-computed 0; 9 | list-style: none; 10 | text-align: center; 11 | @include clearfix; 12 | li { 13 | display: inline; 14 | > a, 15 | > span { 16 | display: inline-block; 17 | padding: 5px 14px; 18 | background-color: $pager-bg; 19 | border: 1px solid $pager-border; 20 | border-radius: $pager-border-radius; 21 | } 22 | 23 | > a:hover, 24 | > a:focus { 25 | text-decoration: none; 26 | background-color: $pager-hover-bg; 27 | } 28 | } 29 | 30 | .next { 31 | > a, 32 | > span { 33 | float: right; 34 | } 35 | } 36 | 37 | .previous { 38 | > a, 39 | > span { 40 | float: left; 41 | } 42 | } 43 | 44 | .disabled { 45 | > a, 46 | > a:hover, 47 | > a:focus, 48 | > span { 49 | color: $pager-disabled-color; 50 | background-color: $pager-bg; 51 | cursor: $cursor-disabled; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/common/actions/login.js: -------------------------------------------------------------------------------- 1 | import request from 'axios'; 2 | export const LOGIN = "LOGIN"; 3 | export const LOGIN_REQUEST = 'LOGIN_REQUEST'; 4 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 5 | export const LOGIN_FAILURE = 'LOGIN_FAILURE'; 6 | 7 | export const REGISTER = "REGISTER"; 8 | export const REGISTER_REQUEST = 'REGISTER_REQUEST'; 9 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; 10 | export const REGISTER_FAILURE = 'REGISTER_FAILURE'; 11 | 12 | 13 | function _doLogin(params) { 14 | return { 15 | type: LOGIN, 16 | promise: request.post("http://localhost:8000/login",params) 17 | } 18 | } 19 | 20 | function _doRegister(params) { 21 | return { 22 | type: REGISTER, 23 | promise: request.post("http://localhost:8000/reg",params) 24 | } 25 | } 26 | 27 | export function doRegister(data) { 28 | return (dispatch, getState) => { 29 | return dispatch(_doRegister(data)); 30 | }; 31 | } 32 | 33 | 34 | export function doLogin(data) { 35 | return (dispatch, getState) => { 36 | return dispatch(_doLogin(data)); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/server/controllers/user.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import { setError , setOk ,sendOk} from '../lib/utils'; 4 | import crypto from 'crypto'; 5 | import uuid from 'node-uuid'; 6 | 7 | const router = express.Router(); 8 | const User = mongoose.model('User'); 9 | 10 | module.exports= function (app) { 11 | app.use("/api/user",router); 12 | }; 13 | //用户登录 14 | router.post('/login', function (req, res) { 15 | const { password , email } = req.body; 16 | const passHash = crypto.createHash('md5').update(password).digest('hex'); 17 | 18 | //根据用户名查询 19 | User.checkUser({email:email,password:passHash},(error,user)=>{ 20 | 21 | if(error || !user){ 22 | res.status(200).json({ 23 | 24 | msg:"邮箱或者密码不正确!", 25 | error:true, 26 | bl:0, 27 | 28 | }); 29 | }else { 30 | req.session.user = user; 31 | res.status(200).json({ 32 | msg:"登录成功!", 33 | bl:1, 34 | error:false, 35 | user 36 | }); 37 | } 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 BSFullStack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/common/containers/BookPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import React, { Component} from 'react'; 3 | import { connect } from 'react-redux'; 4 | import Book from '../components/Book'; 5 | import * as BookActions from '../actions/book'; 6 | 7 | //Data that needs to be called before rendering the component 8 | //This is used for server side rending via the fetchComponentDataBeforeRending() method 9 | 10 | 11 | function mapStateToProps(state) { 12 | /* let { selectedType, booksByType } = state; 13 | 14 | const { 15 | isFetching, 16 | lastUpdated, 17 | error, 18 | items:books 19 | } = booksByType[selectedType] || { 20 | isFetching: true, 21 | error:{}, 22 | items: [] 23 | }; 24 | 25 | return { 26 | selectedType, 27 | books, 28 | isFetching, 29 | lastUpdated, 30 | error 31 | };*/ 32 | return { 33 | item: state.item 34 | }; 35 | } 36 | 37 | function mapDispatchToProps(dispatch) { 38 | return bindActionCreators(BookActions, dispatch); 39 | } 40 | 41 | export default connect(mapStateToProps,mapDispatchToProps)(Book); 42 | -------------------------------------------------------------------------------- /src/common/reducers/login.js: -------------------------------------------------------------------------------- 1 | import { 2 | LOGIN_SUCCESS , 3 | LOGIN_REQUEST , 4 | LOGIN_FAILURE , 5 | REGISTER_REQUEST , 6 | REGISTER_SUCCESS , 7 | REGISTER_FAILURE 8 | } from '../actions/login'; 9 | 10 | const initialState = { 11 | data:{ 12 | bl:0, 13 | msg:"", 14 | error:false 15 | } 16 | }; 17 | 18 | //登录 19 | export function login(state = initialState , action) { 20 | 21 | switch (action.type) { 22 | case LOGIN_REQUEST: 23 | return Object.assign({},initialState); 24 | case LOGIN_SUCCESS: 25 | 26 | return Object.assign({},state.data,action.req.data); 27 | default: 28 | 29 | return state; 30 | } 31 | } 32 | 33 | 34 | //注册 35 | export function register(state = initialState , action) { 36 | 37 | switch (action.type) { 38 | case REGISTER_REQUEST: 39 | return Object.assign({},initialState); 40 | case REGISTER_SUCCESS: 41 | console.log("xxxx"); 42 | return Object.assign({},state.data,action.req.data.data); 43 | default: 44 | 45 | return state; 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/server/models/Tag.js: -------------------------------------------------------------------------------- 1 | import mongoose , { Schema } from 'mongoose'; 2 | import { TAG } from '../constants/tag'; 3 | import _ from 'lodash'; 4 | /** 5 | * 标签实体 与 topics 是多对多关系 6 | */ 7 | const Tag = new Schema({ 8 | id:String, //id 9 | name:String, // 标签名称 10 | description:String, // 标签描述 11 | createTime:{ // 创建日期 12 | type:Date, 13 | default:Date.now 14 | }, 15 | status:{ //标签状态 1 显示 2 已删除 16 | type:Number, 17 | default:TAG.STATUS.ENABLE 18 | }, 19 | createUserId:String, 20 | topicIds:Array //话题ID数组 21 | 22 | }); 23 | 24 | Tag.statics.getTagByIds=function(params,ids,fields,cb){ 25 | const sFields = fields.join(' '); 26 | 27 | this.find({...params}).select(sFields).where('id').in(ids).find((err,doc)=>{ 28 | 29 | if(err){ 30 | return cb(err); 31 | } 32 | const tags = doc.map((tag)=>{ 33 | return tag.toObject(); 34 | }) 35 | 36 | cb(err,tags); 37 | }); 38 | } 39 | Tag.statics.getAll=function(cb){ 40 | this.find({}).select('id name description').exec(cb) 41 | } 42 | 43 | 44 | 45 | //编译Tag实体 46 | mongoose.model('Tag',Tag); 47 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------------------------------- 3 | 4 | // Utilities 5 | @import "mixins/hide-text"; 6 | @import "mixins/opacity"; 7 | @import "mixins/image"; 8 | @import "mixins/labels"; 9 | @import "mixins/reset-filter"; 10 | @import "mixins/resize"; 11 | @import "mixins/responsive-visibility"; 12 | @import "mixins/size"; 13 | @import "mixins/tab-focus"; 14 | @import "mixins/reset-text"; 15 | @import "mixins/text-emphasis"; 16 | @import "mixins/text-overflow"; 17 | @import "mixins/vendor-prefixes"; 18 | 19 | // Components 20 | @import "mixins/alerts"; 21 | @import "mixins/buttons"; 22 | @import "mixins/panels"; 23 | @import "mixins/pagination"; 24 | @import "mixins/list-group"; 25 | @import "mixins/nav-divider"; 26 | @import "mixins/forms"; 27 | @import "mixins/progress-bar"; 28 | @import "mixins/table-row"; 29 | 30 | // Skins 31 | @import "mixins/background-variant"; 32 | @import "mixins/border-radius"; 33 | @import "mixins/gradients"; 34 | 35 | // Layout 36 | @import "mixins/clearfix"; 37 | @import "mixins/center-block"; 38 | @import "mixins/nav-vertical-align"; 39 | @import "mixins/grid-framework"; 40 | @import "mixins/grid"; 41 | -------------------------------------------------------------------------------- /src/common/components/topic/Topics.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | import Topic from './Topic'; 3 | import NoTopic from './NoTopic'; 4 | /** 5 | * 图书列表明细 6 | */ 7 | class Topics extends Component{ 8 | 9 | render(){ 10 | const { topics , isFetching } = this.props; 11 | let topicsComponent ; 12 | if( topics.length == 0 && isFetching){ 13 | topicsComponent = ( 14 |

    正在加载...

    15 | ); 16 | } else { 17 | topicsComponent = this._renderTopic(topics); 18 | } 19 | return ( 20 |
    21 | {topicsComponent} 22 |
    23 | ); 24 | } 25 | _renderTopic(topics){ 26 | 27 | return topics.map((topic,index)=>{ 28 | return ( 29 | 30 | ); 31 | }); 32 | } 33 | handlerViewDetail(topicId){ 34 | const { handlerClickTitle } = this.props; 35 | handlerClickTitle && handlerClickTitle(topicId); 36 | } 37 | } 38 | 39 | export default Topics; 40 | 41 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_media.scss: -------------------------------------------------------------------------------- 1 | .media { 2 | // Proper spacing between instances of .media 3 | margin-top: 15px; 4 | 5 | &:first-child { 6 | margin-top: 0; 7 | } 8 | } 9 | 10 | .media, 11 | .media-body { 12 | zoom: 1; 13 | overflow: hidden; 14 | } 15 | 16 | .media-body { 17 | width: 10000px; 18 | } 19 | 20 | .media-object { 21 | display: block; 22 | 23 | // Fix collapse in webkit from max-width: 100% and display: table-cell. 24 | &.img-thumbnail { 25 | max-width: none; 26 | } 27 | } 28 | 29 | .media-right, 30 | .media > .pull-right { 31 | padding-left: 10px; 32 | } 33 | 34 | .media-left, 35 | .media > .pull-left { 36 | padding-right: 10px; 37 | } 38 | 39 | .media-left, 40 | .media-right, 41 | .media-body { 42 | display: table-cell; 43 | vertical-align: top; 44 | } 45 | 46 | .media-middle { 47 | vertical-align: middle; 48 | } 49 | 50 | .media-bottom { 51 | vertical-align: bottom; 52 | } 53 | 54 | // Reset margins on headings for tighter default spacing 55 | .media-heading { 56 | margin-top: 0; 57 | margin-bottom: 5px; 58 | } 59 | 60 | // Media list variation 61 | // 62 | // Undo default ul/ol styles 63 | .media-list { 64 | padding-left: 0; 65 | list-style: none; 66 | } 67 | -------------------------------------------------------------------------------- /src/common/containers/TopicDetailPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import React, { Component} from 'react'; 3 | import { connect } from 'react-redux'; 4 | import TopicDetail from '../components/TopicDetail'; 5 | import * as TopicDetailActions from '../actions/topicDetail'; 6 | 7 | //Data that needs to be called before rendering the component 8 | //This is used for server side rending via the fetchComponentDataBeforeRending() method 9 | 10 | TopicDetail.need = [ 11 | TopicDetailActions.fetchTopicDetail 12 | ]; 13 | 14 | function mapStateToProps(state) { 15 | 16 | let { topicDetail , user} = state; 17 | 18 | const { 19 | data, 20 | error, 21 | newAnswer, 22 | isFetching 23 | } = topicDetail || { 24 | data:null, 25 | error:false, 26 | newAnswer:null, 27 | isFetching:false 28 | }; 29 | 30 | return { 31 | newAnswer, 32 | data, 33 | user, 34 | error, 35 | isFetching 36 | }; 37 | } 38 | 39 | function mapDispatchToProps(dispatch) { 40 | return bindActionCreators(TopicDetailActions, dispatch); 41 | } 42 | 43 | export default connect(mapStateToProps,mapDispatchToProps)(TopicDetail); 44 | -------------------------------------------------------------------------------- /src/common/api/makeRouteHooksSafe.js: -------------------------------------------------------------------------------- 1 | import { createRoutes } from 'react-router/lib/RouteUtils'; 2 | 3 | // Wrap the hooks so they don't fire if they're called before 4 | // the store is initialised. This only happens when doing the first 5 | // client render of a route that has an onEnter hook 6 | function makeHooksSafe(routes, store) { 7 | if (Array.isArray(routes)) { 8 | return routes.map((route) => makeHooksSafe(route, store)); 9 | } 10 | 11 | const onEnter = routes.onEnter; 12 | 13 | if (onEnter) { 14 | routes.onEnter = function safeOnEnter(...args) { 15 | try { 16 | store.getState(); 17 | } catch (err) { 18 | if (onEnter.length === 3) { 19 | args[2](); 20 | } 21 | 22 | // There's no store yet so ignore the hook 23 | return; 24 | } 25 | 26 | onEnter.apply(null, args); 27 | }; 28 | } 29 | 30 | if (routes.childRoutes) { 31 | makeHooksSafe(routes.childRoutes, store); 32 | } 33 | 34 | if (routes.indexRoute) { 35 | makeHooksSafe(routes.indexRoute, store); 36 | } 37 | 38 | return routes; 39 | } 40 | 41 | export default function makeRouteHooksSafe(_getRoutes) { 42 | return (store) => makeHooksSafe(createRoutes(_getRoutes(store)), store); 43 | } 44 | -------------------------------------------------------------------------------- /src/server/models/TopicTagRelation.js: -------------------------------------------------------------------------------- 1 | import mongoose , { Schema } from 'mongoose'; 2 | 3 | import _ from 'lodash'; 4 | /** 5 | * 话题标签多对多关系中间实体 6 | */ 7 | const TopicTagRelation = new Schema({ 8 | id:String, //id 9 | topicId:String, // topicId 10 | tagId:String // tagId 11 | }); 12 | 13 | TopicTagRelation.statics.getAllTagsByTopicId = function(topicId,cb){ 14 | const Tag = mongoose.model('Tag'); 15 | 16 | this.find({topicId}).exec((err,relations)=>{ 17 | if(err){ 18 | return cb(err); 19 | } 20 | let tagIds = _.union(_.map(relations,item=>item.tagId)); 21 | //根据Ids数组查询Tag 22 | Tag.getTagByIds({},tagIds,['name','id'],cb); 23 | }) 24 | } 25 | //根据topicIds获取所有的tagsIds 26 | TopicTagRelation.statics.getAllTagsByTopicIds = function(topicIds,cb){ 27 | const Tag = mongoose.model('Tag'); 28 | 29 | this.where('topicId').in(topicIds).find((err,relations)=>{ 30 | 31 | if(err){ 32 | return cb(err); 33 | } 34 | let tagIds = _.union(_.map(relations,item=>item.tagId)); 35 | 36 | //根据Ids数组查询Tag 37 | Tag.getTagByIds({},tagIds,['name','id'],cb); 38 | }) 39 | } 40 | 41 | //编译Tag实体 42 | mongoose.model('TopicTagRelation',TopicTagRelation); 43 | -------------------------------------------------------------------------------- /src/common/containers/TopicPage.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import React, { Component} from 'react'; 3 | import { connect } from 'react-redux'; 4 | import Topic from '../components/Topic'; 5 | import * as TopicActions from '../actions/topic'; 6 | 7 | 8 | Topic.need = [ 9 | TopicActions.fetchTopics 10 | ] 11 | 12 | function mapStateToProps(state) { 13 | 14 | let { selectedCategory, topicsByCategory , user } = state; 15 | selectedCategory = selectedCategory.present; 16 | topicsByCategory = topicsByCategory.present; 17 | const { 18 | isFetching, 19 | lastUpdated, 20 | error, 21 | count, 22 | topics 23 | } = topicsByCategory[selectedCategory] || { 24 | isFetching: false, 25 | lastUpdated:false, 26 | error:{}, 27 | count:0, 28 | topics: [] 29 | }; 30 | 31 | return { 32 | selectedCategory , 33 | user, 34 | topics , 35 | count, 36 | isFetching , 37 | lastUpdated , 38 | error 39 | }; 40 | } 41 | 42 | function mapDispatchToProps(dispatch) { 43 | return bindActionCreators(TopicActions, dispatch); 44 | } 45 | 46 | export default connect(mapStateToProps,mapDispatchToProps)(Topic); 47 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/components/TabList.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import cx from 'classnames'; 3 | import _ from 'lodash' 4 | module.exports = React.createClass({ 5 | displayName: 'TabList', 6 | 7 | propTypes: { 8 | className: PropTypes.string, 9 | children: PropTypes.oneOfType([ 10 | PropTypes.object, 11 | PropTypes.array 12 | ]) 13 | }, 14 | 15 | render() { 16 | const { children } = this.props; 17 | let activeIndex = 0; 18 | let cList = React.Children.map(children,(item,index)=>{ 19 | const { selected } = item.props; 20 | if(selected){ 21 | activeIndex = index; 22 | } 23 | return React.cloneElement(item,{ 24 | selectTab:this.selectTab 25 | }); 26 | 27 | }); 28 | 29 | return ( 30 | 31 | 32 |
    41 | {cList} 42 | 43 |
    44 | ); 45 | }, 46 | selectTab(item) { 47 | 48 | this.props.onSelectTab && this.props.onSelectTab(item); 49 | } 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /src/common/actions/user.js: -------------------------------------------------------------------------------- 1 | import request from 'axios'; 2 | //登录 3 | export const USER_LOGIN = "USER_LOGIN"; 4 | export const USER_LOGIN_REQUEST = "USER_LOGIN_REQUEST"; 5 | export const USER_LOGIN_SUCCESS = "USER_LOGIN_SUCCESS"; 6 | export const USER_LOGIN_FAILURE = "USER_LOGIN_FAILURE"; 7 | //注册 8 | export const USER_SIGNUP = "USER_SIGNUP"; 9 | export const USER_SIGNUP_REQUEST = "USER_SIGNUP_REQUEST"; 10 | export const USER_SIGNUP_SUCCESS = "USER_SIGNUP_SUCCESS"; 11 | export const USER_SIGNUP_FAILURE = "USER_SIGNUP_FAILURE"; 12 | //登出 13 | export const USER_LOGOUT = "USER_LOGOUT"; 14 | export const USER_LOGOUT_REQUEST = "USER_LOGOUT_REQUEST"; 15 | export const USER_LOGOUT_SUCCESS = "USER_LOGOUT_SUCCESS"; 16 | export const USER_LOGOUT_FAILURE = "USER_LOGOUT_FAILURE"; 17 | 18 | 19 | function _doLogin({ email , password }){ 20 | return { 21 | type: USER_LOGIN, 22 | email , 23 | password , 24 | promise: request.post("http://localhost:8000/api/user/login",{email , password}) 25 | } 26 | } 27 | 28 | 29 | /** 30 | * 登录 31 | * @param {[type]} [description] 32 | * @return {[type]} [description] 33 | */ 34 | export function manualLogin({email,password}) { 35 | return (dispatch, getState) => { 36 | return dispatch(_doLogin({email,password})); 37 | }; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/common/components/layout/ExitHeader.js: -------------------------------------------------------------------------------- 1 | import React,{Component,PropTypes} from 'react'; 2 | export default class EixtWidget extends Component { 3 | constructor (props) { 4 | super (props); 5 | } 6 | mouseover () { 7 | $(".exit-menu").show(); 8 | } 9 | mouseout () { 10 | $(".exit-menu").hide(); 11 | 12 | } 13 | render () { 14 | const { user } = this.props; 15 | let menuComponent; 16 | if(user){ 17 | menuComponent = ( 18 | 23 | ) 24 | }else{ 25 | menuComponent = ( 26 | 31 | ) 32 | } 33 | return ( 34 |
    35 | { menuComponent } 36 |
    37 | ); 38 | } 39 | //处理登出单击 40 | handlerClick(e){ 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/server/lib/emailHelper.js: -------------------------------------------------------------------------------- 1 | var nodemailer = require('nodemailer'); 2 | 3 | module.exports=function(credentials){ 4 | console.log(credentials); 5 | var mailTransport = nodemailer.createTransport("SMTP",{ 6 | host: "smtp.163.com", // 主机 7 | secureConnection: true, // 使用 SSL 8 | port: 465, // SMTP 端口 9 | auth:{ 10 | user:credentials.gmail.user, 11 | pass:credentials.gmail.password 12 | } 13 | }); 14 | 15 | var from = credentials.gmail.user; 16 | var errorRecipient = credentials.gmail.user; 17 | 18 | return { 19 | send:function(to,title,txt){ 20 | mailTransport.sendMail({ 21 | from:from, 22 | to:to, 23 | subject:title, 24 | html:txt, 25 | generateTextFromHtml:true 26 | },function(err){ 27 | if(err) console.error('Unable to email: '+err) 28 | }) 29 | 30 | }, 31 | emailError:function(msg,file,exception){ 32 | var body ='

    site error

    '+ 33 | 'message:
    '+message+'

    '; 34 | // if(file) body +=; 35 | // if(exception) body+=; 36 | mailTransport.sendMail({ 37 | from:from, 38 | to:errorRecipient, 39 | subject:'site error', 40 | html:body, 41 | generateTextFromHtml:true 42 | },function(err){ 43 | if(err) console.error('Unable to email: '+err) 44 | }) 45 | } 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/common/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | 3 | import { bindActionCreators } from 'redux'; 4 | import { connect } from 'react-redux'; 5 | import { Link } from 'react-router'; 6 | import classNames from 'classnames'; 7 | 8 | import * as LayoutActions from '../actions/layout'; 9 | import Home from '../components/Home' 10 | import Header from '../components/layout/Header' 11 | import Footer from '../components/layout/Footer' 12 | 13 | //框架组件 14 | class App extends Component{ 15 | 16 | render(){ 17 | const { user , layout , version } = this.props; 18 | const { sidebarOpen } = layout; 19 | const layoutClass = classNames('wrapper',{open : sidebarOpen}); 20 | 21 | return ( 22 |
    23 | 24 | 25 | {!this.props.children && } 26 | {this.props.children} 27 | 28 | 29 |
    30 | ); 31 | } 32 | } 33 | function mapStateToProps(state) { 34 | return { 35 | version : state.version, 36 | user : state.user, 37 | layout : state.layout.present 38 | }; 39 | } 40 | 41 | function mapDispatchToProps(dispatch) { 42 | return bindActionCreators(LayoutActions,dispatch); 43 | } 44 | 45 | export default connect(mapStateToProps, mapDispatchToProps)(App); 46 | -------------------------------------------------------------------------------- /src/common/components/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Carousel ,CarouselItem , Image } from 'react-bootstrap'; 3 | class Home extends Component { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 |
    10 |

    电子图书馆

    11 |

    基于Sass+React+Redux+React-Router+React-Bootstrap+Express MVC+MongoDB实现!

    12 |
    13 |
    14 | 15 | 16 |
    17 |

    电子图书馆

    18 |

    基于Sass+React+Redux+React-Router+React-Bootstrap+Express MVC+MongoDB实现!

    19 |
    20 |
    21 | 22 | 23 |
    24 |

    电子图书馆

    25 |

    基于Sass+React+Redux+React-Router+React-Bootstrap+Express MVC+MongoDB实现!

    26 |
    27 |
    28 |
    29 | 30 | ); 31 | } 32 | } 33 | 34 | export default Home; 35 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_image.scss: -------------------------------------------------------------------------------- 1 | // Image Mixins 2 | // - Responsive image 3 | // - Retina image 4 | 5 | 6 | // Responsive image 7 | // 8 | // Keep images from scaling beyond the width of their parents. 9 | @mixin img-responsive($display: block) { 10 | display: $display; 11 | max-width: 100%; // Part 1: Set a maximum relative to the parent 12 | height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching 13 | } 14 | 15 | 16 | // Retina image 17 | // 18 | // Short retina mixin for setting background-image and -size. Note that the 19 | // spelling of `min--moz-device-pixel-ratio` is intentional. 20 | @mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { 21 | background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-1x}"), "#{$file-1x}")); 22 | 23 | @media 24 | only screen and (-webkit-min-device-pixel-ratio: 2), 25 | only screen and ( min--moz-device-pixel-ratio: 2), 26 | only screen and ( -o-min-device-pixel-ratio: 2/1), 27 | only screen and ( min-device-pixel-ratio: 2), 28 | only screen and ( min-resolution: 192dpi), 29 | only screen and ( min-resolution: 2dppx) { 30 | background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-2x}"), "#{$file-2x}")); 31 | background-size: $width-1x $height-1x; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_jumbotron.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding-top: $jumbotron-padding; 8 | padding-bottom: $jumbotron-padding; 9 | margin-bottom: $jumbotron-padding; 10 | color: $jumbotron-color; 11 | background-color: $jumbotron-bg; 12 | 13 | h1, 14 | .h1 { 15 | color: $jumbotron-heading-color; 16 | } 17 | 18 | p { 19 | margin-bottom: ($jumbotron-padding / 2); 20 | font-size: $jumbotron-font-size; 21 | font-weight: 200; 22 | } 23 | 24 | > hr { 25 | border-top-color: darken($jumbotron-bg, 10%); 26 | } 27 | 28 | .container &, 29 | .container-fluid & { 30 | border-radius: $border-radius-large; // Only round corners at higher resolutions if contained in a container 31 | padding-left: ($grid-gutter-width / 2); 32 | padding-right: ($grid-gutter-width / 2); 33 | } 34 | 35 | .container { 36 | max-width: 100%; 37 | } 38 | 39 | @media screen and (min-width: $screen-sm-min) { 40 | padding-top: ($jumbotron-padding * 1.6); 41 | padding-bottom: ($jumbotron-padding * 1.6); 42 | 43 | .container &, 44 | .container-fluid & { 45 | padding-left: ($jumbotron-padding * 2); 46 | padding-right: ($jumbotron-padding * 2); 47 | } 48 | 49 | h1, 50 | .h1 { 51 | font-size: $jumbotron-heading-font-size; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_labels.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: $label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // [converter] extracted a& to a.label 18 | 19 | // Empty labels collapse automatically (not available in IE8) 20 | &:empty { 21 | display: none; 22 | } 23 | 24 | // Quick fix for labels in buttons 25 | .btn & { 26 | position: relative; 27 | top: -1px; 28 | } 29 | } 30 | 31 | // Add hover effects, but only for links 32 | a.label { 33 | &:hover, 34 | &:focus { 35 | color: $label-link-hover-color; 36 | text-decoration: none; 37 | cursor: pointer; 38 | } 39 | } 40 | 41 | // Colors 42 | // Contextual variations (linked labels get darker on :hover) 43 | 44 | .label-default { 45 | @include label-variant($label-default-bg); 46 | } 47 | 48 | .label-primary { 49 | @include label-variant($label-primary-bg); 50 | } 51 | 52 | .label-success { 53 | @include label-variant($label-success-bg); 54 | } 55 | 56 | .label-info { 57 | @include label-variant($label-info-bg); 58 | } 59 | 60 | .label-warning { 61 | @include label-variant($label-warning-bg); 62 | } 63 | 64 | .label-danger { 65 | @include label-variant($label-danger-bg); 66 | } 67 | -------------------------------------------------------------------------------- /src/common/components/ui/topicDetail/Answers.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import VoteWidget from './widgets/Vote'; 3 | import AvatorWidget from './widgets/Avator'; 4 | import OperationWidget from './widgets/Operation'; 5 | import FromNow from '../common/FromNow'; 6 | import _ from 'lodash'; 7 | /** 8 | * 回答详情 9 | */ 10 | export default class Answers extends Component { 11 | render(){ 12 | 13 | const { answer } = this.props; 14 | const { content , star , id, accept ,userInfo ={}, answerTime , updateTime } = answer ; 15 | return ( 16 | 17 | 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_badges.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: $font-size-small; 12 | font-weight: $badge-font-weight; 13 | color: $badge-color; 14 | line-height: $badge-line-height; 15 | vertical-align: middle; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: $badge-bg; 19 | border-radius: $badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | 26 | // Quick fix for badges in buttons 27 | .btn & { 28 | position: relative; 29 | top: -1px; 30 | } 31 | 32 | .btn-xs &, 33 | .btn-group-xs > .btn & { 34 | top: 0; 35 | padding: 1px 5px; 36 | } 37 | 38 | // [converter] extracted a& to a.badge 39 | 40 | // Account for badges in navs 41 | .list-group-item.active > &, 42 | .nav-pills > .active > a > & { 43 | color: $badge-active-color; 44 | background-color: $badge-active-bg; 45 | } 46 | 47 | .list-group-item > & { 48 | float: right; 49 | } 50 | 51 | .list-group-item > & + & { 52 | margin-right: 5px; 53 | } 54 | 55 | .nav-pills > li > a > & { 56 | margin-left: 3px; 57 | } 58 | } 59 | 60 | // Hover state, but only for links 61 | a.badge { 62 | &:hover, 63 | &:focus { 64 | color: $badge-link-hover-color; 65 | text-decoration: none; 66 | cursor: pointer; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/server/controllers/ask.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import uuid from 'node-uuid'; 4 | import { setError , setOk } from '../lib/utils'; 5 | const router = express.Router(); 6 | const Topic = mongoose.model('Topic'); 7 | const TopicTagRelation = mongoose.model('TopicTagRelation'); 8 | 9 | module.exports= function (app) { 10 | app.use("/",router); 11 | }; 12 | 13 | //发布话题 14 | 15 | router.post('/ask/publish', function (req, res) { 16 | 17 | const { 18 | title , 19 | content , 20 | tagId = [] 21 | } = req.body; 22 | 23 | const { user } = req.session; 24 | 25 | const { id:userId } = user; 26 | const topicId = uuid.v4(); 27 | const tagIds = tagId.split(","); 28 | const topicEntity = new Topic({ 29 | title , 30 | content , 31 | id:topicId, 32 | userId , 33 | tagIds 34 | }); 35 | 36 | topicEntity.save((err,topic)=>{ 37 | if(err || !topic){ 38 | res.status(200).json(setError({msg:"发表失败!"})); 39 | } 40 | const ttrArray = tagIds.map((tagId)=>{ 41 | return { 42 | topicId, 43 | tagId, 44 | id:uuid.v4() 45 | }; 46 | }); 47 | //保存关联 48 | TopicTagRelation.create(ttrArray,(err)=>{ 49 | if(err){ 50 | res.status(200).json(setError({msg:"发表失败!"})); 51 | } 52 | res.status(200).json(setOk({msg:"发表成功",topic,user:req.session.user})); 53 | }); 54 | 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/components/Tab.js: -------------------------------------------------------------------------------- 1 | import React, {PropTypes} from 'react'; 2 | import { findDOMNode } from 'react-dom'; 3 | import cx from 'classnames'; 4 | 5 | function syncNodeAttributes(node, props) { 6 | if (props.selected) { 7 | node.setAttribute('tabindex', 0); 8 | node.setAttribute('selected', 'selected'); 9 | if (props.focus) { 10 | node.focus(); 11 | } 12 | } else { 13 | node.removeAttribute('tabindex'); 14 | node.removeAttribute('selected'); 15 | } 16 | } 17 | 18 | module.exports = React.createClass({ 19 | displayName: 'Tab', 20 | 21 | 22 | 23 | getDefaultProps() { 24 | return { 25 | focus: false, 26 | selected: false, 27 | id: null, 28 | panelId: null 29 | }; 30 | }, 31 | 32 | componentDidMount() { 33 | syncNodeAttributes(findDOMNode(this), this.props); 34 | }, 35 | 36 | componentDidUpdate() { 37 | syncNodeAttributes(findDOMNode(this), this.props); 38 | }, 39 | 40 | render() { 41 | return ( 42 | 55 | {this.props.children} 56 | 57 | ); 58 | }, 59 | handlerClick(e){ 60 | 61 | let node = e.target; 62 | const index = node.getAttribute('data-index') -0 ; 63 | this.props.selectTab(index); 64 | } 65 | }); 66 | -------------------------------------------------------------------------------- /src/common/components/ui/tabs/helpers/childrenPropType.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tab from '../components/Tab'; 3 | import TabList from '../components/TabList'; 4 | 5 | module.exports = function childrenPropTypes(props, propName) { 6 | let error; 7 | let tabsCount = 0; 8 | let panelsCount = 0; 9 | const children = props[propName]; 10 | 11 | React.Children.forEach(children, (child) => { 12 | // null happens when conditionally rendering TabPanel/Tab 13 | // see https://github.com/rackt/react-tabs/issues/37 14 | if (child === null) { 15 | return; 16 | } 17 | 18 | if (child.type === TabList) { 19 | React.Children.forEach(child.props.children, (c) => { 20 | // null happens when conditionally rendering TabPanel/Tab 21 | // see https://github.com/rackt/react-tabs/issues/37 22 | if (c === null) { 23 | return; 24 | } 25 | 26 | if (c.type === Tab) { 27 | tabsCount++; 28 | } else { 29 | error = new Error( 30 | 'Expected `Tab` but found `' + (c.type.displayName || c.type) + '`' 31 | ); 32 | } 33 | }); 34 | } else if (child.type.displayName === 'TabPanel') { 35 | panelsCount++; 36 | } else { 37 | error = new Error( 38 | 'Expected `TabList` or `TabPanel` but found `' + (child.type.displayName || child.type) + '`' 39 | ); 40 | } 41 | }); 42 | 43 | if (tabsCount !== panelsCount) { 44 | error = new Error( 45 | 'There should be an equal number of `Tabs` and `TabPanels`. ' + 46 | 'Received ' + tabsCount + ' `Tabs` and ' + panelsCount + ' `TabPanels`.' 47 | ); 48 | } 49 | 50 | return error; 51 | }; 52 | -------------------------------------------------------------------------------- /src/common/components/ui/sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import HotTopic from './HotTopic'; 3 | import TagList from '../topicDetail/TagList'; 4 | 5 | export default class Sidebar extends Component { 6 | 7 | _getHotTopics(hotTopics){ 8 | return hotTopics.map((arr,index)=>{ 9 | return 10 | }) 11 | 12 | } 13 | render(){ 14 | 15 | 16 | let dataArr=[{title:"222",answers:10,isSolved:true,href:"222.com"} 17 | ,{title:"222",answers:10,isSolved:true,href:"222.com"} 18 | ,{title:"222",answers:10,isSolved:true,href:"222.com"} 19 | ,{title:"222",answers:10,isSolved:true,href:"222.com"} 20 | ] 21 | 22 | let tags=[ 23 | {"name":"nodeJs",id:"1"} 24 | ,{"name":"talentJs",id:"2"} 25 | ,{"name":"react",id:"3"} 26 | ] 27 | 28 | return ( 29 |
    30 |
    31 | 32 |

    全部标签 »

    33 | 34 | 35 |
    36 |
    37 | 38 |

    最近热门的

    39 |
      40 | 41 | {this._getHotTopics(dataArr)} 42 |
    43 |
    44 | 45 |
    46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_code.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and block) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | kbd, 9 | pre, 10 | samp { 11 | font-family: $font-family-monospace; 12 | } 13 | 14 | // Inline code 15 | code { 16 | padding: 2px 4px; 17 | font-size: 90%; 18 | color: $code-color; 19 | background-color: $code-bg; 20 | border-radius: $border-radius-base; 21 | } 22 | 23 | // User input typically entered via keyboard 24 | kbd { 25 | padding: 2px 4px; 26 | font-size: 90%; 27 | color: $kbd-color; 28 | background-color: $kbd-bg; 29 | border-radius: $border-radius-small; 30 | box-shadow: inset 0 -1px 0 rgba(0,0,0,.25); 31 | 32 | kbd { 33 | padding: 0; 34 | font-size: 100%; 35 | font-weight: bold; 36 | box-shadow: none; 37 | } 38 | } 39 | 40 | // Blocks of code 41 | pre { 42 | display: block; 43 | padding: (($line-height-computed - 1) / 2); 44 | margin: 0 0 ($line-height-computed / 2); 45 | font-size: ($font-size-base - 1); // 14px to 13px 46 | line-height: $line-height-base; 47 | word-break: break-all; 48 | word-wrap: break-word; 49 | color: $pre-color; 50 | background-color: $pre-bg; 51 | border: 1px solid $pre-border-color; 52 | border-radius: $border-radius-base; 53 | 54 | // Account for some code outputs that place code tags in pre tags 55 | code { 56 | padding: 0; 57 | font-size: inherit; 58 | color: inherit; 59 | white-space: pre-wrap; 60 | background-color: transparent; 61 | border-radius: 0; 62 | } 63 | } 64 | 65 | // Enable scrollable blocks of code 66 | .pre-scrollable { 67 | max-height: $pre-scrollable-max-height; 68 | overflow-y: scroll; 69 | } 70 | -------------------------------------------------------------------------------- /sass/bootstrap/_bootstrap.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // Core variables and mixins 8 | @import "bootstrap/variables"; 9 | @import "bootstrap/mixins"; 10 | 11 | // Reset and dependencies 12 | @import "bootstrap/normalize"; 13 | @import "bootstrap/print"; 14 | @import "bootstrap/glyphicons"; 15 | 16 | // Core CSS 17 | @import "bootstrap/scaffolding"; 18 | @import "bootstrap/type"; 19 | @import "bootstrap/code"; 20 | @import "bootstrap/grid"; 21 | @import "bootstrap/tables"; 22 | @import "bootstrap/forms"; 23 | @import "bootstrap/buttons"; 24 | 25 | // Components 26 | @import "bootstrap/component-animations"; 27 | @import "bootstrap/dropdowns"; 28 | @import "bootstrap/button-groups"; 29 | @import "bootstrap/input-groups"; 30 | @import "bootstrap/navs"; 31 | @import "bootstrap/navbar"; 32 | @import "bootstrap/breadcrumbs"; 33 | @import "bootstrap/pagination"; 34 | @import "bootstrap/pager"; 35 | @import "bootstrap/labels"; 36 | @import "bootstrap/badges"; 37 | @import "bootstrap/jumbotron"; 38 | @import "bootstrap/thumbnails"; 39 | @import "bootstrap/alerts"; 40 | @import "bootstrap/progress-bars"; 41 | @import "bootstrap/media"; 42 | @import "bootstrap/list-group"; 43 | @import "bootstrap/panels"; 44 | @import "bootstrap/responsive-embed"; 45 | @import "bootstrap/wells"; 46 | @import "bootstrap/close"; 47 | 48 | // Components w/ JavaScript 49 | @import "bootstrap/modals"; 50 | @import "bootstrap/tooltip"; 51 | @import "bootstrap/popovers"; 52 | @import "bootstrap/carousel"; 53 | 54 | // Utility classes 55 | @import "bootstrap/utilities"; 56 | @import "bootstrap/responsive-utilities"; 57 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Button variants 2 | // 3 | // Easily pump out default styles, as well as :hover, :focus, :active, 4 | // and disabled options for all buttons 5 | 6 | @mixin button-variant($color, $background, $border) { 7 | color: $color; 8 | background-color: $background; 9 | border-color: $border; 10 | 11 | &:focus, 12 | &.focus { 13 | color: $color; 14 | background-color: darken($background, 10%); 15 | border-color: darken($border, 25%); 16 | } 17 | &:hover { 18 | color: $color; 19 | background-color: darken($background, 10%); 20 | border-color: darken($border, 12%); 21 | } 22 | &:active, 23 | &.active, 24 | .open > &.dropdown-toggle { 25 | color: $color; 26 | background-color: darken($background, 10%); 27 | border-color: darken($border, 12%); 28 | 29 | &:hover, 30 | &:focus, 31 | &.focus { 32 | color: $color; 33 | background-color: darken($background, 17%); 34 | border-color: darken($border, 25%); 35 | } 36 | } 37 | &:active, 38 | &.active, 39 | .open > &.dropdown-toggle { 40 | background-image: none; 41 | } 42 | &.disabled, 43 | &[disabled], 44 | fieldset[disabled] & { 45 | &:hover, 46 | &:focus, 47 | &.focus { 48 | background-color: $background; 49 | border-color: $border; 50 | } 51 | } 52 | 53 | .badge { 54 | color: $background; 55 | background-color: $color; 56 | } 57 | } 58 | 59 | // Button sizes 60 | @mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { 61 | padding: $padding-vertical $padding-horizontal; 62 | font-size: $font-size; 63 | line-height: $line-height; 64 | border-radius: $border-radius; 65 | } 66 | -------------------------------------------------------------------------------- /src/common/reducers/topicDetail.js: -------------------------------------------------------------------------------- 1 | import { 2 | TOPICDETAIL, 3 | TOPICDETAIL_REQUEST, 4 | TOPICDETAIL_SUCCESS, 5 | TOPICDETAIL_FAILURE, 6 | ADDANSWER , 7 | ADDANSWER_REQUEST , 8 | ADDANSWER_SUCCESS , 9 | ADDANSWER_FAILURE 10 | } from '../actions/topicDetail'; 11 | 12 | const initialState = { 13 | 14 | error: false, 15 | isFetching:false, 16 | data:null 17 | 18 | } 19 | 20 | export default function topicDetail(state = initialState , action) { 21 | 22 | switch (action.type) { 23 | //发表之前 24 | case ADDANSWER_REQUEST: 25 | return Object.assign({}, state,{ 26 | newAnswer:null 27 | }); 28 | //发表答案成功 29 | case ADDANSWER_SUCCESS: 30 | const { data:answerData } = action.req.data; 31 | const { data:stateData } = state; 32 | stateData.answers.push(answerData); 33 | 34 | return Object.assign({}, state,{ 35 | data:stateData, 36 | newAnswer:answerData, 37 | 38 | }); 39 | 40 | 41 | case TOPICDETAIL_REQUEST: 42 | 43 | return Object.assign({}, state, { 44 | isFetching: true, 45 | data:null 46 | }); 47 | case TOPICDETAIL_SUCCESS: 48 | console.log("--------成功了----------"); 49 | 50 | const { data } = action.req.data; 51 | console.log(data); 52 | return Object.assign({}, state, { 53 | isFetching: false, 54 | data 55 | }); 56 | case TOPICDETAIL_FAILURE: 57 | return Object.assign({}, state, { 58 | isFetching: false 59 | }); 60 | default: 61 | return state; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /webpack/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 基础配置 3 | */ 4 | 'use strict' 5 | 6 | import path from 'path'; 7 | import webpack from 'webpack'; 8 | const srcRoot = path.join(__dirname, '../src'); 9 | const nodeRoot = path.join(__dirname, '../node_modules'); 10 | const publicRoot = path.join(__dirname, '../public'); 11 | export default { 12 | 13 | resolve: { 14 | root: [srcRoot, nodeRoot,publicRoot], 15 | extensions: ['', '.js', '.jsx', '.css'] 16 | }, 17 | 18 | module: { 19 | loaders: [ 20 | 21 | { 22 | test: /\.js/, 23 | loader: 'babel', 24 | exclude: /node_modules/, 25 | query:{ 26 | stage:0, 27 | plugins: ['react-transform'], 28 | extra:{ 29 | 'react-transform': { 30 | transforms: [{ 31 | transform: 'react-transform-hmr', 32 | imports: ['react'], 33 | locals: ['module'] 34 | },{ 35 | transform: 'react-transform-catch-errors', 36 | imports: ['react','redbox-react' ] 37 | }] 38 | } 39 | } 40 | } 41 | } 42 | , { test: /\.css$/, loader: 'style-loader!css-loader!autoprefixer-loader' } 43 | , { test: /\.scss$/, loader: 'style-loader!css-loader!autoprefixer-loader!sass-loader' } 44 | , { test: /\.(ttf|eot|svg|woff|woff2)?$/, loader: "file-loader?name=[hash:8].[name].[ext]" } 45 | , { test: /\.(png|gif)$/, loader: "file-loader?name=../../images/[hash:8].[name].[ext]" } 46 | 47 | ] 48 | } 49 | } 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_grid.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Grid system 3 | // -------------------------------------------------- 4 | 5 | 6 | // Container widths 7 | // 8 | // Set the container width, and override it for fixed navbars in media queries. 9 | 10 | .container { 11 | @include container-fixed; 12 | 13 | @media (min-width: $screen-sm-min) { 14 | width: $container-sm; 15 | } 16 | @media (min-width: $screen-md-min) { 17 | width: $container-md; 18 | } 19 | @media (min-width: $screen-lg-min) { 20 | width: $container-lg; 21 | } 22 | } 23 | 24 | 25 | // Fluid container 26 | // 27 | // Utilizes the mixin meant for fixed width containers, but without any defined 28 | // width for fluid, full width layouts. 29 | 30 | .container-fluid { 31 | @include container-fixed; 32 | } 33 | 34 | 35 | // Row 36 | // 37 | // Rows contain and clear the floats of your columns. 38 | 39 | .row { 40 | @include make-row; 41 | } 42 | 43 | 44 | // Columns 45 | // 46 | // Common styles for small and large grid columns 47 | 48 | @include make-grid-columns; 49 | 50 | 51 | // Extra small grid 52 | // 53 | // Columns, offsets, pushes, and pulls for extra small devices like 54 | // smartphones. 55 | 56 | @include make-grid(xs); 57 | 58 | 59 | // Small grid 60 | // 61 | // Columns, offsets, pushes, and pulls for the small device range, from phones 62 | // to tablets. 63 | 64 | @media (min-width: $screen-sm-min) { 65 | @include make-grid(sm); 66 | } 67 | 68 | 69 | // Medium grid 70 | // 71 | // Columns, offsets, pushes, and pulls for the desktop device range. 72 | 73 | @media (min-width: $screen-md-min) { 74 | @include make-grid(md); 75 | } 76 | 77 | 78 | // Large grid 79 | // 80 | // Columns, offsets, pushes, and pulls for the large desktop device range. 81 | 82 | @media (min-width: $screen-lg-min) { 83 | @include make-grid(lg); 84 | } 85 | -------------------------------------------------------------------------------- /src/common/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createStore, applyMiddleware, compose } from 'redux'; 3 | import { devTools } from 'redux-devtools'; 4 | import { reduxReactRouter } from 'redux-router'; 5 | import thunk from 'redux-thunk'; 6 | import createHistory from 'history/lib/createBrowserHistory'; 7 | import createLogger from 'redux-logger'; 8 | /*import apiMiddleware from '../api/api-promise-middleware';*/ 9 | import promiseMiddleware from '../api/promiseMiddleware'; 10 | import rootReducer from '../reducers'; 11 | 12 | const middlewareBuilder = () => { 13 | 14 | let middleware = {}; 15 | let universalMiddleware = [thunk,promiseMiddleware]; 16 | let allComposeElements = []; 17 | 18 | if(process.browser){ 19 | if(process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test'){ 20 | middleware = applyMiddleware(...universalMiddleware); 21 | allComposeElements = [ 22 | middleware 23 | ] 24 | }else{ 25 | middleware = applyMiddleware(...universalMiddleware,createLogger()); 26 | allComposeElements = [ 27 | middleware 28 | ] 29 | } 30 | }else{ 31 | middleware = applyMiddleware(...universalMiddleware); 32 | allComposeElements = [ 33 | middleware 34 | ] 35 | } 36 | 37 | return allComposeElements; 38 | 39 | } 40 | 41 | const finalCreateStore = compose(...middlewareBuilder())(createStore); 42 | 43 | export default function configureStore(initialState) { 44 | const store = finalCreateStore(rootReducer, initialState); 45 | 46 | if (module.hot) { 47 | // Enable Webpack hot module replacement for reducers 48 | module.hot.accept('../reducers', () => { 49 | const nextRootReducer = require('../reducers'); 50 | store.replaceReducer(nextRootReducer); 51 | }); 52 | } 53 | 54 | return store; 55 | } 56 | -------------------------------------------------------------------------------- /src/common/actions/topicDetail.js: -------------------------------------------------------------------------------- 1 | import request from 'axios'; 2 | export const TOPICDETAIL = 'TOPICDETAIL'; 3 | export const TOPICDETAIL_REQUEST = 'TOPICDETAIL_REQUEST'; 4 | export const TOPICDETAIL_SUCCESS = 'TOPICDETAIL_SUCCESS'; 5 | export const TOPICDETAIL_FAILURE = 'TOPICDETAIL_FAILURE'; 6 | 7 | //发表答案 8 | export const ADDANSWER = 'ADDANSWER'; 9 | export const ADDANSWER_REQUEST = 'ADDANSWER_REQUEST'; 10 | export const ADDANSWER_SUCCESS = 'ADDANSWER_SUCCESS'; 11 | export const ADDANSWER_FAILURE = 'ADDANSWER_FAILURE'; 12 | 13 | 14 | 15 | export function fetchTopicDetail({topicId}) { 16 | 17 | return { 18 | type: TOPICDETAIL, 19 | topicId, 20 | promise: request.post("http://localhost:8000/topics/getDetail",{topicId}) 21 | } 22 | } 23 | 24 | function shouldFetchTopicDetail(state, topicId) { 25 | // debugger; 26 | //const topicDetail = state.topicDetail; 27 | 28 | //if (!topicDetail) { 29 | return true; 30 | //} else if (topicDetail.isFetching) { 31 | // return false; 32 | // } else { 33 | // return topicDetail.didInvalidate; 34 | // } 35 | } 36 | 37 | export function fetchTopicDetailIfNeeded(topicId) { 38 | 39 | return (dispatch, getState) => { 40 | if (shouldFetchTopicDetail(getState(), topicId)) { 41 | 42 | return dispatch(fetchTopicDetail({topicId})); 43 | } 44 | }; 45 | } 46 | 47 | function _sendAnswer(topicId,answerContent){ 48 | return { 49 | type: ADDANSWER, 50 | 51 | topicId, 52 | answerContent, 53 | promise: request.post("http://localhost:8000/topics/addAnswer",{topicId,answerContent}) 54 | } 55 | } 56 | 57 | /** 58 | * 发表答案 59 | */ 60 | export function sendAnswer(topicId,answerContent){ 61 | return (dispatch, getState) => { 62 | return dispatch(_sendAnswer(topicId,answerContent)); 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_alerts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: $alert-padding; 11 | margin-bottom: $line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: $alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing $headings-color 19 | color: inherit; 20 | } 21 | 22 | // Provide class for links that match alerts 23 | .alert-link { 24 | font-weight: $alert-link-font-weight; 25 | } 26 | 27 | // Improve alignment and spacing of inner content 28 | > p, 29 | > ul { 30 | margin-bottom: 0; 31 | } 32 | 33 | > p + p { 34 | margin-top: 5px; 35 | } 36 | } 37 | 38 | // Dismissible alerts 39 | // 40 | // Expand the right padding and account for the close button's positioning. 41 | 42 | .alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0. 43 | .alert-dismissible { 44 | padding-right: ($alert-padding + 20); 45 | 46 | // Adjust close link position 47 | .close { 48 | position: relative; 49 | top: -2px; 50 | right: -21px; 51 | color: inherit; 52 | } 53 | } 54 | 55 | // Alternate styles 56 | // 57 | // Generate contextual modifier classes for colorizing the alert. 58 | 59 | .alert-success { 60 | @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); 61 | } 62 | 63 | .alert-info { 64 | @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); 65 | } 66 | 67 | .alert-warning { 68 | @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); 69 | } 70 | 71 | .alert-danger { 72 | @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); 73 | } 74 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-core/polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import { Router } from 'react-router'; 5 | import createBrowserHistory from 'history/lib/createBrowserHistory' 6 | import { Provider } from 'react-redux'; 7 | import { syncReduxAndRouter } from 'redux-simple-router'; 8 | import configureStore from '../common/store/configureStore'; 9 | import routes from '../common/routes'; 10 | import makeRouteHooksSafe from '../common/api/makeRouteHooksSafe'; 11 | import immutifyState from '../common/api/immutifyState'; 12 | import moment from 'moment'; 13 | const history = createBrowserHistory(); 14 | const initialState = window.__INITIAL_STATE__; 15 | const store = configureStore(initialState); 16 | 17 | syncReduxAndRouter(history, store); 18 | 19 | 20 | moment.locale('en', { 21 | relativeTime : { 22 | future: "在 %s", 23 | past: "%s", 24 | s: "刚刚", 25 | m: "1分钟前", 26 | mm: "%d分钟前", 27 | h: "1小时前", 28 | hh: "%d小时前", 29 | d: "1天前", 30 | dd: "%d天前", 31 | M: "1个月前", 32 | MM: "%d个月前", 33 | y: "1年前", 34 | yy: "%d年前" 35 | } 36 | }); 37 | moment.locale('zh-cn', { 38 | relativeTime : { 39 | future: "在%s", 40 | past: "%s", 41 | s: "刚刚", 42 | m: "1分钟前", 43 | mm: "%d分钟前", 44 | h: "1小时前", 45 | hh: "%d小时前", 46 | d: "1天前", 47 | dd: "%d天前", 48 | M: "1个月前", 49 | MM: "%d个月前", 50 | y: "1年前", 51 | yy: "%d年前" 52 | } 53 | }); 54 | 55 | //挂载点 56 | const mountId = document.getElementById('root'); 57 | /* BJ_REPORT.init({ 58 | id: 1, 59 | url: "http://127.0.0.1/badjs" 60 | }); 61 | 62 | BJ_REPORT.tryJs().spyAll();*/ 63 | //渲染 64 | render( 65 | 66 | 67 | {routes} 68 | 69 | , 70 | mountId 71 | ); 72 | -------------------------------------------------------------------------------- /src/common/actions/topic.js: -------------------------------------------------------------------------------- 1 | import request from 'axios'; 2 | 3 | export const SELECT_CATEGORY = 'SELECT_TCATEGORY'; 4 | export const INVALIDATE_CATEGORY = 'INVALIDATE_CATEGORY'; 5 | 6 | export const TOPICS_GET = 'TOPICS_GET'; 7 | export const TOPICS_GET_REQUEST = 'TOPICS_GET_REQUEST'; 8 | export const TOPICS_GET_SUCCESS = 'TOPICS_GET_SUCCESS'; 9 | export const TOPICS_GET_FAILURE = 'TOPICS_GET_FAILURE'; 10 | 11 | export function selectCategory(category) { 12 | return { 13 | type: SELECT_CATEGORY, 14 | category 15 | }; 16 | } 17 | 18 | export function invalidateCategory(category) { 19 | return { 20 | type: INVALIDATE_CATEGORY, 21 | category 22 | }; 23 | } 24 | 25 | export function fetchTopics({category = 'hot'}) { 26 | 27 | return { 28 | type: TOPICS_GET, 29 | category, 30 | promise: request.post("http://localhost:8000/topics/get",{category}) 31 | } 32 | } 33 | 34 | function shouldFetchTopics(state, category) { 35 | 36 | const topics = state.topicsByCategory[category]; 37 | 38 | //if (!topics) { 39 | return true; 40 | //} else if (topics.isFetching) { 41 | //return false; 42 | // } else { 43 | // return topics.didInvalidate; 44 | //} 45 | } 46 | 47 | export function fetchTopicsIfNeeded(reddit) { 48 | return (dispatch, getState) => { 49 | if (shouldFetchTopics(getState(), reddit)) { 50 | return dispatch(fetchTopics(reddit)); 51 | } 52 | }; 53 | } 54 | 55 | 56 | function _getTopics({pageIndex,pageSize,lastTime,category='hot'}) { 57 | return { 58 | type: TOPICS_GET, 59 | pageIndex, 60 | pageSize, 61 | lastTime, 62 | category, 63 | promise: request.post("http://localhost:8000/topics/get",{pageIndex,pageSize,lastTime,category}) 64 | } 65 | } 66 | //获取更多 67 | export function getTopics({pageIndex,pageSize,lastTime,category='hot'}) { 68 | return (dispatch, getState) => { 69 | return dispatch(_getTopics({pageIndex,pageSize,lastTime,category})); 70 | }; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/server/controllers/register.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import mongoose from 'mongoose'; 3 | import crypto from 'crypto'; 4 | import uuid from 'node-uuid'; 5 | 6 | import async from 'async'; 7 | import { setError , setOk } from '../lib/utils'; 8 | //初始化router 9 | const router = express.Router(); 10 | //导入User的Model 11 | const User = mongoose.model('User'); 12 | 13 | 14 | 15 | module.exports= function (app) { 16 | 17 | app.use("/reg",router); 18 | }; 19 | //用户注册 20 | router.post('/', function (req, res) { 21 | const { name , password , email } = req.body ; 22 | const passHash = crypto.createHash('md5').update(password).digest('hex'); 23 | 24 | 25 | var userEntity = new User({ 26 | name, 27 | password:passHash, 28 | email, 29 | id:uuid.v4() 30 | }); 31 | //查看用户名是否唯一 32 | User.checkUser({name},(err,user)=>{ 33 | if(err){ 34 | return res.status(200).json(setError()); 35 | } 36 | if(user){ //存在,则已经占用 37 | return res.status(200).json(setError({msg:"用户名已经被占用!"})); 38 | } 39 | //查看邮箱是否唯一 40 | User.checkUser({email},(err,user)=>{ 41 | if(err){ 42 | return res.status(200).json(setError()); 43 | } 44 | if(user){ //存在,则已经占用 45 | return res.status(200).json(setError({msg:"邮箱已经被占用!"})); 46 | } 47 | userEntity.save((err,user)=>{ 48 | if(!err){ 49 | req.session.user = user; 50 | res.status(200).json(setOk()); 51 | } 52 | }); 53 | }) 54 | 55 | }); 56 | /* async.waterfall([ 57 | User.findByName({name:userEntity.name},function(cb){ 58 | return cb(); 59 | }), 60 | function 61 | ]);*/ 62 | //查找是否有同名的 63 | /* User.findByName({name:userEntity.name},(error,user)=>{ 64 | if(!user){ 65 | userEntity.save((err,user)=>{ 66 | if(!err){ 67 | res.status(200).json(setOk()); 68 | } 69 | }); 70 | }else { 71 | res.json(setError()) 72 | } 73 | })*/ 74 | 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /src/server/models/Answers.js: -------------------------------------------------------------------------------- 1 | import mongoose , { Schema } from 'mongoose'; 2 | import moment from 'moment'; 3 | import _ from 'lodash'; 4 | /** 5 | * 回答实体 6 | */ 7 | const Answers = new Schema({ 8 | id:String, //id 9 | content:String, //内容 10 | topicId:String, //所属话题 11 | userId:String, //回答人的id 12 | star:{ //赞同的数量 13 | type:Number, 14 | default:0 15 | }, 16 | accept:{ // 1 被采纳 0 没有被采纳 17 | type:Number, 18 | default:0 19 | }, 20 | status:{ // 状态 1 显示中 0 已删除 21 | type:Number, 22 | default:1 23 | }, 24 | answerTime:String, //回答时间 25 | 26 | updateTime:String //更新时间 27 | 28 | }); 29 | 30 | Answers.statics.getAllByTopicIds=function(topicIds,cb){ 31 | 32 | this.aggregate( 33 | [ 34 | { 35 | $match:{ 36 | topicId: { 37 | $in:topicIds 38 | } 39 | } 40 | }, 41 | { 42 | $group:{ 43 | "_id":"$topicId", 44 | "count":{ 45 | $sum:1 46 | } 47 | } 48 | } 49 | 50 | ], function (err, res) { 51 | let topicArr = []; 52 | if (err){ 53 | 54 | return cb(err,null); 55 | } 56 | if(!res){ 57 | topicArr = topicIds.map((topicId)=>{ 58 | return { 59 | _id:topicId, 60 | count:0 61 | } 62 | }); 63 | }else{ 64 | topicArr = topicIds.map((topicId)=>{ 65 | let target = _.find(res,(item)=>{ 66 | return item._id == topicId; 67 | }); 68 | if(!target){ 69 | target = { 70 | _id:topicId, 71 | count:0 72 | } 73 | } 74 | return target; 75 | }); 76 | } 77 | 78 | return cb(err,topicArr); 79 | }) 80 | 81 | } 82 | Answers.statics.getAllByTopicId=function(topicId,cb){ 83 | 84 | return this.find({topicId},cb) 85 | 86 | } 87 | 88 | 89 | mongoose.model('Answers',Answers); 90 | -------------------------------------------------------------------------------- /src/common/reducers/user.js: -------------------------------------------------------------------------------- 1 | //登录 2 | import { 3 | USER_LOGIN , 4 | USER_LOGIN_REQUEST , 5 | USER_LOGIN_SUCCESS , 6 | USER_LOGIN_FAILURE , 7 | USER_SIGNUP , 8 | USER_SIGNUP_REQUEST , 9 | USER_SIGNUP_SUCCESS , 10 | USER_SIGNUP_FAILURE , 11 | USER_LOGOUT , 12 | USER_LOGOUT_REQUEST , 13 | USER_LOGOUT_SUCCESS , 14 | USER_LOGOUT_FAILURE 15 | } from '../actions/user' 16 | 17 | 18 | const initialState= { 19 | isWaiting: false, 20 | authenticated: false 21 | } 22 | export default function user(state = initialState,action) { 23 | 24 | switch(action.type){ 25 | case USER_LOGIN_REQUEST: 26 | return Object.assign({}, state, { 27 | isWaiting: true 28 | }); 29 | 30 | case USER_LOGIN_SUCCESS: 31 | 32 | return Object.assign({}, state, { 33 | isWaiting: false, 34 | authenticated: true, 35 | ...action.req.data 36 | }); 37 | 38 | case USER_LOGIN_FAILURE: 39 | return Object.assign({}, state, { 40 | isWaiting: false, 41 | authenticated: false 42 | }); 43 | case USER_SIGNUP_REQUEST: 44 | return Object.assign({}, state, { 45 | isWaiting: true 46 | }); 47 | case USER_SIGNUP_SUCCESS: 48 | return Object.assign({}, state, { 49 | isWaiting: false, 50 | authenticated: true 51 | }); 52 | case USER_SIGNUP_FAILURE: 53 | return Object.assign({}, state, { 54 | isWaiting: false, 55 | authenticated: false 56 | }); 57 | case USER_LOGOUT_REQUEST: 58 | return Object.assign({}, state, { 59 | isWaiting: true 60 | }); 61 | case USER_LOGOUT_SUCCESS: 62 | return Object.assign({}, state, { 63 | isWaiting: false, 64 | authenticated: false 65 | }); 66 | case USER_LOGOUT_FAILURE: 67 | return Object.assign({}, state, { 68 | isWaiting: false, 69 | authenticated: true 70 | }); 71 | default: 72 | return state; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/common/components/layout/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import EixtWidget from './ExitHeader'; 3 | export default class Header extends Component { 4 | render() { 5 | const { user } = this.props; 6 | 7 | return ( 8 |
    9 | 41 |
    42 | ); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_progress-bars.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Progress bars 3 | // -------------------------------------------------- 4 | 5 | 6 | // Bar animations 7 | // ------------------------- 8 | 9 | // WebKit 10 | @-webkit-keyframes progress-bar-stripes { 11 | from { background-position: 40px 0; } 12 | to { background-position: 0 0; } 13 | } 14 | 15 | // Spec and IE10+ 16 | @keyframes progress-bar-stripes { 17 | from { background-position: 40px 0; } 18 | to { background-position: 0 0; } 19 | } 20 | 21 | 22 | // Bar itself 23 | // ------------------------- 24 | 25 | // Outer container 26 | .progress { 27 | overflow: hidden; 28 | height: $line-height-computed; 29 | margin-bottom: $line-height-computed; 30 | background-color: $progress-bg; 31 | border-radius: $progress-border-radius; 32 | @include box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); 33 | } 34 | 35 | // Bar of progress 36 | .progress-bar { 37 | float: left; 38 | width: 0%; 39 | height: 100%; 40 | font-size: $font-size-small; 41 | line-height: $line-height-computed; 42 | color: $progress-bar-color; 43 | text-align: center; 44 | background-color: $progress-bar-bg; 45 | @include box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); 46 | @include transition(width .6s ease); 47 | } 48 | 49 | // Striped bars 50 | // 51 | // `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the 52 | // `.progress-bar-striped` class, which you just add to an existing 53 | // `.progress-bar`. 54 | .progress-striped .progress-bar, 55 | .progress-bar-striped { 56 | @include gradient-striped; 57 | background-size: 40px 40px; 58 | } 59 | 60 | // Call animation for the active one 61 | // 62 | // `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the 63 | // `.progress-bar.active` approach. 64 | .progress.active .progress-bar, 65 | .progress-bar.active { 66 | @include animation(progress-bar-stripes 2s linear infinite); 67 | } 68 | 69 | 70 | // Variations 71 | // ------------------------- 72 | 73 | .progress-bar-success { 74 | @include progress-bar-variant($progress-bar-success-bg); 75 | } 76 | 77 | .progress-bar-info { 78 | @include progress-bar-variant($progress-bar-info-bg); 79 | } 80 | 81 | .progress-bar-warning { 82 | @include progress-bar-variant($progress-bar-warning-bg); 83 | } 84 | 85 | .progress-bar-danger { 86 | @include progress-bar-variant($progress-bar-danger-bg); 87 | } 88 | -------------------------------------------------------------------------------- /src/common/reducers/reddit.js: -------------------------------------------------------------------------------- 1 | import { 2 | SELECT_REDDIT, 3 | INVALIDATE_REDDIT, 4 | POSTS_GET, POSTS_GET_REQUEST, POSTS_GET_SUCCESS, POSTS_GET_FAILURE 5 | } from '../actions/reddit'; 6 | 7 | function posts(state = { 8 | error: {}, 9 | isFetching: false, 10 | didInvalidate: false, 11 | items: [] 12 | }, action) { 13 | switch (action.type) { 14 | case INVALIDATE_REDDIT: 15 | return Object.assign({}, state, { 16 | didInvalidate: true 17 | }); 18 | case POSTS_GET_REQUEST: 19 | return Object.assign({}, state, { 20 | isFetching: true, 21 | didInvalidate: false 22 | }); 23 | case POSTS_GET_SUCCESS: 24 | console.log("****************请求成功***********************"); 25 | return Object.assign({}, state, { 26 | isFetching: false, 27 | didInvalidate: false, 28 | items: action.posts, 29 | lastUpdated: action.receivedAt 30 | }); 31 | case POSTS_GET_FAILURE: 32 | return Object.assign({}, state, { 33 | isFetching: false, 34 | didInvalidate: false 35 | }); 36 | default: 37 | return state; 38 | } 39 | } 40 | 41 | export function selectedReddit(state = 'reactjs', action) { 42 | switch (action.type) { 43 | case SELECT_REDDIT: 44 | return action.reddit; 45 | default: 46 | return state; 47 | } 48 | } 49 | 50 | export function postsByReddit(state = { }, action) { 51 | switch (action.type) { 52 | case INVALIDATE_REDDIT: 53 | case POSTS_GET_REQUEST: 54 | case POSTS_GET_SUCCESS: 55 | let postsArray = []; 56 | if(action.req && action.req.data){ 57 | let data = action.req.data.data; 58 | postsArray = data.children.map(child => child.data); 59 | } 60 | return Object.assign({}, state, { 61 | [action.reddit]: posts(state[action.reddit], { 62 | type: action.type, 63 | reddit: action.reddit, 64 | posts: postsArray, 65 | receivedAt: Date.now() 66 | }) 67 | }); 68 | 69 | case POSTS_GET_FAILURE: 70 | return Object.assign({}, state, { 71 | [action.reddit]: posts(state[action.reddit], { 72 | type: action.type, 73 | reddit: action.reddit, 74 | posts: [], 75 | receivedAt: Date.now(), 76 | error : { 77 | status: action.error.status, 78 | statusText : action.error.statusText 79 | } 80 | }) 81 | }); 82 | 83 | default: 84 | return state; 85 | } 86 | } -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_pagination.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pagination (multiple pages) 3 | // -------------------------------------------------- 4 | .pagination { 5 | display: inline-block; 6 | padding-left: 0; 7 | margin: $line-height-computed 0; 8 | border-radius: $border-radius-base; 9 | 10 | > li { 11 | display: inline; // Remove list-style and block-level defaults 12 | > a, 13 | > span { 14 | position: relative; 15 | float: left; // Collapse white-space 16 | padding: $padding-base-vertical $padding-base-horizontal; 17 | line-height: $line-height-base; 18 | text-decoration: none; 19 | color: $pagination-color; 20 | background-color: $pagination-bg; 21 | border: 1px solid $pagination-border; 22 | margin-left: -1px; 23 | } 24 | &:first-child { 25 | > a, 26 | > span { 27 | margin-left: 0; 28 | @include border-left-radius($border-radius-base); 29 | } 30 | } 31 | &:last-child { 32 | > a, 33 | > span { 34 | @include border-right-radius($border-radius-base); 35 | } 36 | } 37 | } 38 | 39 | > li > a, 40 | > li > span { 41 | &:hover, 42 | &:focus { 43 | z-index: 2; 44 | color: $pagination-hover-color; 45 | background-color: $pagination-hover-bg; 46 | border-color: $pagination-hover-border; 47 | } 48 | } 49 | 50 | > .active > a, 51 | > .active > span { 52 | &, 53 | &:hover, 54 | &:focus { 55 | z-index: 3; 56 | color: $pagination-active-color; 57 | background-color: $pagination-active-bg; 58 | border-color: $pagination-active-border; 59 | cursor: default; 60 | } 61 | } 62 | 63 | > .disabled { 64 | > span, 65 | > span:hover, 66 | > span:focus, 67 | > a, 68 | > a:hover, 69 | > a:focus { 70 | color: $pagination-disabled-color; 71 | background-color: $pagination-disabled-bg; 72 | border-color: $pagination-disabled-border; 73 | cursor: $cursor-disabled; 74 | } 75 | } 76 | } 77 | 78 | // Sizing 79 | // -------------------------------------------------- 80 | 81 | // Large 82 | .pagination-lg { 83 | @include pagination-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $border-radius-large); 84 | } 85 | 86 | // Small 87 | .pagination-sm { 88 | @include pagination-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $border-radius-small); 89 | } 90 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/_print.scss: -------------------------------------------------------------------------------- 1 | /*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ 2 | 3 | // ========================================================================== 4 | // Print styles. 5 | // Inlined to avoid the additional HTTP request: h5bp.com/r 6 | // ========================================================================== 7 | 8 | @media print { 9 | *, 10 | *:before, 11 | *:after { 12 | background: transparent !important; 13 | color: #000 !important; // Black prints faster: h5bp.com/s 14 | box-shadow: none !important; 15 | text-shadow: none !important; 16 | } 17 | 18 | a, 19 | a:visited { 20 | text-decoration: underline; 21 | } 22 | 23 | a[href]:after { 24 | content: " (" attr(href) ")"; 25 | } 26 | 27 | abbr[title]:after { 28 | content: " (" attr(title) ")"; 29 | } 30 | 31 | // Don't show links that are fragment identifiers, 32 | // or use the `javascript:` pseudo protocol 33 | a[href^="#"]:after, 34 | a[href^="javascript:"]:after { 35 | content: ""; 36 | } 37 | 38 | pre, 39 | blockquote { 40 | border: 1px solid #999; 41 | page-break-inside: avoid; 42 | } 43 | 44 | thead { 45 | display: table-header-group; // h5bp.com/t 46 | } 47 | 48 | tr, 49 | img { 50 | page-break-inside: avoid; 51 | } 52 | 53 | img { 54 | max-width: 100% !important; 55 | } 56 | 57 | p, 58 | h2, 59 | h3 { 60 | orphans: 3; 61 | widows: 3; 62 | } 63 | 64 | h2, 65 | h3 { 66 | page-break-after: avoid; 67 | } 68 | 69 | // Bootstrap specific changes start 70 | 71 | // Bootstrap components 72 | .navbar { 73 | display: none; 74 | } 75 | .btn, 76 | .dropup > .btn { 77 | > .caret { 78 | border-top-color: #000 !important; 79 | } 80 | } 81 | .label { 82 | border: 1px solid #000; 83 | } 84 | 85 | .table { 86 | border-collapse: collapse !important; 87 | 88 | td, 89 | th { 90 | background-color: #fff !important; 91 | } 92 | } 93 | .table-bordered { 94 | th, 95 | td { 96 | border: 1px solid #ddd !important; 97 | } 98 | } 99 | 100 | // Bootstrap specific changes end 101 | } 102 | -------------------------------------------------------------------------------- /src/server/config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * express配置 3 | */ 4 | import fs from 'fs'; 5 | import express from 'express'; 6 | 7 | import glob from 'glob'; 8 | import mongoose from 'mongoose'; 9 | import favicon from 'serve-favicon'; 10 | import logger from 'morgan'; 11 | import cookieParser from 'cookie-parser'; 12 | import bodyParser from 'body-parser'; 13 | import compress from 'compression'; 14 | import session from 'express-session'; 15 | import ConnectMongo from 'connect-mongo'; 16 | import methodOverride from 'method-override'; 17 | 18 | const accessLog = fs.createWriteStream('access.log', {flags: 'a'}); 19 | const errorLog = fs.createWriteStream('error.log', {flags: 'a'}); 20 | const mongoStore = new ConnectMongo(session); 21 | 22 | 23 | export default function (app, config) { 24 | 25 | const env = process.env.NODE_ENV || 'development'; 26 | app.locals.ENV = env; 27 | app.locals.ENV_DEVELOPMENT = env == 'development'; 28 | //favicon图标 29 | 30 | // app.use(favicon(config.root + '/public/img/favicon.ico')); 31 | app.use(logger('dev')); 32 | app.use(logger({stream: accessLog})); 33 | app.use(bodyParser.json()); 34 | app.use(bodyParser.urlencoded({ 35 | extended: true 36 | })); 37 | 38 | app.use(compress()); 39 | app.use(express.static(config.root + '/public')); 40 | app.set('trust proxy', 'loopback'); 41 | app.use(methodOverride()); 42 | 43 | app.use(cookieParser()); 44 | app.use(session({ 45 | resave: true, 46 | saveUninitialized: false, 47 | proxy: true, // The "X-Forwarded-Proto" header will be used. 48 | name: 'sessionId', 49 | secret: config.cookieSecret, 50 | cookie: { 51 | httpOnly: true, 52 | secure: false, 53 | }, 54 | store:new mongoStore({ 55 | url:config.db, 56 | secret: config.cookieSecret, 57 | autoReconnect: true 58 | }) 59 | })); 60 | const models = glob.sync(config.root + '/src/server/models/*.js'); 61 | models.forEach(function (model) { 62 | require(model); 63 | }); 64 | 65 | const controllers = glob.sync(config.root + '/src/server/controllers/*.js'); 66 | 67 | controllers.forEach(function (controller) { 68 | require(controller)(app); 69 | }); 70 | 71 | app.use(function (err, req, res, next) { 72 | const meta = '[' + new Date() + '] ' + req.url + '\n'; 73 | errorLog.write(meta + err.stack + '\n'); 74 | next(); 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_grid-framework.scss: -------------------------------------------------------------------------------- 1 | // Framework grid generation 2 | // 3 | // Used only by Bootstrap to generate the correct number of grid classes given 4 | // any value of `$grid-columns`. 5 | 6 | // [converter] This is defined recursively in LESS, but Sass supports real loops 7 | @mixin make-grid-columns($i: 1, $list: ".col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}") { 8 | @for $i from (1 + 1) through $grid-columns { 9 | $list: "#{$list}, .col-xs-#{$i}, .col-sm-#{$i}, .col-md-#{$i}, .col-lg-#{$i}"; 10 | } 11 | #{$list} { 12 | position: relative; 13 | // Prevent columns from collapsing when empty 14 | min-height: 1px; 15 | // Inner gutter via padding 16 | padding-left: ceil(($grid-gutter-width / 2)); 17 | padding-right: floor(($grid-gutter-width / 2)); 18 | } 19 | } 20 | 21 | 22 | // [converter] This is defined recursively in LESS, but Sass supports real loops 23 | @mixin float-grid-columns($class, $i: 1, $list: ".col-#{$class}-#{$i}") { 24 | @for $i from (1 + 1) through $grid-columns { 25 | $list: "#{$list}, .col-#{$class}-#{$i}"; 26 | } 27 | #{$list} { 28 | float: left; 29 | } 30 | } 31 | 32 | 33 | @mixin calc-grid-column($index, $class, $type) { 34 | @if ($type == width) and ($index > 0) { 35 | .col-#{$class}-#{$index} { 36 | width: percentage(($index / $grid-columns)); 37 | } 38 | } 39 | @if ($type == push) and ($index > 0) { 40 | .col-#{$class}-push-#{$index} { 41 | left: percentage(($index / $grid-columns)); 42 | } 43 | } 44 | @if ($type == push) and ($index == 0) { 45 | .col-#{$class}-push-0 { 46 | left: auto; 47 | } 48 | } 49 | @if ($type == pull) and ($index > 0) { 50 | .col-#{$class}-pull-#{$index} { 51 | right: percentage(($index / $grid-columns)); 52 | } 53 | } 54 | @if ($type == pull) and ($index == 0) { 55 | .col-#{$class}-pull-0 { 56 | right: auto; 57 | } 58 | } 59 | @if ($type == offset) { 60 | .col-#{$class}-offset-#{$index} { 61 | margin-left: percentage(($index / $grid-columns)); 62 | } 63 | } 64 | } 65 | 66 | // [converter] This is defined recursively in LESS, but Sass supports real loops 67 | @mixin loop-grid-columns($columns, $class, $type) { 68 | @for $i from 0 through $columns { 69 | @include calc-grid-column($i, $class, $type); 70 | } 71 | } 72 | 73 | 74 | // Create grid for specific class 75 | @mixin make-grid($class) { 76 | @include float-grid-columns($class); 77 | @include loop-grid-columns($grid-columns, $class, width); 78 | @include loop-grid-columns($grid-columns, $class, pull); 79 | @include loop-grid-columns($grid-columns, $class, push); 80 | @include loop-grid-columns($grid-columns, $class, offset); 81 | } 82 | -------------------------------------------------------------------------------- /src/common/components/topic/Topic.js: -------------------------------------------------------------------------------- 1 | import React , { Component } from 'react'; 2 | import { TOPIC } from '../../constants/topic'; 3 | import cx from 'classnames'; 4 | import moment from 'moment'; 5 | import TagList from '../ui/topicDetail/TagList'; 6 | /** 7 | * 列表明细 8 | */ 9 | class Topic extends Component{ 10 | 11 | render(){ 12 | 13 | const { 14 | id , 15 | title , 16 | viewtotals = 0, 17 | answeredTime , 18 | createTime , 19 | answersCount , 20 | userInfo , 21 | tags , 22 | status } = this.props; 23 | let replyTime , statusCls = TOPIC.STATUS[status]; 24 | 25 | if( answersCount === 0){ 26 | replyTime = ( 27 | {moment(createTime).fromNow()}{' '}提问 28 | ); 29 | }else{ 30 | replyTime = ( 31 | {moment(answeredTime).fromNow()}{' '}回答 32 | ); 33 | } 34 | return ( 35 |
    36 |
    37 |
    38 | 0 39 | 投票 40 |
    41 |
    42 | { answersCount } 43 | 回答 44 |
    45 |
    46 | { viewtotals } 47 | 浏览 48 |
    49 |
    50 |
    51 | 60 |

    61 | 62 | {title} 63 | 64 |

    65 | 66 |
    67 |
    68 | ); 69 | } 70 | viewDetail(topicId){ 71 | const { viewDetail } = this.props; 72 | viewDetail && viewDetail(topicId); 73 | } 74 | } 75 | 76 | export default Topic; 77 | 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webcy", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "set NODE_ENV=development&&node ./src/server/index.js", 7 | "clean": "rm -rf dest", 8 | "slate": "rm -rf node_modules && npm install", 9 | "build": "npm run clean && set NODE_ENV=development&&webpack -p --progress --colors --profile" 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.13.3", 13 | "bootstrap-sass": "^3.3.6", 14 | "compression": "^1.5.2", 15 | "connect-mongo": "", 16 | "cookie-parser": "^1.3.3", 17 | "ejs": "^2.3.1", 18 | "express": "^4.13.3", 19 | "express-session": "", 20 | "glob": "^5.0.3", 21 | "markdown": "^0.5.0", 22 | "mditor": "^0.1.4", 23 | "method-override": "^2.3.0", 24 | "mongoose": "^4.1.2", 25 | "morgan": "^1.6.1", 26 | "react-redux": "^4.0.1", 27 | "react-render-html": "^0.1.3", 28 | "redux-actions": "^0.9.0", 29 | "redux-logger": "^2.3.1", 30 | "redux-undo": "^0.6.0", 31 | "serve-favicon": "^2.3.0" 32 | }, 33 | "devDependencies": { 34 | "antd": "^0.11.2", 35 | "async": "^1.5.0", 36 | "autoprefixer-loader": "^3.1.0", 37 | "axios": "^0.7.0", 38 | "babel": "^5.8.21", 39 | "babel-core": "^5.8.22", 40 | "babel-loader": "^5.3.2", 41 | "babel-plugin-dev-expression": "^0.1.0", 42 | "babel-plugin-react-transform": "^1.1.1", 43 | "babel-runtime": "^5.8.25", 44 | "classnames": "^2.2.1", 45 | "css-loader": "^0.19.0", 46 | "es5-shim": "^4.1.14", 47 | "es6-promise": "^3.0.2", 48 | "file-loader": "^0.8.5", 49 | "history": "1.13.1", 50 | "immutable": "^3.7.5", 51 | "isomorphic-fetch": "^2.2.0", 52 | "js-stylesheet": "0.0.1", 53 | "jsdom": "^7.2.2", 54 | "jsdom-no-contextify": "^3.1.0", 55 | "lodash": "^3.10.1", 56 | "moment": "^2.11.1", 57 | "node-uuid": "^1.4.7", 58 | "nodemailer": "^0.7.1", 59 | "object-assign": "^4.0.1", 60 | "query-string": "^3.0.0", 61 | "react": "^0.14.6", 62 | "react-bootstrap": "^0.28.1", 63 | "react-document-meta": "^2.0.0", 64 | "react-dom": "^0.14.6", 65 | "react-router": "^1.0.0", 66 | "react-transform-catch-errors": "^1.0.0", 67 | "react-transform-hmr": "^1.0.1", 68 | "redbox-react": "^1.2.0", 69 | "redux": "^3.0.5", 70 | "redux-devtools": "^3.0.0", 71 | "redux-devtools-dock-monitor": "^1.0.1", 72 | "redux-devtools-log-monitor": "^1.0.1", 73 | "redux-simple-router": "^1.0.2", 74 | "redux-thunk": "^1.0.2", 75 | "sass-loader": "^3.1.2", 76 | "scss-loader": "0.0.1", 77 | "serialize-javascript": "^1.1.2", 78 | "style-loader": "^0.12.4", 79 | "webpack": "^1.12.2", 80 | "webpack-dev-middleware": "^1.2.0", 81 | "webpack-dev-server": "^1.14.0", 82 | "webpack-hot-middleware": "^2.6.0" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/server/middlewares/renderPage-middleware.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { RoutingContext, match } from 'react-router'; 3 | import { Provider } from 'react-redux'; 4 | import createLocation from 'history/lib/createLocation'; 5 | import { fetchComponentDataBeforeRender } from '../../common/api/fetchComponentDataBeforeRender'; 6 | 7 | import configureStore from '../../common/store/configureStore'; 8 | 9 | 10 | import routes from '../../common/routes'; 11 | import packagejson from '../../../package.json'; 12 | 13 | import serialize from 'serialize-javascript'; 14 | import { renderToString } from 'react-dom/server'; 15 | 16 | //渲染页面 17 | const renderPage = (html, initialState) => { 18 | 19 | return ` 20 | 21 | 22 | 23 | 24 | SKT 25 | 26 | 27 | 28 | 29 |
    ${html}
    30 | 33 | 34 | 35 | 36 | 37 | 38 | `; 39 | } 40 | 41 | export default function reactServerMiddleware(req,res,next){ 42 | 43 | const { url } = req ; 44 | 45 | match({routes, location: url}, (error, redirectLocation, renderProps) => { 46 | 47 | if(error){ 48 | 49 | return res.status(500).end('Internal server error'); 50 | }else if (!renderProps) { 51 | return res.status(404).end('Not found'); 52 | } else { 53 | const { session } = req ; 54 | let loginUser = (session && session.user) ? session.user : null; 55 | 56 | 57 | 58 | const store = configureStore({ 59 | user:{ 60 | authenticated: loginUser ? true :false, 61 | isWaiting: false, 62 | ...loginUser 63 | } 64 | }); 65 | 66 | fetchComponentDataBeforeRender(store.dispatch, renderProps.components, renderProps.params) 67 | .then(html=>{ 68 | const InitialView = ( 69 | 70 | 71 | 72 | ); 73 | const componentHTML = renderToString(InitialView); 74 | const initialState = store.getState(); 75 | res.status(200).send(renderPage(componentHTML,initialState)) 76 | }).catch(err =>{ 77 | console.log(err) 78 | res.end(renderPage("",{})) 79 | }); 80 | } 81 | }); 82 | 83 | 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /sass/bootstrap/bootstrap/mixins/_forms.scss: -------------------------------------------------------------------------------- 1 | // Form validation states 2 | // 3 | // Used in forms.less to generate the form validation CSS for warnings, errors, 4 | // and successes. 5 | 6 | @mixin form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) { 7 | // Color the label and help text 8 | .help-block, 9 | .control-label, 10 | .radio, 11 | .checkbox, 12 | .radio-inline, 13 | .checkbox-inline, 14 | &.radio label, 15 | &.checkbox label, 16 | &.radio-inline label, 17 | &.checkbox-inline label { 18 | color: $text-color; 19 | } 20 | // Set the border and box shadow on specific inputs to match 21 | .form-control { 22 | border-color: $border-color; 23 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work 24 | &:focus { 25 | border-color: darken($border-color, 10%); 26 | $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%); 27 | @include box-shadow($shadow); 28 | } 29 | } 30 | // Set validation states also for addons 31 | .input-group-addon { 32 | color: $text-color; 33 | border-color: $border-color; 34 | background-color: $background-color; 35 | } 36 | // Optional feedback icon 37 | .form-control-feedback { 38 | color: $text-color; 39 | } 40 | } 41 | 42 | 43 | // Form control focus state 44 | // 45 | // Generate a customized focus state and for any input with the specified color, 46 | // which defaults to the `$input-border-focus` variable. 47 | // 48 | // We highly encourage you to not customize the default value, but instead use 49 | // this to tweak colors on an as-needed basis. This aesthetic change is based on 50 | // WebKit's default styles, but applicable to a wider range of browsers. Its 51 | // usability and accessibility should be taken into account with any change. 52 | // 53 | // Example usage: change the default blue border and shadow to white for better 54 | // contrast against a dark gray background. 55 | @mixin form-control-focus($color: $input-border-focus) { 56 | $color-rgba: rgba(red($color), green($color), blue($color), .6); 57 | &:focus { 58 | border-color: $color; 59 | outline: 0; 60 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px $color-rgba); 61 | } 62 | } 63 | 64 | // Form control sizing 65 | // 66 | // Relative text size, padding, and border-radii changes for form controls. For 67 | // horizontal sizing, wrap controls in the predefined grid classes. `