diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9fd3dec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+dist/*
+node_modules/*
+target/*
+.tmp/*
+.sass-cache/*
+app/styles/*
+app/vendor/*
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..14e0ee4
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,239 @@
+'use strict';
+
+var livereload = require('connect-livereload'),
+ path = require('path');
+
+module.exports = function (grunt) {
+
+ // show elapsed time at the end
+ require('time-grunt')(grunt);
+
+ // load all grunt tasks
+ require('load-grunt-tasks')(grunt);
+
+ // Project settings
+ var yeomanConfig = {
+ app: require('./bower.json').appPath || 'app',
+ dist: 'dist'
+ };
+
+ grunt.initConfig({
+ yeoman: yeomanConfig,
+ connect: {
+ options: {
+ port: 3000,
+ hostname: '0.0.0.0' //change to 'localhost' to disable outside connections
+ },
+ livereload: {
+ options: {
+ middleware: function (connect) {
+ return [
+ livereload({port: 35729}),
+ connect.static(path.resolve('.tmp')),
+ connect.static(path.resolve(yeomanConfig.app))
+ ];
+ }
+ }
+ }
+ },
+ watch: {
+ options: {
+ livereload: 35729
+ },
+ less: {
+ files: ['app/less/*.less'],
+ tasks: ['less:dev']
+ },
+ gruntfile: {
+ files: ['Gruntfile.js']
+ },
+ livereload: {
+ options: {
+ livereload: '<%= connect.options.livereload %>'
+ },
+ files: [
+ '<%= yeoman.app %>/{,*/}*.html',
+ '.tmp/styles/{,*/}*.css',
+ '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
+ ]
+ },
+ react: {
+ files: ['<%= yeoman.app %>/scripts/**/*.{jsx,js}'],
+ tasks: ['browserify:dist'],
+ options: {
+ livereload: true
+ }
+ },
+ images: {
+ files: [
+ '<%= yeoman.app %>/*.html',
+ '<%= yeoman.app %>/images/{,*/}*.{ico,icon,png,jpg,jpeg,gif,webp,svg}'
+ ]
+ }
+ },
+ clean: {
+ dist: ['.tmp', '<%= yeoman.dist %>/*'],
+ serve: '.tmp'
+ },
+ less: {
+ dev: {
+ options: {
+ compress: true,
+ paths: ['app/vendor/bootstrap/less', 'app/styles']
+ },
+ files: {
+ 'app/styles/main.css': 'app/less/main.less'
+ }
+ }
+ },
+ browserify: {
+ options: {
+ transform: [
+ [ 'reactify', {'es6': true} ]
+ ]
+ },
+ dist: {
+ files: {
+ '.tmp/scripts/bundle/app.js': '<%= yeoman.app %>/scripts/app.js'
+ },
+ options: {
+ browserifyOptions: {
+ extensions: '.jsx'
+ },
+ transform: [
+ ['babelify', {
+ }]
+ ]
+ }
+ }
+ },
+ useminPrepare: {
+ src: '<%= yeoman.app %>/index.html',
+ options: {
+ dest: '<%= yeoman.dist %>'
+ }
+ },
+ imagemin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.{png,jpg,jpeg}',
+ dest: '<%= yeoman.dist %>/images'
+ }]
+ }
+ },
+ htmlmin: {
+ dist: {
+ options: {
+ //removeCommentsFromCDATA: true,
+ // https://github.com/yeoman/grunt-usemin/issues/44
+ collapseWhitespace: true,
+ collapseBooleanAttributes: true,
+ //removeAttributeQuotes: true,
+ removeRedundantAttributes: true,
+ //useShortDoctype: true,
+ removeEmptyAttributes: true,
+ //removeOptionalTags: true
+ },
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.dist %>',
+ src: '*.html',
+ dest: '<%= yeoman.dist %>'
+ }]
+ }
+ },
+ filerev: {
+ dist: {
+ files: [{
+ src: [
+ '<%= yeoman.dist %>/scripts/**/*.js',
+ '<%= yeoman.dist %>/styles/**/*.css',
+ '<%= yeoman.dist %>/vendor/**/*.js'
+ ]
+ }]
+ }
+ },
+ autoprefixer: {
+ options: {
+ browsers: [
+ 'last 5 versions'
+ ]
+ },
+ dist: {
+ expand: true,
+ src: '.tmp/concat/styles/*.css'
+ }
+ },
+ copy: {
+ dist: {
+ files: [{
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>',
+ dest: '<%= yeoman.dist %>',
+ src: [
+ '*.html',
+ '*.wav',
+ '*.{ico,txt}',
+ 'images/{,*/}*.{ico,webp,gif}'
+ ]
+ }, {
+ expand: true,
+ flatten: true,
+ cwd: '<%= yeoman.app %>',
+ src: [
+ 'vendor/bootstrap/dist/fonts/*.*',
+ 'vendor/font-awesome/fonts/*.*'
+ ],
+ dest: '<%= yeoman.dist %>/fonts'
+ }]
+ }
+ },
+ usemin: {
+ html: ['<%= yeoman.dist %>/{,*/}*.html'],
+ css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
+ options: {
+ dirs: ['<%= yeoman.dist %>']
+ }
+ },
+ env : {
+ options : {
+ },
+ dev : {
+ NODE_ENV : 'production'
+ },
+ build : {
+ NODE_ENV : 'production'
+ }
+ }
+ });
+
+ grunt.registerTask('server', [
+ 'clean:serve',
+ 'less:dev',
+ 'browserify',
+ // 'connect:livereload',
+ 'watch'
+ ]);
+
+ grunt.registerTask('build', [
+ 'clean:dist',
+ 'browserify',
+ 'useminPrepare',
+ 'autoprefixer',
+ 'concat',
+ 'imagemin',
+ 'cssmin',
+ 'uglify',
+ 'copy',
+ 'filerev',
+ 'usemin',
+ 'htmlmin'
+ ]);
+
+ grunt.registerTask('default', 'build');
+
+ grunt.loadNpmTasks('grunt-env');
+};
diff --git a/README.md b/README.md
index 669dd5f..db589e8 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-# play-dashboard-frontend
\ No newline at end of file
+## like-dashboard
diff --git a/app/404.html b/app/404.html
new file mode 100644
index 0000000..fdace4a
--- /dev/null
+++ b/app/404.html
@@ -0,0 +1,157 @@
+
+
+
+
+ Page Not Found :(
+
+
+
+
+
Not found :(
+
Sorry, but the page you were trying to view does not exist.
+
It looks like this was the result of either:
+
+ - a mistyped address
+ - an out-of-date link
+
+
+
+
+
+
diff --git a/app/images/favicon.ico b/app/images/favicon.ico
new file mode 100644
index 0000000..05a67e8
Binary files /dev/null and b/app/images/favicon.ico differ
diff --git a/app/index.html b/app/index.html
new file mode 100644
index 0000000..b242fa7
--- /dev/null
+++ b/app/index.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+ Play玩具控 - Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/less/draft.less b/app/less/draft.less
new file mode 100644
index 0000000..593824c
--- /dev/null
+++ b/app/less/draft.less
@@ -0,0 +1,223 @@
+.edit-section {
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.edit-section .insert-window {
+ position: relative;;
+ left: 20%;
+ bottom: 200px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ width: 60%;
+ min-height: 200px;
+ z-index: 1000;
+
+ -webkit-box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
+ -moz-box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
+ box-shadow: 0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
+
+ .close {
+ cursor:pointer;
+ }
+
+ .video-code {
+ width: 100%;
+ height: auto;
+ }
+
+ .modal-body {
+ max-height: 400px;
+ overflow: auto;
+ float: left;
+ }
+}
+
+.edit-section {
+ .gallery-image {
+ float: left;
+ margin-right: 10px;
+ margin-bottom: 10px;
+ img {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+ border: 2px solid #fff;
+ }
+ img:hover {
+ border: 2px solid #40E837;
+ }
+ }
+ .dropzone {
+ border-width: 2px;
+ border-color: black;
+ border-style: dashed;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ padding: 15px;
+ width: 100px;
+ height: 100px;
+ font-size:
+ transition: all 0.5s;
+ }
+ .cover-img {
+ width: auto;
+ height: 100px;
+ object-fit: cover;
+ border: 2px solid #fff;
+ }
+}
+
+
+.RichEditor-root {
+ background: #fff;
+ border: 1px solid #ddd;
+ font-family: 'Georgia', serif;
+ font-size: 14px;
+ padding: 15px;
+}
+
+.RichEditor-editor {
+ cursor: text;
+ font-size: 16px;
+}
+
+.DraftEditor-root {
+ border-bottom: 1px solid #ddd;
+ padding-bottom: 15px;
+ margin-bottom: 10px;
+}
+
+.RichEditor-editor .public-DraftEditorPlaceholder-root,
+.RichEditor-editor .public-DraftEditor-content {
+ margin: 0 -15px -15px;
+ padding: 15px;
+}
+
+.RichEditor-editor .public-DraftEditor-content {
+ min-height: 100px;
+}
+
+.RichEditor-hidePlaceholder .public-DraftEditorPlaceholder-root {
+ display: none;
+}
+
+.RichEditor-editor .RichEditor-blockquote {
+ border-left: 5px solid #eee;
+ color: #666;
+ font-family: 'Hoefler Text', 'Georgia', serif;
+ font-style: italic;
+ margin: 16px 0;
+ padding: 10px 20px;
+}
+
+.RichEditor-editor .public-DraftStyleDefault-pre {
+ background-color: rgba(0, 0, 0, 0.05);
+ font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
+ font-size: 16px;
+ padding: 20px;
+}
+
+.RichEditor-controls {
+ font-family: 'Helvetica', sans-serif;
+ font-size: 14px;
+ margin-bottom: 5px;
+ user-select: none;
+ display: inline-block;
+}
+
+.RichEditor-controls-btn {
+ font-size: 14px;
+ margin-bottom: 5px;
+ user-select: none;
+ display: inline-block;
+ margin-left: 5px;
+ margin-right: 5px;
+ cursor: pointer;
+}
+
+.RichEditor-styleButton {
+ color: #999;
+ cursor: pointer;
+ margin-right: 4px;
+ display: inline-block;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ width: 30px;
+ height: 30px;
+ text-align: center;
+ line-height: 30px;
+}
+
+.RichEditor-styleButton::before {
+ line-height: 30px;
+}
+
+.RichEditor-activeButton {
+ color: #5890ff;
+ border: 1px solid #5890ff;
+}
+
+//
+.DraftEditor-editorContainer,.DraftEditor-root,.public-DraftEditor-content{height:inherit;text-align:initial}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:0}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol,.public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4}
+
+
+//
+.title-input {
+ background-color: #fff;
+ border: 1px solid #ccc;
+ overflow: hidden;
+ padding-left: 5px;
+ padding-top: 5px;
+ width: 100%;
+ line-height: 30px;
+ font-size: 20px;
+ font-weight: 300;
+}
+
+// tags-input
+.react-tagsinput {
+ background-color: #fff;
+ border: 1px solid #ccc;
+ overflow: hidden;
+ padding-left: 5px;
+ padding-top: 5px;
+}
+
+.react-tagsinput-tag {
+ background-color: #cde69c;
+ border-radius: 2px;
+ border: 1px solid #a5d24a;
+ color: #638421;
+ display: inline-block;
+ font-family: sans-serif;
+ font-size: 13px;
+ font-weight: 400;
+ margin-bottom: 5px;
+ margin-right: 5px;
+ padding: 5px;
+}
+
+.react-tagsinput-remove {
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.react-tagsinput-tag a::before {
+ content: " x";
+}
+
+.react-tagsinput-input {
+ background: transparent;
+ border: 0;
+ color: #777;
+ font-family: sans-serif;
+ font-size: 13px;
+ font-weight: 400;
+ margin-bottom: 6px;
+ margin-top: 1px;
+ outline: none;
+ padding: 5px;
+ width: 80px;
+}
diff --git a/app/less/main.less b/app/less/main.less
new file mode 100644
index 0000000..143bf00
--- /dev/null
+++ b/app/less/main.less
@@ -0,0 +1,495 @@
+body {
+ background: #fafafa;
+ font-family: system, -apple-system, ".SFNSDisplay-Regular", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: #333;
+}
+
+html, body {
+ height: 100%;
+}
+
+.content-wrapper {
+ background-color: #fafafa;
+}
+
+.fluid-container {
+ .row {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
+
+.main {
+ padding-top: 80px;
+}
+
+.footer {
+ bottom: 0;
+ margin-top: 20px;
+ width: 100%;
+ height: 60px;
+ background-color: #f5f5f5;
+}
+
+.wrap {
+ min-height: 100%;
+ height: auto;
+ margin: 0 auto -60px;
+}
+
+.wrap > .container {
+ padding: 70px 15px 20px;
+}
+
+.col {
+ padding-right:5px;
+ padding-left:5px;
+}
+
+.footer {
+ height: 60px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ padding-top: 20px;
+}
+
+.jumbotron {
+ text-align: center;
+ background-color: transparent;
+}
+
+.jumbotron .btn {
+ font-size: 21px;
+ padding: 14px 24px;
+}
+
+.not-set {
+ color: #c55;
+ font-style: italic;
+}
+
+// New version
+//////////////
+
+// panel ...
+.no-padding {
+ padding: 0;
+}
+
+.no-top-padding {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.panel-video {
+ position:absolute;
+ height:100%;
+ width:100%;
+ overflow: hidden;
+}
+
+video {
+ width: 100% !important;
+ height: auto !important;
+}
+
+.panel-photos-wrapper {
+ width: 100%;
+ height: 0;
+ padding-bottom: 100%;
+ overflow: hidden;
+}
+.panel-photos-big {
+ object-fit: cover;
+ max-height: 300px;
+ width: 100%;
+}
+.panel-photos-small {
+ object-fit: cover;
+ max-height: 100px;
+ padding: 2px 2px 2px 0;
+}
+.panel-photos div {
+ width: 12.5%;
+ height: 0;
+ padding-bottom: 12.5%;
+ overflow: hidden;
+ display: inline;
+}
+.panel-photos img {
+ object-fit: cover;
+ width: 100% !important;
+ height: auto !important;
+ display: inline;
+}
+// panel end...
+
+.toy-item .products-list .product-img img {
+ width: 100px;
+ height: 100px;
+ object-fit: cover;
+ object-position: 0px 0px;
+}
+
+.toy-item .products-list .product-info {
+ margin-left: 110px;
+}
+
+.products-list .product-title {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.toy-item .products-list .product-title {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ overflow: hidden;
+ line-height: 1.1em;
+ height: 2.2em;
+}
+
+.label {
+ font-size: 100% !important;
+ a {
+ color: #fff;
+ cursor: pointer;
+ }
+ a:visited {
+ color: #fff;
+ }
+ i {
+ cursor: pointer;
+ }
+}
+
+.tag-info {
+ color: #999;
+ font-size: 12px;
+}
+
+.box-footer a {
+ color: #333;
+}
+
+.label-margin {
+ margin: 3px;
+ display: inline-block;
+}
+
+.pl-form {
+ margin: 10px 50px;
+}
+
+.ql-toolbar-container {
+ border-bottom: 1px solid #ccc;
+ padding: 5px 12px;
+}
+
+.alloyeditor-container {
+ padding: 10px;
+ margin: 10px;
+}
+
+//////////////
+
+
+/* add sorting icons to gridview sort links */
+a.asc:after, a.desc:after {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ padding-left: 5px;
+}
+
+a.asc:after {
+ content: /*"\e113"*/ "\e151";
+}
+
+a.desc:after {
+ content: /*"\e114"*/ "\e152";
+}
+
+.sort-numerical a.asc:after {
+ content: "\e153";
+}
+
+.sort-numerical a.desc:after {
+ content: "\e154";
+}
+
+.sort-ordinal a.asc:after {
+ content: "\e155";
+}
+
+.sort-ordinal a.desc:after {
+ content: "\e156";
+}
+
+.grid-view th {
+ white-space: nowrap;
+}
+
+.hint-block {
+ display: block;
+ margin-top: 5px;
+ color: #999;
+}
+
+.error-summary {
+ color: #a94442;
+ background: #fdf7f7;
+ border-left: 3px solid #eed3d7;
+ padding: 10px 20px;
+ margin: 0 0 15px 0;
+}
+
+.table > tbody > tr > td {
+ vertical-align: middle;
+}
+
+.post-panel {
+
+ .post-heading {
+ padding:0;
+ background-color: #fdfdfd;
+ }
+
+ .post-heading>a {
+ display: block;
+ padding: 0 10px;
+ clear: both;
+ font-weight: 400;
+ line-height: 1.42857143;
+ color: #333;
+ white-space: nowrap
+ }
+
+ .post-heading>a:focus,.post-heading>a:hover {
+ color: #262626;
+ text-decoration: none;
+ background-color: #f5f5f5;
+ }
+
+}
+
+.post-caption {
+ padding: 5px;
+ .post-caption-btn {
+ // margin-right: 2px;
+ }
+}
+
+.post-image {
+ position: relative;
+ /*padding: 2px;*/
+ img {
+ width: 100% !important;
+ display: block !important;
+ }
+}
+
+.image-modal {
+ max-width: 100%;
+}
+
+.media, .media-body {
+ overflow: hidden;
+ zoom: 1;
+}
+
+.media .media-object {
+ margin: 5px 0 5px 0;
+}
+
+.media .post-user-info {
+ padding: 10px 5px 10px 0;
+ width: 60%;
+ .block {
+ display: block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin: 0;
+ }
+ .text-muted {
+ }
+}
+
+.thumb {
+ display: inline-block;
+}
+
+.post-summary {
+ padding: 0 0 15px 15px;
+}
+
+.user-profile {
+ display: inline-block;
+ color: #979898;
+ vertical-align: middle;
+ padding: 0 0 0 10px;
+}
+
+.user-profile h2 {
+ margin-top: 5px;
+ margin-bottom: 0px;
+}
+
+img.img-corona {
+ -webkit-box-shadow: 0 0 0 4px rgba(221,221,221,.3);
+ -moz-box-shadow: 0 0 0 4px rgba(221,221,221,.3);
+ box-shadow: 0 0 0 4px rgba(221,221,221,.3);
+}
+
+.user-info {
+
+}
+
+.publish-btn {
+ padding-top: 28px;
+}
+
+.empty {
+ padding:15px;
+}
+
+.post-pagination {
+ padding-right:15px;
+}
+
+.post-image .thumb-pane {
+ position: absolute;
+ bottom: 40px;
+ width: 100%;
+ opacity: 0.6;
+ background-color: #000;
+
+ .post-mark-li {
+ cursor: pointer;
+ }
+ .liked {
+ background-color: #ff442c;
+ border-radius: 2px;
+ }
+}
+
+.post-panel:hover .thumb-pane {
+ opacity: 0.8;
+}
+
+.thumb-pane ul {
+ list-style: none;
+ padding: 0;
+}
+
+.thumb-pane ul li {
+ float: left;
+ padding: 3px;
+ margin: 5px;
+ color: #fff;
+ font-size: 14px;
+ font-weight: normal;
+}
+
+.thumbnail-text p {
+ margin: 0;
+}
+
+.post-info {
+ display: inline-block;
+ color: #979898;
+ vertical-align: middle;
+ padding: 0 0 0 10px;
+}
+
+.post-info .glyphicon {
+ font-size: 18px;
+}
+
+.load-more-btn {
+ text-align: center;
+ margin: auto;
+ width: 350px;
+ border: 1px solid #dddddd;
+ padding: 20px 40px;
+ cursor: pointer;
+}
+
+.tag-panel {
+ .panel-heading {
+ padding: 5px;
+ }
+ .panel-body {
+ padding: 0;
+ }
+}
+
+.tag-add-btn {
+ padding: 5px;
+ margin-left: 5px;
+}
+
+.tag-btn {
+ margin: 5px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 5px;
+}
+
+.browsehappy {
+ margin: 0.2em 0;
+ background: #ccc;
+ color: #000;
+ padding: 0.2em 0;
+}
+
+.nav-tabs-custom .nav-tabs {
+ margin: 0;
+ border-bottom-color: #f4f4f4;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+}
+.nav-tabs-custom .nav-tabs>li:first-of-type {
+ margin-left: 0;
+}
+.nav-tabs-custom .nav-tabs>li.active {
+ border-top-color: #3c8dbc;
+}
+.nav-tabs-custom .nav-tabs>li {
+ border-top: 3px solid transparent;
+ margin-bottom: -2px;
+ margin-right: 5px;
+}
+.nav-tabs-custom .nav-tabs>li:first-of-type.active>a {
+ border-left-color: transparent;
+}
+.nav-tabs-custom .nav-tabs>li.active>a {
+ border-top-color: transparent;
+ border-left-color: #f4f4f4;
+ border-right-color: #f4f4f4;
+}
+.nav-tabs-custom .nav-tabs>li.active>a, .nav-tabs-custom .nav-tabs>li.active:hover>a {
+ background-color: #fff;
+ color: #444;
+}
+.nav-tabs-custom .nav-tabs>li>a, .nav-tabs-custom .nav-tabs>li>a:hover {
+ background: transparent;
+ margin: 0;
+}
+.nav-tabs-custom .nav-tabs>li>a {
+ color: #444;
+ border-radius: 0;
+}
+.nav-tabs-custom .nav-tabs>li:not(.active)>a {
+ border: none;
+}
+
+
+@import "draft.less";
diff --git a/app/robots.txt b/app/robots.txt
new file mode 100644
index 0000000..9417495
--- /dev/null
+++ b/app/robots.txt
@@ -0,0 +1,3 @@
+# robotstxt.org
+
+User-agent: *
diff --git a/app/scripts/actions/article.js b/app/scripts/actions/article.js
new file mode 100644
index 0000000..5aebede
--- /dev/null
+++ b/app/scripts/actions/article.js
@@ -0,0 +1,5 @@
+export const TOGGLE_PUBLISH = 'TOGGLE_PUBLISH'
+
+export function toggleArticlePublish(id) {
+ return { type: TOGGLE_PUBLISH, id }
+}
diff --git a/app/scripts/actions/articleactions.js b/app/scripts/actions/articleactions.js
new file mode 100644
index 0000000..0006720
--- /dev/null
+++ b/app/scripts/actions/articleactions.js
@@ -0,0 +1,8 @@
+var Reflux = require('reflux');
+
+var ArticleActions = Reflux.createActions([
+ 'fetchArticleList',
+ 'toggleArticlePublish'
+]);
+
+module.exports = ArticleActions;
diff --git a/app/scripts/actions/banuseractions.js b/app/scripts/actions/banuseractions.js
new file mode 100644
index 0000000..8cba5c0
--- /dev/null
+++ b/app/scripts/actions/banuseractions.js
@@ -0,0 +1,9 @@
+var Reflux = require('reflux');
+
+var BanUserActions = Reflux.createActions([
+ 'fetchBannedUsers',
+ 'banUser',
+ 'removeBanUser'
+]);
+
+module.exports = BanUserActions;
diff --git a/app/scripts/actions/explorebanneractions.js b/app/scripts/actions/explorebanneractions.js
new file mode 100644
index 0000000..f536b3c
--- /dev/null
+++ b/app/scripts/actions/explorebanneractions.js
@@ -0,0 +1,9 @@
+var Reflux = require('reflux');
+
+var BannerActions = Reflux.createActions([
+ 'fetchBannerList',
+ 'addBanner',
+ 'deleteBanner'
+]);
+
+module.exports = BannerActions;
diff --git a/app/scripts/actions/explorethemeactions.js b/app/scripts/actions/explorethemeactions.js
new file mode 100644
index 0000000..6ee0f88
--- /dev/null
+++ b/app/scripts/actions/explorethemeactions.js
@@ -0,0 +1,9 @@
+var Reflux = require('reflux');
+
+var ThemeActions = Reflux.createActions([
+ 'fetchThemeList',
+ 'addTheme',
+ 'deleteTheme'
+]);
+
+module.exports = ThemeActions;
diff --git a/app/scripts/actions/feedbackactions.js b/app/scripts/actions/feedbackactions.js
new file mode 100644
index 0000000..71f50f6
--- /dev/null
+++ b/app/scripts/actions/feedbackactions.js
@@ -0,0 +1,8 @@
+var Reflux = require('reflux');
+
+var FeedbackActions = Reflux.createActions([
+ 'fetchFeedbackList',
+ 'deleteFeedback'
+]);
+
+module.exports = FeedbackActions;
diff --git a/app/scripts/actions/judgepostactions.js b/app/scripts/actions/judgepostactions.js
new file mode 100644
index 0000000..6cea81a
--- /dev/null
+++ b/app/scripts/actions/judgepostactions.js
@@ -0,0 +1,8 @@
+var Reflux = require('reflux');
+
+var JudgePostActions = Reflux.createActions([
+ 'fetchPostList',
+ 'judge'
+]);
+
+module.exports = JudgePostActions;
diff --git a/app/scripts/actions/postactions.js b/app/scripts/actions/postactions.js
new file mode 100644
index 0000000..54eadd7
--- /dev/null
+++ b/app/scripts/actions/postactions.js
@@ -0,0 +1,20 @@
+var Reflux = require('reflux');
+
+var PostActions = Reflux.createActions([
+ 'fetchPostList',
+ 'deletePost',
+ 'toggleRecommendPost',
+ 'toggleBlockPost',
+ 'toggleR18Post',
+ 'setPostClassification',
+ 'removePostClassification',
+ 'addSku',
+ 'addTag',
+ 'removeTag',
+ 'deleteMark',
+ 'addMark',
+ 'like',
+ 'unlike'
+]);
+
+module.exports = PostActions;
diff --git a/app/scripts/actions/recommendhomeactions.js b/app/scripts/actions/recommendhomeactions.js
new file mode 100644
index 0000000..ba07a59
--- /dev/null
+++ b/app/scripts/actions/recommendhomeactions.js
@@ -0,0 +1,8 @@
+var Reflux = require('reflux');
+
+var HomeAdActions = Reflux.createActions([
+ 'fetchHomeAdList',
+ 'deleteHomeAd'
+]);
+
+module.exports = HomeAdActions;
diff --git a/app/scripts/actions/reportactions.js b/app/scripts/actions/reportactions.js
new file mode 100644
index 0000000..fe1663d
--- /dev/null
+++ b/app/scripts/actions/reportactions.js
@@ -0,0 +1,10 @@
+var Reflux = require('reflux');
+
+var ReportActions = Reflux.createActions([
+ 'fetchReportList',
+ 'deleteReport',
+ 'hidePost',
+ 'deletePost'
+]);
+
+module.exports = ReportActions;
diff --git a/app/scripts/actions/skuactions.js b/app/scripts/actions/skuactions.js
new file mode 100644
index 0000000..2bdc0a5
--- /dev/null
+++ b/app/scripts/actions/skuactions.js
@@ -0,0 +1,12 @@
+var Reflux = require('reflux');
+
+var SkuActions = Reflux.createActions([
+ 'fetchSkus',
+ 'deleteSku',
+ 'toggleR18',
+ 'toggleRecommend',
+ 'addSku',
+ 'recommendToy'
+]);
+
+module.exports = SkuActions;
diff --git a/app/scripts/actions/statsactions.js b/app/scripts/actions/statsactions.js
new file mode 100644
index 0000000..ee5c2e0
--- /dev/null
+++ b/app/scripts/actions/statsactions.js
@@ -0,0 +1,7 @@
+var Reflux = require('reflux');
+
+var StatsActions = Reflux.createActions([
+ 'updateStats'
+]);
+
+module.exports = StatsActions;
diff --git a/app/scripts/actions/stickeractions.js b/app/scripts/actions/stickeractions.js
new file mode 100644
index 0000000..812fc1f
--- /dev/null
+++ b/app/scripts/actions/stickeractions.js
@@ -0,0 +1,13 @@
+var Reflux = require('reflux');
+
+var StickerActions = Reflux.createActions([
+ 'fetchStickers',
+ 'addStickerSet',
+ 'removeStickerSet',
+ 'updateCollection',
+ 'deleteSticker',
+ 'riseSticker',
+ 'riseStickerSet'
+]);
+
+module.exports = StickerActions;
diff --git a/app/scripts/actions/tagactions.js b/app/scripts/actions/tagactions.js
new file mode 100644
index 0000000..6b6883b
--- /dev/null
+++ b/app/scripts/actions/tagactions.js
@@ -0,0 +1,11 @@
+var Reflux = require('reflux');
+
+var TagActions = Reflux.createActions([
+ 'fetchTags',
+ 'setTagClassification',
+ 'removeTagClassification',
+ 'recommendTag',
+ 'deleteTag'
+]);
+
+module.exports = TagActions;
diff --git a/app/scripts/actions/tagclassactions.js b/app/scripts/actions/tagclassactions.js
new file mode 100644
index 0000000..75f0012
--- /dev/null
+++ b/app/scripts/actions/tagclassactions.js
@@ -0,0 +1,7 @@
+var Reflux = require('reflux');
+
+var TagClassActions = Reflux.createActions([
+ 'fetchClassifications'
+]);
+
+module.exports = TagClassActions;
diff --git a/app/scripts/actions/useractions.js b/app/scripts/actions/useractions.js
new file mode 100644
index 0000000..2d5769a
--- /dev/null
+++ b/app/scripts/actions/useractions.js
@@ -0,0 +1,16 @@
+var Reflux = require('reflux');
+
+var UserActions = Reflux.createActions([
+ 'updateParams',
+ 'fetchUserList',
+ 'recommendUser',
+ 'toggleVerifyUser',
+ 'deletePost',
+ 'toggleRecommendPost',
+ 'toggleBlockPost',
+ 'deleteMark',
+ 'like',
+ 'unlike'
+]);
+
+module.exports = UserActions;
diff --git a/app/scripts/actions/userdetailactions.js b/app/scripts/actions/userdetailactions.js
new file mode 100644
index 0000000..22fd493
--- /dev/null
+++ b/app/scripts/actions/userdetailactions.js
@@ -0,0 +1,13 @@
+var Reflux = require('reflux');
+
+var UserDetailActions = Reflux.createActions([
+ 'fetchUserDetailInfo',
+ 'fetchUserPosts',
+ 'updateUserId',
+ 'setActive',
+ 'updateUserInfo',
+ 'refreshUserCount',
+ 'changeForm'
+]);
+
+module.exports = UserDetailActions;
diff --git a/app/scripts/app.js b/app/scripts/app.js
new file mode 100644
index 0000000..7d8c0df
--- /dev/null
+++ b/app/scripts/app.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Router, Route, IndexRoute, hashHistory } from 'react-router';
+
+import Layout from './components/layout';
+import Home from './components/home';
+import RecommendHome from './components/recommendhome';
+import ExplorePage from './components/explorepage';
+import PostList from './components/postlist';
+import UserList from './components/userlist';
+import SkuList from './components/skulist';
+import UserDetail from './components/userdetail';
+import TagList from './components/taglist';
+import StickerList from './components/stickerlist';
+import ArticleList from './components/articlelist';
+
+import EditTag from './components/edittag';
+import EditShortVideo from './components/editshortvideo';
+import EditSticker from './components/editsticker';
+import EditStickerSet from './components/editstickerset';
+import EditRecommend from './components/editrecommend';
+import EditSku from './components/editsku';
+import EditArticle from './components/editarticle';
+
+import Test from './components/test';
+
+ReactDOM.render((
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+), document.getElementById('app'));
diff --git a/app/scripts/components/articlelist.jsx b/app/scripts/components/articlelist.jsx
new file mode 100644
index 0000000..b153140
--- /dev/null
+++ b/app/scripts/components/articlelist.jsx
@@ -0,0 +1,56 @@
+import React from 'react'
+import Reflux from 'reflux'
+import {Link} from 'react-router'
+import {Row, Button} from 'react-bootstrap'
+import Moment from 'moment'
+import ArticleStore from '../stores/articlestore'
+import ArticleActions from '../actions/articleactions'
+
+var ArticleList = React.createClass({
+ mixins: [Reflux.connect(ArticleStore, 'articles')],
+ getInitialState: function() {
+ return { query: '' };
+ },
+ fetchMoreArticles: function() {
+ ArticleActions.fetchArticleList(this.state.query);
+ },
+ render: function() {
+ if (this.state.articles) {
+ return (
+
+
+
+
+
+
+
+ {this.state.articles.map(function(article) {
+ return (
+
+ ![]({article.cover}) |
+ {article.title} |
+ {article.author} |
+ {article.category} |
+ {article.tags.join()} |
+ {article.counts.views} views |
+ {Moment.unix(article.created / 1000).fromNow()} |
+ 预览 |
+
+ );
+ },this)}
+
+
+
+
+
+ Load More
+
+
+ )
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = ArticleList;
diff --git a/app/scripts/components/bannedusers.jsx b/app/scripts/components/bannedusers.jsx
new file mode 100644
index 0000000..d332e33
--- /dev/null
+++ b/app/scripts/components/bannedusers.jsx
@@ -0,0 +1,56 @@
+var React = require('react/addons');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Input = require('react-bootstrap').Input;
+var BanUserStore = require('../stores/banuserstore');
+var BanUserActions = require('../actions/banuseractions');
+var Moment = require('moment');
+
+var BanUserList = React.createClass({
+ mixins: [Reflux.connect(BanUserStore, 'banusers'), React.addons.LinkedStateMixin],
+ getInitialState: function() {
+ return { banid: '' };
+ },
+ banUser: function() {
+ BanUserActions.banUser(this.state.banid);
+ },
+ removeBanUser: function(id) {
+ BanUserActions.removeBanUser(id);
+ },
+ render: function() {
+ if (this.state.banusers) {
+ return (
+
+
+ Total {this.state.banusers.length} users are banned
+
+
+
+
+ #ID | Avatar | Nickname | Likes | Action |
+
+ {this.state.banusers.map(function (user) {
+ return (
+ {user.id} | ![]({user.avatar}) | {user.nickname} | {user.likes} | Remove |
+ );
+ }, this)}
+
+
+
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = BanUserList;
diff --git a/app/scripts/components/brandlist.jsx b/app/scripts/components/brandlist.jsx
new file mode 100644
index 0000000..50e73a1
--- /dev/null
+++ b/app/scripts/components/brandlist.jsx
@@ -0,0 +1,62 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Link = require('react-router').Link;
+var Button = require('react-bootstrap').Button;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var BrandStore = require('../stores/brandstore');
+var BrandActions = require('../actions/brandactions');
+
+var Brands = React.createClass({
+ mixins: [Reflux.connect(BrandStore, 'brandlist')],
+ fetchMoreBrands: function() {
+ BrandActions.fetchBrandList();
+ },
+ togglePromoteBrand: function(id) {
+ BrandActions.togglePromoteBrand(id);
+ },
+ deleteBrand: function(id) {
+ if (confirm('Delete this brand?')) {
+ BrandActions.deleteBrand(id);
+ }
+ },
+ render: function() {
+ if (this.state.brandlist) {
+ return (
+
+
+
+
+ {this.state.brandlist.map(function (brand) {
+ var promoteBtn =
Promote;
+ if (brand.isPromoted) {
+ promoteBtn =
Promoted;
+ }
+ return (
+
+
+ {brand.name}
+ {brand.description}
+
+
+ Edit
+ {promoteBtn}
+ Delete
+
+
+
+ );
+ }.bind(this))}
+
+ Load More
+
+
+ );
+ } else {
+ return ();
+ }
+ }
+});
+
+module.exports = Brands;
diff --git a/app/scripts/components/editarticle.jsx b/app/scripts/components/editarticle.jsx
new file mode 100644
index 0000000..dc4b331
--- /dev/null
+++ b/app/scripts/components/editarticle.jsx
@@ -0,0 +1,439 @@
+import React from 'react';
+import {AtomicBlockUtils, Editor, EditorState, Entity, RichUtils, convertToRaw} from 'draft-js';
+import stateToHTML from '../utils/stateToHTML';
+import Dropzone from 'react-dropzone';
+import TagsInput from 'react-tagsinput';
+import Request from 'superagent';
+import $ from 'jquery';
+import CDN from '../widgets/cdn';
+
+
+function isFunction(fn) {
+ let getType = {};
+ return fn && getType.toString.call(fn) === '[object Function]';
+}
+
+function makeid() {
+ let text = '';
+ const possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
+ for( let i=0; i < 10; i++ ) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+}
+
+function getBlockStyle(block) {
+ switch (block.getType()) {
+ case 'blockquote': return 'RichEditor-blockquote';
+ case 'unstyled': return 'paragraph';
+ default: return null;
+ }
+}
+
+class StyleButton extends React.Component {
+ constructor() {
+ super();
+ this.onToggle = (e) => {
+ e.preventDefault();
+ this.props.onToggle(this.props.style);
+ };
+ }
+
+ render() {
+ let className = 'RichEditor-styleButton';
+ if (this.props.active) {
+ className += ' RichEditor-activeButton';
+ }
+ if (this.props.icon) {
+ className += (' ' + this.props.icon);
+ }
+
+ return (
+
+ {this.props.icon ? null : this.props.label}
+
+ );
+ }
+}
+
+const BLOCK_TYPES = [
+ {label: 'H1', style: 'header-one', icon: ''},
+ {label: 'H2', style: 'header-two', icon: ''},
+ {label: 'Blockquote', style: 'blockquote', icon: 'fa fa-quote-left'},
+ {label: 'UL', style: 'unordered-list-item', icon: 'fa fa-list-ul'},
+ {label: 'OL', style: 'ordered-list-item', icon: 'fa fa-list-ol'},
+];
+
+const BlockStyleControls = (props) => {
+ const {editorState} = props;
+ const selection = editorState.getSelection();
+ const blockType = editorState
+ .getCurrentContent()
+ .getBlockForKey(selection.getStartKey())
+ .getType();
+
+ return (
+
+ {BLOCK_TYPES.map((type) =>
+
+ )}
+
+ );
+};
+
+
+const INLINE_STYLES = [
+ {label: 'Bold', style: 'BOLD', icon: 'fa fa-bold'},
+ {label: 'Italic', style: 'ITALIC', icon: 'fa fa-italic'},
+ {label: 'Underline', style: 'UNDERLINE', icon: 'fa fa-underline'},
+];
+
+const InlineStyleControls = (props) => {
+ var currentStyle = props.editorState.getCurrentInlineStyle();
+ return (
+
+ {INLINE_STYLES.map(type =>
+
+ )}
+
+ );
+};
+
+const Media = (props) => {
+ const entity = Entity.get(props.block.getEntityAt(0));
+ const {src, html} = entity.getData();
+ const type = entity.getType();
+ let media;
+ if (type === 'image') {
+ media = (
+
![]({src})
+
);
+ } else if (type === 'video') {
+ media = ;
+ }
+ return media;
+};
+
+export default class ArticleEditor extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ editorState: EditorState.createEmpty(),
+ showImageWindow: false,
+ showVideoWindow: false,
+ videoCode: '',
+ title: '',
+ cover: '',
+ tags: [],
+ category: '评测',
+ authorId: '',
+ gallery:[],
+ uploadUrl: 'http://upload.qiniu.com/',
+ };
+
+ this.focus = () => this.refs.editor.focus();
+ this.onChange = (editorState) => this.setState({editorState});
+
+ this.handleKeyCommand = (command) => this._handleKeyCommand(command);
+ this.toggleBlockType = (type) => this._toggleBlockType(type);
+ this.toggleInlineStyle = (style) => this._toggleInlineStyle(style);
+ this.toggleImageWindow = () => this.setState({showImageWindow: !this.state.showImageWindow});
+ this.toggleVideoWindow = () => this.setState({showVideoWindow: !this.state.showVideoWindow});
+ this.handleTagsChange = (tags) => this.setState({tags});
+ this.handleTitleChange = (e) => this.setState({title: e.target.value});
+ this.handleCategoryChange = (e) => this.setState({category: e.target.value});
+ this.handleAuthorChange = (e) => this.setState({authorId: e.target.value});
+
+ this.onDrop = (files) => this._onDrop(files);
+ this.onDropCover = (files) => this._onDropCover(files);
+ this.onChangeVideoCode = (e) => this.setState({videoCode: e.target.value});
+ this.addImage = (imageKey) => this._addImage(imageKey);
+ this.addVideo = () => this._addVideo();
+ this.publish = () => this._publish();
+ this.blockRenderer = (block) => {
+ if (block.getType() === 'atomic') {
+ return {
+ component: Media,
+ editable: false,
+ };
+ }
+ return null;
+ };
+ }
+ _handleKeyCommand(command) {
+ const {editorState} = this.state;
+ const newState = RichUtils.handleKeyCommand(editorState, command);
+ if (newState) {
+ this.onChange(newState);
+ return true;
+ }
+ return false;
+ }
+ _toggleBlockType(blockType) {
+ this.onChange(
+ RichUtils.toggleBlockType(
+ this.state.editorState,
+ blockType
+ )
+ );
+ }
+ _toggleInlineStyle(inlineStyle) {
+ this.onChange(
+ RichUtils.toggleInlineStyle(
+ this.state.editorState,
+ inlineStyle
+ )
+ );
+ }
+ _addImage(imageKey) {
+ const src = CDN.show(imageKey) + '?articlestyle';
+ const entityKey = Entity.create('image', 'IMMUTABLE', {src});
+ this.onChange(AtomicBlockUtils.insertAtomicBlock(
+ this.state.editorState,
+ entityKey,
+ ' '
+ ));
+ this.setState({showImageWindow: false});
+ }
+ _addVideo() {
+ if (this.state.videoCode.trim().length === 0) {
+ return false;
+ }
+ const html = this.state.videoCode.trim().replace("width=510", "width=640");
+ const entityKey = Entity.create('video', 'IMMUTABLE', {html});
+ this.onChange(AtomicBlockUtils.insertAtomicBlock(
+ this.state.editorState,
+ entityKey,
+ ' '
+ ));
+ this.setState({videoCode:'', showVideoWindow: false});
+ }
+ _onDrop(files) {
+ let _this = this;
+ $.ajax({
+ url : '/api/uptoken',
+ type : 'GET',
+ success : function(data) {
+ let uploadToken = data.uptoken;
+ let progresses = {};
+ files.forEach((file)=> {
+ // genereate upload key
+ let d = new Date();
+ let id = makeid();
+ let uploadKey = 'article/photo/' + Math.round(d.getTime()/1000) + '_' + id + '.' + file.name.split('.').pop();
+ // file.preview = URL.createObjectURL(file);
+ file.onprogress = function(e) {
+ progresses[file.preview] = e.percent;
+ _this.setState({progresses: progresses});
+ };
+ file.request = _this.uploadToQiniu(file, uploadKey, uploadToken);
+ });
+ }
+ });
+ }
+ uploadToQiniu(file, uploadKey, uploadToken) {
+ if (!file || file.size === 0) {
+ return null;
+ }
+ let _this = this;
+ const req = Request
+ .post(this.state.uploadUrl)
+ .field('key', uploadKey)
+ .field('token', uploadToken)
+ .field('x:filename', file.name)
+ .field('x:size', file.size)
+ .attach('file', file, file.name)
+ .set('Accept', 'application/json');
+
+ if (isFunction(file.onprogress)) {
+ req.on('progress', file.onprogress);
+ }
+ req.end(function(err, res){
+ let value = _this.state.gallery.slice();
+ value.push(uploadKey);
+ _this.setState({gallery: value});
+ });
+ return req;
+ }
+ _onDropCover(files) {
+ let _this = this;
+ Request.get('/api/uptoken').end(function(err, res){
+ let uploadToken = res.body.uptoken;
+ const file = files[0];
+ const img = new Image();
+ img.onload = () => {
+ console.log(img.width + ' ' + img.height);
+ const id = makeid();
+ const date = new Date();
+ const uploadKey = 'article/cover/' + Math.round(date.getTime()/1000) + '_w_' + img.width + '_h_' + img.height + '_' + id + '.' + file.name.split('.').pop();
+ Request
+ .post(_this.state.uploadUrl)
+ .field('key', uploadKey)
+ .field('token', uploadToken)
+ .field('x:filename', file.name)
+ .field('x:size', file.size)
+ .attach('file', file, file.name)
+ .set('Accept', 'application/json')
+ .end(function(err, res){
+ _this.setState({cover: uploadKey});
+ });
+ };
+ img.src = file.preview;
+ });
+ }
+ _publish() {
+ let _this = this;
+
+ let title = this.state.title;
+ let cover = this.state.cover;
+ let tags = this.state.tags;
+ let category = this.state.category;
+ let authorId = this.state.authorId;
+ let gallery = this.state.gallery;
+ let html = stateToHTML(this.state.editorState.getCurrentContent()).replace('', '');
+ let raw = convertToRaw(this.state.editorState.getCurrentContent());
+
+ let data = {title, cover, tags, category, authorId, gallery, html, raw};
+ console.log(data);
+ Request
+ .post('/api/article')
+ .send(data)
+ .end(function(err, res){
+ console.log(err);
+ console.log(res);
+ console.log((err || !res.ok));
+ if (err || !res.ok) {
+ alert('保存失败!');
+ } else {
+ alert('保存成功.');
+ location.href = `/article/${res.text}/preview`;
+ }
+ });
+ }
+ render() {
+ const {editorState} = this.state;
+
+ // If the user changes block type before entering any text, we can
+ // either style the placeholder or hide it. Let's just hide it now.
+ let className = 'RichEditor-editor';
+ let contentState = editorState.getCurrentContent();
+ if (!contentState.hasText()) {
+ if (contentState.getBlockMap().first().getType() !== 'unstyled') {
+ className += ' RichEditor-hidePlaceholder';
+ }
+ }
+ return (
+
+
+
+
文章标题
+
+
+
+
+
+
+
+
+
+
添加图片
+
添加视频
+
+ {this.state.showImageWindow ?
+
+
+
+
选择图片
+
+
+
+ 点击此处选取图片或将文件拖入该区域
+
+ {this.state.gallery.map(function (imageKey) {
+ return (
+
+
![]({CDN.show(imageKey)})
this.addImage(imageKey)}/>
+
+ );
+ }, this)}
+
+
+ : null
+ }
+ {this.state.showVideoWindow ?
+
+
+
+
粘贴视频通用代码
+
+
+
+
+
+
+
+ : null
+ }
+
+
+
添加标签
+ this.handleTagsChange(tags)} />
+
+
+
+ 上传文章封面
+
+ {this.state.cover !== '' ?
+
![]({CDN.show(this.state.cover)})
+ :null
+ }
+
+
+
文章分类
+
+
+
+
作者ID
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/app/scripts/components/editrecommend.jsx b/app/scripts/components/editrecommend.jsx
new file mode 100644
index 0000000..66e6b47
--- /dev/null
+++ b/app/scripts/components/editrecommend.jsx
@@ -0,0 +1,149 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var $ = require('jquery');
+var Dropzone = require('react-dropzone');
+var Formsy = require('formsy-react');
+var Modal = require('react-modal');
+var RB = require('react-bootstrap');
+var FRC = require('formsy-react-components');
+import CDN from '../widgets/cdn';
+
+var EditBannerSet = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+ getInitialState: function() {
+ return {
+ image: "",
+ alert: false
+ };
+ },
+ componentDidMount: function() {
+ $.get('/api/recommend/'+this.props.params.id, function(data) {
+ console.log(data);
+ if (this.isMounted()) {
+ this.refs.form.resetModel(data);
+ this.setState({image: data.image});
+ }
+ }.bind(this));
+ },
+ onConfirm: function() {
+ this.context.router.push('/manage/explore');
+ },
+ onClose: function() {
+ this.setState({alert: false});
+ },
+ onDropImage: function(images) {
+ console.log(images);
+ var formData = new FormData();
+ formData.append('file', images[0]);
+ $.ajax({
+ url : '/api/upload?key=recommend_'+this.props.params.id + '.jpg&temp=false',
+ type : 'POST',
+ data : formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ success : function(data) {
+ console.log(data);
+ this.refs.image.setValue(data);
+ this.setState({image: data});
+ }.bind(this)
+ });
+ },
+ submit: function(model) {
+ console.log(model);
+ var filteredModel = _.pickBy(model, function(value){
+ return value !== '' && value !== null;
+ });
+ $.ajax({
+ url: '/api/recommend/'+this.props.params.id, //Server script to process data
+ type: 'POST',
+ data: JSON.stringify(filteredModel),
+ contentType: 'application/json; charset=utf-8',
+ dataType: 'json',
+ success: function (res) {
+ this.setState({alert: true});
+ }.bind(this)
+ });
+ },
+ render: function() {
+ var radioOptions = [
+ {value: 'post', label: '图片'},
+ {value: 'tag', label: '标签'},
+ {value: 'user', label: '用户'},
+ {value: 'url', label: 'URL链接'},
+ {value: 'page', label: '文章'},
+ {value: 'toy', label: '玩具'},
+ {value: 'promotion', label: '商品集'}
+ ];
+ var placeOptions = [
+ {value: 'banner', label: '发现页面Banner'},
+ {value: 'toy', label: '玩具页面Banner'},
+ {value: 'home', label: '首页推荐'},
+ {value: 'theme', label: '主题'}
+ ];
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+ return (
+
+
+
+
+
+
+ 保存成功
+
+
+ 确定
+
+
+
+
+
+ );
+ }
+});
+
+module.exports = EditBannerSet;
diff --git a/app/scripts/components/editshortvideo.jsx b/app/scripts/components/editshortvideo.jsx
new file mode 100644
index 0000000..ac7d2b1
--- /dev/null
+++ b/app/scripts/components/editshortvideo.jsx
@@ -0,0 +1,210 @@
+/*global window, alert */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Dropzone from 'react-dropzone';
+import Request from 'superagent';
+import $ from 'jquery';
+import Modal from 'react-modal';
+var RB = require('react-bootstrap');
+
+function isFunction(fn) {
+ let getType = {};
+ return fn && getType.toString.call(fn) === '[object Function]';
+}
+
+function makeid() {
+ let text = '';
+ const possible = 'abcdefghijklmnopqrstuvwxyz0123456789';
+ for( let i=0; i < 10; i++ ) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+}
+
+export default class EditShortVideo extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ videoUrl: '',
+ uploadUrl: 'http://upload.qiniu.com/',
+ userId: '56f2b9811400000e0077d8f8',
+ progress: 0,
+ uploadKey: '',
+ caption: '',
+ alert: false
+ };
+ this.onDrop = this.onDrop.bind(this);
+ this.changeCaption = this.changeCaption.bind(this);
+ this.onSubmit = this.onSubmit.bind(this);
+ this.onConfirm = this.onConfirm.bind(this);
+ this.onClose = this.onClose.bind(this);
+ }
+ changeCaption(e) {
+ this.setState({caption:e.target.value});
+ }
+ uploadQiniu(file, uploadKey, uploadToken) {
+ if (!file || file.size === 0) {
+ return null;
+ }
+ const req = Request
+ .post(this.state.uploadUrl)
+ .field('key', uploadKey)
+ .field('token', uploadToken)
+ .field('x:filename', file.name)
+ .field('x:size', file.size)
+ .attach('file', file, file.name)
+ .set('Accept', 'application/json');
+
+ if (isFunction(file.onprogress)) {
+ req.on('progress', file.onprogress);
+ }
+ req.end(function(err, res){
+ console.log('done!');
+ });
+ return req;
+ }
+ onDrop(files) {
+ let video = files[0];
+
+ // 初始化progress
+ let _this = this;
+ video.onprogress = function(e) {
+ console.log(e.percent);
+ _this.setState({progress: e.percent.toFixed(2)});
+ };
+
+ // 获取视频meta
+ let URL = window.URL || window.webkitURL;
+ video.preview = URL.createObjectURL(video);
+
+ let vdom = ReactDOM.findDOMNode(this.refs.vtag);
+
+ this.setState({videoUrl: video.preview});
+
+ let timer = setInterval(function () {
+ if (vdom.readyState === 4){
+ let d = new Date();
+ let id = makeid();
+ let uploadKey = 'user/video/file/' + id + '_' + Math.round(d.getTime()/1000) + '_w_' + vdom.videoWidth +
+ '_h_' + vdom.videoHeight + '_d_' + Math.floor(vdom.duration) + '_' + _this.state.userId + '.mp4';
+ console.log(uploadKey);
+ $.ajax({
+ url : '/api/uptoken?key=' + uploadKey,
+ type : 'GET',
+ success : function(data) {
+ let uploadToken = data.uptoken;
+ _this.setState({uploadKey: uploadKey});
+ video.request = _this.uploadQiniu(video, uploadKey, uploadToken);
+ // video.uploadPromise = video.request.promise();
+ }
+ });
+ clearInterval(timer);
+ }
+ }, 500);
+ }
+ onSubmit() {
+ if (this.state.uploadKey === '') {
+ alert('请先上传视频');
+ return;
+ }
+ let data = {
+ userId: this.state.userId,
+ uploadKey: this.state.uploadKey
+ };
+ if (this.state.caption.trim() !== '') {
+ data.caption = this.state.caption;
+ }
+ let _this = this;
+ Request
+ .post('/api/uploadvideo')
+ .send(data)
+ .set('ContentType', 'application/json')
+ .end(function(err, res){
+ console.log(err);
+ console.log(res);
+ _this.setState({alert: true});
+ // Calling the end function will send the request
+ });
+ // $.ajax({
+ // url : '/api/uploadvideo',
+ // type : 'POST',
+ // data: JSON.stringify(data),
+ // contentType: 'application/json; charset=utf-8',
+ // dataType: 'json',
+ // success : function() {
+ // this.setState({alert: true});
+ // }.bind(this)
+ // });
+ }
+ onConfirm() {
+ this.setState({
+ videoUrl: '',
+ progress: 0,
+ uploadKey: '',
+ caption: '',
+ alert: false
+ });
+ }
+ onClose() {
+ this.setState({alert: false});
+ }
+ render() {
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+ let dropZoneStyles = {
+ margin: '20px auto',
+ border: '2px dashed #ccc',
+ borderRadius: '5px',
+ width: '300px',
+ height: '200px',
+ color: '#aaa'
+ };
+ return (
+
+
+
+ 将视频文件拖入该区域
+
+
+
{'USERID: ' + this.state.userId}
+
{this.state.uploadKey}
+
{(this.state.progress || 0) + '% uploaded'}
+
+
+
+
+
+
+
+
+
+
+
+
+ 提交成功
+
+
+ 确定
+
+
+
+
+ );
+ }
+}
diff --git a/app/scripts/components/editsku.jsx b/app/scripts/components/editsku.jsx
new file mode 100644
index 0000000..d44bcdd
--- /dev/null
+++ b/app/scripts/components/editsku.jsx
@@ -0,0 +1,248 @@
+var React = require('react');
+var LinkedStateMixin = require('react-addons-linked-state-mixin');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var $ = require('jquery');
+var _ = require('lodash');
+var Dropzone = require('react-dropzone');
+var Formsy = require('formsy-react');
+var Modal = require('react-modal');
+var RB = require('react-bootstrap');
+var FRC = require('formsy-react-components');
+import CDN from '../widgets/cdn';
+
+var EditSku = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+ getInitialState: function() {
+ return {
+ cover: '',
+ images: [],
+ tags: [],
+ otherInfo: [],
+ newKey: '',
+ newValue: '',
+ alert: false
+ };
+ },
+ componentDidMount: function() {
+ $.get('/api/sku/'+this.props.params.id, function(data) {
+ console.log(data);
+ if (this.isMounted()) {
+ this.refs.form.resetModel(data);
+ var release = '';
+ if (data.releaseDateMap.year) {
+ release = data.releaseDateMap.year + '';
+ }
+ if (data.releaseDateMap.month) {
+ release = release + '/' + data.releaseDateMap.month;
+ }
+
+ this.refs.release.setValue(release);
+ this.setState({cover: data.cover, images: data.images, tags: data.tags});
+ }
+ }.bind(this));
+ },
+ onDropCover: function(images) {
+ console.log(images);
+ var formData = new FormData();
+ formData.append('file', images[0]);
+ $.ajax({
+ url : '/api/upload?key=sku/cover/'+this.props.params.id + '.jpg',
+ type : 'POST',
+ data : formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ success : function(data) {
+ console.log(data);
+ this.refs.cover.setValue(data);
+ this.setState({cover: data});
+ }.bind(this)
+ });
+ },
+ onDropOfficialImage: function(images) {
+ console.log(images);
+ var formData = new FormData();
+ formData.append('file', images[0]);
+ $.ajax({
+ url : '/api/upload?key=sku/img/'+this.props.params.id + '_' + (Date.now() / 1000) + '.jpg',
+ type : 'POST',
+ data : formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ success : function(data) {
+ console.log(data);
+ this.state.images.push(data);
+ this.setState({images: this.state.images});
+ }.bind(this)
+ });
+ },
+ onChangeNewKey: function(e) {
+ this.setState({newKey: e.target.value});
+ },
+ onChangeNewValue: function(e) {
+ this.setState({newValue: e.target.value});
+ },
+ addInfo: function(e) {
+ this.state.otherInfo.push({ key:this.state.newKey, value:this.state.newValue });
+ this.setState({otherInfo: this.state.otherInfo, newKey:'', newValue:''});
+ e.preventDefault();
+ },
+ removeInfo: function(key) {
+ var newInfo = _.filter(this.state.otherInfo, function(info){
+ return info.key !== key;
+ });
+ this.setState({otherInfo: newInfo});
+ },
+ submit: function(model) {
+ model.images = this.state.images;
+ model.tags = this.state.tags;
+ model.otherInfo = this.state.otherInfo;
+ var filteredModel = _.pickBy(model, function(value){
+ return value !== '' && value !== null;
+ });
+
+ if (filteredModel.money) {
+ filteredModel.money = parseInt(filteredModel.money);
+ }
+
+ if (filteredModel.scale) {
+ filteredModel.scale = parseInt(filteredModel.scale);
+ }
+
+ console.log(model);
+ console.log('Filtered model:');
+ console.log(filteredModel);
+
+ $.ajax({
+ url: '/api/sku/'+this.props.params.id, //Server script to process data
+ type: 'POST',
+ data: JSON.stringify(filteredModel),
+ contentType: 'application/json; charset=utf-8',
+ dataType: 'json',
+ success: function(res) {
+ console.log(res);
+ this.setState({alert: true});
+ }.bind(this)
+ });
+ },
+ onConfirm: function() {
+ this.context.router.push('/sku');
+ },
+ onClose: function() {
+ this.setState({alert: false});
+ },
+ render: function() {
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 更新成功
+
+
+ 确定
+
+
+
+
+ );
+ }
+});
+
+module.exports = EditSku;
diff --git a/app/scripts/components/editsticker.jsx b/app/scripts/components/editsticker.jsx
new file mode 100644
index 0000000..2a269bc
--- /dev/null
+++ b/app/scripts/components/editsticker.jsx
@@ -0,0 +1,102 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var $ = require('jquery');
+var Dropzone = require('react-dropzone');
+var Formsy = require('formsy-react');
+var FRC = require('formsy-react-components');
+
+var EditSticker = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+ getInitialState: function() {
+ return {
+ image: ""
+ };
+ },
+ componentDidMount: function() {
+ this.refs.setId.setValue(this.props.params.id);
+ },
+ onDropImage: function(images) {
+ console.log(images);
+ var formData = new FormData();
+ formData.append('file', images[0]);
+ $.ajax({
+ url : '/api/upload?key=sticker_$id.png',
+ type : 'POST',
+ data : formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ success : function(data) {
+ console.log(data);
+ this.refs.image.setValue(data);
+ var id = data.split(/[_\.]/)[1];
+ this.refs.id.setValue(id);
+ this.setState({image: data});
+ }.bind(this)
+ });
+ },
+ submit: function(model) {
+ console.log(model);
+ model.score = 0;
+ $.ajax({
+ url: '/api/sticker', //Server script to process data
+ type: 'POST',
+ data: JSON.stringify(model),
+ contentType: 'application/json; charset=utf-8',
+ dataType: 'json',
+ success: function (res) {
+ console.log(res);
+ alert("success!");
+ }
+ });
+ },
+ render: function() {
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+ return (
+
+ );
+ }
+});
+
+module.exports = EditSticker;
diff --git a/app/scripts/components/editstickerset.jsx b/app/scripts/components/editstickerset.jsx
new file mode 100644
index 0000000..2b20c21
--- /dev/null
+++ b/app/scripts/components/editstickerset.jsx
@@ -0,0 +1,125 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var $ = require('jquery');
+var Dropzone = require('react-dropzone');
+var Formsy = require('formsy-react');
+var Modal = require('react-modal');
+var RB = require('react-bootstrap');
+var FRC = require('formsy-react-components');
+var CDN = require('../widgets/cdn');
+
+var EditStickerSet = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+ getInitialState: function() {
+ return {
+ image: "",
+ alert: false
+ };
+ },
+ componentDidMount: function() {
+ $.get('/api/sticker/set/'+this.props.params.id, function(data) {
+ if (this.isMounted()) {
+ this.refs.form.resetModel(data);
+ this.setState({id: data.id, image: data.image});
+ }
+ }.bind(this));
+ },
+ onDropImage: function(images) {
+ console.log(images);
+ var formData = new FormData();
+ formData.append('file', images[0]);
+ $.ajax({
+ url : '/api/upload?key=stickerset/'+this.props.params.id + '.png',
+ type : 'POST',
+ data : formData,
+ processData: false, // tell jQuery not to process the data
+ contentType: false, // tell jQuery not to set contentType
+ success : function(data) {
+ console.log(data);
+ this.refs.image.setValue(data);
+ this.setState({image: data});
+ }.bind(this)
+ });
+ },
+ onConfirm: function() {
+ this.context.router.push('/sticker');
+ },
+ onClose: function() {
+ this.setState({alert: false});
+ },
+ submit: function(model) {
+ console.log(model);
+ $.ajax({
+ url: '/api/sticker/set/'+this.props.params.id, //Server script to process data
+ type: 'POST',
+ data: JSON.stringify(model),
+ contentType: 'application/json; charset=utf-8',
+ dataType: 'json',
+ success: function (res) {
+ console.log(res);
+ this.setState({alert: true});
+ }.bind(this)
+ });
+ },
+ render: function() {
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 更新成功
+
+
+ 确定
+
+
+
+
+ );
+ }
+});
+
+module.exports = EditStickerSet;
diff --git a/app/scripts/components/edittag.jsx b/app/scripts/components/edittag.jsx
new file mode 100644
index 0000000..80634b8
--- /dev/null
+++ b/app/scripts/components/edittag.jsx
@@ -0,0 +1,173 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Router = require('react-router');
+var $ = require('jquery');
+var TagStore = require('../stores/tagstore');
+var TagClassStore = require('../stores/tagclassstore');
+var TagActions = require('../actions/tagactions');
+var Modal = require('react-modal');
+var RB = require('react-bootstrap');
+import Dropzone from 'react-dropzone';
+import Request from 'superagent';
+import CDN from '../widgets/cdn';
+
+var TagForm = React.createClass({
+ mixins: [Reflux.connect(TagStore, 'taglist'), Reflux.connect(TagClassStore, 'class')],
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+ getInitialState: function() {
+ return {
+ id: this.props.params.id,
+ text: '',
+ image: '',
+ type: '',
+ description: '',
+ classifications: [],
+ alert: false
+ };
+ },
+ componentDidMount: function() {
+ $.ajax({
+ url: '/api/tag/'+this.props.params.id, //Server script to process data
+ type: 'GET',
+ success: function (data) {
+ this.setState({
+ text: data.text,
+ image: data.image,
+ description: data.description ? data.description: '',
+ type: data.type ? data.type: '',
+ classifications: data.classifications
+ });
+ }.bind(this)
+ });
+ },
+ onChangeDescription: function(e) {
+ this.setState({description: e.target.value});
+ },
+ onChangeType: function(e) {
+ this.setState({type: e.target.value});
+ },
+ onDrop: function(files) {
+ let _this = this;
+ let file = files[0];
+ let uploadKey = 'tag/cover/' + _this.props.params.id + '.' + file.name.split('.').pop();
+ $.get('/api/uptoken?key='+uploadKey, function(data) {
+ let uploadToken = data.uptoken;
+ Request
+ .post('http://upload.qiniu.com/')
+ .field('key', uploadKey)
+ .field('token', uploadToken)
+ .field('x:filename', file.name)
+ .field('x:size', file.size)
+ .attach('file', file, file.name)
+ .set('Accept', 'application/json')
+ .end(function(err, res){
+ _this.setState({image: uploadKey});
+ });
+ });
+ },
+ submitForm: function(e) {
+ e.preventDefault();
+ var data = {
+ image: this.state.image
+ };
+ if (this.state.description.trim() !== '') {
+ data.description = this.state.description;
+ }
+ if (this.state.type !== '') {
+ data.type = this.state.type;
+ }
+ $.ajax({
+ type: 'POST',
+ url: '/api/tag/' + this.state.id,
+ data: JSON.stringify(data),
+ contentType: 'application/json; charset=utf-8',
+ dataType: 'json',
+ success: function(tag) {
+ var foundTag = _.find(this.state.taglist, function(t) {
+ return t.id === tag.id;
+ });
+ if (foundTag !== null) {
+ foundTag.description = this.state.description;
+ foundTag.image = this.state.image;
+ foundTag.type = this.state.type;
+ }
+ this.setState({alert: true});
+ }.bind(this)
+ });
+ return false;
+ },
+ onConfirm: function() {
+ this.context.router.push('/tag');
+ },
+ onClose: function() {
+ this.setState({alert: false});
+ },
+ render: function() {
+ var customStyles = {
+ content : {
+ zIndex : 3,
+ width : '300px',
+ top : '50%',
+ left : '50%',
+ right : 'auto',
+ bottom : 'auto',
+ marginRight : '-50%',
+ transform : 'translate(-50%, -50%)'
+ }
+ };
+ return (
+
+
+
+
编辑: {this.state.text}
+
+
+
+
+
+
+ 更新成功
+
+
+ 确定
+
+
+
+
+
+
+ );
+ }
+});
+
+module.exports = TagForm;
diff --git a/app/scripts/components/explorepage.jsx b/app/scripts/components/explorepage.jsx
new file mode 100644
index 0000000..91a6215
--- /dev/null
+++ b/app/scripts/components/explorepage.jsx
@@ -0,0 +1,147 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Link = require('react-router').Link;
+var Button = require('react-bootstrap').Button;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var If = require('../widgets/if');
+var BannerStore = require('../stores/explorebannerstore');
+var BannerActions = require('../actions/explorebanneractions');
+var ThemeStore = require('../stores/explorethemestore');
+var ThemeActions = require('../actions/explorethemeactions');
+
+var ExplorePage = React.createClass({
+ mixins: [Reflux.connect(BannerStore, 'bannerlist'), Reflux.connect(ThemeStore, 'themelist')],
+ fetchMoreThemes: function() {
+ ThemeActions.fetchThemeList();
+ },
+ addBanner: function() {
+ if (confirm('创建一个新Banner?')) {
+ BannerActions.addBanner();
+ }
+ },
+ deleteBanner: function(id) {
+ if (confirm('删除这个Banner?')) {
+ BannerActions.deleteBanner(id);
+ }
+ },
+ addTheme: function() {
+ if (confirm('创建一个新主题?')) {
+ ThemeActions.addTheme();
+ }
+ },
+ deleteTheme: function(id) {
+ if (confirm('删除这个主题?')) {
+ ThemeActions.deleteTheme(id);
+ }
+ },
+ render: function() {
+ return (
+
+
+
+ Banner管理 { " " }
+
+
+
+
+
+
+ {this.state.bannerlist.map(function(banner) {
+ return (
+
+
+
+
+ {banner.title}
+
+
+
+
+
+
+
+
+
+
+
![]({banner.image})
+
+
+
+ );
+ }.bind(this))}
+
+
+
+
+
+ 主题管理 { " " }
+
+
+
+
+
+
+ {this.state.themelist.map(function(theme) {
+ return (
+
+
+
+
+ {theme.title}
+
+
+
+
+
+
+
+
+
+
+
![]({theme.image})
+
+
+
+ );
+ }.bind(this))}
+
+
+
+
+ Load More
+
+
+
+ );
+ }
+});
+
+module.exports = ExplorePage;
diff --git a/app/scripts/components/feedback.jsx b/app/scripts/components/feedback.jsx
new file mode 100644
index 0000000..aa2ad1f
--- /dev/null
+++ b/app/scripts/components/feedback.jsx
@@ -0,0 +1,54 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var FeedbackStore = require('../stores/feedbackstore');
+var FeedbackActions = require('../actions/feedbackactions');
+var Link = require('react-router').Link;
+var Moment = require('moment');
+
+var Feedbacks = React.createClass({
+ mixins: [Reflux.connect(FeedbackStore, 'feedbacklist')],
+ fetchMoreFeedbacks: function() {
+ FeedbackActions.fetchFeedbackList();
+ },
+ deleteFeedback: function(id) {
+ if (confirm('Delete this Feedback?')) {
+ FeedbackActions.deleteFeedback(id);
+ }
+ },
+ render: function() {
+ if (this.state.feedbacklist) {
+ return (
+
+
+
+ #ID | User | Feedback | Created | Action |
+
+ {this.state.feedbacklist.map(function (feedback) {
+ return (
+
+ {feedback.id} |
+ ![]({feedback.user.avatar}) |
+ {feedback.content} |
+ {Moment.unix(feedback.created).fromNow()} |
+ Delete |
+
+ );
+ }.bind(this))}
+
+
+
+
+
+ Load More
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = Feedbacks;
diff --git a/app/scripts/components/home.jsx b/app/scripts/components/home.jsx
new file mode 100644
index 0000000..4da3f89
--- /dev/null
+++ b/app/scripts/components/home.jsx
@@ -0,0 +1,64 @@
+var Reflux = require('reflux');
+var React = require('react');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var StatsStore = require('../stores/statsstore');
+var StatsActions = require('../actions/statsactions');
+var NumberCard = require('../widgets/numbercard');
+
+var Home = React.createClass({
+ mixins: [Reflux.connect(StatsStore, 'stats')],
+ render: function() {
+ if (this.state.stats) {
+ return (
+
+
+
+
+
We will change the world.
+
Innovation distinguishes between a leader and a follower.
+
+ — Steve Jobs
+
+
+
+
+
+
+
+
+
+
{this.state.stats.posts}
+ 照片数
+
+
+
+
+
{this.state.stats.users}
+ 用户数
+
+
+
+
+
{this.state.stats.toys}
+ 玩具数
+
+
+
+
+
{this.state.stats.tags}
+ 标签数
+
+
+
+
+
+
+ );
+ } else {
+ return ();
+ }
+ }
+});
+
+module.exports = Home;
diff --git a/app/scripts/components/judgeposts.jsx b/app/scripts/components/judgeposts.jsx
new file mode 100644
index 0000000..090755e
--- /dev/null
+++ b/app/scripts/components/judgeposts.jsx
@@ -0,0 +1,45 @@
+var React = require('react/addons');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var JudgePostStore = require('../stores/judgepoststore');
+var JudgePostActions = require('../actions/judgepostactions');
+
+var JudgePostList = React.createClass({
+ mixins: [Reflux.connect(JudgePostStore, 'postlist')],
+ judge: function(id, judge) {
+ JudgePostActions.judge(id, judge);
+ },
+ fetchMorePosts: function() {
+ JudgePostActions.fetchPostList();
+ },
+ render: function() {
+ if (this.state.postlist) {
+ return (
+
+
+ {this.state.postlist.map(function (post) {
+ return (
+
+
+
+
+
+
+
+ );
+ }, this)}
+
+
+ Load More
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = JudgePostList;
diff --git a/app/scripts/components/layout.jsx b/app/scripts/components/layout.jsx
new file mode 100644
index 0000000..cbdf300
--- /dev/null
+++ b/app/scripts/components/layout.jsx
@@ -0,0 +1,124 @@
+import $ from 'jquery';
+import React from 'react';
+import Reflux from 'reflux';
+import {RouteHandler, Link} from 'react-router';
+import CDN from '../widgets/cdn';
+
+export default class Layout extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ admin: { user : { nickname: '', avatar: ''} }
+ };
+ }
+ componentDidMount() {
+ // Init admin data
+ $.get('/api/admin/current', function(data){
+ if (this.isMounted()) {
+ this.setState({ admin: data });
+ }
+ }.bind(this));
+ }
+ render() {
+ return (
+
+
+
+
+
+
+
+
![User Image]({CDN.show(this.state.admin.user.avatar)})
+
+
+
{this.state.admin.user.nickname}
+
Online
+
+
+
+ - 菜单
+ - 概况
+ -
+
+
+ 推荐
+
+
+
+
+ - 图片
+ - 文章
+ - 用户
+ - 玩具
+ - 标签
+ - 贴纸
+
+
+
+
+
+ {this.props.children}
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/app/scripts/components/postlist.jsx b/app/scripts/components/postlist.jsx
new file mode 100644
index 0000000..7218957
--- /dev/null
+++ b/app/scripts/components/postlist.jsx
@@ -0,0 +1,142 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Link = require('react-router').Link;
+var Modal = require('react-bootstrap').Modal;
+var Form = require('react-bootstrap').Form;
+var FormGroup = require('react-bootstrap').FormGroup;
+var InputGroup = require('react-bootstrap').InputGroup;
+var FormControl = require('react-bootstrap').FormControl;
+var Button = require('react-bootstrap').Button;
+var PostStore = require('../stores/poststore');
+var TagClassStore = require('../stores/tagclassstore');
+var PostActions = require('../actions/postactions');
+var PostPanel = require('./postpanel');
+
+var PostList = React.createClass({
+ mixins: [Reflux.connect(PostStore, 'postlist'), Reflux.connect(TagClassStore, 'classifications')],
+ getInitialState: function() {
+ return {
+ filter: '',
+ query: '',
+ showModal: false,
+ showImage: '',
+ selectedPost: null
+ };
+ },
+ onChangeQuery: function(e) {
+ this.setState({query: e.target.value});
+ },
+ onChangeFilter: function(e) {
+ this.setState({filter: e.target.value});
+ },
+ closeImage: function() {
+ this.setState({ showModal: false });
+ },
+ openImage: function(img) {
+ this.setState({ showModal: true, showImage: img });
+ },
+ openClass: function(post) {
+ this.setState({ selectedPost: post });
+ },
+ closeClass: function() {
+ this.setState({ selectedPost: null });
+ },
+ setPostClassification: function(pid, cid) {
+ PostActions.setPostClassification(pid, cid);
+ return false;
+ },
+ removePostClassification: function(pid, cid) {
+ PostActions.removePostClassification(pid, cid);
+ return false;
+ },
+ fetchMorePosts: function() {
+ PostActions.fetchPostList(this.state.filter, this.state.query.trim());
+ },
+ search: function(e) {
+ PostActions.fetchPostList(this.state.filter, this.state.query.trim());
+ e.preventDefault();
+ },
+ render: function() {
+ var modal = ();
+ if (this.state.selectedPost !== null) {
+ var cls = _.filter(this.state.classifications, function(c){
+ return this.state.selectedPost.cls.indexOf(c.id) === -1;
+ }.bind(this));
+ modal = (
+
+
+
+ 已选类别
+
+ {this.state.selectedPost.cls.map(function(c){
+ return ({_.isEmpty(this.state.classifications) ? c : this.state.classifications[c].name});
+ }, this)}
+
+ 全部类别
+
+ {_.map(cls, function (c, key) {
+ return ({c.name});
+ }.bind(this))}
+
+
+
+
+ );
+ }
+ if (this.state.postlist) {
+ return (
+
+
+
+
+
+ {this.state.postlist.map(function (post) {
+ return (
+
+ );
+ }, this)}
+
+
+ Load More
+
+
+
+
+
+
+
+
+ {modal}
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = PostList;
diff --git a/app/scripts/components/postpanel.jsx b/app/scripts/components/postpanel.jsx
new file mode 100644
index 0000000..18b9070
--- /dev/null
+++ b/app/scripts/components/postpanel.jsx
@@ -0,0 +1,146 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Link = require('react-router').Link;
+var Cookie = require('react-cookie');
+var Col = require('react-bootstrap').Col;
+var Tooltip = require('react-bootstrap').Tooltip;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
+var PostActions = require('../actions/postactions');
+var TagClassStore = require('../stores/tagclassstore');
+var Moment = require('moment');
+var _ = require('lodash');
+var If = require('../widgets/if');
+
+var PostPanel = React.createClass({
+ mixins: [Reflux.connect(TagClassStore, 'classifications')],
+ propTypes: {
+ post: React.PropTypes.object,
+ openImage: React.PropTypes.func,
+ openClass: React.PropTypes.func,
+ showUser: React.PropTypes.bool
+ },
+ getDefaultProps: function() {
+ return { showUser: true };
+ },
+ deletePost: function() {
+ if (confirm('删除这个post?')) {
+ PostActions.deletePost(this.props.post.id);
+ }
+ },
+ toggleRecommendPost: function() {
+ PostActions.toggleRecommendPost(this.props.post.id);
+ },
+ toggleBlockPost: function() {
+ PostActions.toggleBlockPost(this.props.post.id);
+ },
+ toggleR18Post: function() {
+ PostActions.toggleR18Post(this.props.post.id);
+ },
+ addSku: function() {
+ var id = prompt('输入玩具ID');
+ if (id) {
+ PostActions.addSku(this.props.post.id, id);
+ }
+ },
+ addTag: function() {
+ var text = prompt('输入标签');
+ if (text) {
+ PostActions.addTag(this.props.post.id, text);
+ }
+ },
+ removeTag: function(id) {
+ if (confirm('删除这个标签?')) {
+ PostActions.removeTag(this.props.post.id, id);
+ }
+ },
+ nothing: function() {
+ console.log('nothing happened');
+ },
+ render: function() {
+ var recommendClass = 'btn btn-sm';
+ if (this.props.post.isRecommended === true) {
+ recommendClass = 'btn bg-orange btn-sm';
+ }
+ var invisibleClass = 'btn btn-sm';
+ if (this.props.post.isBlocked === true) {
+ invisibleClass = 'btn bg-orange btn-sm';
+ }
+
+ var r18Class = 'btn btn-sm';
+ if (this.props.post.isR18 === true) {
+ r18Class = 'btn bg-orange btn-sm';
+ }
+
+ var skuDiv = "";
+ if (this.props.post.sku !== 'undefined' && this.props.post.sku !== null) {
+ skuDiv = ({this.props.post.sku.name.substring(0, 25)+'...'});
+ }
+
+ var contentDiv = '';
+ if (this.props.post.video !== null) {
+ contentDiv = (
+ );
+ }
+ if (this.props.post.video === null) {
+ contentDiv = (
+
+
![Photo]({this.props.post.photos[0].url})
+
+
+ {this.props.post.photos.slice(1, this.props.post.photos.length).map(function (photo, i) {
+ return (
);
+ }, this)}
+
+
);
+ }
+
+ return (
+
+
+
+
+
+
![User Image]({)
+
+
{ this.props.post.user.nickname }
+
{ Moment.unix(this.props.post.created / 1000).fromNow() }
+
+
+ {contentDiv}
+
+
{this.props.post.caption}
+
+
+ {this.props.post.tags.map(function (t) {
+ return ({t.text}{" "});
+ },this)}
+ {skuDiv}
+
+
+ {this.props.post.cls.map(function(c){
+ return ({_.isEmpty(this.state.classifications) ? c : this.state.classifications[c].name});
+ }, this)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+});
+
+ module.exports = PostPanel;
diff --git a/app/scripts/components/recommendhome.jsx b/app/scripts/components/recommendhome.jsx
new file mode 100644
index 0000000..1105869
--- /dev/null
+++ b/app/scripts/components/recommendhome.jsx
@@ -0,0 +1,64 @@
+/*global confirm */
+
+import React from 'react';
+import Reflux from 'reflux';
+import {Row, Col} from 'react-bootstrap';
+import HomeAdStore from '../stores/recommendhomestore';
+import HomeAdActions from '../actions/recommendhomeactions';
+import CDN from '../widgets/cdn';
+
+var HomeAds = React.createClass({
+ mixins: [Reflux.connect(HomeAdStore, 'homeads')],
+ fetchHomeAds: function() {
+ HomeAdActions.fetchHomeAdList();
+ },
+ deleteHomeAd: function(id) {
+ if (confirm('删除这个推荐?')) {
+ HomeAdActions.deleteHomeAd(id);
+ }
+ },
+ render: function() {
+ if (this.state.homeads) {
+ return (
+
+
+ {this.state.homeads.map(function (ad) {
+ return (
+
+
+
+
+
+ -
+
+
![Ad Image]({ad.image?CDN.show(ad.image):''})
+
+
+
+
+
+
+
+ );
+ }.bind(this))}
+
+
+ );
+ } else {
+ return ();
+ }
+ }
+});
+
+module.exports = HomeAds;
diff --git a/app/scripts/components/report.jsx b/app/scripts/components/report.jsx
new file mode 100644
index 0000000..6c77e16
--- /dev/null
+++ b/app/scripts/components/report.jsx
@@ -0,0 +1,118 @@
+var React = require('react');
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Modal = require('react-bootstrap').Modal;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var ReportStore = require('../stores/reportstore');
+var ReportActions = require('../actions/reportactions');
+var Link = require('react-router').Link;
+var Moment = require('moment');
+
+var Reports = React.createClass({
+ mixins: [Reflux.connect(ReportStore, 'reportlist')],
+ getInitialState: function() {
+ return { showModal: false, showPost: null };
+ },
+ fetchMoreReports: function() {
+ ReportActions.fetchReportList();
+ },
+ deleteReport: function(id) {
+ if (confirm('Delete this report?')) {
+ ReportActions.deleteReport(id);
+ }
+ },
+ deletePost: function(id) {
+ if (confirm('Delete this post?')) {
+ ReportActions.deletePost(id);
+ }
+ },
+ hidePost: function(id) {
+ if (confirm('Hide this post?')) {
+ ReportActions.hidePost(id);
+ }
+ },
+ close: function() {
+ this.setState({ showModal: false });
+ },
+ open: function(post) {
+ console.log(post);
+ this.setState({ showModal: true, showPost: post });
+ },
+ render: function() {
+ var modalBody = ();
+ if (this.state.showPost) {
+ var marks = this.state.showPost.marks.map(function (mark) {
+ return (
+ {mark.tag}
+ );
+ });
+ modalBody = (
+
+
+
+
![user image]({this.state.showPost.user.avatar})
+
+ {this.state.showPost.user.nickname}
+
+
{this.state.showPost.user.likes}
+
+
+
![]({this.state.showPost.preview}/)
+
+
+ {marks}
+
+
+
+ );
+ }
+ if (this.state.reportlist) {
+ return (
+
+
+
+ #ID | Reporter | Post | Reason | Created | Action |
+
+ {this.state.reportlist.map(function (report) {
+ var hideBtnClass = 'btn btn-default btn-flat';
+ if (report.post.hidden === true) {
+ hideBtnClass = 'btn btn-warning btn-flat';
+ }
+ return (
+
+ {report.id} |
+ ![]({report.user.avatar}) |
+ ![]({report.post.thumbnail}) |
+ {report.reason} |
+ {Moment.unix(report.created).fromNow()} |
+
+
+
+
+
+
+ |
+
+ );
+ }.bind(this))}
+
+
+
+
+
+ Load More
+
+
+
+ {modalBody}
+
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = Reports;
diff --git a/app/scripts/components/skulist.jsx b/app/scripts/components/skulist.jsx
new file mode 100644
index 0000000..764b772
--- /dev/null
+++ b/app/scripts/components/skulist.jsx
@@ -0,0 +1,152 @@
+var React = require('react');
+var Reflux = require('reflux');
+var _ = require('lodash');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Modal = require('react-bootstrap').Modal;
+var Form = require('react-bootstrap').Form;
+var FormGroup = require('react-bootstrap').FormGroup;
+var InputGroup = require('react-bootstrap').InputGroup;
+var FormControl = require('react-bootstrap').FormControl;
+var Button = require('react-bootstrap').Button;
+var DropdownButton = require('react-bootstrap').DropdownButton;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var Link = require('react-router').Link;
+var SkuStore = require('../stores/skustore');
+var SkuActions = require('../actions/skuactions');
+
+var SkuList = React.createClass({
+ mixins: [Reflux.connect(SkuStore, 'skulist')],
+ getInitialState: function() {
+ return { filter: '', query: '', sort: 'created' };
+ },
+ onChangeQuery: function(e) {
+ this.setState({query: e.target.value});
+ },
+ onChangeFilter: function(e) {
+ this.setState({filter: e.target.value});
+ },
+ onChangeSort: function(e) {
+ this.setState({sort: e.target.value});
+ },
+ addSku: function() {
+ if (confirm('创建一个新的玩具?')) {
+ SkuActions.addSku();
+ }
+ },
+ recommend: function(id) {
+ if (confirm('推荐这个玩具?')) {
+ SkuActions.recommendToy(id);
+ }
+ },
+ fetchMoreSkus: function() {
+ SkuActions.fetchSkus(this.state.filter, this.state.query.trim(), this.state.sort);
+ },
+ search: function(e) {
+ console.log('search query: ' + this.state.query);
+ SkuActions.fetchSkus(this.state.filter, this.state.query, this.state.sort);
+ e.preventDefault();
+ },
+ deleteSku: function(id) {
+ if (confirm('Delete this SKU?')) {
+ SkuActions.deleteSku(id);
+ }
+ return false;
+ },
+ toggleRecommend: function(id) {
+ SkuActions.toggleRecommend(id);
+ },
+ toggleR18: function(id) {
+ SkuActions.toggleR18(id);
+ },
+ render: function() {
+ return (
+
+
+
+
+
+ {this.state.skulist.map(function (sku) {
+ var recommendClass = 'btn btn-sm';
+ if (sku.isRec === true) {
+ recommendClass = 'btn bg-orange btn-sm';
+ }
+ var r18Class = 'btn btn-sm';
+ if (sku.isR18 === true) {
+ r18Class = 'btn bg-orange btn-sm';
+ }
+ return (
+
+
+
+
+ -
+
+
+
![{sku.name}]({sku.cover})
+
+
+
{sku.name}
+
+ {sku.company}
+ {sku.release}
+ {sku.money}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }, this)}
+
+
+ Load More
+
+
+ );
+
+ }
+});
+
+module.exports = SkuList;
diff --git a/app/scripts/components/stickerlist.jsx b/app/scripts/components/stickerlist.jsx
new file mode 100644
index 0000000..9b73599
--- /dev/null
+++ b/app/scripts/components/stickerlist.jsx
@@ -0,0 +1,94 @@
+var React = require('react');
+var Link = require('react-router').Link;
+var Reflux = require('reflux');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Button = require('react-bootstrap').Button;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var StickerStore = require('../stores/stickerstore');
+var StickerActions = require('../actions/stickeractions');
+
+var StickerSet = React.createClass({
+ mixins: [Reflux.connect(StickerStore, 'sets')],
+ getInitialState: function() {
+ return {
+ showStickerForm: false
+ };
+ },
+ addStickerSet: function() {
+ if (confirm('创建一个新的贴纸集?')) {
+ StickerActions.addStickerSet();
+ }
+ },
+ deleteSticker: function(setId, stickerId) {
+ if (confirm('确定要删除这个贴纸?')) {
+ StickerActions.deleteSticker(setId, stickerId);
+ }
+ },
+ riseSticker: function(setId, stickerId) {
+ StickerActions.riseSticker(setId, stickerId);
+ },
+ riseStickerSet: function(setId) {
+ StickerActions.riseStickerSet(setId);
+ },
+ toggleStickerForm: function(sticker) {
+ var value = this.state.showStickerForm;
+ this.setState({ showStickerForm: !value});
+ },
+ render: function() {
+ if (this.state.sets) {
+ return (
+
+
+
+
+
+
+
+ {this.state.sets.map(function(set) {
+ // var toggleSetForm = this.toggleSetForm;
+ return (
+
+
+
+
+
{' '}{set.name}
+
+
+
+
+
+
+
+ {set.stickers.map(function(sticker) {
+ return (
+
+
+
+
![]({sticker.image})
+
+
+
+
+
+
+
+
+
+ );
+ }, this)}
+
+
+
+
+ );
+ }, this)}
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = StickerSet;
diff --git a/app/scripts/components/taglist.jsx b/app/scripts/components/taglist.jsx
new file mode 100644
index 0000000..f4ee2c8
--- /dev/null
+++ b/app/scripts/components/taglist.jsx
@@ -0,0 +1,160 @@
+var React = require('react');
+var Reflux = require('reflux');
+var _ = require('lodash');
+var Col = require('react-bootstrap').Col;
+var Row = require('react-bootstrap').Row;
+var Modal = require('react-bootstrap').Modal;
+var Link = require('react-router').Link;
+var Form = require('react-bootstrap').Form;
+var FormGroup = require('react-bootstrap').FormGroup;
+var InputGroup = require('react-bootstrap').InputGroup;
+var FormControl = require('react-bootstrap').FormControl;
+var Button = require('react-bootstrap').Button;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var TagStore = require('../stores/tagstore');
+var TagClassStore = require('../stores/tagclassstore');
+var TagActions = require('../actions/tagactions');
+import CDN from '../widgets/cdn';
+var If = require('../widgets/if');
+
+var TagList = React.createClass({
+ mixins: [Reflux.connect(TagStore, 'taglist'), Reflux.connect(TagClassStore, 'classifications')],
+ getInitialState: function() {
+ return {
+ query: '',
+ selectedTag: null
+ };
+ },
+ onChangeQuery: function(e) {
+ this.setState({query: e.target.value});
+ },
+ fetchMoreTags: function() {
+ TagActions.fetchTags(this.state.query);
+ },
+ search: function(e) {
+ console.log('click query: ' + this.state.query);
+ TagActions.fetchTags(this.state.query);
+ e.preventDefault();
+ },
+ openTag: function(tag) {
+ this.setState({ selectedTag: tag });
+ },
+ closeTag: function() {
+ this.setState({ selectedTag: null });
+ },
+ setTagClassification: function(tid, cid) {
+ TagActions.setTagClassification(tid, cid);
+ return false;
+ },
+ removeTagClassification: function(tid, cid) {
+ TagActions.removeTagClassification(tid, cid);
+ return false;
+ },
+ deleteTag: function(id) {
+ if (confirm('删除这个标签?')) {
+ TagActions.deleteTag(id);
+ }
+ },
+ recommend: function(id) {
+ if (confirm('推荐这个标签?')) {
+ TagActions.recommendTag(id);
+ }
+ },
+ render: function() {
+ var modal = ();
+ if (!_.isEmpty(this.state.classifications) && this.state.selectedTag !== null) {
+ var cls = _.filter(this.state.classifications, function(c){
+ return this.state.selectedTag.cls.indexOf(c.id) === -1;
+ }.bind(this));
+ modal = (
+
+
+
+ 已选类别
+
+ {this.state.selectedTag.cls.map(function(c){
+ return ({_.isEmpty(this.state.classifications) ? c : this.state.classifications[c].name});
+ }, this)}
+
+ 全部类别
+
+ {_.map(cls, function (c, key) {
+ return ({c.name});
+ }.bind(this))}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {this.state.taglist.map(function(tag) {
+ return (
+
+
+
+
{tag.text}
+ {tag.counts.posts + ' 照片 ' + tag.counts.follows + ' 关注'}
+
+
+
![]({tag.image?CDN.show(tag.image):''})
+
+ {tag.cls.map(function(c){
+ return ({_.isEmpty(this.state.classifications) ? c : this.state.classifications[c].name});
+ }, this)}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }.bind(this))}
+
+
+ Load More
+
+ {modal}
+
+ );
+
+ }
+});
+
+module.exports = TagList;
diff --git a/app/scripts/components/test.jsx b/app/scripts/components/test.jsx
new file mode 100644
index 0000000..ae9c1e5
--- /dev/null
+++ b/app/scripts/components/test.jsx
@@ -0,0 +1,18 @@
+var Reflux = require('reflux');
+var React = require('react');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+
+var Test = React.createClass({
+ render: function() {
+ console.log('run test');
+ var heights = [100, 50, 200, 300, 500, 90, 1000, 300, 200, 50];
+ return (
+
+
+
+ );
+ }
+});
+
+module.exports = Test;
diff --git a/app/scripts/components/userdetail.jsx b/app/scripts/components/userdetail.jsx
new file mode 100644
index 0000000..7faccc9
--- /dev/null
+++ b/app/scripts/components/userdetail.jsx
@@ -0,0 +1,184 @@
+var React = require('react');
+var Reflux = require('reflux');
+var LinkedStateMixin = require('react-addons-linked-state-mixin');
+var Row = require('react-bootstrap').Row;
+var Col = require('react-bootstrap').Col;
+var Input = require('react-bootstrap').Input;
+var Modal = require('react-bootstrap').Modal;
+var Tab = require('react-bootstrap').Tab;
+var Tabs = require('react-bootstrap').Tabs;
+var ButtonToolbar = require('react-bootstrap').ButtonToolbar;
+var UserDetailStore = require('../stores/userdetailstore');
+var UserDetailActions = require('../actions/userdetailactions');
+var PostPanel = require('./postpanel');
+
+var UserDetail = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.func
+ },
+ mixins: [Reflux.connect(UserDetailStore, 'userDetail'), LinkedStateMixin],
+ getInitialState: function() {
+ return { filter: '', showModal: false, showImage: '', user: null };
+ },
+ componentDidMount: function() {
+ $.get('/api/user/'+this.props.params.id, function(data) {
+ if (this.isMounted()) {
+ UserDetailActions.updateUserId(this.props.params.id);
+ this.setState({ user : data });
+ }
+ }.bind(this));
+ },
+ close: function() {
+ this.setState({ showModal: false });
+ },
+ openImage: function(img) {
+ console.log('show image: ' + img);
+ var url = img.split('?')[0];
+ this.setState({ showModal: true, showImage: url });
+ },
+ openClass: function(post) {
+ },
+ refreshUserCount: function() {
+ UserDetailActions.refreshUserCount();
+ },
+ changeNickname: function(event) {
+ var user = this.state.user;
+ user.nickname = event.target.value;
+ UserDetailActions.changeForm(user);
+ },
+ changeEmail: function(event) {
+ var user = this.state.user;
+ user.email = event.target.value;
+ UserDetailActions.changeForm(user);
+ },
+ changeMobile: function(event) {
+ var user = this.state.user;
+ user.mobile = event.target.value;
+ UserDetailActions.changeForm(user);
+ },
+ updateUserInfo: function() {
+ var data = {};
+ if (this.state.user.nickname.trim() !== '') {
+ data.nickname = this.state.user.nickname.trim();
+ }
+ if (this.state.user.email.trim() !== '') {
+ data.email = this.state.user.email.trim();
+ }
+ if (this.state.user.mobile.trim() !== '') {
+ data.mobile = this.state.user.mobile.trim();
+ }
+ console.log(data);
+ UserDetailActions.updateUserInfo(data);
+ },
+ setActive: function() {
+ if (confirm('Are you sure to delete this user??? The user will never be recovered again!')) {
+ UserDetailActions.setActive(this.props.params.id, !this.state.user.isActive);
+ }
+ },
+ fetchMorePosts: function() {
+ UserDetailActions.fetchUserPosts();
+ },
+ render: function() {
+ if (this.state.user) {
+ return (
+
+
+
+
+
+
{this.state.user.nickname}
+ {this.state.user.bio}
+
+
+
![User Avatar]({this.state.user.avatar})
+
+
+
+
+
+
{this.state.user.counts.posts}
+ POSTS
+
+
+
+
+
{this.state.user.counts.followers}
+ FOLLOWERS
+
+
+
+
+
{this.state.user.counts.following}
+ FOLLOWING
+
+
+
+
+
+
+
+
+
+
+
+
+ {this.state.userDetail.postlist.map(function (post) {
+ return (
+
+ );
+ }, this)}
+
+
+ Load More
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = UserDetail;
diff --git a/app/scripts/components/userlist.jsx b/app/scripts/components/userlist.jsx
new file mode 100644
index 0000000..6a7d7c8
--- /dev/null
+++ b/app/scripts/components/userlist.jsx
@@ -0,0 +1,93 @@
+var React = require('react');
+var Reflux = require('reflux');
+var LinkedStateMixin = require('react-addons-linked-state-mixin');
+var Row = require('react-bootstrap').Row;
+var Link = require('react-router').Link;
+var Form = require('react-bootstrap').Form;
+var FormGroup = require('react-bootstrap').FormGroup;
+var InputGroup = require('react-bootstrap').InputGroup;
+var FormControl = require('react-bootstrap').FormControl;
+var Button = require('react-bootstrap').Button;
+var UserStore = require('../stores/userliststore');
+var UserActions = require('../actions/useractions');
+var Moment = require('moment');
+
+var UserList = React.createClass({
+ mixins: [Reflux.connect(UserStore, 'userlist'), LinkedStateMixin],
+ getInitialState: function() {
+ return { query: '' };
+ },
+ onChangeQuery: function(e) {
+ this.setState({query: e.target.value});
+ },
+ fetchMoreUsers: function() {
+ UserActions.fetchUserList(this.state.query);
+ },
+ search: function(e) {
+ UserActions.fetchUserList(this.state.query);
+ e.preventDefault();
+ },
+ recommend: function(id) {
+ if (confirm('推荐这个用户?')) {
+ UserActions.recommendUser(id);
+ }
+ },
+ renderAccounts: function(accounts) {
+ return (
+ {accounts.map(function (acc) {
+ if (acc.providerID === "weibo") {
+ return ();
+ } else if (acc.providerID === "mobile") {
+ return ();
+ }})
+ }
+ );
+ },
+ render: function() {
+ if (this.state.userlist) {
+ return (
+
+
+
+
+
+
+ | 用户名 | 照片数 | 绑定账号 | 最近登陆 | |
+
+ {this.state.userlist.map(function(user) {
+ return (
+
+ ![]({user.avatar}) |
+ {user.nickname} |
+ {user.counts.posts} |
+ {this.renderAccounts(user.accounts)} |
+ {Moment.unix(user.lastSeen / 1000).fromNow()} |
+ |
+
+ );
+ },this)}
+
+
+
+
+
+ Load More
+
+
+ );
+ } else {
+ return (
);
+ }
+ }
+});
+
+module.exports = UserList;
diff --git a/app/scripts/reducers/article.js b/app/scripts/reducers/article.js
new file mode 100644
index 0000000..8998701
--- /dev/null
+++ b/app/scripts/reducers/article.js
@@ -0,0 +1,14 @@
+
+
+// function getArticles(state = {
+// articles: []
+// }, action) {
+// switch (action.type) {
+// case expression:
+//
+// break;
+// default:
+//
+// }
+// return state
+// }
diff --git a/app/scripts/stores/activeusersstore.js b/app/scripts/stores/activeusersstore.js
new file mode 100644
index 0000000..7a55ab0
--- /dev/null
+++ b/app/scripts/stores/activeusersstore.js
@@ -0,0 +1,32 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var ActiveUsersAction = require('../actions/activeuseractions');
+
+var ActiveUsersStore = Reflux.createStore({
+ listenables: [ActiveUsersAction],
+
+ init: function() {
+ this.data = { userlist: [], total: 0 };
+ },
+ getInitialState: function() {
+ if (this.data.userlist.length === 0) {
+ this.onFetchActiveUsers();
+ }
+ return this.data;
+ },
+ onFetchActiveUsers: function() {
+ $.ajax({
+ url: '/api/users/active',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch active users complete');
+ this.data.userlist = data.users;
+ this.data.total = data.total;
+ this.trigger(this.data);
+ }
+ });
+ }
+});
+
+module.exports = ActiveUsersStore;
diff --git a/app/scripts/stores/articlestore.js b/app/scripts/stores/articlestore.js
new file mode 100644
index 0000000..d777197
--- /dev/null
+++ b/app/scripts/stores/articlestore.js
@@ -0,0 +1,75 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var ArticleActions = require('../actions/articleactions');
+
+var ArticleListStore = Reflux.createStore({
+ listenables: [ArticleActions],
+
+ init: function() {
+ this.articles = [];
+ this.filter = '';
+ this.timestamp = '';
+ },
+ getInitialState: function() {
+ if (this.articles.length === 0) {
+ this.onFetchArticleList(this.filter);
+ }
+ return this.articles;
+ },
+ onFetchArticleList: function(filter) {
+ if (filter !== null && filter !== this.filter) {
+ this.articles = [];
+ this.filter = filter.trim();
+ this.timestamp = '';
+ }
+ var sourceUrl = '/api/articles'
+
+ var param = [];
+ if (this.timestamp !== '' && this.timestamp !== null) {
+ param.push('ts=' + this.timestamp);
+ }
+ if (this.filter !== '') {
+ param.push('filter=' + this.filter);
+ }
+
+ var paramStr = param.join('&');
+ if (paramStr !== '') {
+ sourceUrl = sourceUrl + '?' + paramStr;
+ }
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch articles complete');
+ if (data.articles.length === 0 && this.timestamp !== '') {
+ alert('no more');
+ } else {
+ this.articles = this.articles.concat(data.articles);
+ this.timestamp = data.nextTs;
+ this.trigger(this.articles);
+ }
+ }
+ });
+ },
+ onToggleArticlePublish: function(id) {
+ var foundArticle = _.find(this.articles, function(article) {
+ return article.id === id;
+ });
+ if (foundArticle) {
+ $.ajax({
+ url: '/api/article/'+id+'/publish',
+ type: 'POST',
+ data: JSON.stringify({ recommend: !foundArticle.isRecommended }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ foundArticle.isPub = !foundArticle.isPub;
+ this.trigger(this.articles);
+ }.bind(this)
+ });
+ }
+ }
+});
+
+module.exports = ArticleListStore;
diff --git a/app/scripts/stores/banuserstore.js b/app/scripts/stores/banuserstore.js
new file mode 100644
index 0000000..bef58e4
--- /dev/null
+++ b/app/scripts/stores/banuserstore.js
@@ -0,0 +1,56 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var BanUserActions = require('../actions/banuseractions');
+
+var BanUserStore = Reflux.createStore({
+ listenables: [BanUserActions],
+
+ init: function() {
+ this.userlist = [];
+ },
+ getInitialState: function() {
+ if (this.userlist.length === 0) {
+ this.onFetchBannedUsers();
+ }
+ return this.userlist;
+ },
+ onFetchBannedUsers: function() {
+ $.ajax({
+ url: '/api/users/banned',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch banbed users complete');
+ this.userlist = data.users;
+ this.trigger(this.userlist);
+ }
+ });
+ },
+ onBanUser: function(id) {
+ $.ajax({
+ url: '/api/user/'+id+'/ban',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ this.userlist.push(data);
+ this.trigger(this.userlist);
+ }
+ });
+ },
+ onRemoveBanUser: function(id) {
+ $.ajax({
+ url: '/api/user/'+id+'/ban',
+ type: 'DELETE',
+ context: this,
+ success: function(data) {
+ this.userlist = _.filter(this.userlist, function(user){
+ return user.id !== id;
+ });
+ this.trigger(this.userlist);
+ }
+ });
+ }
+});
+
+module.exports = BanUserStore;
diff --git a/app/scripts/stores/explorebannerstore.js b/app/scripts/stores/explorebannerstore.js
new file mode 100644
index 0000000..a613fb0
--- /dev/null
+++ b/app/scripts/stores/explorebannerstore.js
@@ -0,0 +1,62 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var BannerActions = require('../actions/explorebanneractions');
+
+var BannerStore = Reflux.createStore({
+ listenables: [BannerActions],
+
+ init: function() {
+ this.bannerlist = [];
+ },
+ getInitialState: function() {
+ if (this.bannerlist.length === 0) {
+ this.onFetchBannerList();
+ }
+ return this.bannerlist;
+ },
+ onFetchBannerList: function() {
+ var sourceUrl = '/api/banners';
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch complete');
+ this.bannerlist = data.banners;
+ this.trigger(this.bannerlist);
+ }
+ });
+ },
+ onAddBanner: function() {
+ $.ajax({
+ url: '/api/recommend?place=banner',
+ dataType: 'json',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ console.log(data);
+ this.bannerlist.unshift(data);
+ this.trigger(this.bannerlist);
+ }
+ });
+ },
+ onDeleteBanner: function(id) {
+ $.ajax({
+ url: '/api/recommend/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete Banner ' + id);
+ this.updateList(_.filter(this.bannerlist, function(banner){
+ return banner.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ updateList: function(list){
+ this.bannerlist = list;
+ this.trigger(this.bannerlist);
+ }
+});
+
+module.exports = BannerStore;
diff --git a/app/scripts/stores/explorethemestore.js b/app/scripts/stores/explorethemestore.js
new file mode 100644
index 0000000..de063ea
--- /dev/null
+++ b/app/scripts/stores/explorethemestore.js
@@ -0,0 +1,67 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var ThemeActions = require('../actions/explorethemeactions');
+
+var ThemeStore = Reflux.createStore({
+ listenables: [ThemeActions],
+
+ init: function() {
+ this.themelist = [];
+ this.page = 0;
+ },
+ getInitialState: function() {
+ if (this.themelist.length === 0) {
+ this.onFetchThemeList();
+ }
+ return this.themelist;
+ },
+ onFetchThemeList: function() {
+ var sourceUrl = '/api/themes?page=' + this.page;
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch complete');
+ this.themelist = this.themelist.concat(data.themes);
+ if (data.themes.length === 0 && this.page > 0) {
+ alert('no more');
+ }
+ this.page = this.page + 1;
+ this.trigger(this.themelist);
+ }
+ });
+ },
+ onAddTheme: function() {
+ $.ajax({
+ url: '/api/recommend?place=theme',
+ dataType: 'json',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ console.log(data);
+ this.themelist.unshift(data);
+ this.trigger(this.themelist);
+ }
+ });
+ },
+ onDeleteTheme: function(id) {
+ $.ajax({
+ url: '/api/recommend/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete Theme ' + id);
+ this.updateList(_.filter(this.themelist, function(theme){
+ return theme.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ updateList: function(list){
+ this.themelist = list;
+ this.trigger(this.themelist);
+ }
+});
+
+module.exports = ThemeStore;
diff --git a/app/scripts/stores/feedbackstore.js b/app/scripts/stores/feedbackstore.js
new file mode 100644
index 0000000..8566215
--- /dev/null
+++ b/app/scripts/stores/feedbackstore.js
@@ -0,0 +1,53 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var FeedbackActions = require('../actions/feedbackactions');
+
+var FeedbackStore = Reflux.createStore({
+ listenables: [FeedbackActions],
+
+ init: function() {
+ this.feedbacklist = [];
+ this.page = 0;
+ },
+ getInitialState: function() {
+ if (this.feedbacklist.length === 0) {
+ this.onFetchFeedbackList();
+ }
+ return this.feedbacklist;
+ },
+ onFetchFeedbackList: function() {
+ var sourceUrl = '/api/feedback/' + this.page;
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ this.feedbacklist = this.feedbacklist.concat(data.feedbacks);
+ this.page = this.page + 1;
+ if (data.feedbacks.length === 0 && this.page > 0) {
+ alert('no more');
+ }
+ this.trigger(this.feedbacklist);
+ }
+ });
+ },
+ onDeleteFeedback: function(id) {
+ $.ajax({
+ url: '/api/feedback/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete Feedback ' + id);
+ this.updateList(_.filter(this.feedbacklist, function(feedback){
+ return feedback.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ updateList: function(list){
+ this.feedbacklist = list;
+ this.trigger(this.feedbacklist);
+ }
+});
+
+module.exports = FeedbackStore;
diff --git a/app/scripts/stores/judgepoststore.js b/app/scripts/stores/judgepoststore.js
new file mode 100644
index 0000000..31daaff
--- /dev/null
+++ b/app/scripts/stores/judgepoststore.js
@@ -0,0 +1,56 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var JudgePostActions = require('../actions/judgepostactions');
+
+var JudgePostListStore = Reflux.createStore({
+ listenables: [JudgePostActions],
+
+ init: function() {
+ this.postlist = [];
+ this.start = null;
+ },
+ getInitialState: function() {
+ if (this.start === null) {
+ this.onFetchPostList();
+ }
+ return this.postlist;
+ },
+ onFetchPostList: function() {
+ var sourceUrl = '/api/unjudgedposts';
+ if (this.start !== null) {
+ sourceUrl = sourceUrl + '?start=' + this.start;
+ }
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ if (data.length === 0) {
+ alert('no more');
+ } else {
+ this.postlist = this.postlist.concat(data);
+ this.start = data[data.length -1].id;
+ this.trigger(this.postlist);
+ }
+ }
+ });
+ },
+ onJudge: function(id, judge) {
+ $.ajax({
+ url: '/api/judge/'+id+'/'+judge,
+ type: 'POST',
+ success: function() {
+ this.updateList(_.filter(this.postlist, function(post){
+ return post.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ updateList: function(list){
+ this.postlist = list;
+ this.trigger(this.postlist);
+ }
+});
+
+module.exports = JudgePostListStore;
diff --git a/app/scripts/stores/poststore.js b/app/scripts/stores/poststore.js
new file mode 100644
index 0000000..872a655
--- /dev/null
+++ b/app/scripts/stores/poststore.js
@@ -0,0 +1,212 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var PostActions = require('../actions/postactions');
+
+module.exports = Reflux.createStore({
+ listenables: [PostActions],
+
+ init: function() {
+ this.posts = [];
+ this.filter = '';
+ this.query = '';
+ this.timestamp = '';
+ },
+ getInitialState: function() {
+ if (this.posts.length === 0) {
+ this.onFetchPostList(this.filter, this.query);
+ }
+ return this.posts;
+ },
+ onFetchPostList: function(filter, query) {
+ if (this.query !== query || this.filter !== filter) {
+ this.filter = filter;
+ this.query = query;
+ this.timestamp = '';
+ this.posts = [];
+ }
+ var sourceUrl = '/api/posts';
+
+ var param = [];
+ if (this.timestamp !== '' && this.timestamp !== null) {
+ param.push('ts=' + this.timestamp);
+ }
+ if (this.filter !== '') {
+ param.push('filter=' + this.filter);
+ }
+ if (this.query !== '') {
+ param.push('query=' + this.query);
+ }
+ var paramStr = param.join('&');
+ if (paramStr !== '') {
+ sourceUrl = sourceUrl + '?' + paramStr;
+ }
+
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch posts complete');
+ if (data.posts.length === 0 && this.timestamp !== '') {
+ alert('no more');
+ } else {
+ this.posts = this.posts.concat(data.posts);
+ this.timestamp = data.nextTs;
+ this.trigger(this.posts);
+ }
+ }
+ });
+ },
+ onDeletePost: function(id) {
+ $.ajax({
+ url: '/api/post/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete post ' + id);
+ this.updateList(_.filter(this.posts, function(post){
+ return post.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ onToggleRecommendPost: function(id) {
+ var foundPost = _.find(this.posts, function(post) {
+ return post.id === id;
+ });
+ if (foundPost) {
+ console.log(foundPost);
+ $.ajax({
+ url: '/api/post/'+id+'/recommend',
+ type: 'POST',
+ data: JSON.stringify({ recommend: !foundPost.isRecommended }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle recommend post ' + id);
+ foundPost.isRecommended = !foundPost.isRecommended;
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onToggleBlockPost: function(id) {
+ var foundPost = _.find(this.posts, function(post) {
+ return post.id === id;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+id+'/block',
+ type: 'POST',
+ data: JSON.stringify({ block: !foundPost.isBlocked }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle block post ' + id);
+ foundPost.isBlocked = !foundPost.isBlocked;
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onToggleR18Post: function(id) {
+ var foundPost = _.find(this.posts, function(post) {
+ return post.id === id;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+id+'/r18',
+ type: 'POST',
+ data: JSON.stringify({ r18: !foundPost.isR18 }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle r18 post ' + id);
+ foundPost.isR18 = !foundPost.isR18;
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onSetPostClassification: function(pid, cid) {
+ var foundPost = _.find(this.posts, function(p) {
+ return p.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/class/'+cid,
+ type: 'POST',
+ success: function() {
+ foundPost.cls.push(cid);
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onRemovePostClassification: function(pid, cid) {
+ var foundPost = _.find(this.posts, function(p) {
+ return p.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/class/'+cid,
+ type: 'DELETE',
+ success: function() {
+ foundPost.cls = _.filter(foundPost.cls, function(c) {
+ return c !== cid;
+ });
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onAddSku: function(pid, sid) {
+ var foundPost = _.find(this.posts, function(p) {
+ return p.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/sku/'+sid,
+ type: 'POST',
+ success: function(sku) {
+ foundPost.sku = sku;
+ console.log(sku);
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onAddTag: function(pid, text) {
+ var foundPost = _.find(this.posts, function(p) {
+ return p.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/tag/'+text,
+ type: 'POST',
+ success: function(tag) {
+ foundPost.tags.push(tag);
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ onRemoveTag: function(pid, tid) {
+ var foundPost = _.find(this.posts, function(p) {
+ return p.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/tag/'+tid,
+ type: 'DELETE',
+ success: function() {
+ foundPost.tags = _.filter(foundPost.tags, function(t) {
+ return t.id !== tid;
+ });
+ this.updateList(this.posts);
+ }.bind(this)
+ });
+ }
+ },
+ updateList: function(list){
+ this.posts = list;
+ this.trigger(this.posts);
+ }
+});
diff --git a/app/scripts/stores/recommendhomestore.js b/app/scripts/stores/recommendhomestore.js
new file mode 100644
index 0000000..75f9eb2
--- /dev/null
+++ b/app/scripts/stores/recommendhomestore.js
@@ -0,0 +1,48 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var HomeAdActions = require('../actions/recommendhomeactions');
+
+var HomeAdStore = Reflux.createStore({
+ listenables: [HomeAdActions],
+ init: function() {
+ this.homeads = [];
+ },
+ getInitialState: function() {
+ if (this.homeads.length === 0) {
+ this.onFetchHomeAdList();
+ }
+ return this.homeads;
+ },
+ onFetchHomeAdList: function() {
+ var sourceUrl = '/api/recommend/homeads';
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch complete');
+ this.homeads = data.items;
+ this.trigger(this.homeads);
+ }
+ });
+ },
+ onDeleteHomeAd: function(id) {
+ $.ajax({
+ url: '/api/recommend/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete HomeAd ' + id);
+ this.updateList(_.filter(this.homeads, function(ad){
+ return ad.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ updateList: function(list){
+ this.homeads = list;
+ this.trigger(this.homeads);
+ }
+});
+
+module.exports = HomeAdStore;
diff --git a/app/scripts/stores/reportstore.js b/app/scripts/stores/reportstore.js
new file mode 100644
index 0000000..19b9167
--- /dev/null
+++ b/app/scripts/stores/reportstore.js
@@ -0,0 +1,85 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var ReportActions = require('../actions/reportactions');
+
+var ReportStore = Reflux.createStore({
+ listenables: [ReportActions],
+
+ init: function() {
+ this.reportlist = [];
+ this.page = 0;
+ },
+ getInitialState: function() {
+ if (this.reportlist.length === 0) {
+ this.onFetchReportList();
+ }
+ return this.reportlist;
+ },
+ onFetchReportList: function() {
+ var sourceUrl = '/api/report/' + this.page;
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ this.reportlist = this.reportlist.concat(data.reports);
+ this.page = this.page + 1;
+ if (data.reports.length === 0 && this.page > 0) {
+ alert('no more');
+ }
+ this.trigger(this.reportlist);
+ }
+ });
+ },
+ onDeleteReport: function(id) {
+ $.ajax({
+ url: '/api/report/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete report ' + id);
+ this.updateList(_.filter(this.reportlist, function(report){
+ return report.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ onDeletePost: function(id) {
+ $.ajax({
+ url: '/api/post/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete post ' + id);
+ this.updateList(_.filter(this.reportlist, function(report){
+ return report.post.postId !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ onHidePost: function(id) {
+ var found = _.find(this.reportlist, function(report) {
+ return report.post.postId === id;
+ });
+ if (found) {
+ var type = 'POST';
+ if (found.post.hidden === true) {
+ type = 'DELETE';
+ }
+ $.ajax({
+ url: '/api/post/'+id+'/block',
+ type: type,
+ success: function() {
+ console.log('toggle block post ' + id);
+ found.post.hidden = !found.post.hidden;
+ this.updateList(this.reportlist);
+ }.bind(this)
+ });
+ }
+ },
+ updateList: function(list){
+ this.reportlist = list;
+ this.trigger(this.reportlist);
+ }
+});
+
+module.exports = ReportStore;
diff --git a/app/scripts/stores/skustore.js b/app/scripts/stores/skustore.js
new file mode 100644
index 0000000..12309f1
--- /dev/null
+++ b/app/scripts/stores/skustore.js
@@ -0,0 +1,146 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var SkuActions = require('../actions/skuactions');
+
+var SkuStore = Reflux.createStore({
+ listenables: [SkuActions],
+
+ init: function() {
+ this.skus = [];
+ this.page = 0;
+ this.filter = '';
+ this.query = '';
+ this.sort = 'created';
+ },
+ getInitialState: function() {
+ if (this.skus.length === 0) {
+ this.onFetchSkus(this.filter, this.query, this.sort);
+ }
+ return this.skus;
+ },
+ onAddSku: function() {
+ $.ajax({
+ url: '/api/sku',
+ dataType: 'json',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ console.log(data);
+ this.skus.unshift(data);
+ this.trigger(this.skus);
+ }
+ });
+ },
+ onFetchSkus: function(filter, query, sort) {
+ if (this.query !== query || this.filter !== filter || this.sort !== sort) {
+ this.filter = filter;
+ this.query = query;
+ this.sort = sort;
+ this.page = 0;
+ this.skus = [];
+ }
+ var sourceUrl = '/api/skus';
+
+ var param = [];
+ if (this.page !== 0) {
+ param.push('page=' + this.page);
+ }
+ if (this.filter !== '') {
+ param.push('filter=' + this.filter);
+ }
+ if (this.query !== '') {
+ param.push('query=' + this.query);
+ }
+ if (this.sort !== '') {
+ param.push('sort=' + this.sort);
+ }
+ var paramStr = param.join('&');
+ if (paramStr !== '') {
+ sourceUrl = sourceUrl + '?' + paramStr;
+ }
+
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ if (this.page === 0) {
+ this.skus = data.skus;
+ } else {
+ this.skus = this.skus.concat(data.skus);
+ }
+ if (data.skus.length === 0 && this.page > 0) {
+ alert('no more');
+ } else {
+ this.page = this.page + 1;
+ }
+ this.trigger(this.skus);
+ }
+ });
+ },
+ deleteSku: function(id) {
+ $.ajax({
+ url: '/api/sku/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete sku ' + id);
+ this.updateList(_.filter(this.skus, function(sku) {
+ return sku.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ onToggleR18: function(id) {
+ var foundSku = _.find(this.skus, function(sku) {
+ return sku.id === id;
+ });
+ if (foundSku) {
+ $.ajax({
+ url: '/api/sku/'+id+'/r18',
+ type: 'POST',
+ data: JSON.stringify({ r18: !foundSku.isR18 }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle r18 sku ' + id);
+ foundSku.isR18 = !foundSku.isR18;
+ this.updateList(this.skus);
+ }.bind(this)
+ });
+ }
+ },
+ onToggleRecommend: function(id) {
+ var foundSku = _.find(this.skus, function(sku) {
+ return sku.id === id;
+ });
+ if (foundSku) {
+ $.ajax({
+ url: '/api/sku/'+id+'/recommend',
+ type: 'POST',
+ data: JSON.stringify({ recommend: !foundSku.isRec }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle recommend sku ' + id);
+ foundSku.isRec = !foundSku.isRec;
+ this.updateList(this.skus);
+ }.bind(this)
+ });
+ }
+ },
+ onRecommendToy: function(id) {
+ $.ajax({
+ url: '/api/recommend/home/'+id+'?type=toy',
+ type: 'POST',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ }
+ });
+ },
+ updateList: function(list) {
+ this.skus = list;
+ this.trigger(this.skus);
+ }
+});
+
+module.exports = SkuStore;
diff --git a/app/scripts/stores/statsstore.js b/app/scripts/stores/statsstore.js
new file mode 100644
index 0000000..177c2a4
--- /dev/null
+++ b/app/scripts/stores/statsstore.js
@@ -0,0 +1,31 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var Router = require('react-router');
+var StatsActions = require('../actions/statsactions');
+
+var StatsStore = Reflux.createStore({
+ listenables: [StatsActions],
+
+ init: function() {
+ this.stats = {};
+ },
+ getInitialState: function() {
+ if (!this.stats.posts) {
+ this.updateStats();
+ }
+ return this.stats;
+ },
+ updateStats: function() {
+ $.ajax({
+ url: '/api/stats',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ this.stats = data;
+ this.trigger(this.stats);
+ }
+ });
+ }
+});
+
+module.exports = StatsStore;
diff --git a/app/scripts/stores/stickerstore.js b/app/scripts/stores/stickerstore.js
new file mode 100644
index 0000000..c349c5f
--- /dev/null
+++ b/app/scripts/stores/stickerstore.js
@@ -0,0 +1,97 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var StickerActions = require('../actions/stickeractions');
+
+var StickerStore = Reflux.createStore({
+ listenables: [StickerActions],
+
+ init: function() {
+ this.sets = [];
+ },
+ getInitialState: function() {
+ if (this.sets.length === 0) {
+ this.onFetchStickers();
+ }
+ return this.sets;
+ },
+ onFetchStickers: function(c) {
+ $.ajax({
+ url: '/api/stickers',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ this.sets = this.sets.concat(data.sets);
+ this.trigger(this.sets);
+ }
+ });
+ },
+ onAddStickerSet: function() {
+ $.ajax({
+ url: '/api/sticker/set',
+ dataType: 'json',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ console.log(data);
+ this.sets.unshift(data);
+ this.trigger(this.sets);
+ }
+ });
+ },
+ onDeleteSticker: function(setId, stickerId) {
+ $.ajax({
+ url: '/api/sticker/'+stickerId,
+ type: 'DELETE',
+ context: this,
+ success: function(data) {
+ var set = _.find(this.sets, function(s) {
+ return s.id === setId;
+ });
+ set.stickers = _.filter(set.stickers, function(sticker) {
+ return sticker.id !== stickerId;
+ });
+ this.trigger(this.sets);
+ }
+ });
+ },
+ onRiseSticker: function(setId, stickerId) {
+ $.ajax({
+ url: '/api/sticker/'+stickerId+'/up',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ var set = _.find(this.sets, function(s) {
+ return s.id === setId;
+ });
+ var sticker = _.find(set.stickers, function(s) {
+ return s.id === stickerId;
+ });
+ set.stickers = _.sortBy(set.stickers, function(s) { return -s.score; });
+ sticker.score = sticker.score + 1;
+ this.trigger(this.sets);
+ }
+ });
+ },
+ onRiseStickerSet: function(setId) {
+ $.ajax({
+ url: '/api/stickerset/'+setId+'/up',
+ type: 'POST',
+ context: this,
+ success: function(data) {
+ var set = _.find(this.sets, function(s) {
+ return s.id === setId;
+ });
+ set.score = set.score + 1;
+ this.sets = _.sortBy(this.sets, function(s) { return -s.score; });
+ this.trigger(this.sets);
+ }
+ });
+ },
+ updateList: function(list){
+ this.sets = list;
+ this.trigger(this.sets);
+ }
+});
+
+module.exports = StickerStore;
diff --git a/app/scripts/stores/tagclassstore.js b/app/scripts/stores/tagclassstore.js
new file mode 100644
index 0000000..71cfc6e
--- /dev/null
+++ b/app/scripts/stores/tagclassstore.js
@@ -0,0 +1,33 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var TagClassActions = require('../actions/tagclassactions');
+
+var TagClassStore = Reflux.createStore({
+ listenables: [TagClassActions],
+
+ init: function() {
+ this.classifications = {};
+ },
+ getInitialState: function() {
+ if (_.isEmpty(this.classifications)) {
+ this.onFetchClassifications();
+ }
+ return this.classifications;
+ },
+ onFetchClassifications: function() {
+ $.ajax({
+ url: '/api/classifications',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ _.each(data, function(c){
+ this.classifications[c.id] = c;
+ }.bind(this));
+ this.trigger(this.classifications);
+ }.bind(this)
+ });
+ }
+});
+
+module.exports = TagClassStore;
diff --git a/app/scripts/stores/tagstore.js b/app/scripts/stores/tagstore.js
new file mode 100644
index 0000000..7e9e59e
--- /dev/null
+++ b/app/scripts/stores/tagstore.js
@@ -0,0 +1,117 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var TagActions = require('../actions/tagactions');
+
+var TagStore = Reflux.createStore({
+ listenables: [TagActions],
+
+ init: function() {
+ this.tags = [];
+ this.selectedClassification = null;
+ this.page = 0;
+ this.filter = '';
+ },
+ getInitialState: function() {
+ if (this.tags.length === 0) {
+ this.onFetchTags(this.filter);
+ }
+ return this.tags;
+ },
+ onFetchTags: function(filter) {
+ if (filter !== null && filter !== this.filter) {
+ this.filter = filter.trim();
+ this.page = 0;
+ }
+
+ var sourceUrl = '/api/tags?page=' + this.page;
+ if (this.selectedClassification !== null && this.selectedClassification !== '') {
+ sourceUrl = sourceUrl + '&class=' + this.selectedClassification;
+ }
+ if (this.filter.length > 0) {
+ sourceUrl = sourceUrl + '&filter=' + this.filter;
+ }
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ if (this.page === 0) {
+ this.tags = data.tags;
+ } else {
+ this.tags = this.tags.concat(data.tags);
+ }
+ if (data.tags.length === 0 && this.page > 0) {
+ alert('no more');
+ } else {
+ this.page = this.page + 1;
+ }
+ this.trigger(this.tags);
+ }
+ });
+ },
+ onSetTagClassification: function(tid, cid) {
+ var foundTag = _.find(this.tags, function(t) {
+ return t.id === tid;
+ });
+ if (foundTag) {
+ $.ajax({
+ url: '/api/tag/'+tid+'/class/'+cid,
+ type: 'POST',
+ success: function() {
+ foundTag.cls.push(cid);
+ this.updateList(this.tags);
+ }.bind(this)
+ });
+ }
+ },
+ onRemoveTagClassification: function(tid, cid) {
+ var foundTag = _.find(this.tags, function(tag) {
+ return tag.id === tid;
+ });
+ if (foundTag) {
+ $.ajax({
+ url: '/api/tag/'+tid+'/class/'+cid,
+ type: 'DELETE',
+ success: function() {
+ foundTag.cls = _.filter(foundTag.cls, function(c) {
+ return c !== cid;
+ });
+ this.updateList(this.tags);
+ }.bind(this)
+ });
+ }
+ },
+ onRecommendTag: function(id) {
+ $.ajax({
+ url: '/api/recommend/home/'+id+'?type=tag',
+ type: 'POST',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ }
+ });
+ },
+ onDeleteTag: function(id) {
+ var foundTag = _.find(this.tags, function(tag) {
+ return tag.id === id;
+ });
+ if (foundTag) {
+ $.ajax({
+ url: '/api/tag/'+id,
+ type: 'DELETE',
+ success: function(data) {
+ this.updateList(_.filter(this.tags, function(tag){
+ return tag.id !== id;
+ }));
+ }.bind(this)
+ });
+ }
+ },
+ updateList: function(list){
+ this.tags = list;
+ this.trigger(this.tags);
+ }
+});
+
+module.exports = TagStore;
diff --git a/app/scripts/stores/userdetailstore.js b/app/scripts/stores/userdetailstore.js
new file mode 100644
index 0000000..f52d195
--- /dev/null
+++ b/app/scripts/stores/userdetailstore.js
@@ -0,0 +1,235 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var _ = require('lodash');
+var UserDetailActions = require('../actions/userdetailactions');
+var PostActions = require('../actions/postactions');
+
+var UserDetailStore = Reflux.createStore({
+ listenables: [UserDetailActions, PostActions],
+ userDetail: {
+ userId: 0,
+ timestamp: '',
+ postlist: []
+ },
+ getInitialState: function() {
+ return this.userDetail;
+ },
+ onUpdateUserId: function(userId) {
+ if (this.userDetail.userId !== userId) {
+ this.userDetail.userId = userId;
+ this.userDetail.timestamp = '';
+ this.userDetail.postlist = [];
+ this.onFetchUserPosts();
+ }
+ },
+ onFetchUserPosts: function() {
+ var sourceUrl = '/api/user/' + this.userDetail.userId + '/posts';
+ if (this.userDetail.timestamp !== '') {
+ sourceUrl = sourceUrl + '?ts=' + this.userDetail.timestamp;
+ }
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ if (data.posts.length === 0 && this.userDetail.timestamp !== '') {
+ alert('no more');
+ } else {
+ console.log('fetch user posts complete');
+ this.userDetail.postlist = this.userDetail.postlist.concat(data.posts);
+ this.userDetail.timestamp = data.nextTs;
+ this.trigger(this.userDetail);
+ }
+ }
+ });
+ },
+ onRefreshUserCount: function() {
+ $.ajax({
+ url: '/api/user/'+this.userDetail.userId+'/refresh',
+ type: 'POST',
+ success: function(data) {
+ location.reload();
+ },
+ error: function() {
+ alert('error!');
+ }
+ });
+ },
+ onSetActive: function(id, act) {
+ var data = { active:act };
+ $.ajax({
+ url: '/api/user/'+id+'/active',
+ type: 'POST',
+ data: JSON.stringify(data),
+ contentType: "application/json; charset=utf-8",
+ dataType: "json",
+ success: function(data) {
+ location.reload();
+ },
+ error: function() {
+ alert('error!');
+ }
+ });
+ },
+ onUpdateUserInfo: function(userdata) {
+ $.ajax({
+ url: '/api/user/'+this.userDetail.userId+'/update',
+ type: 'POST',
+ data: JSON.stringify(userdata),
+ contentType:"application/json",
+ success: function(data) {
+ location.reload();
+ },
+ error: function() {
+ alert('error!');
+ }
+ });
+ },
+ onToggleBan: function(id) {
+ console.log('toggle ban:' + id);
+ },
+ onToggleVerify: function(id) {
+ console.log('toggle verify:' + id);
+ },
+ onDeletePost: function(id) {
+ $.ajax({
+ url: '/api/post/'+id,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete post ' + id);
+ this.updateList(_.filter(this.userDetail.postlist, function(post){
+ return post.id !== id;
+ }));
+ }.bind(this)
+ });
+ },
+ onToggleRecommendPost: function(id) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === id;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+id+'/recommend',
+ type: 'POST',
+ data: JSON.stringify({ recommend: !foundPost.isRecommended }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle recommend post ' + id);
+ foundPost.isRecommended = !foundPost.isRecommended;
+ this.updateList(this.userDetail.postlist);
+ }.bind(this)
+ });
+ }
+ },
+ onToggleBlockPost: function(id) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === id;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+id+'/block',
+ type: 'POST',
+ data: JSON.stringify({ block: !foundPost.isBlocked }),
+ contentType: 'application/json; charset=utf-8',
+ success: function() {
+ console.log('toggle block post ' + id);
+ foundPost.isBlocked = !foundPost.isBlocked;
+ this.updateList(this.userDetail.postlist);
+ }.bind(this)
+ });
+ }
+ },
+ onDeleteMark: function(pid, mid) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/mark/'+mid,
+ type: 'DELETE',
+ success: function() {
+ console.log('delete mark ' + mid);
+ foundPost.marks = _.filter(foundPost.marks, function(mark){
+ return mark.markId !== mid;
+ });
+ this.updateList(this.userDetail.postlist);
+ }.bind(this)
+ });
+ }
+ },
+ onAddMark: function(pid, tagName, uid) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/post/'+pid+'/mark/'+tagName+'/'+uid,
+ type: 'POST',
+ success: function(data) {
+ console.log('add mark ' + tagName);
+ foundPost.marks.push(data);
+ this.updateList(this.userDetail.postlist);
+ }.bind(this),
+ error: function(data) {
+ alert(data);
+ }
+ });
+ }
+ },
+ onLike: function(pid, mark, uid) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/mark/'+mark.markId+'/'+uid,
+ type: 'POST',
+ success: function(data) {
+ console.log(mark.markId + ' like by ' + uid);
+ var foundMark = _.find(foundPost.marks, function(m) {
+ return m.markId === mark.markId;
+ });
+ if (foundMark) {
+ foundMark.likedBy.push(uid);
+ }
+ this.updateList(this.userDetail.postlist);
+ }.bind(this),
+ error: function(data) {
+ alert(data);
+ }
+ });
+ }
+ },
+ onUnlike: function(pid, mark, uid) {
+ var foundPost = _.find(this.userDetail.postlist, function(post) {
+ return post.id === pid;
+ });
+ if (foundPost) {
+ $.ajax({
+ url: '/api/mark/'+mark.markId+'/'+uid,
+ type: 'DELETE',
+ success: function(data) {
+ console.log(mark.markId + ' unlike by ' + uid);
+ var foundMark = _.find(foundPost.marks, function(m) {
+ return m.markId === mark.markId;
+ });
+ if (foundMark) {
+ foundMark.likedBy = _.filter(foundMark.likedBy, function(l){
+ return l !== uid;
+ });
+ }
+ this.updateList(this.userDetail.postlist);
+ }.bind(this),
+ error: function(data) {
+ alert(data);
+ }
+ });
+ }
+ },
+ updateList: function(list){
+ this.userDetail.postlist = list;
+ this.trigger(this.userDetail);
+ }
+});
+
+module.exports = UserDetailStore;
diff --git a/app/scripts/stores/userliststore.js b/app/scripts/stores/userliststore.js
new file mode 100644
index 0000000..19b49c8
--- /dev/null
+++ b/app/scripts/stores/userliststore.js
@@ -0,0 +1,56 @@
+var Reflux = require('reflux');
+var $ = require('jquery');
+var UserActions = require('../actions/useractions');
+
+var UserListStore = Reflux.createStore({
+ listenables: [UserActions],
+
+ init: function() {
+ this.userlist = [];
+ this.page = 0;
+ this.filter = '';
+ },
+ getInitialState: function() {
+ if (this.userlist.length === 0) {
+ this.onFetchUserList(this.filter);
+ }
+ return this.userlist;
+ },
+ onFetchUserList: function(filter) {
+ if (filter !== null && filter !== this.filter) {
+ this.userlist = [];
+ this.filter = filter.trim();
+ this.page = 0;
+ }
+ var sourceUrl = '/api/users?page=' + this.page;
+ if (this.filter.length > 0) {
+ sourceUrl = sourceUrl + '&filter=' + this.filter;
+ }
+ $.ajax({
+ url: sourceUrl,
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ console.log('fetch users complete');
+ this.userlist = this.userlist.concat(data.users);
+ this.page = this.page + 1;
+ if (data.users.length === 0) {
+ alert('no more');
+ }
+ this.trigger(this.userlist);
+ }
+ });
+ },
+ onRecommendUser: function(id) {
+ $.ajax({
+ url: '/api/recommend/home/'+id+'?type=user',
+ type: 'POST',
+ dataType: 'json',
+ context: this,
+ success: function(data) {
+ }
+ });
+ }
+});
+
+module.exports = UserListStore;
diff --git a/app/scripts/utils/stateToHTML.js b/app/scripts/utils/stateToHTML.js
new file mode 100644
index 0000000..ebcb7e3
--- /dev/null
+++ b/app/scripts/utils/stateToHTML.js
@@ -0,0 +1,337 @@
+import {Entity} from 'draft-js';
+import {getEntityRanges, BLOCK_TYPE, ENTITY_TYPE, INLINE_STYLE} from 'draft-js-utils';
+
+import type {ContentState, ContentBlock, EntityInstance} from 'draft-js';
+import type {CharacterMetaList} from 'draft-js-utils';
+
+type StringMap = {[key: string]: ?string};
+type AttrMap = {[key: string]: StringMap};
+
+const {
+ BOLD,
+ CODE,
+ ITALIC,
+ STRIKETHROUGH,
+ UNDERLINE,
+} = INLINE_STYLE;
+
+const INDENT = ' ';
+const BREAK = '
';
+
+// Map entity data to element attributes.
+const ENTITY_ATTR_MAP: AttrMap = {
+ [ENTITY_TYPE.LINK]: {url: 'href', rel: 'rel', target: 'target', title: 'title', className: 'class'},
+ [ENTITY_TYPE.IMAGE]: {src: 'src', height: 'height', width: 'width', alt: 'alt', className: 'class'},
+};
+
+// Map entity data to element attributes.
+const DATA_TO_ATTR = {
+ [ENTITY_TYPE.LINK](entityType: string, entity: EntityInstance): StringMap {
+ let attrMap = ENTITY_ATTR_MAP.hasOwnProperty(entityType) ? ENTITY_ATTR_MAP[entityType] : {};
+ let data = entity.getData();
+ let attrs = {};
+ for (let dataKey of Object.keys(data)) {
+ let dataValue = data[dataKey];
+ if (attrMap.hasOwnProperty(dataKey)) {
+ let attrKey = attrMap[dataKey];
+ attrs[attrKey] = dataValue;
+ }
+ }
+ return attrs;
+ },
+ [ENTITY_TYPE.IMAGE](entityType: string, entity: EntityInstance): StringMap {
+ let attrMap = ENTITY_ATTR_MAP.hasOwnProperty(entityType) ? ENTITY_ATTR_MAP[entityType] : {};
+ let data = entity.getData();
+ let attrs = {};
+ for (let dataKey of Object.keys(data)) {
+ let dataValue = data[dataKey];
+ if (attrMap.hasOwnProperty(dataKey)) {
+ let attrKey = attrMap[dataKey];
+ attrs[attrKey] = dataValue;
+ }
+ }
+ return attrs;
+ },
+};
+
+// The reason this returns an array is because a single block might get wrapped
+// in two tags.
+function getTags(blockType: string): Array {
+ switch (blockType) {
+ case BLOCK_TYPE.HEADER_ONE:
+ return ['h1'];
+ case BLOCK_TYPE.HEADER_TWO:
+ return ['h2'];
+ case BLOCK_TYPE.HEADER_THREE:
+ return ['h3'];
+ case BLOCK_TYPE.HEADER_FOUR:
+ return ['h4'];
+ case BLOCK_TYPE.HEADER_FIVE:
+ return ['h5'];
+ case BLOCK_TYPE.HEADER_SIX:
+ return ['h6'];
+ case BLOCK_TYPE.UNORDERED_LIST_ITEM:
+ case BLOCK_TYPE.ORDERED_LIST_ITEM:
+ return ['li'];
+ case BLOCK_TYPE.BLOCKQUOTE:
+ return ['blockquote'];
+ case BLOCK_TYPE.CODE:
+ return ['pre', 'code'];
+ case BLOCK_TYPE.ATOMIC:
+ return ['figure'];
+ default:
+ return ['p'];
+ }
+}
+
+function getWrapperTag(blockType: string): ?string {
+ switch (blockType) {
+ case BLOCK_TYPE.UNORDERED_LIST_ITEM:
+ return 'ul';
+ case BLOCK_TYPE.ORDERED_LIST_ITEM:
+ return 'ol';
+ default:
+ return null;
+ }
+}
+
+class MarkupGenerator {
+ blocks: Array;
+ contentState: ContentState;
+ currentBlock: number;
+ indentLevel: number;
+ output: Array;
+ totalBlocks: number;
+ wrapperTag: ?string;
+
+ constructor(contentState: ContentState) {
+ this.contentState = contentState;
+ }
+
+ generate(): string {
+ this.output = [];
+ this.blocks = this.contentState.getBlocksAsArray();
+ this.totalBlocks = this.blocks.length;
+ this.currentBlock = 0;
+ this.indentLevel = 0;
+ this.wrapperTag = null;
+ while (this.currentBlock < this.totalBlocks) {
+ this.processBlock();
+ }
+ this.closeWrapperTag();
+ return this.output.join('').trim();
+ }
+
+ processBlock() {
+ let block = this.blocks[this.currentBlock];
+ let blockType = block.getType();
+ let newWrapperTag = getWrapperTag(blockType);
+ if (this.wrapperTag !== newWrapperTag) {
+ if (this.wrapperTag) {
+ this.closeWrapperTag();
+ }
+ if (newWrapperTag) {
+ this.openWrapperTag(newWrapperTag);
+ }
+ }
+ this.indent();
+ this.writeStartTag(blockType);
+ this.output.push(this.renderBlockContent(block));
+ // Look ahead and see if we will nest list.
+ let nextBlock = this.getNextBlock();
+ if (
+ canHaveDepth(blockType) &&
+ nextBlock &&
+ nextBlock.getDepth() === block.getDepth() + 1
+ ) {
+ this.output.push(`\n`);
+ // This is a litle hacky: temporarily stash our current wrapperTag and
+ // render child list(s).
+ let thisWrapperTag = this.wrapperTag;
+ this.wrapperTag = null;
+ this.indentLevel += 1;
+ this.currentBlock += 1;
+ this.processBlocksAtDepth(nextBlock.getDepth());
+ this.wrapperTag = thisWrapperTag;
+ this.indentLevel -= 1;
+ this.indent();
+ } else {
+ this.currentBlock += 1;
+ }
+ this.writeEndTag(blockType);
+ }
+
+ processBlocksAtDepth(depth: number) {
+ let block = this.blocks[this.currentBlock];
+ while (block && block.getDepth() === depth) {
+ this.processBlock();
+ block = this.blocks[this.currentBlock];
+ }
+ this.closeWrapperTag();
+ }
+
+ getNextBlock(): ContentBlock {
+ return this.blocks[this.currentBlock + 1];
+ }
+
+ writeStartTag(blockType) {
+ let tags = getTags(blockType);
+ for (let tag of tags) {
+ this.output.push(`<${tag}>`);
+ }
+ }
+
+ writeEndTag(blockType) {
+ let tags = getTags(blockType);
+ if (tags.length === 1) {
+ this.output.push(`${tags[0]}>\n`);
+ } else {
+ let output = [];
+ for (let tag of tags) {
+ output.unshift(`${tag}>`);
+ }
+ this.output.push(output.join('') + '\n');
+ }
+ }
+
+ openWrapperTag(wrapperTag: string) {
+ this.wrapperTag = wrapperTag;
+ this.indent();
+ this.output.push(`<${wrapperTag}>\n`);
+ this.indentLevel += 1;
+ }
+
+ closeWrapperTag() {
+ if (this.wrapperTag) {
+ this.indentLevel -= 1;
+ this.indent();
+ this.output.push(`${this.wrapperTag}>\n`);
+ this.wrapperTag = null;
+ }
+ }
+
+ indent() {
+ this.output.push(INDENT.repeat(this.indentLevel));
+ }
+
+ renderBlockContent(block: ContentBlock): string {
+ let blockType = block.getType();
+ if (blockType === BLOCK_TYPE.ATOMIC) {
+ const entity = Entity.get(block.getEntityAt(0));
+ const {src, html} = entity.getData();
+ const type = entity.getType();
+ if (type === 'video') {
+ return html;
+ }
+ }
+ let text = block.getText();
+ if (text === '') {
+ // Prevent element collapse if completely empty.
+ return BREAK;
+ }
+ text = this.preserveWhitespace(text);
+ let charMetaList: CharacterMetaList = block.getCharacterList();
+ let entityPieces = getEntityRanges(text, charMetaList);
+ return entityPieces.map(([entityKey, stylePieces]) => {
+ let content = stylePieces.map(([text, style]) => {
+ let content = encodeContent(text);
+ // These are reverse alphabetical by tag name.
+ if (style.has(BOLD)) {
+ content = `${content}`;
+ }
+ if (style.has(UNDERLINE)) {
+ content = `${content}`;
+ }
+ if (style.has(ITALIC)) {
+ content = `${content}`;
+ }
+ if (style.has(STRIKETHROUGH)) {
+ content = `${content}`;
+ }
+ if (style.has(CODE)) {
+ // If our block type is CODE then we are already wrapping the whole
+ // block in a `` so don't wrap inline code elements.
+ content = (blockType === BLOCK_TYPE.CODE) ? content : `${content}
`;
+ }
+ return content;
+ }).join('');
+ let entity = entityKey ? Entity.get(entityKey) : null;
+ // Note: The `toUpperCase` below is for compatability with some libraries that use lower-case for image blocks.
+ let entityType = (entity == null) ? null : entity.getType().toUpperCase();
+ if (entityType != null && entityType === ENTITY_TYPE.LINK) {
+ let attrs = DATA_TO_ATTR.hasOwnProperty(entityType) ? DATA_TO_ATTR[entityType](entityType, entity) : null;
+ let strAttrs = stringifyAttrs(attrs);
+ return `${content}`;
+ } else if (entityType != null && entityType === ENTITY_TYPE.IMAGE) {
+ let attrs = DATA_TO_ATTR.hasOwnProperty(entityType) ? DATA_TO_ATTR[entityType](entityType, entity) : null;
+ let strAttrs = stringifyAttrs(attrs);
+ return `
`;
+ } else {
+ return content;
+ }
+ }).join('');
+ }
+
+ preserveWhitespace(text: string): string {
+ let length = text.length;
+ // Prevent leading/trailing/consecutive whitespace collapse.
+ let newText = new Array(length);
+ for (let i = 0; i < length; i++) {
+ if (
+ text[i] === ' ' &&
+ (i === 0 || i === length - 1 || text[i - 1] === ' ')
+ ) {
+ newText[i] = '\xA0';
+ } else {
+ newText[i] = text[i];
+ }
+ }
+ return newText.join('');
+ }
+
+}
+
+function stringifyAttrs(attrs) {
+ if (attrs == null) {
+ return '';
+ }
+ let parts = [];
+ for (let attrKey of Object.keys(attrs)) {
+ let attrValue = attrs[attrKey];
+ if (attrValue != null) {
+ parts.push(` ${attrKey}="${encodeAttr(attrValue + '')}"`);
+ }
+ }
+ return parts.join('');
+}
+
+function canHaveDepth(blockType: string): boolean {
+ switch (blockType) {
+ case BLOCK_TYPE.UNORDERED_LIST_ITEM:
+ case BLOCK_TYPE.ORDERED_LIST_ITEM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+function encodeContent(text: string): string {
+ return text
+ .split('&').join('&')
+ .split('<').join('<')
+ .split('>').join('>')
+ .split('\xA0').join(' ')
+ .split('\n').join(BREAK + '\n');
+}
+
+function encodeAttr(text: string): string {
+ return text
+ .split('&').join('&')
+ .split('<').join('<')
+ .split('>').join('>')
+ .split('"').join('"');
+}
+
+export default function stateToHTML(content: ContentState): string {
+ return new MarkupGenerator(content).generate();
+}
diff --git a/app/scripts/widgets/cdn.jsx b/app/scripts/widgets/cdn.jsx
new file mode 100644
index 0000000..0513d44
--- /dev/null
+++ b/app/scripts/widgets/cdn.jsx
@@ -0,0 +1,26 @@
+import $ from 'jquery';
+
+class CDN {
+ constructor() {
+ this.isDev = true;
+ $.get('/api/mode', function(data){
+ this.isDev = data.isDev;
+ }.bind(this));
+ }
+
+ show(img) {
+ if (img.startsWith('http')) {
+ return img;
+ } else {
+ if (this.isDev) {
+ return 'http://7xiuyp.com1.z0.glb.clouddn.com/' + img;
+ } else {
+ return 'http://img.playalot.cn/' + img;
+ }
+ }
+ }
+}
+
+let _cdn = new CDN();
+
+export default _cdn;
diff --git a/app/scripts/widgets/if.jsx b/app/scripts/widgets/if.jsx
new file mode 100644
index 0000000..f9ec24d
--- /dev/null
+++ b/app/scripts/widgets/if.jsx
@@ -0,0 +1,13 @@
+var React = require('react');
+
+var If = React.createClass({
+ render: function() {
+ if (this.props.test) {
+ return this.props.children;
+ } else {
+ return null;
+ }
+ }
+});
+
+module.exports = If;
diff --git a/app/scripts/widgets/numbercard.jsx b/app/scripts/widgets/numbercard.jsx
new file mode 100644
index 0000000..ad01618
--- /dev/null
+++ b/app/scripts/widgets/numbercard.jsx
@@ -0,0 +1,36 @@
+var React = require('react');
+
+var NumberCard = React.createClass({
+ getInitialState: function() {
+ return {data: ''};
+ },
+ componentDidMount: function() {
+ this.loadData();
+ },
+ loadData: function() {
+ $.ajax({
+ url: this.props.url,
+ dataType: 'json',
+ cache: false,
+ success: function(data) {
+ this.setState({data: data});
+ }.bind(this),
+ error: function(xhr, status, err) {
+ console.error(this.props.url, status, err.toString());
+ }.bind(this)
+ });
+ },
+ render: function() {
+ return (
+
+
+
+ {this.props.title}
+ {this.state.data}
+
+
+ );
+ }
+});
+
+module.exports = NumberCard;
diff --git a/app/scripts/widgets/test.jsx b/app/scripts/widgets/test.jsx
new file mode 100644
index 0000000..0ebcda1
--- /dev/null
+++ b/app/scripts/widgets/test.jsx
@@ -0,0 +1,21 @@
+/*jshint esversion: 6 */
+
+var sum = (a, b = 6) => (a + b);
+
+var square = (b) => {
+ return b * b;
+};
+
+var variable = 8;
+
+class MyClass {
+ constructor(credentials) {
+ this.name = credentials.name;
+ this.enrollmentNo = credentials.enrollmentNo;
+ }
+ getName() {
+ return this.name;
+ }
+}
+
+export { sum, square, variable, MyClass };
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..a33773b
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,25 @@
+{
+ "name": "play-dashboard",
+ "version": "1.0.0",
+ "authors": [
+ "Guan Guan"
+ ],
+ "appPath": "app",
+ "moduleType": [
+ "node"
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "app/vendor",
+ "test",
+ "tests"
+ ],
+ "dependencies": {
+ "bootstrap": "*",
+ "font-awesome": "*",
+ "admin-lte": "*",
+ "ionicons": "*"
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f533628
--- /dev/null
+++ b/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "play-dashboard",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Play Dashboard",
+ "author": "Guan Guan",
+ "license": "MIT",
+ "dependencies": {
+ "classnames": "^2.2.5",
+ "draft-js": "^0.7.0",
+ "draft-js-utils": "^0.1.5",
+ "dropzone": "^4.3.0",
+ "formsy-react": "^0.18.0",
+ "formsy-react-components": "^0.8.0",
+ "immutable": "*",
+ "jquery": "^3.0.0",
+ "lodash": "^4.12.0",
+ "lodash-deep": "^2.0.0",
+ "moment": "^2.13.0",
+ "react": "^15.1.0",
+ "react-addons-linked-state-mixin": "^15.1.0",
+ "react-bootstrap": "^0.29.5",
+ "react-cookie": "^0.4.7",
+ "react-dom": "^15.1.0",
+ "react-dropzone": "^3.5.1",
+ "react-dropzone-component": "^1.0.4",
+ "react-modal": "^1.3.0",
+ "react-qiniu": "*",
+ "react-redux": "^4.4.5",
+ "react-router": "^2.5.1",
+ "react-tagsinput": "^3.9.0",
+ "redux": "^3.5.2",
+ "reflux": "^0.4.1",
+ "superagent": "*"
+ },
+ "devDependencies": {
+ "babel-plugin-transform-class-properties": "^6.10.2",
+ "babel-preset-es2015": "*",
+ "babel-preset-react": "^6.11.0",
+ "babelify": "^7.3.0",
+ "connect-livereload": "~0.5.4",
+ "grunt": "^1.0.1",
+ "grunt-autoprefixer": "^3.0.4",
+ "grunt-bower": "^0.21.1",
+ "grunt-browserify": "^5.0.0",
+ "grunt-cli": "^1.2.0",
+ "grunt-contrib-clean": "^1.0.0",
+ "grunt-contrib-concat": "^1.0.1",
+ "grunt-contrib-connect": "^1.0.2",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-cssmin": "^1.0.1",
+ "grunt-contrib-htmlmin": "^1.4.0",
+ "grunt-contrib-imagemin": "^1.0.0",
+ "grunt-contrib-less": "^1.3.0",
+ "grunt-contrib-uglify": "^1.0.1",
+ "grunt-contrib-watch": "^1.0.0",
+ "grunt-env": "^0.4.4",
+ "grunt-filerev": "~2.3.1",
+ "grunt-usemin": "~3.1.1",
+ "grunt-wiredep": "^3.0.1",
+ "load-grunt-tasks": "^3.5.0",
+ "reactify": "~1.1.1",
+ "time-grunt": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "scripts": {
+ "start": "./node_modules/grunt-cli/bin/grunt serve",
+ "build": "NODE_ENV=production ./node_modules/grunt-cli/bin/grunt build"
+ }
+}