├── .gitignore ├── README.md ├── app ├── __init__.py ├── models.py ├── static │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── index.css │ └── js │ │ ├── build │ │ └── bundle.js │ │ ├── components │ │ ├── Message.js │ │ ├── MessageBoard.js │ │ ├── MessageForm.js │ │ ├── MessageList.js │ │ ├── Pager.js │ │ └── SearchBar.js │ │ ├── index.js │ │ ├── lib │ │ └── jquery.min.js │ │ ├── package.json │ │ └── webpack.config.js ├── templates │ └── index.html └── views.py ├── config.py ├── manage.py ├── requirenments.txt ├── run.py └── screenshot └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | *.pyc 3 | .idea/ 4 | app/static/js/node_modules/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | The react version of this project is out of date. 4 | 5 | ## Message Board 6 | 7 | Web: 8 | 1. Bootstrap 9 | 2. React 10 | 3. jQuery 11 | 4. webpack for js packing 12 | 13 | Backend: 14 | 1. Flask 15 | 2. flask-mongoengine 16 | 17 | Database: 18 | MongoDB 19 | 20 | Run: 21 | use virtualenv 22 | ```bash 23 | $ virtualenv venv 24 | $ source venv/bin/activate 25 | $ pip install -r requirenments.txt 26 | 27 | $ python run.py 28 | ``` 29 | 30 | JavaScript: 31 | Change js file,need node and webpack 32 | 33 | ```bash 34 | $ cd app/static/js 35 | $ npm install 36 | $ npm install -g webpack 37 | $ webpack --watch 38 | ``` 39 | ScreenShot: 40 | ![screenshot](https://raw.githubusercontent.com/defshine/message-board/master/screenshot/screenshot.png) 41 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask.ext.mongoengine import MongoEngine 3 | 4 | app = Flask(__name__) 5 | app.config.from_object("config") 6 | 7 | db = MongoEngine(app) 8 | 9 | from app import views, models -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | from datetime import datetime 3 | 4 | 5 | class Message(db.Document): 6 | author = db.StringField(max_length=20, required=True) 7 | content = db.StringField(max_length=200, required=True) 8 | time = db.DateTimeField(default=datetime.now()) 9 | 10 | def to_json(self): 11 | return { 12 | 'author': self.author, 13 | 'content': self.content, 14 | 'time': self.time 15 | } 16 | -------------------------------------------------------------------------------- /app/static/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 100px; /* 100px is double the height of the navbar - I made it a big larger for some more space - keep it at 50px at least if you want to use the fixed top nav */ 3 | } 4 | 5 | footer { 6 | margin: 50px 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /app/static/js/components/Message.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | 3 | var Message = React.createClass({ 4 | render : function(){ 5 | var msg = this.props.message; 6 | return( 7 |
8 |

{msg.author}   9 | {msg.time.toLocaleString()} 10 |

11 |

{msg.content}

