Merge branch 'dev_react' of https://git.trustie.net/Hjqreturn/trustieplus into develop

This commit is contained in:
caishi 2018-07-05 15:19:05 +08:00
commit 33d9eea3a4
56 changed files with 3790 additions and 63 deletions

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>Educoder</title><script type="text/javascript">window.__isR=!0</script><link rel="stylesheet" href="/react/build/css/css_min_all.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_653600_euyzqz9ex50cnmi.css"><link href="/react/build/./static/css/main.aeee84e5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="page -layout-v -fit"></div><div id="picture_display" style="display:none"></div><script type="text/javascript" src="/react/build/js/js_min_all_2.js"></script><script type="text/javascript" src="/assets/kindeditor/kindeditor.js"></script><script type="text/javascript" src="/react/build/js/create_kindeditor.js"></script><script type="text/javascript" src="/react/build/./static/js/main.1220ee9a.js"></script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>Educoder</title><script type="text/javascript">window.__isR=!0</script><link rel="stylesheet" href="/react/build/css/css_min_all.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_653600_euyzqz9ex50cnmi.css"><link href="/react/build/./static/css/main.d04ce8a5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="page -layout-v -fit"></div><div id="picture_display" style="display:none"></div><script type="text/javascript" src="/react/build/js/js_min_all_2.js"></script><script type="text/javascript" src="/assets/kindeditor/kindeditor.js"></script><script type="text/javascript" src="/react/build/js/create_kindeditor.js"></script><script type="text/javascript" src="/react/build/./static/js/main.92a4278d.js"></script></body></html>

View File

