From e5e8032ba1a256239254dee4ae0c67d234214192 Mon Sep 17 00:00:00 2001 From: Rokt33r Date: Tue, 13 Oct 2015 16:09:37 +0900 Subject: [PATCH] revive articledetail --- browser/main/Components/MarkdownPreview.js | 53 ++++++++ browser/main/Components/MarkdownPreview.jsx | 43 ------- .../Components/{ModeIcon.jsx => ModeIcon.js} | 22 ++-- .../HomeContainer/Components/ArticleDetail.js | 52 +++++++- .../HomeContainer/Components/ArticleList.js | 63 +++++++++- .../Components/ArticleNavigator.js | 6 +- browser/main/HomeContainer/actions.js | 20 +++ browser/main/HomeContainer/index.js | 15 ++- browser/main/HomeContainer/lib/api.js | 16 +++ browser/main/HomeContainer/lib/markdown.js | 11 ++ browser/main/HomeContainer/reducer.js | 35 +++++- browser/main/index.js | 21 +++- .../components/ArticleDetail.styl | 3 + .../HomeContainer/components/ArticleList.styl | 118 +++++++++--------- .../components/ArticleNavigator.styl | 7 +- webpack.config.js | 4 +- 16 files changed, 354 insertions(+), 135 deletions(-) create mode 100644 browser/main/Components/MarkdownPreview.js delete mode 100644 browser/main/Components/MarkdownPreview.jsx rename browser/main/Components/{ModeIcon.jsx => ModeIcon.js} (88%) create mode 100644 browser/main/HomeContainer/lib/markdown.js diff --git a/browser/main/Components/MarkdownPreview.js b/browser/main/Components/MarkdownPreview.js new file mode 100644 index 00000000..1dbe3c15 --- /dev/null +++ b/browser/main/Components/MarkdownPreview.js @@ -0,0 +1,53 @@ +import shell from 'shell' +import React, { PropTypes } from 'react' +import markdown from '../HomeContainer/lib/markdown' + +function handleAnchorClick (e) { + shell.openExternal(e.target.href) + e.preventDefault() +} + +export default class MarkdownPreview extends React.Component { + componentDidMount () { + this.addListener() + } + + componentDidUpdate () { + this.addListener() + } + + componentWillUnmount () { + this.removeListener() + } + + componentWillUpdate () { + this.removeListener() + } + + addListener () { + var anchors = React.findDOMNode(this).querySelectorAll('a') + + for (var i = 0; i < anchors.length; i++) { + anchors[i].addEventListener('click', handleAnchorClick) + } + } + + removeListener () { + var anchors = React.findDOMNode(this).querySelectorAll('a') + + for (var i = 0; i < anchors.length; i++) { + anchors[i].removeEventListener('click', handleAnchorClick) + } + } + + render () { + return ( +
+ ) + } +} + +MarkdownPreview.propTypes = { + className: PropTypes.string, + content: PropTypes.string +} diff --git a/browser/main/Components/MarkdownPreview.jsx b/browser/main/Components/MarkdownPreview.jsx deleted file mode 100644 index 80df45c5..00000000 --- a/browser/main/Components/MarkdownPreview.jsx +++ /dev/null @@ -1,43 +0,0 @@ -var React = require('react') - -var Markdown = require('../Mixins/Markdown') -var ExternalLink = require('../Mixins/ExternalLink') - -module.exports = React.createClass({ - mixins: [Markdown, ExternalLink], - propTypes: { - className: React.PropTypes.string, - content: React.PropTypes.string - }, - componentDidMount: function () { - this.addListener() - }, - componentDidUpdate: function () { - this.addListener() - }, - componentWillUnmount: function () { - this.removeListener() - }, - componentWillUpdate: function () { - this.removeListener() - }, - addListener: function () { - var anchors = React.findDOMNode(this).querySelectorAll('a') - - for (var i = 0; i < anchors.length; i++) { - anchors[i].addEventListener('click', this.openExternal) - } - }, - removeListener: function () { - var anchors = React.findDOMNode(this).querySelectorAll('a') - - for (var i = 0; i < anchors.length; i++) { - anchors[i].removeEventListener('click', this.openExternal) - } - }, - render: function () { - return ( -
- ) - } -}) diff --git a/browser/main/Components/ModeIcon.jsx b/browser/main/Components/ModeIcon.js similarity index 88% rename from browser/main/Components/ModeIcon.jsx rename to browser/main/Components/ModeIcon.js index 6e1bfb6c..0b3e4886 100644 --- a/browser/main/Components/ModeIcon.jsx +++ b/browser/main/Components/ModeIcon.js @@ -1,11 +1,7 @@ -var React = require('react') +import React, { PropTypes } from 'react' -module.exports = React.createClass({ - propTypes: { - className: React.PropTypes.string, - mode: React.PropTypes.string - }, - getClassName: function () { +export default class ModeIcon extends React.Component { + getClassName () { var mode = this.props.mode switch (mode) { // Script @@ -69,11 +65,17 @@ module.exports = React.createClass({ return 'fa fa-fw fa-file-text-o' } return 'fa fa-fw fa-code' - }, - render: function () { + } + + render () { var className = this.getClassName() return ( ) } -}) +} + +ModeIcon.propTypes = { + className: PropTypes.string, + mode: PropTypes.string +} diff --git a/browser/main/HomeContainer/Components/ArticleDetail.js b/browser/main/HomeContainer/Components/ArticleDetail.js index e8d31aa5..afb4aa71 100644 --- a/browser/main/HomeContainer/Components/ArticleDetail.js +++ b/browser/main/HomeContainer/Components/ArticleDetail.js @@ -1,9 +1,59 @@ import React, { PropTypes } from 'react' +import moment from 'moment' +import { findWhere } from 'lodash' +import ModeIcon from '../../Components/ModeIcon' +import MarkdownPreview from '../../Components/MarkdownPreview' +import CodeEditor from '../../Components/CodeEditor' export default class ArticleDetail extends React.Component { render () { + let { article, status, user } = this.props + + let tags = article.Tags.length > 0 ? article.Tags.map(tag => { + return ( + {tag.name} + ) + }) : ( + Not tagged yet + ) + let folder = findWhere(user.Folders, {id: article.FolderId}) + let folderName = folder != null ? folder.name : '(unknown)' + return ( -
+
+
+
+
+ {folderName}  + by {article.User.profileName}  + Created {moment(article.createdAt).format('YYYY/MM/DD')}  + Updated {moment(article.updatedAt).format('YYYY/MM/DD')} +
+
{tags}
+
+ +
+ + + +
+
+
+
+
+ +
{article.title}
+
+ {article.mode === 'markdown' ? : } +
+
+
) } } + +ArticleDetail.propTypes = { + article: PropTypes.shape(), + status: PropTypes.shape(), + user: PropTypes.shape() +} diff --git a/browser/main/HomeContainer/Components/ArticleList.js b/browser/main/HomeContainer/Components/ArticleList.js index ef0f0042..a59af0d5 100644 --- a/browser/main/HomeContainer/Components/ArticleList.js +++ b/browser/main/HomeContainer/Components/ArticleList.js @@ -1,11 +1,66 @@ import React, { PropTypes } from 'react' +import ProfileImage from '../../components/ProfileImage' +import ModeIcon from '../../Components/ModeIcon' +import moment from 'moment' +import { IDLE_MODE, CREATE_MODE, EDIT_MODE } from '../actions' + +export default class ArticleList extends React.Component { + render () { + let { articles, status } = this.props + + let articlesEl = articles.map(article => { + let tags = Array.isArray(article.Tags) && article.Tags.length > 0 ? article.Tags.map(tag => { + return ( + #{tag.name} + ) + }) : ( + Not tagged yet + ) + + return ( +
+
+
+ + by {article.User.profileName} + {article.status != null ? article.status : moment(article.updatedAt).fromNow()} +
+
+
{article.status !== 'new' ? article.title : '(New article)'}
+
+
+
{tags}
+
+
+
+
+ ) + }) -class ArticleList extends React.Component { - render() { return ( -
+
+ { status.mode === 'CREATE_MODE' ? ( +
+
+
+ {} +
+
+
'(New article)'
+
+
+
+
+
+
+
+ ) : null} + {articlesEl} +
) } } -export default ArticleList +ArticleList.propTypes = { + articles: PropTypes.array +} diff --git a/browser/main/HomeContainer/Components/ArticleNavigator.js b/browser/main/HomeContainer/Components/ArticleNavigator.js index 1c1ef5c8..8ff56a80 100644 --- a/browser/main/HomeContainer/Components/ArticleNavigator.js +++ b/browser/main/HomeContainer/Components/ArticleNavigator.js @@ -6,7 +6,6 @@ export default class ArticleNavigator extends React.Component { render () { let { user, status } = this.props if (user == null) return (
) - console.log(user.Folders) let activeFolder = findWhere(user.Folders, {id: status.folderId}) @@ -68,5 +67,8 @@ export default class ArticleNavigator extends React.Component { } ArticleNavigator.propTypes = { - user: PropTypes.object + user: PropTypes.object, + state: PropTypes.shape({ + folderId: PropTypes.number + }) } diff --git a/browser/main/HomeContainer/actions.js b/browser/main/HomeContainer/actions.js index ba6eeb5e..55a4a497 100644 --- a/browser/main/HomeContainer/actions.js +++ b/browser/main/HomeContainer/actions.js @@ -1,6 +1,12 @@ export const USER_UPDATE = 'USER_UPDATE' +export const ARTICLE_UPDATE = 'ARTICLE_UPDATE' export const SWITCH_USER = 'SWITCH_USER' export const SWITCH_FOLDER = 'SWITCH_FOLDER' +export const SWITCH_MODE = 'SWITCH_MODE' + +export const IDLE_MODE = 'IDLE_MODE' +export const CREATE_MODE = 'CREATE_MODE' +export const EDIT_MODE = 'EDIT_MODE' export function updateUser (user) { return { @@ -9,6 +15,13 @@ export function updateUser (user) { } } +export function updateArticles (userId, articles) { + return { + type: ARTICLE_UPDATE, + data: {userId, articles} + } +} + export function switchUser (userId) { return { type: SWITCH_USER, @@ -22,3 +35,10 @@ export function switchFolder (folderId) { data: folderId } } + +export function switchMode (mode) { + return { + type: SWITCH_MODE, + data: mode + } +} diff --git a/browser/main/HomeContainer/index.js b/browser/main/HomeContainer/index.js index 59bd72f7..6bbb5319 100644 --- a/browser/main/HomeContainer/index.js +++ b/browser/main/HomeContainer/index.js @@ -27,15 +27,15 @@ class HomeContainer extends React.Component { } render () { - const { users, user, status } = this.props + const { users, user, status, articles, article } = this.props return (
- - + +
) } @@ -49,11 +49,17 @@ function remap (state) { let users = [currentUser, ...teams] let user = findWhere(users, {id: parseInt(status.userId, 10)}) + if (user == null) user = users[0] + let articles = state.articles['team-' + user.id] + let article = findWhere(users, {id: status.articleId}) + if (article == null) article = articles[0] return { users, user, - status + status, + articles, + article } } @@ -67,6 +73,7 @@ HomeContainer.propTypes = { userId: PropTypes.string, folderId: PropTypes.number }), + articles: PropTypes.array, dispatch: PropTypes.func } diff --git a/browser/main/HomeContainer/lib/api.js b/browser/main/HomeContainer/lib/api.js index b212b5c1..7d5cfdf2 100644 --- a/browser/main/HomeContainer/lib/api.js +++ b/browser/main/HomeContainer/lib/api.js @@ -1,6 +1,22 @@ var request = require('superagent-promise')(require('superagent'), Promise) var apiUrl = require('../../../../config').apiUrl +export function fetchCurrentUser () { + return request + .get(apiUrl + 'auth/user') + .set({ + Authorization: 'Bearer ' + localStorage.getItem('token') + }) +} + +export function fetchArticles (userId) { + return request + .get(apiUrl + 'teams/' + userId + '/articles') + .set({ + Authorization: 'Bearer ' + localStorage.getItem('token') + }) +} + export function createTeam (input) { return request .post(apiUrl + 'teams') diff --git a/browser/main/HomeContainer/lib/markdown.js b/browser/main/HomeContainer/lib/markdown.js new file mode 100644 index 00000000..2f17530e --- /dev/null +++ b/browser/main/HomeContainer/lib/markdown.js @@ -0,0 +1,11 @@ +import markdownit from 'markdown-it' + +var md = markdownit({ + typographer: true, + linkify: true +}) + +export default function markdown (content) { + if (content == null) content = '' + return md.render(content.toString()) +} diff --git a/browser/main/HomeContainer/reducer.js b/browser/main/HomeContainer/reducer.js index 329fa448..166f48e9 100644 --- a/browser/main/HomeContainer/reducer.js +++ b/browser/main/HomeContainer/reducer.js @@ -1,8 +1,17 @@ import { combineReducers } from 'redux' -import { SWITCH_USER, SWITCH_FOLDER, USER_UPDATE } from './actions' +import { SWITCH_USER, SWITCH_FOLDER, SWITCH_MODE, USER_UPDATE, ARTICLE_UPDATE, IDLE_MODE, CREATE_MODE, EDIT_MODE } from './actions' const initialCurrentUser = JSON.parse(localStorage.getItem('currentUser')) -const initialParams = {} +const initialStatus = { + mode: IDLE_MODE +} +// init articles +let teams = Array.isArray(initialCurrentUser.Teams) ? initialCurrentUser.Teams : [] +let users = [initialCurrentUser, ...teams] +const initialArticles = users.reduce((res, user) => { + res['team-' + user.id] = JSON.parse(localStorage.getItem('team-' + user.id)) + return res +}, {}) function currentUser (state, action) { switch (action.type) { @@ -26,13 +35,31 @@ function status (state, action) { case SWITCH_FOLDER: state.folderId = action.data return state + case SWITCH_MODE: + state.mode = action.data + return state default: - if (state == null) return initialParams + if (state == null) return initialStatus + return state + } +} + +function articles (state, action) { + switch (action.type) { + case ARTICLE_UPDATE: + let { userId, articles } = action.data + let teamKey = 'team-' + userId + localStorage.setItem(teamKey, JSON.stringify(articles)) + state[teamKey] = articles + return state + default: + if (state == null) return initialArticles return state } } export default combineReducers({ currentUser, - status + status, + articles }) diff --git a/browser/main/index.js b/browser/main/index.js index ca966b99..82f7c834 100644 --- a/browser/main/index.js +++ b/browser/main/index.js @@ -1,9 +1,9 @@ import React from 'react' import { createStore } from 'redux' import { Provider } from 'react-redux' -import { updateUser } from './HomeContainer/actions' +import { updateUser, updateArticles } from './HomeContainer/actions' import reducer from './HomeContainer/reducer' -import Hq from './Services/Hq' +import { fetchCurrentUser, fetchArticles } from './HomeContainer/lib/api' import { Router, Route, IndexRoute } from 'react-router' import MainContainer from './Containers/MainContainer' import LoginContainer from './Containers/LoginContainer' @@ -58,9 +58,22 @@ React.render(( loadingCover.parentNode.removeChild(loadingCover) // Refresh user information - Hq.getUser() + fetchCurrentUser() .then(function (res) { - store.dispatch(updateUser(res.body)) + let user = res.body + store.dispatch(updateUser(user)) + + let users = [user].concat(user.Teams) + users.forEach(user => { + fetchArticles(user.id) + .then(res => { + store.dispatch(updateArticles(user.id, res.body)) + }) + .catch(err => { + if (err.status == null) throw err + console.error(err) + }) + }) }) .catch(function (err) { console.error(err.message) diff --git a/browser/styles/main/HomeContainer/components/ArticleDetail.styl b/browser/styles/main/HomeContainer/components/ArticleDetail.styl index bf58b525..3783ca2a 100644 --- a/browser/styles/main/HomeContainer/components/ArticleDetail.styl +++ b/browser/styles/main/HomeContainer/components/ArticleDetail.styl @@ -5,6 +5,9 @@ noTagsColor = #999 top 60px left 510px padding 10px + background-color #E6E6E6 + border-top 1px solid borderColor + border-left 1px solid borderColor * -webkit-user-select all .detailInfo diff --git a/browser/styles/main/HomeContainer/components/ArticleList.styl b/browser/styles/main/HomeContainer/components/ArticleList.styl index 876b5b70..d7ad7c05 100644 --- a/browser/styles/main/HomeContainer/components/ArticleList.styl +++ b/browser/styles/main/HomeContainer/components/ArticleList.styl @@ -6,65 +6,65 @@ articleItemColor = #777 top 60px left 260px width 250px - border-right solid 1px highlightenBorderColor - &>ul - absolute top bottom left right - overflow-y auto - noSelect() - li - .articleItem - border solid 2px transparent - position relative - height 88px - width 100% - cursor pointer - transition 0.1s - background-color white - padding 0 10px - font-size 12px - .top - clearfix() + border-top 1px solid borderColor + border-right 4px solid #E6E6E6 + overflow-y auto + noSelect() + &>div + border-right 1px solid borderColor + .articleItem + border solid 2px transparent + position relative + height 88px + width 100% + cursor pointer + transition 0.1s + background-color white + padding 0 10px + font-size 12px + .top + clearfix() + line-height 20px + padding 5px 0 + color articleItemColor + .profileImage + vertical-align middle + .updatedAt + float right line-height 20px - padding 5px 0 + .middle + clearfix() + padding 3px 0 7px + font-size 16px + .mode + float left + font-size 12px + line-height 16px + .title + float left + overflow ellipsis + padding 0 5px + .bottom + padding 5px 0 + overflow-x auto + white-space nowrap + .tags color articleItemColor - .profileImage - vertical-align middle - .updatedAt - float right - line-height 20px - .middle - clearfix() - padding 3px 0 7px - font-size 16px - .mode - float left - font-size 12px - line-height 16px - .title - float left - overflow ellipsis - padding 0 5px - .bottom - padding 5px 0 - overflow-x auto - white-space nowrap - .tags - color articleItemColor - a - background-color brandColor - color white - border-radius 2px - padding 1.5px 5px - margin 2px - font-size 10px - opacity 0.8 - &:hover - opacity 1 - &:hover, &.hover - background-color articleItemHoverBgColor - &:active, &.active - background-color white + a + background-color brandColor + color white + border-radius 2px + padding 1.5px 5px + margin 2px + font-size 10px + opacity 0.8 + &:hover + opacity 1 + &:hover, &.hover + background-color articleItemHoverBgColor &:active, &.active - border-color brandBorderColor - .divider - border-bottom solid 1px borderColor + background-color white + &:active, &.active + border-color brandBorderColor + .divider + border-bottom solid 1px borderColor diff --git a/browser/styles/main/HomeContainer/components/ArticleNavigator.styl b/browser/styles/main/HomeContainer/components/ArticleNavigator.styl index d65b5280..a2f02d45 100644 --- a/browser/styles/main/HomeContainer/components/ArticleNavigator.styl +++ b/browser/styles/main/HomeContainer/components/ArticleNavigator.styl @@ -5,6 +5,7 @@ articleNavBgColor = #353535 absolute top bottom left 60px width 200px + border-right 1px solid borderColor color white .userInfo height 60px @@ -48,7 +49,7 @@ articleNavBgColor = #353535 .header border-bottom 1px solid borderColor padding-bottom 5px - margin-bottom 5px + margin-bottom 10px clearfix() .title float left @@ -75,8 +76,8 @@ articleNavBgColor = #353535 .folders margin-bottom 15px .folderList button - height 44px - width 200px + height 33px + width 199px border none text-align left font-size 14px diff --git a/webpack.config.js b/webpack.config.js index 962a8110..e711ec55 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -43,7 +43,9 @@ module.exports = { 'react-transform-catch-errors', 'redux-devtools', 'redux-devtools/lib/react', - 'react-select' + 'react-select', + 'markdown-it', + 'moment' ], resolve: { extensions: ['', '.js', '.jsx', 'styl']