12 |
13 | ) 14 | } 15 | }); 16 | 17 | module.exports = Message; -------------------------------------------------------------------------------- /app/static/js/components/MessageBoard.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | var MessageList = require("./MessageList"); 3 | var SearchBar = require("./SearchBar"); 4 | var MessageForm = require("./MessageForm"); 5 | var Pager = require("./Pager"); 6 | 7 | var MessageBoard = React.createClass({ 8 | getInitialState : function(){ 9 | return { 10 | messages: [], 11 | page:1, 12 | pages:1, 13 | searchValue:'' 14 | } 15 | }, 16 | submitMessage : function (author, content) { 17 | $.ajax({ 18 | type:'post', 19 | url:'/message', 20 | data:{author:author,content:content} 21 | }).done(function (data) { 22 | 23 | this.listMessage(1); 24 | }.bind(this)); 25 | }, 26 | searchHandler : function (searchValue) { 27 | //There is no guarantee that `this.state` will be immediately updated, so 28 | //accessing `this.state` after calling this method may return the old value. 29 | //so call listMessage in callback 30 | this.setState({ 31 | searchValue:searchValue 32 | }, function () { 33 | this.listMessage(1); 34 | }.bind(this)); 35 | }, 36 | listMessage : function(page){ 37 | 38 | var data = { 39 | page : page 40 | }; 41 | var searchValue = this.state.searchValue; 42 | if(searchValue){ 43 | data.searchValue = searchValue; 44 | } 45 | 46 | $.ajax({ 47 | type:'get', 48 | url:'/messages', 49 | data:data 50 | }).done(function (resp) { 51 | if(resp.status == "success"){ 52 | var pager = resp.pager; 53 | this.setState({ 54 | messages:pager.messages, 55 | page:pager.page, 56 | pages:pager.pages 57 | }); 58 | } 59 | }.bind(this)); 60 | }, 61 | componentDidMount : function(){ 62 | this.listMessage(1); 63 | }, 64 | render : function(){ 65 | var pager_props = { 66 | page : this.state.page, 67 | pages : this.state.pages, 68 | listMessage : this.listMessage 69 | }; 70 | return( 71 |
72 | 73 |
74 | 75 | 76 | 77 |
78 |
79 | ) 80 | } 81 | }); 82 | 83 | module.exports = MessageBoard; 84 | -------------------------------------------------------------------------------- /app/static/js/components/MessageForm.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | 3 | var MessageForm = React.createClass({ 4 | handleSubmit : function (e) { 5 | e.preventDefault(); 6 | var author = this.refs.author.getDOMNode().value.trim(); 7 | var content = this.refs.content.getDOMNode().value.trim(); 8 | 9 | this.props.submitMessage(author,content); 10 | 11 | this.refs.author.getDOMNode().value = ""; 12 | this.refs.content.getDOMNode().value = "" 13 | }, 14 | render : function(){ 15 | return( 16 |
17 |

Leave a Message:

18 |
19 |
20 | 21 | 22 |
23 | Submit 24 |
25 |
26 | ) 27 | } 28 | }); 29 | 30 | module.exports = MessageForm; -------------------------------------------------------------------------------- /app/static/js/components/MessageList.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | var Message = require("./Message"); 3 | 4 | var MessageList = React.createClass({ 5 | render : function () { 6 | var messages = this.props.messages.map(function(item){ 7 | return 8 | }); 9 | return( 10 |
11 | {messages} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | module.exports = MessageList; -------------------------------------------------------------------------------- /app/static/js/components/Pager.js: -------------------------------------------------------------------------------- 1 | var React = require("react/addons"); 2 | 3 | var Pager = React.createClass({ 4 | getDefaultProps : function(){ 5 | return{ 6 | page:1, 7 | pages:1 8 | } 9 | }, 10 | clickHandler: function(e){ 11 | e.stopPropagation(); 12 | this.props.listMessage(e.target.dataset.page); 13 | 14 | }, 15 | render : function(){ 16 | var cx = React.addons.classSet; 17 | var preClass = cx({ 18 | 'previous':true, 19 | 'disabled':this.props.page == 1 20 | }); 21 | var nextClass = cx({ 22 | 'next':true, 23 | 'disabled':this.props.page == this.props.pages 24 | }); 25 | 26 | return( 27 | 38 | ) 39 | } 40 | }); 41 | 42 | module.exports = Pager; -------------------------------------------------------------------------------- /app/static/js/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | 3 | var SearchBar = React.createClass({ 4 | clickHandler: function () { 5 | var searchContent = this.refs.content.getDOMNode().value.trim(); 6 | this.props.searchHandler(searchContent); 7 | }, 8 | render: function () { 9 | return( 10 |
11 | 12 | 13 | 14 | 15 |
16 | ) 17 | } 18 | }); 19 | 20 | module.exports=SearchBar; -------------------------------------------------------------------------------- /app/static/js/index.js: -------------------------------------------------------------------------------- 1 | var React = require("react"); 2 | var MessageBoard = require("./components/MessageBoard"); 3 | 4 | React.render(,document.getElementById("message-board-container")); 5 | 6 | -------------------------------------------------------------------------------- /app/static/js/lib/jquery.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("