@ -1,20 +1,24 @@
{
"./static/js/0.8178fab1.chunk.js": "./static/js/0.8178fab1.chunk.js",
"./static/js/0.8178fab1.chunk.js.map": "./static/js/0.8178fab1.chunk.js.map",
"./static/js/1.0774461e.chunk.js": "./static/js/1.0774461e.chunk.js",
"./static/js/1.0774461e.chunk.js.map": "./static/js/1.0774461e.chunk.js.map",
"./static/js/2.ff99d076.chunk.js": "./static/js/2.ff99d076.chunk.js",
"./static/js/2.ff99d076.chunk.js.map": "./static/js/2.ff99d076.chunk.js.map",
"./static/js/3.3c55656b.chunk.js": "./static/js/3.3c55656b.chunk.js",
"./static/js/3.3c55656b.chunk.js.map": "./static/js/3.3c55656b.chunk.js.map",
"./static/js/4.b4f49c8d.chunk.js": "./static/js/4.b4f49c8d.chunk.js",
"./static/js/4.b4f49c8d.chunk.js.map": "./static/js/4.b4f49c8d.chunk.js.map",
"./static/js/5.06a0f326.chunk.js": "./static/js/5.06a0f326.chunk.js",
"./static/js/5.06a0f326.chunk.js.map": "./static/js/5.06a0f326.chunk.js.map",
"main.css": "./static/css/main.aeee84e5.css",
"main.css.map": "./static/css/main.aeee84e5.css.map",
"main.js": "./static/js/main.1220ee9a.js",
"main.js.map": "./static/js/main.1220ee9a.js.map",
"./static/js/0.5c3673c2.chunk.js": "./static/js/0.5c3673c2.chunk.js",
"./static/js/0.5c3673c2.chunk.js.map": "./static/js/0.5c3673c2.chunk.js.map",
"./static/js/1.29e1ced4.chunk.js": "./static/js/1.29e1ced4.chunk.js",
"./static/js/1.29e1ced4.chunk.js.map": "./static/js/1.29e1ced4.chunk.js.map",
"./static/js/2.d7daf6bd.chunk.js": "./static/js/2.d7daf6bd.chunk.js",
"./static/js/2.d7daf6bd.chunk.js.map": "./static/js/2.d7daf6bd.chunk.js.map",
"./static/js/3.0db7e213.chunk.js": "./static/js/3.0db7e213.chunk.js",
"./static/js/3.0db7e213.chunk.js.map": "./static/js/3.0db7e213.chunk.js.map",
"./static/js/4.5ff2c663.chunk.js": "./static/js/4.5ff2c663.chunk.js",
"./static/js/4.5ff2c663.chunk.js.map": "./static/js/4.5ff2c663.chunk.js.map",
"./static/js/5.9b55d5dc.chunk.js": "./static/js/5.9b55d5dc.chunk.js",
"./static/js/5.9b55d5dc.chunk.js.map": "./static/js/5.9b55d5dc.chunk.js.map",
"./static/js/6.d70c9fdd.chunk.js": "./static/js/6.d70c9fdd.chunk.js",
"./static/js/6.d70c9fdd.chunk.js.map": "./static/js/6.d70c9fdd.chunk.js.map",
"./static/js/7.11f39dc9.chunk.js": "./static/js/7.11f39dc9.chunk.js",
"./static/js/7.11f39dc9.chunk.js.map": "./static/js/7.11f39dc9.chunk.js.map",
"main.css": "./static/css/main.d04ce8a5.css",
"main.css.map": "./static/css/main.d04ce8a5.css.map",
"main.js": "./static/js/main.92a4278d.js",
"main.js.map": "./static/js/main.92a4278d.js.map",
"static\\media\\logo.svg": "static/media/logo.5d5d9eef.svg",
"static\\media\\match_ad.jpg": "static/media/match_ad.4e957369.jpg",
"static\\media\\message.svg": "static/media/message.a7af2a8f.svg",

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>Educoder</title><script type="text/javascript">window.__isR=!0</script><link rel="stylesheet" href="/css/css_min_all.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_653600_euyzqz9ex50cnmi.css"><link href="/react/build/./static/css/main.aeee84e5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="page -layout-v -fit"></div><div id="picture_display" style="display:none"></div><script type="text/javascript" src="/js/js_min_all_2.js"></script><script type="text/javascript" src="http://localhost:3000/assets/kindeditor/kindeditor.js"></script><script type="text/javascript" src="/js/create_kindeditor.js"></script><script type="text/javascript" src="/react/build/./static/js/main.1220ee9a.js"></script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><meta name="theme-color" content="#000000"><link rel="manifest" href="/manifest.json"><link rel="shortcut icon" href="/favicon.ico"><title>Educoder</title><script type="text/javascript">window.__isR=!0</script><link rel="stylesheet" href="/css/css_min_all.css"><link rel="stylesheet" href="//at.alicdn.com/t/font_653600_euyzqz9ex50cnmi.css"><link href="/react/build/./static/css/main.d04ce8a5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="page -layout-v -fit"></div><div id="picture_display" style="display:none"></div><script type="text/javascript" src="/js/js_min_all_2.js"></script><script type="text/javascript" src="http://localhost:3000/assets/kindeditor/kindeditor.js"></script><script type="text/javascript" src="/js/create_kindeditor.js"></script><script type="text/javascript" src="/react/build/./static/js/main.92a4278d.js"></script></body></html>

View File

@ -1 +1 @@
"use strict";var precacheConfig=[["/react/build/index.html","2bca516e9e1247d675780a6401df53f2"],["/react/build/static/css/main.aeee84e5.css","0c3bca40170c1c2b1c24f98ef5f6048a"],["/react/build/static/js/0.8178fab1.chunk.js","9fee95c3b447aaa6340624ba4a846afa"],["/react/build/static/js/1.0774461e.chunk.js","957738506f07420208d36eaabbb22063"],["/react/build/static/js/2.ff99d076.chunk.js","0c7d2eb1de8a69f0743029ee3e19420a"],["/react/build/static/js/3.3c55656b.chunk.js","320a2e6e1d03c47c954d8757baf619ce"],["/react/build/static/js/4.b4f49c8d.chunk.js","914795939ca56335e711899121817e2d"],["/react/build/static/js/5.06a0f326.chunk.js","6abcd79d1c9d7cb6b40f60f011ef5416"],["/react/build/static/js/main.1220ee9a.js","3353f62ce281edaabac2dd08f1dcb652"],["/react/build/static/media/logo.5d5d9eef.svg","5d5d9eefa31e5e13a6610d9fa7a283bb"],["/react/build/static/media/match_ad.4e957369.jpg","4e957369790f079662eb2dca03ece6be"],["/react/build/static/media/message.a7af2a8f.svg","a7af2a8f2f74540f8d77004b10a10ec3"],["/react/build/static/media/messagegrey.8c1dff55.svg","8c1dff55058b2926819eeda567a3ec9d"],["/react/build/static/media/passall.46817e26.png","46817e2696bbb2ac4bf0e5bc4e8b101e"],["/react/build/static/media/passpart.4aaf3e6b.png","4aaf3e6b4667dc531eee7f4e5f807d70"],["/react/build/static/media/rc-tree.776c7fe6.png","776c7fe67df0637005b1a1372066ed84"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,a,n){var r=new URL(e);return n&&r.pathname.match(n)||(r.search+=(r.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),r.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,a){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return a.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),r=createCacheKey(n,hashParamName,a,/\.\w{8}\./);return[n.toString(),r]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(n){return setOfCachedUrls(n).then(function(a){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!a.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return n.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var a=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!a.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,a=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),n="index.html";(e=urlsToCacheKeys.has(a))||(a=addDirectoryIndex(a,n),e=urlsToCacheKeys.has(a));var r="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(a=new URL(r,self.location).toString(),e=urlsToCacheKeys.has(a)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}});
"use strict";var precacheConfig=[["/react/build/index.html","e033a9375f02289ebc74a5237203c415"],["/react/build/static/css/main.d04ce8a5.css","d4b9332171b8b346a5b7b42c6a82ea97"],["/react/build/static/js/0.5c3673c2.chunk.js","8a384455c8ac118b921346e7709cf59a"],["/react/build/static/js/1.29e1ced4.chunk.js","0d68566907b14aa19c82673a08553901"],["/react/build/static/js/2.d7daf6bd.chunk.js","22fa29f39e479ad64b1e575208c888d0"],["/react/build/static/js/3.0db7e213.chunk.js","620d01587912c553e60c27371d30ed6f"],["/react/build/static/js/4.5ff2c663.chunk.js","ac6bbe148d67aebe533b15b66ac8d4b5"],["/react/build/static/js/5.9b55d5dc.chunk.js","9341d548562003aa9a6e35abe5e47910"],["/react/build/static/js/6.d70c9fdd.chunk.js","bc4c1ca86abbc719dbc21d8d869ba4cb"],["/react/build/static/js/7.11f39dc9.chunk.js","ab394048b8c9aba9fdf2a0f13dfd1002"],["/react/build/static/js/main.92a4278d.js","77b660ed35d310e9a83ee2a289224e02"],["/react/build/static/media/logo.5d5d9eef.svg","5d5d9eefa31e5e13a6610d9fa7a283bb"],["/react/build/static/media/match_ad.4e957369.jpg","4e957369790f079662eb2dca03ece6be"],["/react/build/static/media/message.a7af2a8f.svg","a7af2a8f2f74540f8d77004b10a10ec3"],["/react/build/static/media/messagegrey.8c1dff55.svg","8c1dff55058b2926819eeda567a3ec9d"],["/react/build/static/media/passall.46817e26.png","46817e2696bbb2ac4bf0e5bc4e8b101e"],["/react/build/static/media/passpart.4aaf3e6b.png","4aaf3e6b4667dc531eee7f4e5f807d70"],["/react/build/static/media/rc-tree.776c7fe6.png","776c7fe67df0637005b1a1372066ed84"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var a=new URL(e);return"/"===a.pathname.slice(-1)&&(a.pathname+=t),a.toString()},cleanResponse=function(t){return t.redirected?("body"in t?Promise.resolve(t.body):t.blob()).then(function(e){return new Response(e,{headers:t.headers,status:t.status,statusText:t.statusText})}):Promise.resolve(t)},createCacheKey=function(e,t,a,n){var r=new URL(e);return n&&r.pathname.match(n)||(r.search+=(r.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(a)),r.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var a=new URL(t).pathname;return e.some(function(e){return a.match(e)})},stripIgnoredUrlParameters=function(e,a){var t=new URL(e);return t.hash="",t.search=t.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(t){return a.every(function(e){return!e.test(t[0])})}).map(function(e){return e.join("=")}).join("&"),t.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],a=e[1],n=new URL(t,self.location),r=createCacheKey(n,hashParamName,a,/\.\w{8}\./);return[n.toString(),r]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(n){return setOfCachedUrls(n).then(function(a){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(t){if(!a.has(t)){var e=new Request(t,{credentials:"same-origin"});return fetch(e).then(function(e){if(!e.ok)throw new Error("Request for "+t+" returned a response with status "+e.status);return cleanResponse(e).then(function(e){return n.put(t,e)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var a=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(t){return t.keys().then(function(e){return Promise.all(e.map(function(e){if(!a.has(e.url))return t.delete(e)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(t){if("GET"===t.request.method){var e,a=stripIgnoredUrlParameters(t.request.url,ignoreUrlParametersMatching),n="index.html";(e=urlsToCacheKeys.has(a))||(a=addDirectoryIndex(a,n),e=urlsToCacheKeys.has(a));var r="/index.html";!e&&"navigate"===t.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],t.request.url)&&(a=new URL(r,self.location).toString(),e=urlsToCacheKeys.has(a)),e&&t.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(a)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(e){return console.warn('Couldn\'t serve response for "%s" from cache: %O',t.request.url,e),fetch(t.request)}))}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
webpackJsonp([5],{852:function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==typeof t&&"function"!==typeof t?e:t}function i(e,t){if("function"!==typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=n(0),c=n.n(u),a=(n(164),n(2)),f=(n.n(a),n(853)),l=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),p=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))}return i(t,e),l(t,[{key:"componentWillReceiveProps",value:function(e,t){}},{key:"render",value:function(){return c.a.createElement(f.a,Object.assign({},this.state,this.props))}}]),t}(u.Component);t.default=p},853:function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==typeof t&&"function"!==typeof t?e:t}function i(e,t){if("function"!==typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(0),c=n.n(u),a=(n(164),n(2)),f=(n.n(a),function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}()),l=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e))}return i(t,e),f(t,[{key:"componentWillReceiveProps",value:function(e,t){}},{key:"render",value:function(){return c.a.createElement("div",null,"TPMChallenge")}}]),t}(u.Component);t.a=l}});
//# sourceMappingURL=5.06a0f326.chunk.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^3.6.5",
"autoprefixer": "7.1.6",
"axios": "^0.18.0",
"babel-core": "6.26.0",

File diff suppressed because one or more lines are too long

View File

@ -51,8 +51,8 @@ const TestCodeMirrorComponent = Loadable({
loading: Loading,
})
const TaskListContainerComponent = Loadable({
loader: () => import('./modules/taskList/TaskListContainer'),
const TestComponent = Loadable({
loader: () => import('./modules/test/TestRC'),
loading: Loading,
})
@ -66,6 +66,13 @@ const ForumsIndexComponent = Loadable({
loading: Loading,
})
// trustie plus forum
const TPForumsIndexComponent = Loadable({
loader: () => import('./modules/tp-forums/TPForumsIndex'),
loading: Loading,
})
// const TestPageComponent = Loadable({
// loader: () => import('./modules/page/Index'),
@ -90,7 +97,7 @@ class App extends Component {
<Route path="/testCodeMirror" component={TestCodeMirrorComponent}/>
<Route path="/taskList" component={TaskListContainerComponent}/>
<Route path="/testRCComponent" component={TestComponent}/>
@ -101,6 +108,9 @@ class App extends Component {
<Route path="/forums" component={ForumsIndexComponent}>
</Route>
<Route path="/tpforums" component={TPForumsIndexComponent}>
</Route>
<Route path="/myshixuns/:shixunId/stages/:stageId" component={Index}/>

View File

@ -24,7 +24,10 @@ class NotFoundPage extends Component {
 | 
<Link to="/taskList">taskList</Link>
 | 
<Link to="/testPage">testPage</Link>
<Link to="/testRCComponent">testRCComponent</Link>
| 
<Link to="/tpforums">tpforums</Link>
</div>
);

View File

@ -35,6 +35,10 @@
width: fit-content;
}
.comment_content img {
max-width: 23% !important;
}
/* ----------------------------------------------------- CommentInput START */
/*收起的时候radius变化*/

View File

@ -147,7 +147,7 @@ class MemoDetail extends Component {
// if (this.props.memo && this.props.memo.content
// && (!prevProps.memo || prevProps.memo.content != this.props.memo.content) ) {
if (this.props.memo && this.props.memo.content && prevState.memoLoading === true && this.state.memoLoading === false) {
// md渲染content
// md渲染content等xhr执行完即memoLoading变化memo.content更新后初始化md
if (this.props.memo.is_md) {
setTimeout(()=>{
var shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", {
@ -641,9 +641,9 @@ class MemoDetail extends Component {
<div className="clearfix">
<div id="forum_list" className="forum_table mh650">
<div className="padding40-30 bor-bottom-greyE">
<div className="font-16 mb5 cdefault clearfix pr">
<span className="noteDetailTitle task-hide fl">{memo.subject}</span>
{ memo.sticky && <span className="btn-cir btn-cir-red ml10 mt10 fl">置顶</span>}
<div className="font-16 mb5 cdefault clearfix pr pr20">
<span className="noteDetailTitle">{memo.subject}</span>
{ memo.sticky && <span className="btn-cir btn-cir-red ml10 mt10">置顶</span>}
{ !!memo.reward && <span className="color-orange font-14 ml15"
data-tip-down={`获得平台奖励金币:${memo.reward}`} >
<i className="iconfont icon-gift mr5"></i>{memo.reward}

View File

@ -40,7 +40,7 @@
.user_default_btn {width: 114px;}
.userPrivateName{line-height: 25px;margin-bottom: 9px;}
.userPrivatePost{line-height: 20px;}
.noteDetailTitle{max-width: 650px;line-height: 38px;font-size: 24px;font-weight: normal}
.noteDetailTitle{line-height: 38px;font-size: 24px;font-weight: normal}
.noteDetailNum{float: left;padding:0px 12px;position: relative;color: #999!important;height: 28px;line-height: 26px;}
.noteDetailNum.rightline:after{position: absolute;content: '';right: 0px;width: 1px;background-color: #EAEAEA;height: 8px;top:10px;}

View File

@ -37,19 +37,34 @@
/* ImageLayer ----------------- Start */
.passContent{
/*width: 652px;*/
height:100%;
margin:0px auto;
margin-top: 4%;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
}
.passImg{
/*position: absolute;*/
z-index: 2;
user-select: none;
flex: 0;
}
/* ImageLayer ----------------- End */
.passTaskContent{
width: 652px;
height:100%;
margin:0px auto;
margin-top: 4%;
}
.passTaskImg{
position: absolute;
z-index: 2;
user-select: none;
}
.winpPerson{
position: absolute;
width: 100%;

View File

@ -32,11 +32,11 @@ class TaskResultLayer extends Component {
setTimeout(()=>{
// true ||
if ( isLastGame) {
var dClass = 'zoomInDown' ;$('.passContent').show().addClass(dClass + ' animated');
setTimeout( function(){$('.passContent').removeClass(dClass + ' animated')}, 1000)
var dClass = 'zoomInDown' ;$('.passTaskContent').show().addClass(dClass + ' animated');
setTimeout( function(){$('.passTaskContent').removeClass(dClass + ' animated')}, 1000)
} else {
var dClass = 'zoomIn' ;$('.passContent').show().addClass(dClass + ' animated');
setTimeout( function(){$('.passContent').removeClass(dClass + ' animated')}, 1000)
var dClass = 'zoomIn' ;$('.passTaskContent').show().addClass(dClass + ' animated');
setTimeout( function(){$('.passTaskContent').removeClass(dClass + ' animated')}, 1000)
}
}, 100)
}
@ -120,9 +120,9 @@ class TaskResultLayer extends Component {
<div>
{currentGamePassed ?
<div className="taskResultLayer">
<div className="pr passContent" style={{display:'none'}} >
<div className="pr passTaskContent" style={{display:'none'}} >
<i className="far fa-times-circle closeIcon" onClick={closeTaskResultLayer} title="关闭"></i>
<img src={isLastGame? passallImg : passpartImg} width="652px" className="passImg"/>
<img src={isLastGame? passallImg : passpartImg} width="652px" className="passTaskImg"/>
<div className="winpPerson expGold">
<p className="inline"><span className=" goldring"/>
{currentPassedGameGainGold >= 0 ? `+${currentPassedGameGainGold}` : '+0'}

View File

@ -0,0 +1,105 @@
import React, { Component } from 'react';
import moment from 'moment'
import Select, {Option, OptGroup} from 'rc-select';
import 'rc-select/assets/index.css';
import { Upload, Icon, message } from 'antd';
import 'antd/lib/upload/style/index.css'
const Dragger = Upload.Dragger;
const props = {
name: 'file',
multiple: true,
action: '//jsonplaceholder.typicode.com/posts/',
onChange(info) {
const status = info.file.status;
if (status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
// -------------------------------------------
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
function beforeUpload(file) {
const isJPG = file.type === 'image/jpeg';
// if (!isJPG) {
// message.error('You can only upload JPG file!');
// }
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('Image must smaller than 2MB!');
}
// return isJPG && isLt2M;
return isLt2M;
}
// -------------------------------------------
const children = [];
for (let i = 10; i < 36; i++) {
// value={i}
children.push(
<Option key={i.toString(36) + i} disabled={i === 10} title={`中文${i}`} >
中文{i}
</Option>
);
}
class TestRC extends Component {
state = {
loading: false,
};
handleChange = (info) => {
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getBase64(info.file.originFileObj, imageUrl => this.setState({
imageUrl,
loading: false,
}));
}
}
render() {
const uploadButton = (
<div>
<Icon type={this.state.loading ? 'loading' : 'plus'} />
<div className="ant-upload-text">Upload</div>
</div>
);
const imageUrl = this.state.imageUrl;
return (
<Upload
name="avatar"
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action="//jsonplaceholder.typicode.com/posts/"
beforeUpload={beforeUpload}
onChange={this.handleChange}
>
{imageUrl ? <img src={imageUrl} alt="avatar" /> : uploadButton}
</Upload>
);
}
}
export default TestRC;

View File

@ -0,0 +1,66 @@
/*Nav START*/
._forum_tab {
padding-bottom: 26px;
}
.discuss-tab {
height: 90px;
}
.discuss-tab ._forum_tab a.navItem:hover {
color: #4CACFF !important;
border-bottom: none !important;
}
.discuss-tab ._forum_tab a.navItem.active {
border-bottom: none !important;
border: 1px solid #4CACFF !important;
color: #4CACFF !important;
border-radius:24px;
}
.discuss-tab a.returnBtnA:hover {
border-bottom: none!important;
}
/*Nav END*/
#forum_list {
display: flex;
flex-direction: column;
}
.rc-pagination {
padding: 30px 20px;
background: #FAFAFA;
margin: 0 auto;
width: fit-content;
}
/*分页*/
.ec-pagination .rc-pagination-item{
border-radius: 2px;
width: 30px;
height: 32px;
line-height: 32px;
}
.ec-pagination a{outline: none;}
.ec-pagination .rc-pagination-jump-next{
height: 32px;
line-height: 32px;
}
.ec-pagination .rc-pagination-item:hover{
border: 1px solid #4cacff;
color: #4cacff;
}
.ec-pagination .rc-pagination-item-active{
background-color: #4CACFF;
}
.ec-pagination .rc-pagination-prev, .ec-pagination .rc-pagination-next{display: none}
.ec-pagination .rc-pagination-options-quick-jumper input{
height: 32px;
border-radius: 2px;
}
.ec-pagination .rc-pagination-options-quick-jumper{
height: 34px;
line-height: 34px;
margin-left: 0px;
}

View File

@ -0,0 +1,100 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class ForumsNavTab extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onNavClick(active) {
// TODO 为什么事件发不过去
// https://github.com/facebook/react/issues/3249#issuecomment-177750141
// window.$(window).trigger('setSearchValue', '', true);
this.props.setSearchValue('')
if (!active) {
this.props.initForumState({
selectedHotLabelIndex: -1,
})
}
}
render() {
const { match, history, currentPage } = this.props
const techSharePath = `/tpforums/categories/5`
const guidePath = `/tpforums/categories/3`
const hottestPath = `/tpforums/categories/all?order=hottest` // ?order=hottest
const newestPath = `/tpforums/categories/all?order=newest` // ?order=newest
const shixunDiscussPath = `/tpforums/categories/shixun_discuss`
const locationPath = history.location.pathname + history.location.search
/*
<ul>
<li className={classNames({'selected': locationPath.indexOf(techSharePath) === 0 })}>
<Link to={`${techSharePath}`} >techShare</Link>
</li>
<li className={classNames({'selected': locationPath.indexOf(guidePath) === 0 })}>
<Link to={`${guidePath}`}>guide</Link>
</li>
</ul>
*/
return (
<div className="discuss-tab pl20 bor-bottom-greyE clearfix pr edu-back-white">
<p className="_forum_tab clearfix">
{/*<a href="/forums" className="fl font-16 ptl5-10 block mr20 active">
<span className="fl">技术分享</span>
<span className="forum_filtrate_span2 forum_filtrate_span2_bg mt10 ml10 fl">219</span>
</a>*/}
<Link to={`${newestPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('order=newest') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('order=newest') !== -1)}
>
<span className="fl">最新回复</span>
</Link>
<Link to={`${hottestPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('order=hottest') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('order=hottest') !== -1)}
>
<span className="fl">热门话题</span>
</Link>
<Link to={`${shixunDiscussPath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf('shixun_discuss') !== -1 })}
onClick={()=>this.onNavClick(locationPath.indexOf('shixun_discuss') !== -1)}
>
<span className="fl">实训回复</span>
</Link>
<Link to={`${techSharePath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf(techSharePath) === 0 })}
onClick={()=>this.onNavClick(locationPath.indexOf(techSharePath) === 0)}
>
<span className="fl">技术分享</span>
</Link>
<Link to={`${guidePath}`} className={classNames("fl font-16 padding5-20 block mr30 navItem"
, {'active': locationPath.indexOf(guidePath) === 0 })}
onClick={()=>this.onNavClick(locationPath.indexOf(guidePath) === 0)}
>
<span className="fl">操作指南</span>
</Link>
{/*<a href="/forums?type=discuss" className="fl font-16 ptl5-10 block mr20">
<span className="fl">实训交流</span>
<span className="forum_filtrate_span2 mt10 ml10 fl">1391</span>
</a>*/}
</p>
</div>
);
}
}
export default ForumsNavTab;

View File

@ -0,0 +1,776 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import axios from 'axios'
import moment from 'moment'
import Comments from '../comment/Comments'
import update from 'immutability-helper'
import Tooltip from 'material-ui/Tooltip';
import RewardDialog from '../common/RewardDialog';
import ImageLayer from '../page/layers/ImageLayer'
import { bytesToSize } from 'educoder'
const $ = window.$
function urlStringify(params) {
let noParams = true;
let paramsUrl = '';
for (let key in params) {
noParams = false;
paramsUrl += `${key}=${params[key]}&`
}
if (noParams) {
return '';
}
paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1);
return paramsUrl;
}
class MemoDetail extends Component {
constructor(props) {
super(props)
this.state = {
memoLoading: true,
hasMoreComments: false,
pageCount: 1,
goldRewardDialogOpen: false
}
}
componentDidMount() {
window.$("html,body").animate({"scrollTop":0})
const { match } = this.props
const memoUrl = `/api/v1/memos/${match.params.memoId}`;
this.setState({
memoLoading: true
})
axios.get(memoUrl,{
withCredentials: true,
})
.then((response) => {
const memo = response.data.memo
if (memo) {
// this.setState({...response.data})
const { memo_replies } = response.data;
const hasMoreComments = memo_replies ? memo_replies.length === 10 : false
this.setState({
hasMoreComments,
pageCount: 1,
comments: memo_replies
})
delete response.data.memo_replies;
this.props.initForumState(response.data)
const user = response.data.current_user;
user.tidding_count = response.data.tidding_count;
this.props.initCommonState(user)
}
this.setState({
memoLoading: false
}, ()=>{
setTimeout(()=> {
// 初始化回复ke
this.keEditor = window.sd_create_editor_from_data(memo.id, null, "100%", "Memo");
window._kk = this.keEditor
}, 300)
})
}).catch((error) => {
console.log(error)
})
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
// 图片放大
setTimeout(()=>{
$(".commentsDelegateParent").delegate(".comment_item_cont .J_Comment_Reply img ","click", (event) => {
const imageSrc = event.target.src
// 非回复里的头像图片; 非emoticons
if (imageSrc.indexOf('/images/avatars/User') === -1 &&
imageSrc.indexOf('kindeditor/plugins/emoticons') === -1 ) {
this.setState({
showImage: true,
imageSrc,
})
}
});
}, 4000)
}
onImageLayerClose = () => {
this.setState({
showImage: false,
imageSrc: '',
})
}
componentWillUnmount() {
$('body>#root').off('onMemoDelete')
}
onMemoDelete(memo) {
const deleteUrl = `/api/v1/memos/${memo.id}`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showSnackbar('删除成功');
this.props.history.push(`/forums`)
}
}).catch((error) => {
console.log(error)
})
}
componentDidUpdate(prevProps, prevState, snapshot) {
// if (this.props.memo && this.props.memo.content
// && (!prevProps.memo || prevProps.memo.content != this.props.memo.content) ) {
if (this.props.memo && this.props.memo.content && prevState.memoLoading === true && this.state.memoLoading === false) {
// md渲染content等xhr执行完即memoLoading变化memo.content更新后初始化md
if (this.props.memo.is_md) {
setTimeout(()=>{
var shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", {
htmlDecode: "style,script,iframe", // you can filter tags decode
taskList: true,
tex: true, // 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true // 默认不解析
});
}, 200)
}
}
}
clickPraise(){
const { memo } = this.props;
const url = `/api/v1/discusses/${memo.id}/plus`;
console.log(url)
axios.post(url, {
container_type: 'Memo',
type: 1 // "踩0赞1"
},
{
withCredentials: true
}
).then((response) => {
console.log(response);
const newMemo = Object.assign({}, this.props.memo)
newMemo.praise_count = response.data.praise_count
newMemo.user_praise = !newMemo.user_praise
this.props.initForumState({memo : newMemo })
}).catch((error) => {
console.log(error)
})
}
renderAttachment() {
const { memo } = this.props;
const attachments = []
memo.attachment_url.forEach((item, index) => {
const ar = item.url.split('/')
const fileName = item.filename || ar[ar.length - 1]
let filesize = 0
if (item.filesize) {
filesize = bytesToSize(item.filesize)
}
attachments.push(
<p className="clearfix" key={index} >
<a href={item.url} className="color-green clearfix notefileDownload">
<i className="iconfont icon-xiazai color-green ml5 fl"></i><span className="fl">{fileName}{filesize? ` ${filesize}` : ''}</span>
</a>
</p>
)
})
return attachments;
}
// ------------------------------------------------------------------------------------------- comments START
// ------------------------------------------------------------------------------------------- comments START
_getUser() {
const { current_user } = this.props;
current_user.user_url = `/users/${current_user.login}`;
return current_user;
}
_findById(id, arg_comments) {
const comments = arg_comments;
for(let i = 0; i < comments.length; i++) {
if (id === comments[i].id) {
return i;
}
}
}
replyComment = (commentContent, id, editor) => {
if (this.props.memo.id === id ) { // 回复帖子
this.createNewComment(commentContent, id, editor);
return;
}
const url = `/api/v1/memos/${id}/reply`;
const { comments } = this.state;
const user = this._getUser();
axios.post(url, {
content: commentContent
},
{
withCredentials: true
}
).then((response) => {
if (response.data.memo) {
let newDiscuss = response.data.memo;
var commentIndex = this._findById(id, comments);
let comment = comments[commentIndex];
if (!comment.children ) {
comment.children = []
}
// TODO userName iamge_url
comment.children.push( {
"can_delete": true,
"content": commentContent,
"image_url": user.image_url,
"username": user.username,
"id": newDiscuss.id,
// "position": newDiscuss.position,
"time": "1分钟前",
"praise_count": 0,
"user_id": newDiscuss.author_id,
})
comments[commentIndex] = comment
editor.html('')
const $ = window.$
var reply_message_el = `#reply_message_${id}`
$(reply_message_el).hide();
this.setState({
// runTesting: false,
comments: comments
}, ()=>{
// keditor代码美化
window.prettyPrint()
})
const newMemo2 = Object.assign({}, this.props.memo);
newMemo2.replies_count = newMemo2.replies_count + 1;
this.props.initForumState({
memo: newMemo2
})
}
console.log(response)
}).catch((error) => {
console.log(error)
})
}
deleteComment = (parrentComment, childCommentId) => {
let deleteCommentId = parrentComment.id
if (childCommentId) {
deleteCommentId = childCommentId;
}
const url = `/api/v1/memos/${deleteCommentId}`
let comments = this.state.comments;
axios.delete(url,
{
withCredentials: true
}
).then((response) => {
// TODO 删除成功或失败
if (response.data) {
const commentIndex = this._findById(parrentComment.id, comments);
// https://stackoverflow.com/questions/29527385/removing-element-from-array-in-component-state
if (!childCommentId) {
this.setState((prevState) => ({
comments: update(prevState.comments, {$splice: [[commentIndex, 1]]})
}))
// if (this.state.comments.length <= 5) {
// this.fetchComment()
// }
} else {
let childCommentIndex = this._findById(childCommentId, comments[commentIndex].children);
comments[commentIndex].children = update(comments[commentIndex].children, {$splice: [[childCommentIndex, 1]]})
this.setState({ comments })
}
const newMemo = Object.assign({}, this.props.memo);
newMemo.replies_count = newMemo.replies_count - 1;
this.props.initForumState({
memo: newMemo
})
}
console.log(response)
}).catch((error) => {
console.log(error)
})
}
// 评论点赞
commentPraise = (discussId) => {
const { comments } = this.state;
const commentIndex = this._findById(discussId, comments);
const url = `/api/v1/discusses/${discussId}/plus`
axios.post(url, {
// id: discussId,
// container_id: challenge.id,
container_type: 'Memo', //Discuss
type: comments[commentIndex].user_praise === true ? 0 : 1, // "踩0赞1"
},
{
withCredentials: true
}
).then((response) => {
if (response.data.praise_count === 0 || response.data.praise_count) {
comments[commentIndex].user_praise = !comments[commentIndex].user_praise;
comments[commentIndex].praise_count = response.data.praise_count;
this.setState({
comments
})
}
console.log(response)
}).catch((error) => {
console.log(error)
})
}
rewardCode = (parrentComment, childComment, amount) => {
const { showSnackbar } = this.props;
const { comments } = this.state;
let handleComment = parrentComment
if (childComment) {
handleComment = childComment;
}
let handleCommentId = handleComment.id;
const url = `/api/v1/discusses/${handleCommentId}/reward_code`
axios.post(url, {
id: handleCommentId,
container_type: 'Memo',
score: amount,
user_id: handleComment.user_id
},
{
withCredentials: true
}
).then((response) => {
if (response.data && response.data.code) {
const commentIndex = this._findById(handleComment.id, comments);
comments[commentIndex].reward = response.data.code;
this.setState({
comments
})
}
console.log(response)
}).catch((error) => {
console.log(error)
showSnackbar('奖励失败,请联系系统管理员!')
})
}
hiddenComment = (item, childCommentId) => {
const id = item.id
const { showSnackbar } = this.props;
const user = this._getUser();
const url = `/api/v1/memos/${id}/hidden`
const { comments } = this.state;
const commentIndex = this._findById(id, comments);
const comment = comments[commentIndex];
axios.post(url, {
hidden: !comment.hidden ? "1" : "0"
},
{
withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
showSnackbar(response.data.message)
return;
}
if (response.data.status === 1) {
if (!childCommentId) {
comment.hidden = !comment.hidden;
this.setState({
comments: comments
})
} else { // TODO 目前子回复没hidden字段
let childCommentIndex = this._findCommentById(childCommentId, comments[commentIndex].children);
const childComment = comments[commentIndex].children[childCommentIndex]
childComment.hidden = !childComment.hidden;
this.setState({ comments })
}
}
// {"message":"Couldn't find Discuss with id=911","status":-1}
console.log(response)
}).catch((error) => {
console.log(error)
})
}
createNewComment = (commentContent, id, editor) => {
const content = commentContent;
const { memo } = this.props;
if(content != undefined){
var beforeImage = content.split("<img");
var afterImage = content.split("/>");
if(beforeImage[0] == "" && afterImage[1] == ""){
window.notice_box('不支持纯图片评论<br/>请在评论中增加文字信息');
return;
}
}
const url = `/api/v1/memos/${memo.id}/reply`;
let { comments } = this.state;
const user = this._getUser();
axios.post(url, {
content: content
},
{
withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
console.error('服务端异常')
return;
}
if (response.data) {
const newMemo = response.data.memo;
editor.html('');
editor.afterBlur()
if (!comments) {
comments = [];
}
comments.unshift( {
"can_delete": true,
"admin": user.admin,
"content": content,
"image_url": user.image_url,
"username": user.username,
"login": user.login,
"id": newMemo.id,
"reward": null,
"hidden": newMemo.hidden,
"user_praise": false,
"time": "1分钟前",
"praise_count": 0,
"user_id": user.user_id,
})
this.setState({
comments
})
const newMemo2 = Object.assign({}, this.props.memo);
newMemo2.replies_count = newMemo2.replies_count + 1;
this.props.initForumState({
memo: newMemo2
})
console.log(response)
}
}).catch((error) => {
console.log(error)
})
}
moreMemos = () => {
let { comments, pageCount } = this.state;
let { memo } = this.props;
const user = this._getUser();
const url = `/api/v1/memos/${memo.id}/more_reply?page=${pageCount}`;
axios.get(url, {
},
{
withCredentials: true
}
).then((response) => {
if (response.data.status === -1) {
console.error('服务端异常')
return;
}
let { memo_replies } = response.data;
if (!memo_replies || memo_replies.length === 0) {
this.setState({
hasMoreComments: false
})
return;
}
if (response.data.memos_count) {
const newComments = comments.concat(memo_replies);
const hasMoreComments = memo_replies.length === 10
this.setState({
comments: newComments,
hasMoreComments,
pageCount: pageCount+1
})
}
}).catch((error) => {
console.log(error)
})
}
// ------------------------------------------------------------------------------------------- comments END
// ------------------------------------------------------------------------------------------- comments END
// 置顶
setTop(memo) {
const params = {
sticky: memo.sticky ? 0 : 1,
}
if (this.state.p_s_order) {
params.order = this.state.p_s_order;
}
if (this.state.p_forum_id) {
params.forum_id = this.state.p_forum_id;
}
let paramsUrl = urlStringify(params)
const set_top_or_down_Url = `/api/v1/memos/${memo.id}/set-top-or-down?${paramsUrl}`;
// 获取memo list
axios.get(set_top_or_down_Url, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showSnackbar( memo.sticky ? '取消置顶成功' : '置顶成功');
memo.sticky = memo.sticky ? false : true
this.setState({
memo: Object.assign({}, memo)
})
}
}).catch((error) => {
console.log(error)
})
}
// --------------------------------------------------------------------------------------------帖子獎勵
rewardCodeMemo = (inputVal) => {
console.log(inputVal)
const { memo, author_info } = this.props;
const newMemo = Object.assign({}, memo);
const _reward = parseInt(inputVal)
const newMemoUrl = `/api/v1/memos/${memo.id}/update`
const url = `/api/v1/discusses/${memo.id}/reward_code`
axios.post(url, {
id: memo.id,
container_type: 'Memo',
score: _reward,
user_id: author_info.user_id
}, {
withCredentials: true,
})
.then((response) => {
const { code } = response.data;
if (code > 0) {
newMemo.reward = code
this.props.initForumState({
memo: newMemo
})
this.props.showSnackbar( '奖励成功' );
} else {
this.props.showSnackbar( '奖励失败,请联系系统管理员!' );
}
}).catch((error) => {
console.log(error)
})
}
setRewardDialogVisible = (visible) => {
this.setState({
goldRewardDialogOpen: visible
})
}
showRewardDialog = () => {
this.setState({
goldRewardDialogOpen: true
})
}
// --------------------------------------------------------------------------------------------帖子獎勵 END
showCommentInput = () => {
$("html, body").animate({ scrollTop: $('#commentInput').offset().top - 100 }, 1000, () => {
this.keEditor && this.keEditor.focus();
});
}
render() {
const { match, history } = this.props
const { memo, recommend_shixun, current_user,author_info } = this.props;
const { comments, hasMoreComments, goldRewardDialogOpen } = this.state;
if (this.state.memoLoading) {
return <div className="edu-back-white" id="forum_index_list"></div>
}
current_user.user_url = `/users/${current_user.login}`;
memo.isDetailPage = true;
// TODO 图片上传地址
return (
<div className="edu-back-white" id="forum_index_list"> {/* fl with100 */}
<RewardDialog goldRewardDialogOpen={goldRewardDialogOpen}
setRewardDialogVisible={this.setRewardDialogVisible}
rewardCode={this.rewardCodeMemo}
></RewardDialog>
<div className="clearfix">
<div id="forum_list" className="forum_table mh650">
<div className="padding40-30 bor-bottom-greyE">
<div className="font-16 mb5 cdefault clearfix pr">
<span className="noteDetailTitle task-hide fl">{memo.subject}</span>
{ memo.sticky && <span className="btn-cir btn-cir-red ml10 mt10 fl">置顶</span>}
{ !!memo.reward && <span className="color-orange font-14 ml15"
data-tip-down={`获得平台奖励金币:${memo.reward}`} >
<i className="iconfont icon-gift mr5"></i>{memo.reward}
</span> }
{ current_user && (current_user.admin === true || current_user.user_id === author_info.user_id) &&
<div className="edu-position-hidebox" style={{position: 'absolute', right: '18px',top:'4px'}}>
<a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a>
<ul className="edu-position-hide undis">
{ current_user.admin === true &&
( memo.sticky === true ?
<li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>取消置顶</a></li>
:
<li><a href="javascript:void(0);" onClick={() => this.setTop(memo)}>&nbsp;&nbsp;</a></li> )
}
<li><Link to={`/forums/${memo.id}/edit`}>&nbsp;&nbsp;</Link></li>
<li>
<a href="javascript:void(0)" onClick={() =>
window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}>
&nbsp;&nbsp;</a>
</li>
</ul>
</div>
}
</div>
<div className="color-grey-9 clearfix">
<span className="fl">{moment(memo.time).fromNow()} 发布</span>
<div className="fr">
{ current_user.admin && <Tooltip title={ "帖子奖励" }>
<span className="noteDetailNum rightline cdefault" style={{padding: '0 4px', cursor: 'pointer'}}>
<i className="iconfont icon-jiangli mr5" onClick={this.showRewardDialog}></i>
</span>
</Tooltip> }
<span className={`noteDetailNum ${!!memo.replies_count ? 'rightline' : ''} cdefault`}>
<i className="iconfont icon-liulanyan mr5"></i>{memo.viewed_count}
</span>
{ !!memo.replies_count &&
<Tooltip title={ "写评论" }>
<a href="javascript:void(0)" className="noteDetailNum">
<i className="iconfont icon-huifu1 mr5" onClick={this.showCommentInput}></i>{memo.replies_count}
</a>
</Tooltip>
}
</div>
</div>
</div>
<div className="padding40 memoContent new_li">
{ !memo.is_md ?
<div dangerouslySetInnerHTML={{__html: memo.content}}></div> :
<div id="memo_content_editorMd" className="new_li">
<textarea style={{'display': 'none'}}>
{memo.content}
</textarea>
</div>
}
</div>
<div className="padding40 bor-bottom-greyE" style={{ paddingTop: '0px'}}>
<div className="mt10 mb20">
<p className={`noteDetailPoint ${memo.user_praise ? 'Pointed' : ''} `} onClick={()=>{this.clickPraise()}} >
<i className="iconfont icon-dianzan"></i><br/>
<span>{memo.praise_count}</span>
</p>
</div>
{ memo.attachment_url && !!memo.attachment_url.length &&
<div>
{this.renderAttachment()}
</div>
}
</div>
<div nhname={`new_message_${memo.id}`} className="" style={{ paddingTop: '20px', paddingBottom: '20px' }}
id="commentInput">
<form acceptCharset="UTF-8" action="/discusses?challenge_id=118&dis_id=61&dis_type=Shixun"
style={{ flexDirection: 'column', width: '94%', marginLeft: '3%'}}
className="df" data-remote="true" id="new_comment_form" method="post">
<div nhname={`toolbar_container_${memo.id}`}></div>
<textarea placeholder="" id={`comment_news_${memo.id}`}
nhname={`new_message_textarea_${memo.id}`} name="content" className="none">
</textarea>
</form>
<a id={`new_message_submit_btn_${memo.id}`} href="javascript:void(0)"
style={{display: 'none'}} className="commentsbtn task-btn task-btn-blue fr">
发送
</a>
</div>
{/* onClick={ this.createNewComment } */}
<div className="padding40 bor-bottom-greyE memoReplies commentsDelegateParent"
style={{ display: (comments && !!comments.length) ? 'block' : 'none' }}>
<div className="replies_count">
<span className="labal">全部回复</span>
<span className="count">{memo.replies_count}</span>
</div>
<ImageLayer {...this.state} onImageLayerClose={this.onImageLayerClose}></ImageLayer>
<Comments comments={comments} user={current_user}
replyComment={this.replyComment}
deleteComment={this.deleteComment}
commentPraise={this.commentPraise}
rewardCode={this.rewardCode}
hiddenComment={this.hiddenComment}
></Comments>
{ hasMoreComments ?
<div className="memoMore">
<a onClick={this.moreMemos}>查看更多评论</a>
<div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div>
</div>
:
<div className="memoMore">
<div className="writeCommentBtn" onClick={this.showCommentInput}>写评论</div>
</div>}
</div>
</div>
</div>
</div>
);
}
}
export default MemoDetail;

View File

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
import { queryString } from 'educoder'
class MemoList extends Component {
render() {
const { match, history, currentPage, memo_count ,memo_list, renderMemoList, onPaginationChange } = this.props
return (
<React.Fragment>
<div id="forum_list" className="forum_table">
<div className="mh650 edu-back-white">
{!memo_list || memo_list.length === 0 ?
<div className="edu-tab-con-box clearfix edu-txt-center">
<img className="edu-nodata-img mb20" src="/images/educoder/nodata.png"/>
<p className="edu-nodata-p mb30">没有数据可以显示</p>
</div>
: renderMemoList()
}
</div>
</div>
{ !!memo_count && memo_count > 15 &&
<div style={{ width: '100%', background: '#FAFAFA'}}>
<Pagination className={'ec-pagination'}
onChange={(pageNum, pageSize) => onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/>
</div> }
</React.Fragment>
);
}
}
export default ( MemoList );

View File

@ -0,0 +1,73 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
// import queryString from 'query-string'
import { queryString } from 'educoder'
import MemoList from './MemoList'
class MemoMyPublish extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
}
componentWillUnmount() {
}
componentWillReceiveProps(newProps, newContext) {
}
renderMemoList() {
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count, memo_list } = this.props
return (
<React.Fragment>
<div className="discuss-tab bor-bottom-greyE clearfix pr boxsizing">
<p className="_forum_tab pl20 pr20 clearfix boxsizing" style={{fontSize:'18px', color:'rgba(5,16,26,1)'}}>
我的发布
<Link className="returnBtnA fr mr10" to={`/forums`}><span className="color-grey-9 font-16">返回</span></Link>
</p>
</div>
<MemoList {...this.props} renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.props.onPaginationChange(pageNum, pageSize) }
>
</MemoList>
</React.Fragment>
);
}
}
export default postPaginationHOC({ isMyPublish: true }) ( MemoMyPublish );

View File

@ -0,0 +1,616 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Select, {Option, OptGroup} from 'rc-select';
// demo http://react-component.github.io/upload/examples/simple.html
import Upload from 'rc-upload';
import axios from 'axios'
const $ = window.$;
const isDevServer = window.location.port === "3007";
let origin = window.location.origin;
let path = "/editormd/lib/" // origin + '/react/build/js/editormd/lib/'
if (isDevServer) {
origin = 'http://localhost:3000'
path = 'http://localhost:3000/editormd/lib/'
}
// load
if (!window.postUpMsg) {
$.getScript(
`${origin}/javascripts/attachments.js`,
(data, textStatus, jqxhr) => {
});
}
// editorMD to create
/**
*
* @param id 渲染DOM的id
* @param width 宽度
* @param high 高度
* @param placeholder
* @param imageUrl 上传图片的url
* @returns {*} 返回一个editorMD实例
*/
function create_editorMD(id, width, high, placeholder, imageUrl, callback){
var editorName = window.editormd(id, {
width : width,
height : high,
syncScrolling : "single",
//你的lib目录的路径我这边用JSP做测试的
path : path , // "/editormd/lib/"
tex : true,
tocm : true,
emoji : true,
taskList : true,
codeFold : true,
searchReplace : true,
htmlDecode : "style,script,iframe",
sequenceDiagram : true,
autoFocus: false,
toolbarIcons : function() {
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear" ]
},
toolbarCustomIcons : {
testIcon : "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1 : "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>"
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea : true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity : 0.6,
placeholder: placeholder,
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL : imageUrl,//url
onload: function(){
// this.previewing();
$("#"+ id +" [type=\"latex\"]").bind("click", function(){
editorName.cm.replaceSelection("```latex");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("```");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line-1, 0);
});
$("#"+ id +" [type=\"inline\"]").bind("click", function(){
editorName.cm.replaceSelection("$$$$");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line, __Cursor.ch-2);
editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
callback && callback()
}
});
return editorName;
}
const typeNameMap = {
'技术分享': 5,
'操作指南': 3,
}
const typeNameMap2 = {
5: '技术分享',
3: '操作指南',
}
const defaultType = '技术分享'
class MemoNew extends Component {
constructor(props) {
super(props)
// https://testbdweb.trustie.net/uploads.js?attachment_id=1&filename=jqui.js
// https://ant.design/components/upload-cn/
this.uploaderProps = {
action: '/uploads.js',
data: { attachment_id: 1 }, // , filename: 2
headers: {
Authorization: 'authorization-text',
},
multiple: true,
beforeUpload(file) {
console.log('beforeUpload', file.name);
},
onStart: (file) => {
console.log('onStart', file.name);
// this.refs.inner.abort(file);
},
onSuccess(file) {
console.log('onSuccess', file);
},
onProgress(step, file) {
console.log('onProgress', Math.round(step.percent), file.name);
},
onError(err) {
console.log('onError', err);
},
};
this.state = {
memoSubject: '',
memoContent: '',
memoType: typeNameMap[defaultType],
memoRepertoire: '',
memoLanguage: '',
repertoires: [],
currentSelectRepertoiresIndex: -1,
repertoiresTagMap: {}
}
}
onCommit() {
const { memoSubject, memoRepertoire, memoLanguage, currentMemoId, memoType } = this.state;
const { showSnackbar } = this.props;
if (!memoSubject) {
showSnackbar('请先输入话题名称')
return
}
let mdVal;
try {
mdVal = this.taskpass_editormd.getValue()
} catch (e) {
showSnackbar('编辑器还未加载完毕,请稍后')
return
}
if (!mdVal) {
showSnackbar('请先输入话题内容')
return
}
if (memoType === 5 && (!memoRepertoire || !memoLanguage)) {
showSnackbar('请先选择技术标签')
return
}
/*
<meta content="authenticity_token" name="csrf-param" />
<meta content="G7peAyb1T37RvdwxnVUKmTXuL8T7FaBze5mK0j6MCKs=" name="csrf-token" />
http://localhost:3000/attachments/download/185790/Git-2.17.1.2-32-bit.exe
https://www.educoder.net/attachments/205112.js?attachment_id=1
*/
// collect attachments
const $ = window.$;
const attachmentsMap = {};
$('#attachments_fields .attachment').each(( index, item ) => {
const filename = $(item).find('.upload_filename').val();
// $($('#attachments_fields .attachment')[0]).find('input:nth-child(6)').val()
const token = $(item).find('input:nth-child(6)').val()
const attachment_id = $(item).find('input:nth-child(7)').val()
attachmentsMap[index] = {
filename,
token,
attachment_id
}
})
if (currentMemoId) {
this.updateMemo(attachmentsMap)
} else {
this.newMemo(attachmentsMap)
}
}
onCancel() {
const { currentMemoId, memoType } = this.state;
if (currentMemoId) { // 编辑
this.props.history.push(`/forums/${currentMemoId}`)
} else { // 新建
this.props.history.push(`/forums`)
}
// debugger;this.props.history.goBack()
}
updateMemo(attachmentsMap) {
const { memoSubject, memoRepertoire, memoLanguage, memoType, currentMemoId, content } = this.state;
const mdVal = this.taskpass_editormd.getValue()
console.log('isContentEdit: ', mdVal === content);
const newMemoUrl = `/api/v1/memos/${currentMemoId}/update`
axios.post(newMemoUrl, {
content_changed: this.contentChanged,
memo:{
subject: memoSubject ,
content: mdVal,
forum_id: memoType,
repertoire_name: memoRepertoire,
language: memoLanguage,
//
},
attachments: attachmentsMap
}, {
withCredentials: true,
})
.then((response) => {
const { status, message, memo_id } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${currentMemoId}`)
} else {
this.props.showSnackbar(message)
}
}).catch((error) => {
console.log(error)
})
}
newMemo(attachmentsMap) {
const { memoSubject, memoRepertoire, memoLanguage, memoType } = this.state;
const mdVal = this.taskpass_editormd.getValue()
const newMemoUrl = `/api/v1/memos/create`
axios.post(newMemoUrl, {
memo:{
subject: memoSubject ,
content: mdVal,
forum_id: memoType,
repertoire_name: memoRepertoire,
language: memoLanguage
},
attachments: attachmentsMap
}, {
withCredentials: true,
})
.then((response) => {
const { status, message, memo_id } = response.data;
if (status === 0) {
window.$("html,body").animate({"scrollTop":0})
this.props.history.push(`/forums/${memo_id}`)
} else {
this.props.showSnackbar(message)
}
}).catch((error) => {
console.log(error)
})
}
componentDidMount() {
const newMemoUrl = `/api/v1/memos/new`
axios.get(newMemoUrl,{
withCredentials: true,
})
.then((response) => {
const data = response.data;
const repertoires = [];
const repertoiresTagMap = {}
if ( data.tag_list ) {
data.tag_list.forEach((item, index)=>{
const tagArray = [];
item.tag.forEach( (tag, index) => {
tagArray.push(tag.name)
})
repertoires.push(item.rep.repertoire.name)
repertoiresTagMap[item.rep.repertoire.name] = tagArray
})
this.setState({
repertoires,
repertoiresTagMap
})
const user = response.data.current_user;
user.tidding_count = response.data.tidding_count;
this.props.initCommonState(user)
// 初始化 csrf meta
const $ = window.$
$('head').append( $('<meta content="authenticity_token" name="csrf-param" />') )
$('head').append( $(`<meta content="${response.data.csrf_token}" name="csrf-token" />`) )
}
}).catch((error) => {
console.log(error)
})
// 如果是编辑
const { match } = this.props
const memoId = match.params.memoId;
if (memoId) {
const memoUrl = `/api/v1/memos/${match.params.memoId}/edit`;
axios.get(memoUrl,{
withCredentials: true,
})
.then((response) => {
const tag_list = response.data.tag_list
if (tag_list) {
// this.setState({...response.data})
const { content, forum_id, id, language, repertoire_name, subject,
current_user, tag_list, attachments_url } = response.data;
this.initMD(content);
this.onRepertoiresChange(repertoire_name)
this.setState({
currentMemoId: id,
memoSubject: subject,
memoType: forum_id,
memoRepertoire: repertoire_name,
memoLanguage: language,
attachments_url,
content
// repertoires: [],
// currentSelectRepertoiresIndex: -1,
})
// 加载完后滚动条滚动
window.$("html,body").animate({"scrollTop":0})
this.props.initForumState({
current_user,
tag_list
})
}
}).catch((error) => {
console.log(error)
})
} else {
this.initMD();
}
}
initMD(initValue) {
this.contentChanged = false;
const placeholder = "";
// amp;
// 编辑时要传memoId
const imageUrl = `/upload_with_markdown?container_id=&container_type=Memo`;
// 创建editorMd
const taskpass_editormd = create_editorMD("memoMD", '100%', 400, placeholder, imageUrl, () => {
if (initValue) {
taskpass_editormd.setValue(initValue)
}
taskpass_editormd.cm.on("change", (_cm, changeObj) =>{
console.log('....contentChanged')
this.contentChanged = true;
})
});
this.taskpass_editormd = taskpass_editormd;
window.taskpass_editormd = taskpass_editormd;
}
renderOptions(array) {
const elementArray = [];
array.forEach(( item, index ) => {
elementArray.push(
<Option key={index} value={item}>{item}</Option>
)
})
return elementArray
}
onRepertoiresChange(value) {
const index = this.state.repertoires.indexOf(value)
this.setState({
currentSelectRepertoiresIndex: index,
memoRepertoire: value,
memoLanguage: ''
});
};
onTagChange(value) {
this.setState({
memoLanguage: value
})
}
onTypeChange(value) {
this.setState({
memoType: typeNameMap[value]
})
}
onMemoNameChange(e) {
this.setState({
memoSubject: e.target.value
})
}
renderAttachment() {
const { attachments_url } = this.state;
const attachments = []
attachments_url.forEach((item, index) => {
const ar = item.url.split('/')
const fileName = ar[ar.length - 1]
/*
<p className="clearfix" key={index} >
<a href={item.url} className="color-green clearfix notefileDownload">
<span className="fl">{fileName}</span><i className="iconfont icon-xiazai color-green ml5 fl"></i>
</a>
</p>
*/
// ?attachment_id=2
/*
<span id="attachments_fields" className="attachments_fields" xmlns="http://www.w3.org/1999/html">
</span>
*/
attachments.push(
<React.Fragment>
<span id={`attachments_10${index}`} className="attachment">
<label className="panel-form-label fl">&nbsp;</label>
<i className="fa fa-folder mr5 color-light-grey" aria-hidden="true"></i>
<input type="text" className="upload_filename readonly hidden" name="attachments[2][filename]" readonly="readonly"
style={{border:'none', maxWidth:'980px',whiteSpace: 'nowrap', textOverflow:'ellipsis',fontFamily: 'Consolas'}}
size="8" value={item.filename}></input>
<a href={`/attachments/${item.id}.js?attachment_id=10${index}`} className="remove-upload"
style={{verticalAlign: 'top', display: 'inlineBlock'}} data-remote="true"
data-method="delete">
<i className="fa fa-trash-o mr5"></i>
</a>
<div className="div_attachments" name="div_attachments_xx"></div>
<input type="hidden" name="attachments[xx][token]"
value="185811.24305bb2c4912f715629aa3615cdbabc"></input>
<input type="hidden" name="attachments[xx][attachment_id]" value="185811"></input>
</span>
<div className="cl"></div>
</React.Fragment>
)
})
return attachments;
}
render() {
const { match, history } = this.props
const { repertoires, memoSubject, repertoiresTagMap, currentSelectRepertoiresIndex, memoType, memoRepertoire,
memoLanguage, attachments_url } = this.state;
return (
<div >
<div className="pt20 pl20 pr20 pb20 bor-bottom-greyE clearfix" style={{background: '#fff'}}>
<span className="fl font-16">发布话题</span>
{/*<a href="/shixuns/mf98sgi3/settings" className="ring-green mt7 fr" id="edit_setting"
data-remote="true" data-tip-down="编辑"><img src="/images/educoder/icon/edit.svg" className="fl ml2 mt3">
</a>*/}
</div>
<div className="edu-back-white mb10 clearfix" id="memoSubject">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">话题名称</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<input type="text" className="input-100-45 greyInput" maxlength="50"
value={memoSubject} onChange={(val)=>this.onMemoNameChange(val)} placeholder="">
</input>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div>
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">内容</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<div className="flex1 break_word show_content_grey new_li" id="memoMD">
<textarea style={{'display':'none'}}></textarea>
</div>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
<form className="newForm">
<span id={`attachments_fields`} className="attachments_fields"
xmlns="http://www.w3.org/1999/html">
{ attachments_url && !!attachments_url.length &&
this.renderAttachment()
}
</span>
<span className="add_attachment">
<input className="file_selector" data-are-you-sure="您确定要删除吗?"
data-delete-all-files="您确定要删除所有文件吗" data-description-placeholder="可选的描述"
data-field-is-public="公开" data-file-count="个文件已上传"
data-lebel-file-uploding="个文件正在上传" data-max-concurrent-uploads="2"
data-max-file-size-message="该文件无法上传。超过文件大小限制 (50 MB)建议上传到百度云等其他共享工具里然后在txt文档里给出链接以及共享密码并上传"
data-max-file-size="52428800" data-upload-path="/uploads.js" id="_file"
multiple="multiple" name="attachments[dummy][file]"
onChange={()=>{debugger;window.addInputFiles( window.$('.file_selector')[0] ) }}
style={{'display':'none'}} type="file">
</input>
</span>
</form>
{/*<Upload {...this.uploaderProps} ref="inner"><a>开始上传</a></Upload>*/}
{/* 请求status 422 */}
<div className="df uploadBtn">
<a href="javascript:void(0);" className="fl" onClick={()=>window.$('#_file').click()}
data-tip-down="请选择文件上传">
<i className="fa fa-upload mr5"></i>
<span>上传附件</span>
</a>
</div>
</div>
</div>
{/* TODOTODO 这里重复的html代码太多如果有其他页面有类似需求需要封装*/}
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">话题类型</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="flex1 mr20">
<Select className="ecSelect" value={typeNameMap2[memoType]}
onChange={(val)=>this.onTypeChange(val)}>
<Option value="技术分享">技术分享</Option>
<Option value="操作指南">操作指南</Option>
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div>
{ memoType === typeNameMap['技术分享'] &&
<div className="edu-back-white mb10 clearfix">
<div className="padding30-20">
<p className="color-grey-6 font-16 mb30">技术标签</p>
<div className="df">
<span className="mr30 color-orange pt10">*</span>
<div className="mr20">
<Select className="ecSelect" placeholder="请选择大标签" onChange={(e) => this.onRepertoiresChange(e)}
value={memoRepertoire}
>
{this.renderOptions(repertoires)}
</Select>
</div>
<div className="flex1 mr20">
<Select className="ecSelect" placeholder="请选择小标签"
onChange={(e) => this.onTagChange(e)} value={memoLanguage}
dropdownStyle={{'maxHeight': '300px', 'overflow': 'auto'}} >
{ currentSelectRepertoiresIndex >= 0
&& this.renderOptions(repertoiresTagMap[repertoires[currentSelectRepertoiresIndex]])}
</Select>
</div>
<div style={{width: '57px'}}>
<span className="color-orange mt8 fl none" >
<i className="fa fa-exclamation-circle mr3"></i>
</span>
</div>
</div>
</div>
</div> }
<div className="clearfix mt30">
<a href="javascript:void(0)" className="defalutSubmitbtn fl mr20"
onClick={()=>{this.onCommit()}}>提交</a>
<a onClick={()=>{ this.onCancel() }} className="defalutCancelbtn fl">取消</a>
</div>
</div>
);
}
}
export default MemoNew;

View File

@ -0,0 +1,65 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoGuide extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoGuide );

View File

@ -0,0 +1,84 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoHottest extends Component {
constructor(props) {
super(props)
this.handleLocationChange = this.handleLocationChange.bind(this);
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
// this.handleLocationChange(this.props.history.location);
this.unlisten = this.props.history.listen(this.handleLocationChange);
}
componentWillUnmount() {
this.unlisten();
}
handleLocationChange(location) {
// your staff here
console.log(`- - - location: '${location.pathname}'`);
if (location.pathname && location.pathname.indexOf('/forums/categories/all') != -1
&& this.locationSearch != location.search) {
this.props.fetchMemos();
}
this.locationSearch = location.search;
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoHottest );

View File

@ -0,0 +1,63 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
class MemoNewest extends Component {
constructor(props) {
super(props)
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} memo={item} user={user} index={index} {...this.props}></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<div id="forum_list" className="forum_table mh650">
{this.renderMemoList()}
{ !!memo_count && <Pagination onChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
showQuickJumper current={currentPage} total={memo_count} pageSize={15}/> }
</div>
</React.Fragment>
);
}
}
export default postPaginationHOC( MemoNewest );

View File

@ -0,0 +1,116 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import { postPaginationHOC } from './PostPaginationHOC'
import PostItem from './PostItem'
import ForumsNavTab from './ForumsNavTab'
// import queryString from 'query-string'
import { queryString } from 'educoder'
import MemoList from './MemoList'
class MemoTechShare extends Component {
constructor(props) {
super(props)
this.handleLocationChange = this.handleLocationChange.bind(this);
this.state = {
}
}
onPaginationChange(pageNum, pageSize) {
this.props.onPaginationChange(pageNum, pageSize)
}
componentDidMount() {
// this.handleLocationChange(this.props.history.location);
// this.unlisten = this.props.history.listen(this.handleLocationChange);
}
componentWillUnmount() {
// this.unlisten();
}
componentDidUpdate(prevProps) {
if(this.props.match.params.memoType !== prevProps.match.params.memoType) {
// do something
console.log(`memoType changed`)
this.props.fetchMemos();
}
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.match.url === this.props.match.url) {
const oldParsed = queryString.parse(this.props.location.search);
const newParsed = queryString.parse(newProps.location.search);
if (!newParsed.page && oldParsed.page ||
(oldParsed.order && newParsed.order && oldParsed.order != newParsed.order)) {
this.props.fetchMemos();
}
console.log('componentWillReceiveProps...')
}
}
handleLocationChange(location) {
console.log(`- - - location: '${location.pathname}'`);
if (location.pathname) {
if (location.pathname.indexOf('/forums/categories/all') != -1
&& this.props.location.search && this.props.location.search.indexOf('order=') != -1
&& location.search.indexOf('order=') != -1) {
const oldParsed = queryString.parse(this.props.location.search);
const newParsed = queryString.parse(location.search);
if (oldParsed.order != newParsed.order) { // 只有在热门和最新间跳转时,才需要处理
this.props.fetchMemos();
}
}
}
}
renderMemoList() {
// const { memo_list, user } = this.props;
// if (!memo_list) {
// return ''
// }
// return memo_list.map( (item, index) => {
// return (
// <PostItem key={item.id} user={user} index={index} {...this.props}
// setTop={(memo)=>this.setTop(memo)}
// setDown={(memo)=>this.setDown(memo)} memo={item}
// ></PostItem>
// )
// })
return this.props.renderMemoList();
}
render() {
const { match, history, currentPage, memo_count ,memo_list } = this.props
return (
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<MemoList {...this.props} renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.props.onPaginationChange(pageNum, pageSize) }
>
</MemoList>
</React.Fragment>
);
}
}
export default postPaginationHOC() ( MemoTechShare );

View File

@ -0,0 +1,177 @@
/*MemoDetail --------------------------------- START */
/* 左侧区域最小高度*/
#forum_index_list {
min-height: 400px;
position: relative;
}
.noMemosTip {
position: absolute;
right: 10px;
top: 58px;
z-index: 999;
}
#forum_list {
background: #f9f9f9;
}
div#forum_list>div {
background: #fff;
}
.memoContent img {
max-width: 815px;
}
.memoReplies {
position: relative;
margin-top: 8px;
}
.memoReplies .-fit {
position: static;
}
.replies_count {
margin-left: 12px;
}
.replies_count .label {
color: #666666;
}
.replies_count .count {
color: #999999;
margin-left: 10px;
}
.memoMore {
padding-top: 10px;
height: 50px;
line-height: 50px;
text-align: center;
color: rgba(69,155,230,1);
cursor: pointer;
position: relative;
}
.memoMore .writeCommentBtn{
position: absolute;
right: 0px;
color: #666666;
top: 15px;
}
/*帖子回復按鈕*/
a.commentsbtn.task-btn-blue {
background: #4CACFF !important;
margin-right: 26px;
}
.iconfont.icon-xiazai {
font-size: 22px!important;
margin-right: 6px;
}
/* MemoDetail --------------------------------- END */
/* PostItem --------------------------------- START */
.forum_table_item {
padding-left: 20px;
}
/* 置顶 */
.forum_table_item .btn-top {
border-radius: 11px;
padding: 0px 6px;
background: #FF4343;
}
/* 管理员操作 */
.edu-position-hide {
position: absolute;
top: 15px;
left: -20px;
box-shadow: 0px 2px 8px rgba(146, 153, 169, 0.5);
background: #fff;
z-index: 1001;
padding: 5px 0;
z-index: 999999;
}
.edu-position-hide li a:hover {
background: #4CACFF;
color: #fff;
}
.edu-position-hidebox>a:link{
color: #4CACFF;
}
.edu-position-hidebox:hover .edu-position-hide {
display: block;
}
.edu-position-hide li a {
display: inline-block;
height: 30px;
width: 100px;
line-height: 30px;
text-align: center;
font-size: 12px!important;
}
/* PostItem --------------------------------- END */
/* MemoNew --------------------------------- START */
/* rc-select样式覆写*/
.ecSelect {
width: 200px;
}
.ecSelect .rc-select-selection {
height: 40px;
}
.ecSelect .rc-select-search--inline .rc-select-search__field {
padding-top: 6px;
}
.ecSelect .rc-select-selection--single .rc-select-selection-selected-value
, .ecSelect .rc-select-selection__placeholder {
top: 6px;
}
.ecSelect .rc-select-arrow {
top: 6px;
}
.newFooter {
z-index: 88 !important;
}
.defalutCancelbtn {
cursor: pointer;
}
#attachments_fields {
margin-left: -77px;
display: flex;
flex-direction: column;
}
.uploadBtn {
margin-left: 46px;
}
#memoMD.show_content_grey {
padding: 0;
}
.newForm .attachments_fields {
/*margin-left: -39px !important*/
}
/* MemoNew --------------------------------- END */
/*RightMyPublish*/
.publishMemoSection {
padding-bottom: 0px !important;
}
.advertisement {
margin-top: 10px;
height: 155px;
}
.advertisement img{
width: 100%;
}
/* MyPublish*/
.returnBtn {
font-size:16px;
color:rgba(153,153,153,1);
float: right;
margin-right: 50px;
position: relative;
bottom: 12px;
}

View File

@ -0,0 +1,102 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import moment from 'moment'
class PostItem extends Component {
_toTenThousand(num) {
if (num > 10000) {
return ( (num - 500)/ 10000 ).toFixed(1) + '万'
}
return num
}
render() {
const { match, history, currentPage, memo, user, setTop, setDown } = this.props
return (
<div class="forum_table_line pl20">
<div className="forum_table_item" id={`memo_detail_${memo.id}`}>
<a href={`/users/${memo.login}`} className="fr mr15">
<img alt="用户头像" className="bor-radius-all mt3" height="50" src={`/images/${memo.image_url}`} width="50">
</img>
</a>
<div className="fl pr" style={{flex: 1}}>
<p className="font-16 clearfix" >
{/* target="_blank" */}
<Link to={`/forums/${memo.id}`} target="_blank" className="clearfix task-hide item_name fl" style={{maxWidth: '750px'}} >
{memo.subject}
</Link>
{ memo.sticky && <span className="btn-top btn-cir-orange mt6 ml5 fl">置顶</span> }
{ memo.reward &&
<span className=" ml10 fl color-orange03" data-tip-down={`获得平台奖励金币:${memo.reward}`}>
<i className="fa fa-gift fa-lg mr5"></i>{memo.reward}
</span>
}
</p>
<div className="clearfix mt5 color-grey-9">
<span className="fl">{memo.username}</span>
{/*todo{memo.username}
memo.language && memo.language != 'other' && <span className="fl language-cir-orange mr10 mt3 ml6">{memo.language}</span>
*/}
<span className="fl ml50">{moment(memo.updated_at).fromNow()}</span>
{memo.language && <span className="fl ml50">来自 {memo.language}</span> }
{/*<span className="fl language-cir-orange mr10 mt3">C++</span>*/}
<p className="font-12 fr mr8 color-grey-6">
{/* data-tip-down="回复数" <i className="fa fa-comments-o mr5"></i>{memo.replies_count}
<i className="fa fa-thumbs-o-up mr5"></i>{memo.praise_count}*/}
{memo.replies_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default'}} >
{memo.replies_count} 回复
</span> :''}
{memo.praise_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default'}} >
{memo.praise_count}
</span> :''}
{memo.viewed_count ?
<span className="mr10 ml10 fl edu-txt-right" style={{cursor: 'default',minWidth:'55px'}}>
{this._toTenThousand(memo.viewed_count)} 浏览
</span> :''}
</p>
</div>
{ user && (user.admin === true || user.user_id === memo.author_id) &&
<div className="edu-position-hidebox" style={{position: 'absolute', right: '18px',top:'0px'}}>
<a href="javascript:void(0);"><i className="fa fa-bars font-16"></i></a>
<ul className="edu-position-hide undis">
{ user.admin === true &&
( memo.sticky === true ?
<li><a href="javascript:void(0);" onClick={() => setDown(memo)}>取消置顶</a></li>
:
<li><a href="javascript:void(0);" onClick={() => setTop(memo)}>&nbsp;&nbsp;</a></li> )
}
<li><Link to={`/forums/${memo.id}/edit`}>&nbsp;&nbsp;</Link></li>
<li>
<a href="javascript:void(0)" onClick={() =>
window.delete_confirm_box_2_react(`onMemoDelete`, '您确定要删除吗?' , memo)}>
&nbsp;&nbsp;</a>
</li>
</ul>
</div>
}
</div>
</div>
</div>
);
}
}
export default PostItem

View File

@ -0,0 +1,345 @@
import React from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
import update from 'immutability-helper'
import PostItem from './PostItem'
import { CircularProgress } from 'material-ui/Progress';
import { queryString } from 'educoder'
import { on, off, updatePageParams } from 'educoder'
import './Post.css'
const $ = window.$
function urlStringify(params) {
let noParams = true;
let paramsUrl = '';
for (let key in params) {
noParams = false;
paramsUrl += `${key}=${params[key]}&`
}
if (noParams) {
return '';
}
paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1);
return paramsUrl;
}
export function postPaginationHOC(options = {}) {
// options.isMyPublish
return function wrap(WrappedComponent) {
return class II extends React.Component {
constructor(props) {
super(props)
this.state = {
currentPage: 1,
loadingMemos: true
}
}
componentDidMount() {
$('body>#root').on('onMemoDelete', (event) => {
// const val = $('body>#root').data('onMemoDelete')
const val = window.onMemoDelete ;
this.onMemoDelete( JSON.parse(decodeURIComponent(val)) )
})
window.$('#shixun_search_input').val('')
this.props.setSearchValue('')
this.fetchMemos(null, '')
const that = this;
$(window).on('popstate', (e) => {
var state = e.originalEvent.state;
console.log('popstate', state)
if (state !== null) {
let currentPage = that.state.currentPage;;
// // 浏览器地址改变了
const search = that.props.history.location.search
const parsed = queryString.parse(search);
if (parsed.page != currentPage) {
currentPage = parseInt( parsed.page || 1)
// that.setSearchValue('')
that.fetchMemos(currentPage)
this.setState({
currentPage,
})
}
}
});
// RightMyPublish组件发过来的消息
// $(window).on('setSearchValue', (event, val, noFetch)=>{
// })
on('hotTagClick', (event, tagName, index) => {
this.props.setHotLabelIndex(tagName.selectedHotLabelIndex, () => {
this.fetchMemos(1, undefined)
})
})
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.enterKeyFlag !== this.props.enterKeyFlag) {
const childPath = this.props.match.path.split('/:')[0]
// 加入一个浏览地址
const _search = this.props.location.search;
if (_search) {
const parsed = queryString.parse(_search);
if (parsed.page != 1) {
parsed.page = 1;
this.props.history.push(`${this.props.match.url}?${queryString.stringify(parsed)}`)
}
}
this.fetchMemos(1, newProps.searchValue, newProps.selectedHotLabelIndex) // 搜索框模糊搜索,重置为第一页
}
}
componentWillUnmount() {
$(window).off('setSearchValue')
$(window).off('popstate')
off('hotTagClick')
}
fetchMemos(arg_currentPage, arg_searchValue, arg_selectedHotLabelIndex) {
let { match, history } = this.props
let searchValue = arg_searchValue != undefined ? arg_searchValue : this.props.searchValue
// 根据参数初始化页数
const memoType = match.params.memoType;
const urlArray = match.url.split('/')
const lastPath = urlArray[2]
// 1 问题反馈
// 3 操作指南 5 技术分享
const memoTypeMap = {
'guide': 3,
'techShare': 5
}
const orderTypeMap = {
'hottest': 'replies_count',
'newest': 'created_at',
}
const _search = this.props.history.location.search;
const parsed = queryString.parse(_search);
let currentPage = parseInt( arg_currentPage ? arg_currentPage : (parsed.page || 1) )
const params = {
// replies_count最热 created_at 最新
// s_order: 'replies_count',
page: currentPage - 1,
// forum: // forum_id
// user_id
}
if (searchValue) {
params.search = searchValue.trim()
}
let orderType = ''
if (memoType==='all') {
orderType = parsed.order || 'hottest';
params.order = orderTypeMap[orderType]
} else if (options.isMyPublish) {
params.user_id = -1
} else if (memoType) {
params.forum = memoType
}
let { selectedHotLabelIndex, hot_tags } = this.props;
selectedHotLabelIndex = arg_selectedHotLabelIndex ? arg_selectedHotLabelIndex : selectedHotLabelIndex
if (selectedHotLabelIndex !== -1 && hot_tags[selectedHotLabelIndex]) {
params.language = encodeURIComponent(hot_tags[selectedHotLabelIndex])
}
let paramsUrl = queryString.stringify(params)
const memosUrl = '/api/v1/memos?' + paramsUrl // /${challenge.identifier}/star
this.setState({
currentPage,
loadingMemos: true,
orderType: orderType
})
// 获取memo list
axios.get(memosUrl,{
withCredentials: true,
})
.then((response) => {
const memo_count = response.data.memo_count
if (memo_count>=0) {
const maxPage = Math.ceil( memo_count / 15 )
// page超出显示最后一页
if (maxPage != 0 && maxPage < currentPage) {
this.fetchMemos(maxPage);
return;
}
const user = response.data.current_user;
user.tidding_count = response.data.tidding_count;
this.props.initCommonState(user)
this.props.initForumState(response.data)
this.setState({
p_forum_id: params.forum,
p_s_order: params.s_order,
loadingMemos: false
})
}
}).catch((error) => {
console.log(error)
})
}
onCurrentPageChange(pageNum) {
this.setState({
currentPage: pageNum
})
}
onPaginationChange(pageNum, pageSize) {
window.$("html,body").animate({"scrollTop":0})
updatePageParams(pageNum, this.props)
// 加入一个浏览地址
// const params = {
// page: pageNum
// }
// if (this.state.orderType) {
// params.order = this.state.orderType;
// }
// this.props.history.push(`${url}?${queryString.stringify(parsed)}`)
this.fetchMemos(pageNum);
// this.setState({
// currentPage: pageNum
// })
}
// 置顶
setTop(memo) {
const params = {
sticky: memo.sticky ? 0 : 1,
}
if (this.state.p_s_order) {
params.order = this.state.p_s_order;
}
if (this.state.p_forum_id) {
params.forum_id = this.state.p_forum_id;
}
let paramsUrl = urlStringify(params)
const set_top_or_down_Url = `/api/v1/memos/${memo.id}/set-top-or-down?${paramsUrl}`;
// 获取memo list
axios.get(set_top_or_down_Url, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
const { memo_list } = response.data;
this.props.initForumState({ memo_list })
// 刷新列表
// TODO 服务端直接返回第一页列表
// this.props.history.replace('/')
}
}).catch((error) => {
console.log(error)
})
}
// 取消置顶
setDown(memo) {
this.setTop(memo);
}
onMemoDelete(memo) {
const deleteUrl = `/api/v1/memos/${memo.id}`;
// 获取memo list
axios.delete(deleteUrl, {
withCredentials: true,
})
.then((response) => {
const status = response.data.status
if (status === 0) {
this.props.showSnackbar('删除成功');
// 刷新列表
this.fetchMemos();
}
}).catch((error) => {
console.log(error)
})
}
// item渲染
//
renderMemoList() {
const { memo_list, user } = this.props;
if (!memo_list) {
return ''
}
return memo_list.map( (item, index) => {
return (
<PostItem key={item.id} user={user} index={index} {...this.props}
setTop={(memo)=>this.setTop(memo)}
setDown={(memo)=>this.setDown(memo)}
memo={item}
></PostItem>
)
})
}
render() {
const { loadingMemos } = this.state;
const { memo_list, searchValue, showSearchValue, memo_count, selectedHotLabelIndex, hot_tags} = this.props;
// 规则: 搜索框输入了值 或者 选择了热门标签的时候显示该提示
const _showSearchValue = showSearchValue || selectedHotLabelIndex != -1
let _searchValue;
if (showSearchValue) {
_searchValue = searchValue
} else if (selectedHotLabelIndex != -1){
_searchValue = hot_tags[selectedHotLabelIndex].name || hot_tags[selectedHotLabelIndex]
}
return (
<div className="edu-back-white" id="forum_index_list"> {/* fl with100 */}
<div className="clearfix">
{ _showSearchValue &&
<div className="noMemosTip" style={{display: loadingMemos ? 'none' : 'block'}}>
<span className="fr pr20" id="search_result">
共找到&nbsp;
<span className="color-orange03">{memo_count}</span>"
<span class="color-orange03">{_searchValue}</span>"
</span>
</div> }
<CircularProgress size={40} thickness={3}
style={{ marginLeft: 'auto', marginRight: 'auto', paddingTop: '20%'
, display: loadingMemos ? 'block': 'none' }}/>
{ !loadingMemos &&
<WrappedComponent {...this.props} {...this.state}
onPaginationChange={(pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize)}
onCurrentPageChange={(pageNum, pageSize) => this.onCurrentPageChange(pageNum, pageSize)}
renderMemoList={() => this.renderMemoList()}
fetchMemos={(arg1, arg2) => this.fetchMemos(arg1, arg2)}
></WrappedComponent>
}
</div>
</div>
)
}
}
}
}

View File

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class RecommendShixun extends Component {
constructor(props) {
super(props)
this.state = {
}
}
showRecommandShixun(){
const { recommend_shixuns } = this.props;
if (!recommend_shixuns) {
return '';
}
const result = [];
recommend_shixuns.forEach((shixun, index) => {
const _shixun = shixun
result.push(
<div className="recomments clearfix df" key={index}>
<a href={`/shixuns/${_shixun.identifier}/challenges`} style={{height:'76px'}} target="_blank">
<img alt={`${_shixun.id}`} style={{maxHeight:'76px'}} src={`/images/${_shixun.image_url}`} width="100" >
</img>
</a>
<div className="ml10 flex1">
<a href={`/shixuns/${_shixun.identifier}/challenges`} target="_blank" data-tip-down={_shixun.name}
className="color-grey-6 task-hide mb12 recomment-name" style={{maxWidth:'147px'}}>
{_shixun.name}
</a>
<p className="color-grey-9">{_shixun.myshixuns_count} 人学习</p>
</div>
</div>
)
})
return result;
}
render() {
const { match, history, currentPage } = this.props
// 参考 TPMShixunDiscuss.js 推荐实训, 页面路径http://localhost:3007/shixuns/uznmbg54/shixun_discuss
return (
<div className="padding10">
<p className="mb20 font-16 clearfix">推荐实训</p>
<div className="recommend-list">
{this.showRecommandShixun()}
</div>
</div>
);
}
}
export default RecommendShixun;

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { trigger } from 'educoder'
class RightHotLabel extends Component {
constructor(props) {
super(props)
this.state = {
}
}
renderTags() {
const { hot_tags, selectedHotLabelIndex } = this.props;
if (!hot_tags) {
return ''
}
const result = hot_tags.map((item, index) => {
let params = {}
if (typeof item === 'string') {
params.name = item;
} else {
params = item;
}
params.selectedHotLabelIndex = index;
return (
<a href="javascript:void(0)" onClick={() => trigger('hotTagClick', params)} key={index}
className={classNames({"selected": selectedHotLabelIndex === index})}>
{item.name || item}
</a> )
})
return result
}
render() {
const { match, history, currentPage, selectedHotLabelIndex } = this.props
return (
<div className="clearfix padding40-20 edu-back-white mt10">
<p className="font-16">热门标签</p>
<div className="mt30 HotLabelList clearfix" id="HotLabelList">
{this.renderTags()}
</div>
</div>
);
}
}
export default RightHotLabel;

View File

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
class RightHotQuestion extends Component {
constructor(props) {
super(props)
this.state = {
}
}
//
renderHotMemos() {
const { hot_memos } = this.props;
if (!hot_memos) {
return ''
}
return hot_memos.map((item, index) => {
return <div className="hotQuestionItem" key={index}>
<Link to={`/forums/${item.id}`} className="color-grey-6 task-hide mb5 questiontName">
{item.subject}
</Link>
<p className="clearfix font-12 color-grey-9">
<span className="fl">{item.replies_count} 回答</span>
{ !!item.language && <span className="fr">来自 {item.language}</span> }
</p>
</div>
})
}
render() {
const { match, history, currentPage } = this.props
return (
<div className="clearfix padding40-20 edu-back-white mt10">
<p className="font-16">热门问题</p>
<div className="mt10 hotQuestionList clearfix" id="hotQuestionList">
{this.renderHotMemos()}
</div>
</div>
);
}
}
export default RightHotQuestion;

View File

@ -0,0 +1,42 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { trigger } from 'educoder'
class MemoLabel extends Component {
constructor(props) {
super(props)
this.state = {
}
}
renderTags() {
const { memo } = this.props;
if (!memo || memo.tag == null) {
return ''
}
return <a href="javascript:void(0)">{memo.tag}</a>
}
render() {
const { match, history, currentPage } = this.props
return (
<div className="clearfix padding30-20 edu-back-white mt10">
<p className="font-16">话题标签</p>
<div className="mt30 HotLabelList clearfix" id="HotLabelList">
{this.renderTags()}
</div>
</div>
);
}
}
export default MemoLabel;

View File

@ -0,0 +1,76 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import { getImageUrl, toPath } from 'educoder'
import match_adImg from '../../images/ad/match_ad.jpg'
const $ = window.$
class RightMyPublish extends Component {
constructor(props) {
super(props)
this.state = {
}
}
handleKeyPress = (event) => {
if(event.type !== 'keypress' || event.key == 'Enter'){
this.props.setSearchValue( this.props.searchValue, true);
// $(window).trigger('setSearchValue', $('#shixun_search_input').val())
}
}
handleInput = (event) => {
this.props.setSearchValue(event.target.value);
}
render() {
const { match, history, currentPage, my_memos_count, setSearchValue, searchValue } = this.props
return (
<React.Fragment>
<div className="clearfix edu-back-white padding40-20 publishMemoSection">
{/*<div className="searchFor h40 mt15 mb5 ml20">
<div className="searchCon fl">
<input type="text" className="searchinput" name="search" value="" placeholder="请输入帖子标题的关键字进行搜索">
</input>
<span className="search_close" onclick="colse_searchbox();" data-tip-down="清除">×</span>
</div>
<i className="fa fa-search mr5 fl color-dark-grey search_icon"
onClick="$('#search_memos').submit();" style={{margin:'8px'}} data-tip-down="搜索"></i>
</div>*/}
<div className="search-new">
<input type="text" className="search-new-input fl" placeholder="搜索您想了解的话题" id="shixun_search_input"
onKeyPress={this.handleKeyPress} onChange={ this.handleInput } value={searchValue}
>
</input>
<span className="search-span"></span>
<img src={getImageUrl("images/educoder/icon/search.svg")} className="fl mt5"
onClick={ this.handleKeyPress }>
</img>
</div>
<Link to={'/forums/new'} className="sendMyQuestion edu-default-btn edu-blueback-btn edu-txt-center font-16 mb30">发布话题</Link>
{/*<p className="edu-txt-center font-16">
<span>我的发布</span><br/>
<Link to={`/forums/categories/my_published`} className="color-blue">{my_memos_count}</Link>
</p>*/}
</div>
<div className="clearfix edu-back-white advertisement" >
<a href="/competitions/home" target="_blank"><img src={match_adImg}></img></a>
</div>
</React.Fragment>
);
}
}
export default RightMyPublish;

View File

@ -0,0 +1,52 @@
/* 右侧搜索区域*/
.searchFor .searchCon {
width: 215px;
}
.search-new {
width:237px!important;
height: 30px;
margin-bottom: 30px;
}
.search-new-input {
padding-left: 16px;
height: 30px;
}
.search-span {
border-radius: 17px;
}
.search-new img {
right: 10px;
}
/* 右侧 热门标签 */
.HotLabelList a{display: block;float: left;padding: 0px 9px;height: 28px;line-height: 28px;border-radius: 14px;background-color: #f5f5f5;color: #666;margin-right: 10px;margin-bottom: 9px;}
.HotLabelList a.selected {
background: #4CACFF;
color: #fff;
}
/* 右侧 热门问题 */
.hotQuestionItem{padding:20px 0px;border-bottom: 1px solid #eee;}
.questiontName{max-width: 100%;display: block;}
/* 用户信息-UserSection*/
.user_default_btn {width: 114px;}
.userPrivateName{line-height: 25px;margin-bottom: 9px;}
.userPrivatePost{line-height: 20px;}
.noteDetailTitle{max-width: 650px;line-height: 38px;font-size: 24px;font-weight: normal}
.noteDetailNum{float: left;padding:0px 12px;position: relative;color: #999!important;height: 28px;line-height: 26px;}
.noteDetailNum.rightline:after{position: absolute;content: '';right: 0px;width: 1px;background-color: #EAEAEA;height: 8px;top:10px;}
/*帖子详情点赞*/
.noteDetailPoint{width: 100px;height: 70px;background-color: #4cacff;border-radius: 35px;color: #FFFFff;text-align: center;margin: 0px auto;box-sizing: border-box;padding: 2px 0px;cursor: pointer; line-height: 22px;
padding-top: 12px;}
.Pointed{background-color:#f0f0f0;color: #b3b3b3; cursor: default}
.notefileDownload{height: 25px;line-height: 22px;}

View File

@ -0,0 +1,260 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import Loading from '../../Loading'
import Loadable from 'react-loadable';
import classNames from 'classnames'
import MemoTechShare from './MemoTechShare'
// import MemoGuide from './MemoGuide'
// import MemoNewest from './MemoNewest'
// import MemoHottest from './MemoHottest'
import MemoDetail from './MemoDetail'
import MemoNew from './MemoNew'
import MemoMyPublish from './MemoMyPublish'
import MemoShixun from './shixun/MemoShixun'
import { TPMIndexHOC } from '../tpm/TPMIndexHOC'
import RightMyPublish from './RightMyPublish'
import UserSection from './UserSection'
import RightHotLabel from './RightHotLabel'
import RightHotQuestion from './RightHotQuestion'
import RightMemoLabel from './RightMemoLabel'
import RecommendShixun from './RecommendShixun'
import ForumsNavTab from './ForumsNavTab'
import axios from 'axios'
import 'rc-select/assets/index.css';
import './ForumsIndex.css'
import './RightSection.css'
import { SnackbarHOC } from 'educoder'
const isPort3007 = window.location.port == 3007;
let _url_origin = isPort3007 ? 'http://localhost:3000' : ''
const $ = window.$
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/css/edu-admin.css?1525440977`) );
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/css/edu-forum.css?1525440977`) );
$('head').append( $('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/stylesheets/educoder/magic-check.css?1525440977`) );
class ForumsIndex extends Component {
constructor(props) {
super(props)
this.state = {
searchValue: '',
enterKeyFlag: false,
showSearchValue: false,
selectedHotLabelIndex: -1,
}
}
setSearchValue = (searchValue, enterKeyFlag) => {
if (enterKeyFlag === true) {
this.setState({
selectedHotLabelIndex: -1
})
}
this.setState({
searchValue,
showSearchValue: (enterKeyFlag && searchValue) ? true : false,
enterKeyFlag: enterKeyFlag === true ? !this.state.enterKeyFlag : this.state.enterKeyFlag
})
}
setHotLabelIndex = (index, callback) => {
const newState = {
selectedHotLabelIndex: index,
}
if (index != -1) {
newState.searchValue = ''
newState.showSearchValue = false
}
this.setState({
...newState
}, callback)
}
initForumState(data) {
this.setState({...data})
}
componentDidMount() {
window.document.title = '讨论区'
}
componentWillReceiveProps(newProps, newContext) {
}
render() {
const { match, history, resLoading } = this.props
const { memo } = this.state;
const techSharePath = `${match.path}/categories/:memoType`
const guidePath = `${match.path}/categories/:memoType`
const hottestPath = `${match.path}/categories/:memoType` // ?order=hottest
const newestPath = `${match.path}/categories/:memoType` // ?order=newest
const shixunDiscussPath = `/tpforums/categories/shixun_discuss`
const locationPath = history.location.pathname
const isWidth100 = (locationPath.indexOf('tpforums/new') !== -1
|| locationPath.indexOf('/edit') !== -1) ? true : false
const pathArray = locationPath.split('/');
const isMemoDetail = (!isWidth100 &&
pathArray.length === 3 && !isNaN(parseInt(pathArray[2])) ) ? true : false
const isGuide = locationPath.indexOf('/tpforums/categories/3') !== -1
return (
<div className="newMain clearfix">
<div className="educontent mt30 clearfix">
{/* component={TechShare}
<ForumsNavTab {...this.props}></ForumsNavTab> */}
<div className={classNames('fl', { with75: !isWidth100}, { width100: isWidth100}) }>
<Switch>
<Route path={`/tpforums/categories/my_published`} render={
(props) => (<MemoMyPublish {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
<Route path={`${shixunDiscussPath}`} render={
(props) => (<MemoShixun {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
<Route path={`${techSharePath}`} render={
(props) => (<MemoTechShare {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
setSearchValue={this.setSearchValue}
setHotLabelIndex={this.setHotLabelIndex}
/>)
}></Route>
{/*
<Route path={`${guidePath}`} render={
(props) => (<MemoGuide {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route>
<Route path={`${hottestPath}`} render={
(props) => (<MemoHottest {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route>
<Route path={`${newestPath}`} render={
(props) => (<MemoNewest {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)} />)
}></Route> */}
{/* :forumTypeId/ */}
<Route path={`/tpforums/new`} render={
(props) => (<MemoNew {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Route path={`/tpforums/:memoId/edit`} render={
(props) => (<MemoNew {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Route path={`${match.path}/:memoId`} render={
(props) => (<MemoDetail {...this.props} {...this.state} {...props}
initForumState={(data)=>this.initForumState(data)}
/>)
}></Route>
<Redirect from={`${match.url}`} to={`/tpforums/categories/all?order=newest`} />
</Switch>
</div>
{/* 右边栏 */}
{ !isWidth100 && <div className="with25 fl">
<div className="ml20">
{isMemoDetail ?
<React.Fragment>
<UserSection {...this.props} {...this.state} initForumState={(data)=>this.initForumState(data)} ></UserSection>
{/*todo 新增RightMemoLabel 和 推荐实训RecommendShixun */}
{ memo && memo.tag && <RightMemoLabel {...this.props} {...this.state}></RightMemoLabel> }
<RecommendShixun {...this.props} {...this.state}></RecommendShixun>
</React.Fragment>
:
<React.Fragment>
<RightMyPublish {...this.props} {...this.state} setSearchValue={this.setSearchValue}></RightMyPublish>
{ !isGuide && <RightHotLabel {...this.props} {...this.state} ></RightHotLabel> }
<RightHotQuestion {...this.props} {...this.state} ></RightHotQuestion>
<RecommendShixun {...this.props} {...this.state}></RecommendShixun>
</React.Fragment>
}
</div>
</div>
}
</div>
</div>
);
}
}
export default SnackbarHOC() ( TPMIndexHOC ( ForumsIndex ) );
/*
:
列表所有
http://localhost:3000/forums/categories/all
:
详情
:
http://localhost:3000/forums/5
:
http://localhost:3000/forums/new
:
http://localhost:3000/forums/categories/my_published
*/

View File

@ -0,0 +1,62 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import axios from 'axios'
class UserSection extends Component {
constructor(props) {
super(props)
this.state = {
}
}
/*点击关注或者取消关注*/
AboutFocus(){
const { author_info } = this.props
/*http://localhost:3000/api/v1/users/155/watch?object_id=156&object_type=user*/
const focusUrl = `/api/v1/users/${author_info.user_id}/${this.props.author_info.watched ? 'unwatch' : 'watch'}?object_id=${author_info.user_id}&object_type=user`
axios.get(focusUrl,{
})
.then((response) => {
const status = response.data.status;
console.log(status);
if(status == 1){
const new_author_info = Object.assign({}, this.props.author_info)
new_author_info.watched = !new_author_info.watched
this.props.initForumState({author_info: new_author_info})
}
}).catch((error) => {
console.log(error)
})
}
render() {
const { match, history, author_info , current_user } = this.props
if (!author_info) {
return <div className="edu-back-white" id="forum_index_list"></div>
}
return (
<div className="edu-back-white padding40-20 edu-txt-center">
<a href={`/users/${author_info.login}`} target="_blank"><img src={`/images/${author_info.image_url}`} width="90" height="90" className="radius mb5"/></a>
<p className="font-20 userPrivateName">{author_info.username}</p>
<p className="color-grey-9 userPrivatePost">{author_info.identity}</p>
{ author_info.user_id !== current_user.user_id &&
<p className="clearfix mt30">
<a href="javascript:void(0)" className="fl font-16 mr10 user_default_btn edu-blueback-btn" onClick={()=>{this.AboutFocus()}}>{ author_info.watched == true ? "取消关注" : "关注" }</a>
<a href={`/users/${current_user.login}/message_detail?user_id=${author_info.user_id}`} className="fr font-16 user_default_btn user_private_btn" target="_blank">私信</a>
</p> }
</div>
);
}
}
export default UserSection;

View File

@ -0,0 +1,21 @@
.sxReturnItem{
width: 100%;
padding-left:20px;
box-sizing: border-box;
}
.ItemLine{
border-bottom: 1px solid #EBEBEB;
padding:30px 0px;
}
.ItemLineHeadPhoto{
height:48px;
width: 48px;
float: left;
margin-top: 7px;
}
.shixunReply{
max-width: 604px;
}
.sxReturnItem:last-child .ItemLine{
border-bottom: none;
}

View File

@ -0,0 +1,226 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import Pagination from 'rc-pagination';
import ShiXunPostItem from './ShiXunPostItem'
import ForumsNavTab from '../ForumsNavTab'
import { CircularProgress } from 'material-ui/Progress';
import { on, off } from 'educoder'
import './MemoShixun.css'
// import queryString from 'query-string'
import { queryString, updatePageParams } from 'educoder'
import MemoList from '../MemoList'
import axios from 'axios'
const $ = window.$;
class MemoShixun extends Component {
constructor(props) {
super(props)
this.state = {
currentPage: 1,
loadingMemos: true
}
}
onPaginationChange(pageNum, pageSize) {
window.$("html,body").animate({"scrollTop":0})
updatePageParams(pageNum, this.props)
this.fetchShixunMemos(pageNum);
}
componentWillReceiveProps(newProps, newContext) {
if (newProps.enterKeyFlag !== this.props.enterKeyFlag) {
// const childPath = this.props.match.path.split('/:')[0]
// // 加入一个浏览地址
// const _search = this.props.location.search;
// if (_search) {
// const parsed = queryString.parse(_search);
// if (parsed.page != 1) {
// parsed.page = 1;
// this.props.history.push(`${this.props.match.url}?${queryString.stringify(parsed)}`)
// }
// }
this.fetchShixunMemos(1, newProps.searchValue, newProps.selectedHotLabelIndex) // 搜索框模糊搜索,重置为第一页
}
}
fetchShixunMemos(arg_currentPage, arg_searchValue, arg_selectedHotLabelIndex) {
/*
page = params[:page].to_i
offset = page * 15
search = params[:search]
tag = params[:tag_repertoire_id]
*/
const _search = this.props.history.location.search;
const parsed = queryString.parse(_search);
let currentPage = parseInt( arg_currentPage ? arg_currentPage : (parsed.page || 1) )
const paramsObject = {
page: currentPage - 1
}
let searchValue = arg_searchValue != undefined ? arg_searchValue : this.props.searchValue
if (searchValue) {
paramsObject.search = searchValue
}
let { selectedHotLabelIndex, hot_tags } = this.props;
selectedHotLabelIndex = arg_selectedHotLabelIndex ? arg_selectedHotLabelIndex : selectedHotLabelIndex
if (selectedHotLabelIndex !== -1 && hot_tags[selectedHotLabelIndex]) {
paramsObject.tag_repertoire_id = hot_tags[selectedHotLabelIndex].id
}
const stringifid = queryString.stringify(paramsObject);
const url = `/api/v1/discusses?${stringifid}` // /${challenge.identifier}/star
// 获取memo list
this.setState({
currentPage: currentPage ,
loadingMemos: true
})
axios.get(url,{
withCredentials: true,
})
.then((response) => {
if (response.data) {
const user = response.data.current_user;
user.tidding_count = response.data.tidding_count;
this.props.initCommonState(user)
this.props.initForumState(response.data)
// const { hot_tags } = response.data;
// if (hot_tags && hot_tags.length) {
// this.tagNameIdMap = {}
// hot_tags.forEach( (item, index) => {
// this.tagNameIdMap[item.name] = item.id
// })
// }
this.setState({
// p_forum_id: params.forum,
// p_s_order: params.s_order,
loadingMemos: false
})
}
}).catch((error) => {
console.log(error)
})
}
componentDidMount() {
this.fetchShixunMemos();
on('hotTagClick', (event, tagName) => {
this.props.setHotLabelIndex(tagName.selectedHotLabelIndex, () => {
this.fetchShixunMemos(1, undefined)
})
})
$(window).on('popstate', (e) => {
var state = e.originalEvent.state;
console.log('popstate', state)
if (state !== null) {
let currentPage = this.state.currentPage;;
// // 浏览器地址改变了
const search = this.props.history.location.search
const parsed = queryString.parse(search);
if (parsed.page != currentPage) {
currentPage = parseInt( parsed.page || 1)
// this.setSearchValue('')
this.fetchShixunMemos(currentPage)
this.setState({
currentPage,
})
}
}
});
}
componentWillUnmount() {
off('hotTagClick')
$(window).off('popstate')
}
renderMemoList() {
const { memo_list, user } = this.props;
if (!memo_list) {
return ''
}
/*
<PostItem key={item.id} user={user} index={index} {...this.props}
setTop={(memo)=>this.setTop(memo)}
setDown={(memo)=>this.setDown(memo)}
memo={item}
></PostItem>
*/
return memo_list.map( (item, index) => {
return (
<ShiXunPostItem key={item.id} user={user} index={index} {...this.props} memo={item}></ShiXunPostItem>
)
})
}
render() {
const { match, history , memo_count ,memo_list, showSearchValue, searchValue
, selectedHotLabelIndex, hot_tags } = this.props;
const { currentPage, loadingMemos } = this.state;
// 规则: 搜索框输入了值 或者 选择了热门标签的时候显示该提示
const _showSearchValue = showSearchValue || selectedHotLabelIndex != -1
let _searchValue;
if (showSearchValue) {
_searchValue = searchValue
} else if (selectedHotLabelIndex != -1){
_searchValue = hot_tags[selectedHotLabelIndex].name || hot_tags[selectedHotLabelIndex]
}
return (
<div className="edu-back-white" id="forum_index_list"> {/* fl with100 */}
<div className="clearfix">
{ _showSearchValue &&
<div className="noMemosTip" style={{display: loadingMemos ? 'none' : 'block'}}>
<span className="fr pr20" id="search_result">
共找到&nbsp;
<span className="color-orange03">{memo_count}</span>"
<span class="color-orange03">{_searchValue}</span>"
</span>
</div> }
<CircularProgress size={40} thickness={3}
style={{ marginLeft: 'auto', marginRight: 'auto', paddingTop: '20%'
, display: loadingMemos ? 'block': 'none' }}/>
{ !loadingMemos &&
<React.Fragment>
<ForumsNavTab {...this.props}></ForumsNavTab>
<MemoList {...this.props}
renderMemoList={() => this.renderMemoList()}
onPaginationChange={ (pageNum, pageSize) => this.onPaginationChange(pageNum, pageSize) }
{...this.state}
>
</MemoList>
</React.Fragment>
}
</div>
</div>
);
}
}
export default MemoShixun;

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import { Redirect } from 'react-router';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import PropTypes from 'prop-types';
import classNames from 'classnames'
import moment from 'moment'
import './MemoShixun.css'
class ShiXunPostItem extends Component
{
constructor(props)
{
super(props)
this.state = {
}
}
render() {
const { memo } = this.props;
let tagStr = ''
if (memo.shixun_tag && memo.shixun_tag.length) {
memo.shixun_tag.forEach( tag => {
tagStr += ` ${tag}`
})
}
return (
<React.Fragment>
<div className="sxReturnItem">
<div className="ItemLine clearfix df">
<a href={`/users/${memo.login}`} className="ItemLineHeadPhoto">
<img src={`/images/${memo.image_url}`}
width="48px" height="48px" className="radius"/>
</a>
<div className="flex1 ml10 pr20">
<div className="clearfix">
<p className="shixunReply task-hide font-16 fl">
<Link to={`${memo.tpm_url}`} target="_blank">{memo.subject}</Link>
</p>
{ memo.reward &&
<span className="color-orange ml20" data-tip-down={`获得平台奖励金币:${memo.reward}`}>
<i className="iconfont icon-gift font-16 mr5"></i>{memo.reward}
</span>
}
</div>
<p className="clearfix mt5">
<span className="fl color-grey-9">{memo.username}</span>
<span className="fl color-grey-9 ml40">{moment(memo.updated_at).fromNow()}</span>
{ !!tagStr && <span className="fl color-grey-9 ml40">来自 {tagStr}</span>}
{ !!memo.praise_count && <span className="fr color-grey-6 ml20 font-12">{memo.praise_count} </span>}
{ !!memo.replies_count && <span className="fr color-grey-6 font-12">{memo.replies_count} 回复</span>}
</p>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default ShiXunPostItem

View File

@ -53,7 +53,7 @@ class TPMBanner extends Component {
<div className="shixunDetail_top">
<div className="educontent clearfix">
<p className="clearfix">
<span className="font-28 color-white mr10">{shixun.name}</span>
<a href={`/shixuns/${shixun.identifier}/challenges`} className="font-28 color-white mr10">{shixun.name}</a>
{/*TODO folk_from*/}
</p>
<div className="clearfix mt30">