├── .DS_Store
├── .babelrc
├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── Components
├── .DS_Store
├── Styles
│ └── Share.scss
├── history
│ └── history.js
├── redux
│ ├── ArticleLoaderAction.js
│ ├── ArticleLoaderReducer.js
│ ├── GlobalURL.js
│ ├── LoginAction.js
│ ├── LoginReducer.js
│ ├── RegisterAction.js
│ ├── RegisterReducer.js
│ ├── UserAction.js
│ ├── UserReducer.js
│ ├── WriteAction.js
│ ├── WriteReducer.js
│ └── _store.js
└── src
│ ├── AlertBox.jsx
│ ├── AlertBox.scss
│ ├── ArticleFilter.jsx
│ ├── ArticleLoader.jsx
│ ├── ArticleLoader.scss
│ ├── ArticleReplyPane.jsx
│ ├── ArticleReplyPane.scss
│ ├── ArticleView.jsx
│ ├── ArticleView.scss
│ ├── AticleFilter.scss
│ ├── Editor.jsx
│ ├── Editor.scss
│ ├── Home.jsx
│ ├── Home.scss
│ ├── ImageLoader.jsx
│ ├── ImageLoader.scss
│ ├── Login.jsx
│ ├── Login.scss
│ ├── LoginBox.jsx
│ ├── NotFound.jsx
│ ├── RegisterBox.jsx
│ ├── ResizeBullets.js
│ ├── Root.jsx
│ ├── TempDocLoader.jsx
│ ├── TempDocLoader.scss
│ ├── TopBar.jsx
│ ├── TopBar.scss
│ ├── VideoLoader.jsx
│ ├── Write.jsx
│ ├── Write.scss
│ ├── app.js
│ ├── filepond-plugin-image-preview.min.css
│ └── filepond.min.css
├── DB
└── MongoTransactions.js
├── EditorImplement
└── rich-text-area.html
├── ScreenShots
├── .DS_Store
├── 2019-03-05_Home.png
├── 2019-06-25_Write.PNG
├── 2019-06-25_Write_Color.PNG
├── 2019-06-25_Write_Size.PNG
├── 2019-06-26_imgDragandDrop.png
├── 2019-06-26_imgLoad.png
├── 2019-06-26_imgLocateAnyWhere.png
├── 2019-07-30_articleListing.png
├── 2019-07-30_articleRead.png
├── 2019-07-30_articleTempSaveLoad.png
├── 2019-08-31_Reply.png
├── 2019-09-08_ArticleFilter.png
├── HomePlan.PNG
├── ethereal.PNG
├── formaluser.PNG
├── login.PNG
├── login_2.PNG
├── register.PNG
├── register_2.PNG
├── register_3.PNG
├── unformaluser.PNG
└── verify_1.PNG
├── app.js
├── clear.sh
├── db_command.txt
├── package-lock.json
├── package.json
├── public
├── .DS_Store
├── Fonts
│ ├── NotoSansKR-Black-Hestia.woff
│ ├── NotoSansKR-Bold-Hestia.woff
│ ├── NotoSansKR-DemiLight-Hestia.woff
│ ├── NotoSansKR-Light-Hestia.woff
│ ├── NotoSansKR-Medium-Hestia.woff
│ ├── NotoSansKR-Regular-Hestia.woff
│ └── NotoSansKR-Thin-Hestia.woff
├── Images
│ ├── no-img.jpg
│ └── post-it.svg
├── UserImages
│ ├── 1562566761370.jpeg
│ ├── 1562633547385.jpeg
│ ├── 1573381871614.png
│ └── no-user-img.svg
├── assets
│ ├── NotoSansKR-Bold-Hestia.woff
│ ├── NotoSansKR-Regular-Hestia.woff
│ └── post-it.svg
├── index.html
├── theeluwin-NotoSansKR-Hestia-fc0616a
│ ├── .DS_Store
│ ├── LICENSE
│ ├── README.md
│ ├── fonts
│ │ ├── .DS_Store
│ │ ├── eot
│ │ │ ├── NotoSansKR-Black-Hestia.eot
│ │ │ ├── NotoSansKR-Bold-Hestia.eot
│ │ │ ├── NotoSansKR-DemiLight-Hestia.eot
│ │ │ ├── NotoSansKR-Light-Hestia.eot
│ │ │ ├── NotoSansKR-Medium-Hestia.eot
│ │ │ ├── NotoSansKR-Regular-Hestia.eot
│ │ │ └── NotoSansKR-Thin-Hestia.eot
│ │ ├── otf
│ │ │ ├── NotoSansKR-Black-Hestia.otf
│ │ │ ├── NotoSansKR-Bold-Hestia.otf
│ │ │ ├── NotoSansKR-DemiLight-Hestia.otf
│ │ │ ├── NotoSansKR-Light-Hestia.otf
│ │ │ ├── NotoSansKR-Medium-Hestia.otf
│ │ │ ├── NotoSansKR-Regular-Hestia.otf
│ │ │ └── NotoSansKR-Thin-Hestia.otf
│ │ └── woff
│ │ │ ├── .DS_Store
│ │ │ ├── NotoSansKR-Black-Hestia.woff
│ │ │ ├── NotoSansKR-Bold-Hestia.woff
│ │ │ ├── NotoSansKR-DemiLight-Hestia.woff
│ │ │ ├── NotoSansKR-Light-Hestia.woff
│ │ │ ├── NotoSansKR-Medium-Hestia.woff
│ │ │ ├── NotoSansKR-Regular-Hestia.woff
│ │ │ └── NotoSansKR-Thin-Hestia.woff
│ ├── ksx1001.txt
│ └── stylesheets
│ │ └── NotoSansKR-Hestia.css
└── theeluwin-NotoSansKR-Hestia-v0.1-4-gfc0616a.zip
├── readme.md
├── routes
├── loginRouter.js
├── mailer.js
├── readRouter.js
├── registerRouter.js
├── test.js
└── writeRouter.js
├── test.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets" : ["env","react","stage-0"],
3 | "plugins": ["emotion"]
4 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | bundle.js
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "globals": {
9 | "Atomics": "readonly",
10 | "SharedArrayBuffer": "readonly"
11 | },
12 | "parserOptions": {
13 | "ecmaFeatures": {
14 | "jsx": true
15 | },
16 | "ecmaVersion": 2018,
17 | "sourceType": "module"
18 | },
19 | "plugins": [
20 | "react"
21 | ],
22 | "rules": {
23 | "no-console" : "off",
24 | "semi" : "off",
25 | "quotes": "off"
26 | }
27 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .idea/
3 | DB/secret.js
4 | SSL/
5 | SharedImages/
6 | dist/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 'lts/*'
4 | sudo: required
5 | addons:
6 | firefox: latest
7 | chrome: stable
8 | cache:
9 | directories:
10 | - node_modules
11 | install:
12 | - npm install
13 | script:
14 | - npm run lint
15 |
--------------------------------------------------------------------------------
/Components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/Components/.DS_Store
--------------------------------------------------------------------------------
/Components/Styles/Share.scss:
--------------------------------------------------------------------------------
1 | $material_skintone : hsl(14, 100%, 70%);
2 | $material_lightpeach : hsl(5, 100%, 75%);
3 | $material_homebackground : hsl(231, 44%, 94%);
4 | $material_lightgreen : hsl(125, 39%, 94%);
5 | $material_lightash : hsl(204, 15%, 94%);
6 |
7 | @font-face{
8 | font-family: "NotoSansKR",sans-serif;
9 | src: url("../../public/fonts/NotoSansKR-Regular-Hestia.woff") format("woff");
10 | font-weight: normal;
11 | }
12 | @font-face{
13 | font-family: "NotoSansKR",sans-serif;
14 | src: url("../../public/fonts/NotoSansKR-Bold-Hestia.woff") format("woff");
15 | font-weight: bold;
16 | }
17 | *{
18 | margin: 0;
19 | padding: 0;
20 | font-family: "NotoSansKR",sans-serif;
21 | font-weight: normal;
22 | }
23 |
24 | .Wrapper{
25 |
26 | width: 100%;
27 | min-height: 100vh;
28 | height: auto;
29 |
30 | }
31 |
32 | .--Bg-lightpeach{
33 | background-color: $material_lightpeach;
34 | }
35 | .--Bg-lightblue{
36 | background-color: $material_homebackground;
37 | }
38 | .--Bg-lightgreen{
39 | background-color: $material_lightgreen;
40 | }
41 | .--Bg-lightash{
42 | background-color: $material_lightash;
43 | }
44 |
45 | %Flex-Center{
46 | display: flex;
47 | justify-content: center;
48 | align-items: center;
49 | }
50 |
--------------------------------------------------------------------------------
/Components/history/history.js:
--------------------------------------------------------------------------------
1 | import {createBrowserHistory} from 'history'
2 |
3 | export default createBrowserHistory()
4 |
--------------------------------------------------------------------------------
/Components/redux/ArticleLoaderAction.js:
--------------------------------------------------------------------------------
1 | import {SERVER_URL} from './GlobalURL.js'
2 |
3 | export const A_LOAD_POSTINGS = "A_LOAD_POSTINGS"
4 | export const A_LOAD_POSTINGS_DONE = "A_LOAD_POSTINGS_DONE"
5 | export const A_FILT_READABLE_DOCS = "A_FILT_READABLE_DOCS"
6 | export const A_TITLE_SEARCH = "A_TITLE_SEARCH"
7 | export const A_POST_EYE_UP = "A_POST_EYE_UP"
8 | export const A_REFRESH_ARTICLE_LOAD_STATE = "A_REFRESH_ARTICLE_LOAD_STATE"
9 |
10 | export const AC_LOAD_POSTINGS = () => {
11 |
12 | return (dispatch,getState) => {
13 |
14 | let SnapShot = getState().articleLoader
15 |
16 | if(SnapShot.IS_FETCHING){
17 | return
18 | }
19 |
20 | fetch(`${SERVER_URL}/read?skip=${SnapShot.SKIP}&limit=${SnapShot.LIMIT}`,{
21 | method : 'GET'
22 | })
23 | .then(res=>res.json())
24 | .then((res) => {
25 | if(res.status === 1){
26 | dispatch({
27 | type : A_LOAD_POSTINGS_DONE,
28 | value : res.payload
29 | })
30 | }
31 | })
32 |
33 | }
34 |
35 | }
36 |
37 | export const AC_FILT_READABLE_DOCS = (type,order) => {
38 |
39 | return (dispatch, getState) => {
40 |
41 | switch(`${type}.${order}`){
42 |
43 | case 'REPLY.RESET' :
44 | case 'REPLY.DOWN' :
45 | dispatch({
46 | type : A_FILT_READABLE_DOCS,
47 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'POST_REPLY','DOWN')
48 | })
49 | break;
50 | case 'REPLY.UP' :
51 | dispatch({
52 | type : A_FILT_READABLE_DOCS,
53 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'POST_REPLY','UP')
54 | })
55 | break;
56 |
57 | case 'EYE.RESET' :
58 | case 'EYE.DOWN' :
59 | dispatch({
60 | type : A_FILT_READABLE_DOCS,
61 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'EYE','DOWN')
62 | })
63 | break;
64 | case 'EYE.UP' :
65 | dispatch({
66 | type : A_FILT_READABLE_DOCS,
67 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'EYE','UP')
68 | })
69 | break;
70 |
71 | case 'THUMB.RESET' :
72 | case 'THUMB.DOWN' :
73 | dispatch({
74 | type : A_FILT_READABLE_DOCS,
75 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'RECOMMEND','DOWN')
76 | })
77 | break;
78 | case 'THUMB.UP' :
79 | dispatch({
80 | type : A_FILT_READABLE_DOCS,
81 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'RECOMMEND','UP')
82 | })
83 | break;
84 |
85 | case 'TIME.RESET' :
86 | case 'TIME.DOWN' :
87 | dispatch({
88 | type : A_FILT_READABLE_DOCS,
89 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'POST_DATE','DOWN')
90 | })
91 | break;
92 | case 'TIME.UP' :
93 | dispatch({
94 | type : A_FILT_READABLE_DOCS,
95 | value : FILT_ARRAY(getState().articleLoader.READABLE_DOCS,'POST_DATE','UP')
96 | })
97 | break;
98 |
99 | default :
100 | break;
101 |
102 | }
103 | }
104 |
105 | }
106 |
107 | export const AC_TITLE_SEARCH = (searchText) => {
108 |
109 | let dispatchObject = {
110 | type : A_TITLE_SEARCH
111 | }
112 |
113 | return (dispatch,getState) => {
114 |
115 | if(searchText.length === 0){
116 | dispatchObject.value = getState().articleLoader.READABLE_DOCS
117 | dispatchObject.searching = false
118 | }
119 | else{
120 | dispatchObject.value = SEARCH_ARRAY(getState().articleLoader.READABLE_DOCS,searchText)
121 | dispatchObject.searching = true
122 | }
123 |
124 | dispatch(dispatchObject)
125 |
126 | }
127 |
128 | }
129 |
130 | export const SEARCH_ARRAY = (array,searchText) => {
131 |
132 | array.forEach((el) => {
133 | if(el.POST_TITLE.includes(searchText)){
134 | el.SEARCH_TOUCHED = true
135 | }
136 | else{
137 | el.SEARCH_TOUCHED = false
138 | }
139 | })
140 |
141 | return array
142 |
143 | }
144 |
145 | export const FILT_ARRAY = (array,key,direction) => {
146 | if(direction === 'UP'){
147 | return array.sort((a,b) => {
148 | if(Array.isArray(a[key])){
149 | return a[key].length - b[key].length
150 | } else {
151 | return a[key] - b[key]
152 | }
153 | })
154 | }
155 | else{
156 | return array.sort((a,b) => {
157 | if(Array.isArray(a[key])){
158 | return b[key].length - a[key].length
159 | } else {
160 | return b[key] - a[key]
161 | }
162 | })
163 | }
164 | }
165 |
166 | export const AC_POST_EYE_UP = (postId) => {
167 |
168 | return (dispatch) => {
169 |
170 | return fetch(`${SERVER_URL}/read/postEyeUp`,{
171 | method : 'PUT',
172 | headers : {
173 | 'Accept' : 'application/json',
174 | 'Content-Type' : 'application/json'
175 | },
176 | body : JSON.stringify({
177 | targetArticleId : postId
178 | })
179 |
180 | }).then(res => res.json())
181 | .then(res => {
182 | if(res.status === 1){
183 | dispatch({
184 | type : A_POST_EYE_UP,
185 | value : res.payload
186 | })
187 | }
188 | })
189 |
190 | }
191 |
192 | }
193 |
194 | export const AC_REFRESH_ARTICLE_LOAD_STATE = () => {
195 |
196 | return {
197 | type : A_REFRESH_ARTICLE_LOAD_STATE
198 | }
199 |
200 | }
--------------------------------------------------------------------------------
/Components/redux/ArticleLoaderReducer.js:
--------------------------------------------------------------------------------
1 | import * as ArticleLoaderActions from './ArticleLoaderAction.js'
2 |
3 | let ArticleLoaderInitialState = {
4 | IS_FETCHING : false,
5 | IS_SEARCHING : false,
6 | SKIP : 0,
7 | LIMIT : 20,
8 | READABLE_DOCS : [],
9 | }
10 |
11 | let ArticleLoaderReducer = (state = ArticleLoaderInitialState , action) => {
12 |
13 | switch(action.type){
14 |
15 | case ArticleLoaderActions.A_LOAD_POSTINGS :
16 |
17 | return Object.assign({},state,{
18 | IS_FETCHING : true
19 | })
20 |
21 | case ArticleLoaderActions.A_LOAD_POSTINGS_DONE :
22 |
23 | return Object.assign({},state,{
24 | IS_FETCHING : false,
25 | READABLE_DOCS : state.READABLE_DOCS.concat(action.value),
26 | SKIP : state.SKIP + state.LIMIT
27 | })
28 |
29 | case ArticleLoaderActions.A_FILT_READABLE_DOCS :
30 |
31 | return Object.assign({},state,{
32 | READABLE_DOCS : action.value
33 | })
34 |
35 | case ArticleLoaderActions.A_TITLE_SEARCH :
36 |
37 | return Object.assign({},state,{
38 | READABLE_DOCS : action.value,
39 | IS_SEARCHING : action.searching
40 | })
41 |
42 | case ArticleLoaderActions.A_POST_EYE_UP :
43 |
44 | return Object.assign({},state,{
45 | READABLE_DOCS : updateReadableDocs(state.READABLE_DOCS,action.value)
46 | })
47 |
48 | case ArticleLoaderActions.A_REFRESH_ARTICLE_LOAD_STATE :
49 |
50 | return Object.assign({},ArticleLoaderInitialState)
51 |
52 | default :
53 | return state
54 | }
55 |
56 | }
57 |
58 | const updateReadableDocs = (origin,newDoc) => {
59 |
60 | origin[origin.findIndex(el => el._id === newDoc._id)] = newDoc
61 | return origin
62 |
63 | }
64 |
65 | export default ArticleLoaderReducer
66 |
67 |
--------------------------------------------------------------------------------
/Components/redux/GlobalURL.js:
--------------------------------------------------------------------------------
1 | export const SERVER_URL = "http://localhost:3000"
2 | export const CLIENT_URL = "http://localhost:9000"
3 |
4 | export const SERVER_SHAREDIMAGES_URL = "http://localhost:3000/SharedImages/"
--------------------------------------------------------------------------------
/Components/redux/LoginAction.js:
--------------------------------------------------------------------------------
1 | export const A_TYPING_LOGIN_FORM_EMAIL = "A_TYPING_LOGIN_FORM_EMAIL"
2 | export const A_TYPING_LOGIN_FORM_PW = "A_TYPING_LOGIN_FORM_PW"
3 | export const A_IS_REMEMBER_CHECKED = "A_IS_REMEMBER_CHECKED"
4 |
5 | export const A_SUBMIT_BUTTON_CLCICKED ="A_SUBMIT_BUTTON_CLCICKED"
6 | export const A_AUTH_SUCCESS = "A_AUTH_SUCCESS"
7 | export const A_AUTH_ERROR = "A_AUTH_ERROR"
8 | export const A_CLEAR_LOGIN = "A_CLEAR_LOGIN"
9 |
10 | export const A_TOGGLE_MODAL = "A_TOGGLE_MODAL"
11 |
12 | export const A_SET_EMAIL_BY_COOKIE = "A_SET_EMAIL_BY_COOKIE"
13 | export const A_USER_LOGOUT = "A_USER_LOGOUT"
14 |
15 | export const AC_TYPING_LOGIN_FORM = (TargetName , TargetValue) => {
16 |
17 | if(TargetName === "INPUT_ID"){
18 | return {
19 | type : A_TYPING_LOGIN_FORM_EMAIL,
20 | value : TargetValue
21 | }
22 | }
23 | else{
24 | return {
25 | type : A_TYPING_LOGIN_FORM_PW,
26 | value : TargetValue
27 | }
28 | }
29 | }
30 |
31 | export const AC_IS_REMEMBER_CHECKED = (CHECK) => {
32 |
33 | return{
34 | type : A_IS_REMEMBER_CHECKED,
35 | value : CHECK
36 | }
37 |
38 | }
39 |
40 | export const AC_SET_EMAIL_BY_COOKIE = (cookie_email) => {
41 | return {
42 | type : A_SET_EMAIL_BY_COOKIE,
43 | value : cookie_email
44 | }
45 | }
46 |
47 | export const AC_TOGGLE_MODAL = () => {
48 | return {
49 | type : A_TOGGLE_MODAL
50 | }
51 | }
52 |
53 | export const refreshSessionStroage = (userInfo) => {
54 |
55 | window.sessionStorage.removeItem('USERNAME')
56 | window.sessionStorage.removeItem('U_IMG_PATH')
57 | window.sessionStorage.removeItem('REG_DATE')
58 | window.sessionStorage.removeItem('NUM_OF_ARTICLES')
59 | window.sessionStorage.removeItem('NUM_OF_REPLIES')
60 | window.sessionStorage.removeItem('NUM_OF_GOTTEN_RECOMMENDS')
61 | window.sessionStorage.removeItem('NUM_OF_HIT_RECOMMENS')
62 | window.sessionStorage.removeItem('EMAIL')
63 |
64 | window.sessionStorage.setItem('USERNAME',userInfo.USERNAME)
65 | window.sessionStorage.setItem('U_IMG_PATH',userInfo.U_IMG_PATH)
66 | window.sessionStorage.setItem('REG_DATE',userInfo.REG_DATE)
67 | window.sessionStorage.setItem('NUM_OF_ARTICLES',userInfo.NUM_OF_ARTICLES)
68 | window.sessionStorage.setItem('NUM_OF_REPLIES',userInfo.NUM_OF_REPLIES)
69 | window.sessionStorage.setItem('NUM_OF_GOTTEN_RECOMMENDS',userInfo.NUM_OF_GOTTEN_RECOMMENDS)
70 | window.sessionStorage.setItem('NUM_OF_HIT_RECOMMENS',userInfo.NUM_OF_HIT_RECOMMENS)
71 | window.sessionStorage.setItem('EMAIL',userInfo.EMAIL)
72 |
73 | }
74 |
75 | export const removeSessionStorage = () => {
76 |
77 | window.sessionStorage.removeItem('USERNAME')
78 | window.sessionStorage.removeItem('U_IMG_PATH')
79 | window.sessionStorage.removeItem('REG_DATE')
80 | window.sessionStorage.removeItem('NUM_OF_ARTICLES')
81 | window.sessionStorage.removeItem('NUM_OF_REPLIES')
82 | window.sessionStorage.removeItem('NUM_OF_GOTTEN_RECOMMENDS')
83 | window.sessionStorage.removeItem('NUM_OF_HIT_RECOMMENS')
84 | window.sessionStorage.removeItem('EMAIL')
85 |
86 | }
--------------------------------------------------------------------------------
/Components/redux/LoginReducer.js:
--------------------------------------------------------------------------------
1 | import * as LoginActions from './LoginAction.js'
2 |
3 | const loginInitialState = {
4 |
5 | EMAIL : "",
6 | PW : "",
7 | REMEMBER : false,
8 | IS_AUTHORIZING : false,
9 | IS_AUTH_SUCCESS : false,
10 | IS_AUTH_ERROR : false,
11 | TOGGLE_MODAL : false,
12 | AUTH_ERROR_MESG : "",
13 |
14 | }
15 |
16 | const loginReducer = (state = loginInitialState , action) => {
17 | switch(action.type){
18 | case LoginActions.A_TYPING_LOGIN_FORM_EMAIL :
19 | return Object.assign({},state,{
20 | EMAIL : action.value
21 | })
22 |
23 | case LoginActions.A_TYPING_LOGIN_FORM_PW :
24 | return Object.assign({},state,{
25 | PW : action.value
26 | })
27 |
28 | case LoginActions.A_IS_REMEMBER_CHECKED :
29 | return Object.assign({},state,{
30 | REMEMBER : action.value
31 | })
32 |
33 | case LoginActions.A_SUBMIT_BUTTON_CLCICKED :
34 | return Object.assign({},state,{
35 | IS_AUTHORIZING : action.value
36 | })
37 |
38 | case LoginActions.A_AUTH_SUCCESS :
39 | return Object.assign({},state,{
40 | IS_AUTHORIZING : !(action.value),
41 | IS_AUTH_ERROR : !(action.value),
42 | IS_AUTH_SUCCESS : action.value,
43 | AUTH_ERROR_MESG : ""
44 | })
45 | case LoginActions.A_AUTH_ERROR :
46 | return Object.assign({},state,{
47 | IS_AUTHORIZING : !(action.value),
48 | IS_AUTH_ERROR : action.value,
49 | IS_AUTH_SUCCESS : !(action.value),
50 | AUTH_ERROR_MESG : action.mesg
51 | })
52 | case LoginActions.A_CLEAR_LOGIN :
53 | return Object.assign({},state,{
54 | IS_AUTHORIZING : action.value,
55 | IS_AUTH_SUCCESS : action.value,
56 | IS_AUTH_ERROR : action.value
57 | })
58 | case LoginActions.A_TOGGLE_MODAL :
59 | return Object.assign({},state,{
60 | TOGGLE_MODAL : !(state.TOGGLE_MODAL)
61 | })
62 | case LoginActions.A_SET_EMAIL_BY_COOKIE :
63 | return Object.assign({},state,{
64 | EMAIL : action.value
65 | })
66 | case LoginActions.A_USER_LOGOUT :
67 | return Object.assign({},state,{
68 | ...loginInitialState
69 | })
70 | default :
71 | return state
72 | }
73 | }
74 |
75 | export default loginReducer
--------------------------------------------------------------------------------
/Components/redux/RegisterAction.js:
--------------------------------------------------------------------------------
1 | import { SERVER_URL } from './GlobalURL.js'
2 |
3 | export const A_TYPING_REGISTER_FORM = "A_TYPING_REGISTER_FORM"
4 | export const A_TYPING_USERNAME = "A_TYPING_USERNAME"
5 | export const A_TYPING_EMAIL = "A_TYPING_EMAIL"
6 | export const A_TYPING_PASSWORD = "A_TYPING_PASSWORD"
7 |
8 | export const A_ASSIGN_PROFILE_IMAGE = "A_ASSIGN_PROFILE_IMAGE"
9 | export const A_SUBMIT_REGISTER_FORM = "A_SUBMIT_REGISTER_FORM"
10 |
11 | export const A_SUBMIT_START = "A_SUBMIT_START"
12 | export const A_SUBMIT_SUCCESS = "A_SUBMIT_SUCCESS"
13 | export const A_SUBMIT_ERROR = "A_SUBMIT_ERROR"
14 |
15 | export const A_CLEAR_REDUCER = "A_CLEAR_REDUCER"
16 |
17 | export const AC_TYPING_REGISTER_FORM = (TYPE,VALUE,VALID) => {
18 |
19 | return {
20 | type : TYPE,
21 | value : VALUE,
22 | valid : VALID
23 | }
24 |
25 | }
26 |
27 | export const AC_ASSIGN_PROFILE_IMAGE = (BOOL , FILE_INFO) => {
28 | return {
29 | type : A_ASSIGN_PROFILE_IMAGE,
30 | value : BOOL,
31 | file : FILE_INFO
32 | }
33 | }
34 |
35 | export const AC_SUBMIT_REGISTER_FORM = () => {
36 |
37 | return (dispatch , getState) => {
38 | let SnapShot = getState()
39 | dispatch(AC_SUBMIT_START())
40 | fetch(`${SERVER_URL}/register/nonformalRegisterSubmit`,{
41 | method : 'POST',
42 | credentials : 'include',
43 | headers: {
44 | 'Accept': 'application/json',
45 | 'Content-Type': 'application/json'
46 | },
47 | body : JSON.stringify({
48 | USERNAME : SnapShot.register.USERNAME,
49 | EMAIL : SnapShot.register.EMAIL,
50 | PW : SnapShot.register.PW,
51 | FILENAME : SnapShot.register.FILENAME
52 | })
53 | }).then((response)=>(response.json())).then((Jres) => {
54 |
55 | if(Jres.status === 1){
56 |
57 | dispatch({
58 | type : A_SUBMIT_SUCCESS
59 | })
60 |
61 | }
62 | else{
63 | dispatch(AC_SUBMIT_ERROR(Jres.mesg))
64 | }
65 |
66 | })
67 | }
68 | }
69 |
70 | export const AC_SUBMIT_START = () => {
71 | return {
72 | type : A_SUBMIT_START
73 | }
74 | }
75 |
76 | export const AC_SUBMIT_ERROR = (mesg) => {
77 | return {
78 | type : A_SUBMIT_ERROR,
79 | mesg : mesg
80 | }
81 | }
82 |
83 | export const AC_CLEAR_REDUCER = () => {
84 | return {
85 | type : A_CLEAR_REDUCER
86 | }
87 | }
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/Components/redux/RegisterReducer.js:
--------------------------------------------------------------------------------
1 | import * as RegisterActions from './RegisterAction.js'
2 |
3 | const registerInitialState = {
4 |
5 | USERNAME : "",
6 | EMAIL : "",
7 | PW : "",
8 | FILENAME : "",
9 | IS_IMG_ASSIGNED : false,
10 | IS_VALID_NAME : false,
11 | IS_VALID_EMAIL : false,
12 | IS_VALID_PW : false,
13 | IS_SUBMITTING : false,
14 | IS_SUBMIT_SUCCESS : false,
15 | IS_SUBMIT_ERROR : false,
16 | SUBMIT_ERROR_MESG : ""
17 |
18 | }
19 |
20 | const registerReducer = (state = registerInitialState , action) => {
21 |
22 | switch(action.type){
23 |
24 | case RegisterActions.A_TYPING_USERNAME :
25 | return Object.assign({},state,{
26 | USERNAME : action.value,
27 | IS_VALID_NAME : action.valid,
28 | })
29 |
30 | case RegisterActions.A_TYPING_EMAIL :
31 | return Object.assign({},state,{
32 | EMAIL : action.value,
33 | IS_VALID_EMAIL : action.valid,
34 | })
35 |
36 | case RegisterActions.A_TYPING_PASSWORD :
37 | return Object.assign({},state,{
38 | PW : action.value,
39 | IS_VALID_PW : action.valid,
40 | })
41 |
42 | case RegisterActions.A_ASSIGN_PROFILE_IMAGE :
43 | return Object.assign({},state,{
44 | FILENAME : action.file,
45 | IS_IMG_ASSIGNED : action.value,
46 | })
47 |
48 | case RegisterActions.A_SUBMIT_START :
49 | return Object.assign({},state,{
50 | IS_SUBMITTING : true
51 | })
52 |
53 | case RegisterActions.A_SUBMIT_SUCCESS :
54 | return Object.assign({},state,{
55 | IS_SUBMITTING : false,
56 | IS_SUBMIT_SUCCESS : true,
57 | })
58 |
59 | case RegisterActions.A_SUBMIT_ERROR :
60 | return Object.assign({},state,{
61 | USERNAME : "",
62 | EMAIL : "",
63 | PW : "",
64 | FILENAME : "",
65 | IS_IMG_ASSIGNED : false,
66 | IS_VALID_NAME : false,
67 | IS_VALID_EMAIL : false,
68 | IS_VALID_PW : false,
69 | IS_SUBMITTING : false,
70 | IS_SUBMIT_SUCCESS : false,
71 | IS_SUBMIT_ERROR : true,
72 | SUBMIT_ERROR_MESG : action.mesg
73 | })
74 |
75 | case RegisterActions.A_CLEAR_REDUCER :
76 |
77 | return Object.assign({},state,{
78 | ...registerInitialState
79 | })
80 |
81 | default :
82 | return state
83 |
84 | }
85 |
86 | }
87 |
88 | export default registerReducer
--------------------------------------------------------------------------------
/Components/redux/UserAction.js:
--------------------------------------------------------------------------------
1 |
2 | import { SERVER_URL } from './GlobalURL.js'
3 | import { refreshSessionStroage } from './LoginAction.js'
4 |
5 | export const A_SET_USER_INFO = "A_SET_USER_INFO"
6 | export const A_USER_CLICK_WRITE = "A_USER_CLICK_WRITE"
7 | export const A_GET_SESSION_DATA = "AC_GET_SESSION_DATA"
8 |
9 |
10 | export const AC_SET_USER_INFO = (userInfo) => {
11 | return refreshSessionStroage(userInfo)
12 | }
13 |
14 | export const AC_GET_SESSION_DATA = (sid) => {
15 |
16 | return (dispatch) => {
17 |
18 | fetch(`${SERVER_URL}/login/getSessionData`,{
19 | method : `POST`,
20 | credentials : `include`,
21 | headers: {
22 | 'Accept': 'application/json',
23 | 'Content-Type': 'application/json'
24 | },
25 | body : JSON.stringify({
26 | SID : sid
27 | })
28 | }).then(response => (response.json())).then((Jres) => {
29 |
30 | if(Jres.status === 1){
31 |
32 | dispatch(AC_SET_USER_INFO(Jres))
33 |
34 | }
35 |
36 | })
37 |
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/Components/redux/UserReducer.js:
--------------------------------------------------------------------------------
1 | import * as UserActions from './UserAction.js'
2 |
3 | const userInitialState = {
4 |
5 | USERNAME : "Unknown",
6 | U_IMG_PATH : "",
7 | REG_DATE : "",
8 | NUM_OF_ARTICLES : 0,
9 | NUM_OF_REPLIES : 0,
10 | NUM_OF_GOTTEN_RECOMMENDS : 0,
11 | NUM_OF_HIT_RECOMMENS : 0,
12 | IS_SESSION_SUCCESS : false
13 |
14 | }
15 |
16 | const userReducer = (state = userInitialState , action) => {
17 |
18 | switch(action.type){
19 |
20 | case UserActions.A_SET_USER_INFO :
21 | return Object.assign({},state,{
22 |
23 | USERNAME : action.value.USERNAME,
24 | U_IMG_PATH : action.value.U_IMG_PATH,
25 | REG_DATE : action.value.REG_DATE,
26 | NUM_OF_ARTICLES : action.value.NUM_OF_ARTICLES,
27 | NUM_OF_REPLIES : action.value.NUM_OF_REPLIES,
28 | NUM_OF_GOTTEN_RECOMMENDS : action.value.NUM_OF_GOTTEN_RECOMMENDS,
29 | NUM_OF_HIT_RECOMMENS : action.value.NUM_OF_HIT_RECOMMENS,
30 | IS_SESSION_SUCCESS : true
31 | })
32 |
33 | default :
34 | return state
35 | }
36 |
37 | }
38 |
39 | export default userReducer
--------------------------------------------------------------------------------
/Components/redux/WriteAction.js:
--------------------------------------------------------------------------------
1 | export const A_CHANGE_VIEW_MODE = 'A_CHANGE_VIEW_MODE'
2 | export const A_CHANGE_MEDIA_MODE = 'A_CHANGE_MEDIA_MODE'
3 |
4 | export const AC_CHANGE_VIEW_MODE = (mode) => {
5 |
6 | if(mode){
7 | return {
8 | type : A_CHANGE_VIEW_MODE,
9 | value : mode
10 | }
11 | }
12 | else{
13 | return {
14 | type: A_CHANGE_VIEW_MODE,
15 | value: mode
16 | }
17 | }
18 |
19 |
20 | }
21 | export const AC_CHANGE_MEDIA_MODE = (mode) => {
22 |
23 | if(mode){
24 | return {
25 | type : A_CHANGE_MEDIA_MODE,
26 | value : mode
27 | }
28 | }
29 | else{
30 | return {
31 | type: A_CHANGE_MEDIA_MODE,
32 | value: mode
33 | }
34 | }
35 |
36 |
37 | }
--------------------------------------------------------------------------------
/Components/redux/WriteReducer.js:
--------------------------------------------------------------------------------
1 | import * as WriteActions from './WriteAction.js'
2 |
3 | const writeInitialState = {
4 |
5 | ViewState : true,
6 | MediaState : true
7 |
8 | }
9 |
10 | const writeReducer = ( state = writeInitialState , action ) => {
11 |
12 | switch(action.type){
13 |
14 | case WriteActions.A_CHANGE_MEDIA_MODE :
15 | return Object.assign({},state,{
16 | MediaState: action.value
17 | })
18 | case WriteActions.A_CHANGE_VIEW_MODE :
19 | return Object.assign({},state,{
20 | ViewState: action.value
21 | })
22 |
23 | default :
24 | return state
25 | }
26 |
27 | }
28 |
29 | export default writeReducer
30 |
--------------------------------------------------------------------------------
/Components/redux/_store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, combineReducers } from "redux";
2 |
3 | import loginReducer from "./LoginReducer.js";
4 | import registerReducer from "./RegisterReducer.js";
5 | import writeReducer from "./WriteReducer.js";
6 | import articleLoaderReducer from "./ArticleLoaderReducer.js";
7 |
8 | import logger from "redux-logger";
9 | import thunk from "redux-thunk";
10 |
11 | let originReducer = combineReducers({
12 | login: loginReducer,
13 | register: registerReducer,
14 | write: writeReducer,
15 | articleLoader: articleLoaderReducer
16 | });
17 | let _store = createStore(originReducer, applyMiddleware(logger, thunk));
18 |
19 | export default _store;
20 |
--------------------------------------------------------------------------------
/Components/src/AlertBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './AlertBox.scss'
4 |
5 | const AlertBox = (props) => {
6 | return(
7 |
10 | )
11 | }
12 |
13 | export default AlertBox
--------------------------------------------------------------------------------
/Components/src/AlertBox.scss:
--------------------------------------------------------------------------------
1 | $material_alertborder : hsl(42, 100%, 50%);
2 | $material_alertbackground : hsl(45, 100%, 85%);
3 | $material_alerttext : hsl(16, 18%, 47%);
4 | $material_verifyborder : hsl(123, 46%, 34%);
5 | $material_verifybackground : hsl(151, 100%, 45%);
6 | $material_errorborder : hsl(333, 84%, 42%);
7 | $material_errorbackground : hsl(5, 100%, 75%);
8 | $material_shadow : hsl(200, 19%, 18%);
9 |
10 | .AlertBox{
11 | border-radius : 12px;
12 | padding : 10px;
13 | }
14 | .AlertBox:nth-child(1){
15 | margin-top: 10px;
16 | }
17 | .AlertBox__Text{
18 | font-size: 12px;
19 | }
20 | .--Alert{
21 | border : 1px solid $material_alertborder;
22 | background-color: $material_alertbackground;
23 | color : $material_alerttext;
24 | }
25 | .--Success{
26 | border : 1px solid $material_verifyborder;
27 | background-color: $material_verifybackground;
28 | color : $material_shadow;
29 | text-align: center;
30 | }
31 | .--Error{
32 | border : 1px solid $material_errorborder;
33 | background-color: $material_errorbackground;
34 | color : $material_shadow;
35 | text-align: center;
36 | font-size: 16px;
37 | }
--------------------------------------------------------------------------------
/Components/src/ArticleFilter.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './AticleFilter.scss'
3 | import {MdFilter, MdUpdate, MdPhotoFilter, MdTrendingUp , MdTrendingDown, MdInsertPhoto} from 'react-icons/md'
4 | import {FaReplyd, FaEye, FaThumbsUp, FaSortAmountUp, FaSortAmountDown} from 'react-icons/fa'
5 | import {AC_FILT_READABLE_DOCS} from '../redux/ArticleLoaderAction.js'
6 |
7 | import {connect} from 'react-redux'
8 |
9 | class ArticleFilter extends React.Component{
10 |
11 | constructor(props){
12 |
13 | super(props)
14 |
15 | this.state = {
16 | REPLY : "RESET",
17 | EYE : "RESET",
18 | TIME : "RESET",
19 | THUMB : "RESET",
20 | }
21 |
22 | this.handleFilterClick = this.handleFilterClick.bind(this)
23 | this.renderQueryOptions = this.renderQueryOptions.bind(this)
24 | }
25 |
26 | handleFilterClick(e,type,order){
27 |
28 | let stateNow = this.state
29 |
30 | if(stateNow[type] === order){
31 | return
32 | }
33 |
34 | Object.keys(stateNow).forEach((el) => {
35 | stateNow[el] = "RESET"
36 | })
37 |
38 | stateNow[type] = order
39 | this.props.articleFilterDispatch.filtReadableDocs(type,order)
40 | return this.setState(stateNow)
41 |
42 | }
43 |
44 | renderQueryOptions(){
45 |
46 | const OPTIONS = [
47 |
48 | ,
49 | ,
50 | ,
51 | ,
52 |
53 | ]
54 |
55 | const UP_ICON = () => {
56 | return
57 | }
58 |
59 | const DOWN_ICON = () => {
60 | return
61 | }
62 |
63 | let filters = OPTIONS.map((el,index,array) => {
64 |
65 | return (
66 |
67 |
68 |
69 |
{
70 | this.handleFilterClick(e,el.props.id,'UP')
71 | }}>
72 | {}
73 |
74 |
75 |
{
76 | this.handleFilterClick(e,el.props.id,'RESET')
77 | }}>
78 | {el}
79 |
80 |
81 |
{
82 | this.handleFilterClick(e,el.props.id,'DOWN')
83 | }}>
84 | {}
85 |
86 |
87 |
88 |
89 | )
90 |
91 | })
92 |
93 | return filters
94 |
95 | }
96 |
97 | render(){
98 | return(
99 |
100 |
101 |
102 | Filter
103 |
104 | {this.renderQueryOptions()}
105 |
110 |
111 | )
112 | }
113 | }
114 |
115 | const mapDispatchToProps = (dispatch) => {
116 | return {
117 | articleFilterDispatch : {
118 | filtReadableDocs(type,order){
119 | dispatch(AC_FILT_READABLE_DOCS(type,order))
120 | }
121 | }
122 | }
123 | }
124 |
125 | ArticleFilter = connect(null,mapDispatchToProps)(ArticleFilter)
126 |
127 | export default ArticleFilter
--------------------------------------------------------------------------------
/Components/src/ArticleLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SERVER_URL } from '../redux/GlobalURL.js'
3 | import './ArticleLoader.scss'
4 | import { MdSearch } from 'react-icons/md'
5 | import { FaThumbsUp, FaEye, FaReplyd } from 'react-icons/fa'
6 | import { connect } from 'react-redux'
7 | import { AC_LOAD_POSTINGS, AC_POST_EYE_UP, AC_REFRESH_ARTICLE_LOAD_STATE } from '../redux/ArticleLoaderAction.js'
8 | import { HashLoader } from 'react-spinners'
9 | import { withRouter } from 'react-router-dom'
10 |
11 | class ArticleLoader extends React.Component{
12 |
13 | constructor(props){
14 | super(props)
15 | this.handleScrollBottom = this.handleScrollBottom.bind(this)
16 | }
17 |
18 | componentDidMount(){
19 |
20 | let { articleLoaderState , articleLoaderDispatch } = this.props
21 |
22 | if(articleLoaderState.SKIP === 0){
23 | articleLoaderDispatch.loadPostings()
24 | }
25 |
26 | let articleWrapper = document.querySelector(".ArticleLoader-Wrapper");
27 | articleWrapper.addEventListener('scroll',this.handleScrollBottom);
28 |
29 | }
30 |
31 | renderReadableDocs(){
32 | let { articleLoaderState, articleLoaderDispatch } = this.props
33 | const makeDocTemplate = (el,index) => {
34 | return (
35 | {
36 | this.props.articleLoaderDispatch.postEyeUp(el._id)
37 | this.handleRouteToPostContent(el._id)
38 | }} className="ArticleLoader-Wrapper__Item" key={index}>
39 |
40 |

41 |
42 |
43 |
44 |
45 |
46 | {el.POST_TITLE}
47 |
48 |
49 | {new Date(el.POST_DATE).toLocaleDateString('kr')}
50 |
51 |
52 |
53 |
54 |
55 |

56 |
{el.AUTHOR}
57 |
58 |
59 |
60 | {(el.RECOMMEND || []).length}
61 |
62 |
63 |
64 | {(el.EYE || 0)}
65 |
66 |
67 |
68 | {(el.POST_REPLY || []).length}
69 |
70 |
71 |
72 |
73 | )
74 | }
75 | return articleLoaderState.READABLE_DOCS.map((el,index) => {
76 | if(articleLoaderState.IS_SEARCHING && !!el.SEARCH_TOUCHED){
77 | return makeDocTemplate(el,index)
78 | }
79 | else if(!articleLoaderState.IS_SEARCHING){
80 | return makeDocTemplate(el,index)
81 | }
82 | })
83 |
84 | }
85 |
86 | handleRouteToPostContent(postId){
87 | return this.props.history.push(`/view?user=${window.sessionStorage.getItem('EMAIL').split("@")[0]}&postId=${postId}`,{
88 | postId : postId
89 | })
90 | }
91 |
92 | handleScrollBottom(event){
93 | if(event.target.scrollHeight - event.target.scrollTop === event.target.clientHeight){
94 | this.props.articleLoaderDispatch.loadPostings()
95 | }
96 | }
97 |
98 | render(){
99 | let {articleLoaderState} = this.props
100 | return (
101 |
102 | {
103 | articleLoaderState.SKIP === 0 ?
104 | ()
105 | :
106 | (this.renderReadableDocs())
107 | }
108 |
109 | )
110 | }
111 | }
112 |
113 | const mapDispatchToProps = (dispatch) => {
114 |
115 | return {
116 | articleLoaderDispatch : {
117 | loadPostings(explicitSkip){
118 | return dispatch(AC_LOAD_POSTINGS(explicitSkip))
119 | },
120 | postEyeUp(postId){
121 | return dispatch(AC_POST_EYE_UP(postId))
122 | }
123 | }
124 | }
125 |
126 | }
127 |
128 | const mapStateToProps = (state) => {
129 | return {
130 | articleLoaderState : state.articleLoader
131 | }
132 | }
133 |
134 | ArticleLoader = withRouter(connect(mapStateToProps,mapDispatchToProps)(ArticleLoader))
135 |
136 | export default ArticleLoader
--------------------------------------------------------------------------------
/Components/src/ArticleLoader.scss:
--------------------------------------------------------------------------------
1 | .ArticleLoader-Wrapper{
2 | overflow-y: auto;
3 | max-height: 100%;
4 | padding : 20px 0;
5 | box-sizing: border-box;
6 | }
7 | .ArticleLoader-Wrapper__Item{
8 | width : 90%;
9 | box-shadow: 0px 3px 3px #ccc;
10 | margin : 0 auto;
11 | margin-bottom : 20px;
12 | background-color : white;
13 | height : 150px;
14 | border-radius: 8px;
15 | display: grid;
16 | grid-template-columns: 150px 1fr;
17 | cursor: pointer;
18 | transition: box-shadow 300ms ease;
19 | }
20 | .ArticleLoader-Wrapper__Item:hover{
21 | box-shadow: 0px 3px 3px #1f2023;
22 | }
23 | .ArticleLoader-Wrapper__Item__Thumbnail{
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | border-right : 0.3pt solid #ccc;
28 | }
29 | .ArticleLoader-Wrapper__Item__Thumbnail img{
30 | width : 80%;
31 | height: 80%;
32 | max-height: 80%;
33 | border-radius: 8px;
34 | }
35 | .ArticleLoader-Wrapper__Item__Sub-Row{
36 | height : 75px;
37 | display: grid;
38 | grid-template-columns: 1fr 120px;
39 | overflow-x: hidden;
40 | border-bottom : 0.3pt solid #ccc;
41 | }
42 | .ArticleLoader-Wrapper__Item__Sub-Row:nth-child(2){
43 | grid-template-columns: 1fr repeat(3,90px);
44 | }
45 | .ArticleLoader-Wrapper__Item__Title__Item{
46 | display: flex;
47 | align-items: center;
48 |
49 | }
50 | .ArticleLoader-Wrapper__Item__Title__Item:nth-child(1){
51 | justify-content: flex-start;
52 | padding-left: 20px;
53 | font-weight: bold;
54 | font-size : 22px;
55 | }
56 | .ArticleLoader-Wrapper__Item__Title__Item:nth-child(2){
57 | justify-content: center;
58 | color : #ccc;
59 | font-size: 18px;
60 | }
61 | .ArticleLoader-Wrapper__Item__Author{
62 | height : 75px;
63 | display: flex;
64 | align-items: center;
65 | justify-content: flex-start;
66 | padding-left : 20px;
67 | }
68 | .ArticleLoader-Wrapper__Item__Author img{
69 | border-radius: 50%;
70 | width : 50px;
71 | height : 50px;
72 | box-shadow: 0px 2px 2px #ccc;
73 | margin-right : 20px;
74 | }
75 | .ArticleLoader-Wrapper__Item__Author span{
76 | color : #4285F4;
77 | }
78 |
79 | .--Element-Centered{
80 | display: flex;
81 | align-items: center;
82 | justify-content: center;
83 | min-height : calc(100vh - 65px);
84 | }
85 | .ArticleLoader-Wrapper__Item__Recommend,
86 | .ArticleLoader-Wrapper__Item__Eye{
87 | display: flex;
88 | align-items: center;
89 | justify-content: center;
90 | }
91 | .ThumbUp-Icon{
92 | color : rgb(77, 141, 245);
93 | font-size: 20px;
94 | }
95 | .Eye-Icon{
96 | color : rgb(233, 30, 99);
97 | font-size: 20px;
98 | margin-top : 5px;
99 | }
100 | .Reply-Icon{
101 | color : rgb(0, 230, 119);
102 | font-size: 20px;
103 | margin-top: 4px;
104 | }
105 | .ThumbUp-Number,
106 | .Eye-Number,
107 | .Reply-Number{
108 | margin-top : 7px;
109 | margin-left : 7px;
110 | }
--------------------------------------------------------------------------------
/Components/src/ArticleReplyPane.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './ArticleReplyPane.scss'
3 | import { FaAngleDoubleRight, FaThumbsUp, FaThumbsDown } from 'react-icons/fa'
4 | import { MdDelete } from 'react-icons/md'
5 | import {SERVER_URL} from '../redux/GlobalURL.js'
6 | import {withRouter} from 'react-router-dom'
7 |
8 | class ArticleReplyPane extends React.Component{
9 |
10 | constructor(props){
11 | super(props)
12 | }
13 |
14 | stampToDate(stamp){
15 | if(stamp){
16 | return new Date(stamp).toJSON().substr(0,10);
17 | }
18 | }
19 |
20 | setImage(imgPath){
21 | if(!imgPath){
22 | return SERVER_URL.concat("/UserImages/no-user-img.svg")
23 | }
24 | else{
25 | return SERVER_URL.concat(`/UserImages/${imgPath}`)
26 | }
27 | }
28 |
29 | renderPostReply(){
30 |
31 | if(!!this.props.post.POST_REPLY && this.props.post.POST_REPLY.length > 0){
32 | return this.props.post.POST_REPLY.map((el,index) => {
33 | return (
34 |
35 |
36 |

37 |
38 |
39 | {el.REPLY_AUTHOR}
40 |
41 |
42 | {el.REPLY_CONTENT}
43 |
44 |
45 | {this.stampToDate(el.REPLY_DATE)}
46 |
47 | {
48 | window.sessionStorage.getItem('EMAIL') === el.REPLY_AUTHOR_EMAIL ?
49 | (
50 | {
51 | console.log(el)
52 | if(!!this.props.location.state.postId){
53 | this.props.replyDeleteHandler(this.props.location.state.postId,el._id)
54 | }
55 | }} className="Delete-Icon" />
56 |
)
57 | :
58 | (
)
59 | }
60 |
61 |
62 | )
63 | })
64 | }
65 |
66 | }
67 |
68 | render(){
69 | return(
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
{this.props.post.POST_TITLE}
80 |
81 |
82 |
})
83 |
84 |
85 |
86 |
87 | {this.props.post.EMAIL}
88 |
89 |
90 | {this.props.post.AUTHOR}
91 |
92 |
93 | {this.stampToDate(this.props.post.POST_DATE)}
94 |
95 |
96 | {
97 | if(this.props.location.state.postId && window.sessionStorage.getItem('EMAIL')){
98 | this.props.thumbUpHandler(this.props.location.state.postId,window.sessionStorage.getItem('EMAIL'))
99 | }
100 | }} id="THUMB_UP" />
101 | {(this.props.post.RECOMMEND || []).length}
102 |
103 |
104 |
105 | {this.renderPostReply()}
106 |
107 |
108 |
109 | {
110 | if(event.keyCode === 13){
111 | this.props.replyPostHandler()
112 | }
113 | }} placeholder="type reply here." className="ArticleReplyPane-Inner__Interface__Item__Input" type="text"/>
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | )
123 | }
124 | }
125 |
126 |
127 |
128 | export default withRouter(ArticleReplyPane)
--------------------------------------------------------------------------------
/Components/src/ArticleReplyPane.scss:
--------------------------------------------------------------------------------
1 | .ArticleReplyPane-Container{
2 | height: calc(100vh - 66px);
3 | width: 60vw;
4 | top: 66px;
5 | right: 0;
6 | position: absolute;
7 | background-color: white;
8 | transition : width 600ms ease;
9 | overflow-x: hidden;
10 | box-sizing: border-box;
11 | box-shadow: -6px 0px 10px 0px #ccc;
12 | }
13 | .--ArticleReplyPane-Hide{
14 | width : 0;
15 | }
16 |
17 | .ArticleReplyPane-Inner{
18 | background-color: white;
19 | height: 100%;
20 | position: relative;
21 | }
22 | .ArticleReplyPane-Inner__Header{
23 | display: grid;
24 | grid-template-columns: 60px 1fr 60px;
25 | height: 60px;
26 | }
27 | .ArticleReplyPane-Inner__Header__Item{
28 | display: flex;
29 | align-items: center;
30 | justify-content: center;
31 | border-bottom: 1px solid #ccc;
32 | position: relative;
33 | }
34 | .ArticleReplyPane-Inner__Header__Item__Title{
35 | font-size: 1.3vw;
36 | font-weight: bold;
37 | }
38 | .ArticleReplyPane-Inner__Header__Item__User-Image{
39 | border-radius: 50%;
40 | width : 45px;
41 | height: 45px;
42 | }
43 |
44 | .ArticleReplyPane-Inner__Sub-Info{
45 | display: flex;
46 | justify-content: flex-end;
47 | align-items: center;
48 | height: 30px;
49 | color : #aaa;
50 | }
51 | .ArticleReplyPane-Inner__Sub-Info__Item{
52 | margin-right: 10px;
53 | border-right: 1px solid #ccc;
54 | padding-right: 10px;
55 | text-align: center;
56 | }
57 | .ArticleReplyPane-Inner__Sub-Info__Item:last-child{
58 | // margin-right: 0;
59 | padding-right : 0;
60 | border-right: none;
61 | }
62 | .ArticleReplyPane-Inner__Sub-Info__Item__Thumb-Number{
63 | margin: 0 5px;
64 | }
65 |
66 | .ArticleReplyPane-Inner__List{
67 | min-height: calc(100% - 151px);
68 | height: calc(100% - 151px);
69 | overflow-y: auto;
70 | }
71 | .ArticleReplyPane-Inner__Interface{
72 | border-top: 1px solid #ccc;
73 | grid-template-columns: 1fr 120px;
74 | height: 60px;
75 | display: grid;
76 | }
77 | .ArticleReplyPane-Inner__Interface__Item{
78 | display: flex;
79 | align-items: center;
80 | justify-content: center;
81 | }
82 | .ArticleReplyPane-Inner__Interface__Item__Input, #SEND_REPLY{
83 | width: 98%;
84 | height: 80%;
85 | border: none;
86 | outline: none;
87 | border-radius: 10px;
88 | padding: 5px 10px;
89 | box-sizing: border-box;
90 | font-size: 1vw;
91 | }
92 | .ArticleReplyPane-Inner__Interface__Item__Input{
93 | background-color: lightgray;
94 | }
95 | #Fold{
96 | color : rgb(32, 33, 35);
97 | font-size: 1.5vw;
98 | cursor: pointer;
99 | }
100 | #THUMB_UP{
101 | color: rgb(69, 133, 195);
102 | font-size: 16px;
103 | cursor: pointer;
104 | transition: font-size 300ms linear;
105 | }
106 | #THUMB_UP:hover{
107 | font-size: 20px;
108 | }
109 | #SEND_REPLY{
110 | width : 95%;
111 | background-color: lightgray;
112 | transition: all 300ms ease;
113 | }
114 | #SEND_REPLY:hover, .OK{
115 | background-color: rgb(32, 33, 35) !important;
116 | color: white;
117 | cursor: pointer;
118 | }
119 | .Reply-Row{
120 | height: 50px;
121 | display: grid;
122 | grid-template-columns: 50px 100px 1fr 100px 30px;
123 | margin-bottom : 3px;
124 | border-bottom : 1px solid #ccc;
125 | }
126 | .Reply-Row:last-child{
127 | border-bottom: none;
128 | }
129 | .Reply-Row__Item img{
130 | width : 45px;
131 | height : 45px;
132 | border-radius: 50%;
133 | }
134 | .Reply-Row__Item{
135 | display: flex;
136 | align-items: center;
137 | }
138 | .Reply-Row__Item:not(:nth-child(3)){
139 | justify-content: center;
140 | }
141 | .Reply-Row__Item:nth-child(2){
142 | color : #4385f4;
143 | }
144 | .Reply-Row__Item:nth-child(3){
145 | justify-content: flex-start;
146 | }
147 | .Font-Bold{
148 | font-weight: bold;
149 | }
150 | .Delete-Icon{
151 | color: rgb(222, 75, 59);
152 | width : 20px;
153 | height: 20px;
154 | cursor: pointer;
155 | }
156 |
157 |
--------------------------------------------------------------------------------
/Components/src/ArticleView.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import TopBar from './TopBar.jsx'
3 | import { SERVER_URL } from '../redux/GlobalURL.js'
4 | import { HashLoader } from 'react-spinners'
5 | import ArticleReplyPane from './ArticleReplyPane.jsx'
6 | import { FaReplyd } from 'react-icons/fa'
7 | import { MdViewList } from 'react-icons/md'
8 |
9 | import './ArticleView.scss'
10 | import {withRouter} from 'react-router-dom'
11 | import {connect} from 'react-redux'
12 |
13 | class ArticleView extends React.Component{
14 |
15 | constructor(props){
16 | super(props)
17 | this.state = {
18 | POST : {},
19 | REPLY_CONTENT : "",
20 | IS_LOADING_CONTENT : false,
21 | }
22 | this.renderPostContent = this.renderPostContent.bind(this)
23 | this.handleRouteToHome = this.handleRouteToHome.bind(this)
24 | this.handleReplyContentChange = this.handleReplyContentChange.bind(this)
25 | this.handleReplyPostClick = this.handleReplyPostClick.bind(this)
26 | this.handleReplyDeleteClick = this.handleReplyDeleteClick.bind(this)
27 | this.handleThumbUpClick = this.handleThumbUpClick.bind(this)
28 | }
29 |
30 | componentDidMount(){
31 |
32 | this.setState({
33 | IS_LOADING_CONTENT : true
34 | })
35 | fetch(`${SERVER_URL}/read/getContentById?_id=${this.props.location.state.postId}`,{
36 | method : 'GET'
37 | })
38 | .then(res=>res.json())
39 | .then((res) => {
40 | if(res.status === 1){
41 |
42 | this.setState({
43 | POST : res.payload[0],
44 | IS_LOADING_CONTENT : false
45 | })
46 |
47 | }
48 | })
49 |
50 | }
51 |
52 | renderPostContent(){
53 | return (
54 |
55 | )
56 | }
57 |
58 | handleShowReplyPane(){
59 | let ReplyPane = document.querySelector('.ArticleReplyPane-Container');
60 | ReplyPane.classList.remove('--ArticleReplyPane-Hide');
61 | }
62 | handleReplyPaneHide(){
63 | let ReplyPane = document.querySelector('.ArticleReplyPane-Container');
64 | ReplyPane.classList.add('--ArticleReplyPane-Hide');
65 | }
66 |
67 | handleRouteToHome(){
68 | let { history } = this.props;
69 | return history.push(`/home?user=${window.sessionStorage.getItem('EMAIL').split("@")[0]}`,null);
70 | }
71 |
72 | handleReplyPostClick(event){
73 |
74 | let inputs = document.querySelectorAll(['.ArticleReplyPane-Inner__Interface__Item__Input','#SEND_REPLY'])
75 | inputs.forEach(el=>el.disabled=true)
76 | inputs[1].textContent = "sending..."
77 | fetch(`${SERVER_URL}/read/postReply`,{
78 | method : "POST",
79 | headers: {
80 | 'Accept': 'application/json',
81 | 'Content-Type': 'application/json'
82 | },
83 | body : JSON.stringify({
84 | _id : this.props.location.state.postId,
85 | REPLY_AUTHOR : window.sessionStorage.getItem('USERNAME'),
86 | REPLY_AUTHOR_IMAGE : window.sessionStorage.getItem('U_IMG_PATH').split("/").reverse()[0],
87 | REPLY_AUTHOR_EMAIL : window.sessionStorage.getItem('EMAIL'),
88 | REPLY_CONTENT : this.state.REPLY_CONTENT,
89 | REPLY_DATE : new Date().getTime().toString()
90 | })
91 | }).then(res => res.json())
92 | .then(res => {
93 | if(res.status === 1){
94 | inputs.forEach(el=>el.disabled=false)
95 | inputs[1].textContent = "reply"
96 | if(inputs[1].classList.contains('OK')){
97 | inputs[1].classList.remove('OK');
98 | }
99 | this.setState({
100 | POST : res.payload,
101 | REPLY_CONTENT : ""
102 | })
103 | let list = document.querySelector('.ArticleReplyPane-Inner__List');
104 | list.scrollTop = list.scrollHeight
105 | }
106 | })
107 |
108 | }
109 |
110 | handleReplyDeleteClick(articleId,replyId){
111 | fetch(`${SERVER_URL}/read/deleteReply`,{
112 | method : 'POST',
113 | headers : {
114 | 'Accept': 'application/json',
115 | 'Content-Type' : 'application/json'
116 | },
117 | body : JSON.stringify({
118 | targetArticleId : articleId,
119 | targetReplyId : replyId
120 | })
121 | }).then(res => res.json())
122 | .then(res => {
123 | if(res.status === 1){
124 | this.setState({
125 | POST : res.payload
126 | })
127 | }
128 | })
129 | }
130 |
131 | handleThumbUpClick(targetArticleId,EMAIL){
132 |
133 | if(this.state.POST.RECOMMEND.includes(EMAIL)){
134 | return alert("이미 추천한 게시물입니다.")
135 | }
136 |
137 | fetch(`${SERVER_URL}/read/recommendUp`,{
138 | method : 'POST',
139 | headers : {
140 | 'Accept': 'application/json',
141 | 'Content-Type' : 'application/json'
142 | },
143 | body : JSON.stringify({
144 | targetArticleId : targetArticleId,
145 | EMAIL : EMAIL
146 | })
147 | }).then(res => res.json())
148 | .then(res => {
149 | if(res.status === 1){
150 |
151 | this.setState({
152 | POST : res.payload
153 | })
154 | }
155 | })
156 |
157 | }
158 |
159 | handleReplyContentChange(event){
160 | let send_btn = document.getElementById("SEND_REPLY")
161 | if(event.target.value.length > 0){
162 | if(!send_btn.classList.contains('OK')){
163 | send_btn.classList.add('OK')
164 | }
165 | }
166 | else{
167 | if(send_btn.classList.contains('OK')){
168 | send_btn.classList.remove('OK')
169 | }
170 | }
171 | return this.setState({
172 | REPLY_CONTENT : event.target.value
173 | })
174 | }
175 |
176 | render(){
177 | return(
178 |
179 |
180 |
181 |
190 |
191 |
192 |
193 | {
194 | this.state.IS_LOADING_CONTENT ?
195 | ()
196 | :
197 | (this.renderPostContent())
198 | }
199 |
200 |
201 |
202 |
203 |
207 |
211 |
212 |
213 |
214 |
215 |
216 |
217 | )
218 | }
219 |
220 | }
221 |
222 | const mapStateToProps = (state) => {
223 | return {
224 | articleLoaderState : state.articleLoader
225 | }
226 | }
227 |
228 | export default withRouter(connect(mapStateToProps,null)(ArticleView))
--------------------------------------------------------------------------------
/Components/src/ArticleView.scss:
--------------------------------------------------------------------------------
1 | .View-Grid-Container{
2 | display: grid;
3 | grid-template-columns: 1fr 350px;
4 | height: 100vh;
5 |
6 | .View-Grid-Container__Item:nth-child(1){
7 | padding : 73px 8px 8px 8px;
8 | background-color: white;
9 | height : calc(100vh - 81px);
10 |
11 | }
12 | .View-Grid-Container__Item:nth-child(2){
13 |
14 | background-color: hsl(225, 6%, 13%);
15 | padding-top : 65px;
16 | min-height: calc(100vh - 65px);
17 | height : calc(100vh - 65px);
18 | width : 350px;
19 | transition : width 300ms ease;
20 |
21 | .View-Grid-Container__Item__Item{
22 | height: 50px;
23 | display: grid;
24 | grid-template-columns: repeat(2, 1fr);
25 | padding: 0 10px;
26 | grid-column-gap: 15px;
27 | margin-top : 15px;
28 | }
29 |
30 | .View-Btn{
31 | outline: none;
32 | border: none;
33 | cursor: pointer;
34 | display: flex;
35 | align-items: center;
36 | justify-content: center;
37 | font-size: 15px;
38 | width: 100%;
39 | height: 100%;
40 | background-color: white;
41 | border-radius: 4px;
42 | box-shadow: 0 3px 3px black;
43 | transition: all 300ms ease;
44 | }
45 | .View-Btn:hover{
46 | background-color: #00E677;
47 | }
48 | }
49 | .--Element-Centered{
50 | display: flex;
51 | align-items: center;
52 | justify-content: center;
53 | min-height : calc(100vh - 81px);
54 | }
55 | }
56 |
57 | .Interface-Icon{
58 | font-size: 25px;
59 | margin-right: 5px;
60 | margin-bottom: 2px;
61 | }
62 |
--------------------------------------------------------------------------------
/Components/src/AticleFilter.scss:
--------------------------------------------------------------------------------
1 | .Filter-Wrapper{
2 | padding : 10px;
3 | }
4 | .Filter-Header{
5 | width : 90%;
6 | margin : 10px auto 0px auto;
7 | background-color: white;
8 | height : 45px;
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | border-radius: 12px;
13 | box-shadow: 0 3px 3px #ccc;
14 | color : black;
15 | padding : 0 40px;
16 | box-sizing: border-box;
17 | position: relative;
18 | }
19 |
20 | .Filter-Header span{
21 | font-size: 22px;
22 | letter-spacing: 0.01em;
23 | }
24 |
25 | #FILTER{
26 | position: absolute;
27 | top : 11px;
28 | left : 11px;
29 | width : 24px;
30 | height : 24px;
31 | margin-right : 20px;
32 | }
33 |
34 | .Filter-Item{
35 | margin-top : 20px;
36 | box-shadow: 0 3px 3px #ccc;
37 | color : cornflowerblue;
38 | width : 90%;
39 | margin : 20px auto 0px auto;
40 | background-color: white;
41 | height : 45px;
42 | border-radius: 12px;
43 | display: grid;
44 | grid-template-columns: repeat(3,1fr);
45 | }
46 |
47 | .Filter-Item div{
48 | display: flex;
49 | align-items: center;
50 | justify-content: center;
51 | cursor: pointer;
52 |
53 | .Query-Icon{
54 | width : 30px;
55 | height : 30px;
56 | transition: all 300ms ease;
57 | }
58 |
59 | }
60 |
61 | .Filter-Item div:nth-child(1),
62 | .Filter-Item div:nth-child(3){
63 | transition: all 300ms ease;
64 | }
65 | .Filter-Item div:nth-child(1){
66 | border-top-left-radius: 12px;
67 | border-bottom-left-radius: 12px;
68 | }
69 | .Filter-Item div:nth-child(3){
70 | border-top-right-radius: 12px;
71 | border-bottom-right-radius: 12px;
72 | }
73 |
74 | .Filter-Item div:nth-child(2){
75 | color : white;
76 | background-color: cornflowerblue;
77 | border-right : 2px solid #ccc;
78 | border-left : 2px solid #ccc;
79 | }
80 | .--Icon-Selected{
81 | background-color: cornflowerblue;
82 | color : white;
83 | }
--------------------------------------------------------------------------------
/Components/src/Editor.scss:
--------------------------------------------------------------------------------
1 | .EditTools-Grid-Container{
2 |
3 | height : 40px;
4 | box-shadow: 0px 3px 3px black;
5 | display: grid;
6 | grid-template-columns: 200px 200px 200px 1fr;
7 |
8 | .Nested-Grid{
9 |
10 | display: grid;
11 | grid-template-columns: repeat(5,40px);
12 | height : 43px;
13 |
14 | .Format-Size{
15 | position: relative;
16 | }
17 |
18 |
19 | .EditTools-Grid-Container__Item{
20 | position: relative;
21 | display: flex;
22 | align-items: center;
23 | justify-content: center;
24 | width : 40px;
25 | height : 40px;
26 | box-sizing: border-box;
27 | cursor: pointer;
28 |
29 | .Color-Picker-Box{
30 | position: absolute;
31 | top : 44px;
32 | left : 0;
33 | }
34 |
35 | .CMD_BTN{
36 | background: transparent;
37 | border : none;
38 | outline: none;
39 | width : 100%;
40 | height : 100%;
41 | cursor: pointer;
42 | }
43 |
44 | .EditTools-Item__Icon{
45 | width : 18px;
46 | height : 18px;
47 | transition : all 100ms;
48 | }
49 | .EditTools-Item__Icon:hover {
50 | width : 26px;
51 | height : 26px;
52 | }
53 |
54 | }
55 |
56 | #FaUnlink{
57 | width : 12px;
58 | height : 12px;
59 | transition: all 100ms;
60 | }
61 | #FaUnlink:hover{
62 | width : 16px;
63 | height : 16px;
64 | }
65 |
66 | #supButton{
67 | font-size: 14px;
68 | border : none;
69 | outline: none;
70 | background : transparent;
71 | cursor : pointer;
72 | transition : all 100ms;
73 | }
74 | #supButton:hover{
75 | font-size: 18px;
76 | }
77 |
78 |
79 |
80 |
81 | }
82 |
83 | .Nested-Flex-View{
84 |
85 | display: flex;
86 | justify-content: flex-end;
87 | align-items: center;
88 | padding-bottom : 3px;
89 |
90 | div{
91 | margin-right : 10px;
92 | }
93 |
94 | .View-Item{
95 |
96 | cursor: pointer;
97 | color : hsl(225, 6%, 13%);
98 | border : 1px solid hsl(225, 6%, 13%);
99 | border-radius: 8px;
100 | height : 25px;
101 | display: flex;
102 | align-items: center;
103 | width : 100px;
104 | justify-content: center;
105 | transition : all 250ms ease;
106 |
107 | }
108 | .View-Item:hover{
109 |
110 | background-color : hsl(225, 6%, 13%);
111 | color : hsl(204, 16%, 94%);
112 | font-weight: bold;
113 |
114 | }
115 |
116 | .--View-Clicked{
117 |
118 | background-color : hsl(225, 6%, 13%);
119 | color : hsl(204, 16%, 94%);
120 | font-weight: bold;
121 |
122 | }
123 |
124 | .Write-Save , .Show-Temp-Doc{
125 |
126 | cursor: pointer;
127 | color: #1f2023;
128 | border-radius: 8px;
129 | background-color : rgb(0, 230, 119);
130 | height: 25px;
131 | width: 100px;
132 | display: flex;
133 | align-items: center;
134 | justify-content: center;
135 | border: 1px solid grey;;
136 | transition: all 250ms ease;
137 | }
138 |
139 | .Write-Save:hover , .Show-Temp-Doc:hover{
140 | box-shadow: 0px 3px 3px hsl(225, 6%, 13%);
141 | transition: all 250ms ease;
142 | }
143 |
144 | }
145 |
146 |
147 | }
148 |
149 | .Title-Row{
150 | height : 50px;
151 | border-top : 1px solid black;
152 | background-color: rgb(237, 240, 242);
153 | box-shadow: 0px 3px 3px black;
154 | display: flex;
155 | align-items: center;
156 | padding-left : 15px;
157 |
158 | .Title-Row__Input-Group{
159 |
160 | display: flex;
161 | align-items: center;
162 | background-color: white;
163 | display: inline-block;
164 | height : 40px;
165 | width : 520px;
166 | border-radius: 4px;
167 | position: relative;
168 |
169 | .Title-Row__Input-Group__Input{
170 |
171 | height : 100%;
172 | width : 100%;
173 | outline: none;
174 | border: none;
175 | border-radius: 4px;
176 | padding-left : 15px;
177 | text-align: center;
178 | color : rgb(31, 32, 35);
179 | font-size : 12pt;
180 |
181 | }
182 | .Title-Row__Input-Group__Input:focus{
183 | border : 1px solid #ccc;
184 | }
185 |
186 | .Title-Row__Input-Group__Icon{
187 |
188 | position: absolute;
189 | width: 30px;
190 | height: 30px;
191 | top: 4px;
192 | right: -8px;
193 | transition : color 500ms ease;
194 | color : #ccc;
195 | cursor: pointer;
196 |
197 | }
198 | .--Input-Active{
199 | border : 1px solid #ccc;
200 |
201 | }
202 | .--Icon-Active{
203 | color : rgb(31, 32, 35);
204 | }
205 |
206 | }
207 |
208 | }
209 |
210 |
211 | .--Hide{
212 | display: none;
213 | }
214 | .--Show{
215 | display: block;
216 | }
217 |
218 | #EDITOR , #EDITOR_HTML{
219 |
220 | overflow-y : hidden;
221 | height : calc(100vh - 156px);
222 | box-sizing: border-box;
223 | position: relative;
224 |
225 | }
226 |
227 | #EDITOR:focus{
228 |
229 | border: none;
230 | outline: none;
231 |
232 | }
233 |
234 | #RICH_TEXT_AREA{
235 | width : 100%;
236 | height : 100%;
237 | }
238 |
239 | .Resize-Wrapper{
240 | position: relative;
241 | border : 2px solid black;
242 | display: inline;
243 | }
244 | .Resize-Bullet{
245 | position: absolute;
246 | border-radius: 50%;
247 | background-color: white;
248 | width : 15px;
249 | height : 15px;
250 | border : 2px solid black;
251 | }
252 |
253 | #FormatSizeList{
254 | position : absolute;
255 | top : 50px;
256 | background-color :rgb(237, 240, 242);
257 | left : calc(50% - 20px);
258 | max-height: 200px;
259 | overflow-x : hidden;
260 | overflow-y : auto;
261 | width : 40px;
262 | box-sizing: border-box;
263 | border : 1px solid #ccc;
264 | padding-top : 15px;
265 | box-shadow: 2px 2px 2px grey;
266 | z-index: 4;
267 | }
268 | #FormatSizeList ::-webkit-scrollbar{
269 | width : 0 !important;
270 | display: none;
271 | }
272 | .Format-Size-li{
273 | width : 100%;
274 | text-align: center;
275 | font-size: 14px;
276 | transition: all 100ms;
277 | margin-bottom: 3px;
278 | }
279 | .Format-Size-li:hover{
280 | color : white;
281 | background : rgb(31, 32, 35);
282 | }
283 | .top-pointing-triangle{
284 | position: absolute;
285 | top : 42px;
286 | left : 29%;
287 | transform: rotate(45deg);
288 | width : 16px;
289 | height : 16px;
290 | background-color :rgb(237, 240, 242);
291 | border : none;
292 | border-top : 1px solid #ccc;
293 | border-left : 1px solid #ccc;
294 | z-index: 5;
295 | }
296 |
297 | /* #Media Queries
298 | ================================================== */
299 | /* Large Desktop */
300 | @media only screen and (min-width: 1441px) {
301 |
302 | }
303 | /* Smaller that standard */
304 | @media only screen and (min-width: 959px) and (max-width: 1440px) {
305 |
306 | }
307 | /* Smaller than standard 960 (devices and browsers) */
308 | @media only screen and (max-width: 959px) {
309 |
310 | }
311 |
312 | /* Tablet Portrait size to standard 960 (devices and browsers) */
313 | @media only screen and (min-width: 768px) and (max-width: 959px) {
314 |
315 | }
316 |
317 | /* All Mobile Sizes (devices and browser) */
318 | @media only screen and (max-width: 767px) {
319 |
320 | }
321 |
322 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
323 | @media only screen and (min-width: 480px) and (max-width: 767px) {
324 |
325 | }
326 |
327 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
328 | @media only screen and (max-width: 479px) {
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/Components/src/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withRouter } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 | import { SERVER_URL } from '../redux/GlobalURL.js'
5 | import { AC_LOAD_POSTINGS } from '../redux/ArticleLoaderAction.js'
6 | import * as LoginActions from '../redux/LoginAction.js'
7 | import _store from '../redux/_store.js'
8 |
9 | import './Home.scss'
10 | import TopBar from './TopBar.jsx'
11 | import ArticleLoader from './ArticleLoader.jsx'
12 | import ArticleFilter from './ArticleFilter.jsx'
13 |
14 | class Home extends React.Component{
15 |
16 | constructor(props){
17 | super(props)
18 | }
19 |
20 | render(){
21 |
22 | return(
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
35 |
36 |
37 |
38 |
39 |
40 | )
41 | }
42 | }
43 |
44 | export default withRouter(Home)
--------------------------------------------------------------------------------
/Components/src/Home.scss:
--------------------------------------------------------------------------------
1 | $material_tomato : hsl(1, 83%, 63%);
2 | $material_skyblue : hsl(207, 89%, 68%);
3 | $material_deepblue : hsl(212, 80%, 42%);
4 | $material_deepblue2 : hsl(200, 97%, 45%);
5 | $material_shadow : hsl(200, 19%, 18%);
6 | $material_gray : hsl(0, 0%, 88%);
7 | $material_deeppurple : hsl(267, 75%, 31%);
8 | $material_bluegrey : hsl(200, 18%, 46%);
9 | $material_lightgrey : hsl(200, 15%, 73%);
10 | $material_lightred : hsl(339, 100%, 48%);
11 | $material_grey : hsl(200, 18%, 26%);
12 | $material_palegreen : hsl(151, 100%, 45%);
13 | $material_deepblack : hsl(225, 6%, 13%);
14 |
15 | ::-webkit-scrollbar{
16 | width : 6px;
17 | }
18 | ::-webkit-scrollbar-track {
19 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
20 | background-color: #F5F5F5;
21 | border-radius: 2px;
22 | } /* the new scrollbar will have a flat appearance with the set background color */
23 |
24 | ::-webkit-scrollbar-thumb {
25 | background-color: hsl(0, 0%, 26%);
26 | border-radius: 2px;
27 |
28 | } /* this will style the thumb, ignoring the track */
29 |
30 | ::-webkit-scrollbar-button {
31 | display: none;
32 | } /* optionally, you can style the top and the bottom buttons (left and right for horizontal bars) */
33 |
34 | .Home-Grid-Container{
35 | display: grid;
36 | height : 100vh;
37 | }
38 |
39 | .Home-Grid-Container__Item{
40 | padding-top : 65px;
41 | min-height: calc(100vh - 65px);
42 | height : calc(100vh - 65px);
43 | }
44 |
45 | /* #Media Queries
46 | ================================================== */
47 | /* Large Desktop */
48 | @media only screen and (min-width: 1441px) {
49 |
50 | .Home-Grid-Container{
51 |
52 | grid-template-columns: 300px 1fr 350px;
53 |
54 | .Home-Grid-Container__Item:nth-child(1){
55 | border-right: 1px solid #ccc;
56 | }
57 | .Home-Grid-Container__Item:nth-child(2){
58 |
59 | }
60 | .Home-Grid-Container__Item:nth-child(3){
61 |
62 | background-color: #1f2023;
63 |
64 | }
65 |
66 | }
67 |
68 | }
69 | /* Smaller that standard */
70 | @media only screen and (min-width: 959px) and (max-width: 1440px) {
71 |
72 | }
73 | /* Smaller than standard 960 (devices and browsers) */
74 | @media only screen and (max-width: 959px) {
75 |
76 | }
77 |
78 | /* Tablet Portrait size to standard 960 (devices and browsers) */
79 | @media only screen and (min-width: 768px) and (max-width: 959px) {
80 |
81 | }
82 |
83 | /* All Mobile Sizes (devices and browser) */
84 | @media only screen and (max-width: 767px) {
85 |
86 | }
87 |
88 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
89 | @media only screen and (min-width: 480px) and (max-width: 767px) {
90 |
91 | }
92 |
93 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
94 | @media only screen and (max-width: 479px) {
95 |
96 | }
--------------------------------------------------------------------------------
/Components/src/ImageLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {connect} from 'react-redux'
4 |
5 | import { MdPhotoLibrary , MdFolderOpen , MdWifi } from 'react-icons/md'
6 |
7 | import { SERVER_SHAREDIMAGES_URL } from '../redux/GlobalURL.js'
8 |
9 | import './ImageLoader.scss'
10 |
11 | class ImageLoader extends React.Component{
12 |
13 | constructor(props){
14 |
15 | super(props)
16 | this.state = {
17 | ImageBasket : []
18 | }
19 | this.fileInput = React.createRef()
20 | this.renderImageLists = this.renderImageLists.bind(this)
21 | this.handleFileInputChange = this.handleFileInputChange.bind(this)
22 |
23 | }
24 |
25 | componentDidMount() {
26 |
27 | let empty24Array = []
28 | for (let index = 0; index <= 23; index ++){
29 | empty24Array.push("")
30 | }
31 |
32 | this.setState({
33 | ImageBasket : empty24Array
34 | })
35 |
36 | }
37 |
38 | renderImageLists(ImageBasket){
39 |
40 | return ImageBasket.map((el,index) => {
41 | if(el === ""){
42 | return(
43 |
46 | )
47 | }
48 | else{
49 | return(
50 |
51 |

56 |
57 | )
58 | }
59 |
60 | })
61 |
62 | }
63 |
64 | handleActivateFileBrowser(){
65 |
66 | let fileInput = document.getElementById('File-Input')
67 | fileInput.value = ''
68 |
69 | let myEvent = document.createEvent('MouseEvents')
70 | myEvent.initEvent('click',false,true)
71 | fileInput.dispatchEvent(myEvent)
72 |
73 | }
74 |
75 | handleFileInputChange(event){
76 |
77 | let fileInput = document.getElementById('File-Input')
78 | let files = fileInput.files
79 | let myImageBasket = this.state.ImageBasket
80 |
81 | for (let index = 0; index < files.length; index++) {
82 |
83 | if(files.item(index)){
84 |
85 | let myFormData = new FormData()
86 | myFormData.append('file',files.item(index))
87 |
88 | fetch('http://localhost:3000/write/imageUpload',{
89 |
90 | method : 'POST',
91 | body : myFormData
92 |
93 | }).then(res=>res.json()).then((res)=>{
94 |
95 | myImageBasket.unshift(`${SERVER_SHAREDIMAGES_URL}${res.loc}`)
96 | myImageBasket.pop()
97 |
98 | this.setState({
99 | ImageBasket : myImageBasket
100 | })
101 |
102 |
103 | })
104 |
105 |
106 | }
107 |
108 | }
109 |
110 | }
111 | render(){
112 |
113 | const { writeState } = this.props
114 |
115 | return (
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 | Photo Library
125 |
126 |
127 |
128 |
129 | {this.renderImageLists(this.state.ImageBasket)}
130 |
131 |
132 |
133 |
134 |
135 |
136 | {this.handleFileInputChange(event)}}
139 | onClick={(event)=> {event.target.value = null}}
140 | className={'--Hide'} type="file" multiple={true} accept={'image/*'}/>
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | )
155 |
156 | }
157 |
158 | }
159 |
160 | const handleDragStart = (event) => {
161 |
162 | event.dataTransfer.setData("text",event.target.src)
163 | console.log(event.target.src)
164 |
165 | }
166 |
167 | const mapStateToProps = (state) => {
168 | return {
169 | writeState : state.write
170 | }
171 | }
172 |
173 | ImageLoader = connect(mapStateToProps , null)(ImageLoader)
174 |
175 | export default ImageLoader
--------------------------------------------------------------------------------
/Components/src/ImageLoader.scss:
--------------------------------------------------------------------------------
1 | .Image-Loader-Wrapper{
2 |
3 |
4 |
5 | }
6 | .Image-Loader-Header{
7 | height : 40px;
8 | display: grid;
9 | grid-template-columns: 50px 1fr;
10 |
11 | .Image-Loader-Header__Item{
12 | display: flex;
13 | align-items: center;
14 | }
15 |
16 | .Image-Loader-Icon{
17 | color : white;
18 | width : 35px;
19 | height : 35px;
20 | }
21 | .Image-Loader-Header__Item:nth-child(1){
22 | justify-content: center;
23 | padding-left : 0;
24 | }
25 | .Image-Loader-Header__Item:nth-child(2){
26 | justify-content: flex-start;
27 | color : white;
28 | font-size: 14pt;
29 | letter-spacing: 0.1em;
30 | padding-left : 10px;
31 | }
32 |
33 | }
34 |
35 | .Image-Loader-Lists{
36 | height : calc(100vh - 208px);
37 | margin-top: 3px;
38 | box-sizing: border-box;
39 | padding : 10px 10px;
40 | display: grid;
41 | grid-template-columns: repeat(3,1fr);
42 | grid-column-gap: 15px;
43 | grid-row-gap: 15px;
44 | overflow: auto;
45 |
46 | .Image-Loader-Lists__Item{
47 | width : 100%;
48 | height : 70px;
49 | display: flex;
50 | align-items: center;
51 | justify-content: center;
52 |
53 |
54 | .Frame-Icon {
55 | width: 100%;
56 | height: 100%;
57 |
58 | background:
59 | linear-gradient(to right, white 4px, transparent 4px) 0 0,
60 | linear-gradient(to right, white 4px, transparent 4px) 0 100%,
61 | linear-gradient(to left, white 4px, transparent 4px) 100% 0,
62 | linear-gradient(to left, white 4px, transparent 4px) 100% 100%,
63 | linear-gradient(to bottom, white 4px, transparent 4px) 0 0,
64 | linear-gradient(to bottom, white 4px, transparent 4px) 100% 0,
65 | linear-gradient(to top, white 4px, transparent 4px) 0 100%,
66 | linear-gradient(to top, white 4px, transparent 4px) 100% 100%;
67 |
68 | background-repeat: no-repeat;
69 | background-size: 20px 20px;
70 | }
71 | .Frame-Image{
72 | width: 100%;
73 | height: 100%;
74 | border : 2px solid white;
75 | border-radius: 2px;
76 | }
77 |
78 | }
79 |
80 | }
81 |
82 | .Image-Loader-Controller{
83 | height : 100px;
84 | box-sizing: border-box;
85 | padding-top : 15px;
86 | .Image-Loader-Controller__Row{
87 |
88 | height : 50px;
89 | display: grid;
90 | grid-template-columns: repeat(2 , 1fr);
91 | padding : 0 10px;
92 | grid-column-gap: 15px;
93 | .Image-Loader-Controller__Row__Item{
94 |
95 | .File-Btn {
96 | outline: none;
97 | border : none;
98 | cursor: pointer;
99 | display: flex;
100 | align-items: center;
101 | justify-content: center;
102 | font-size: 15px;
103 | width : 100%;
104 | height : 100%;
105 | background-color: white;
106 | border-radius: 4px;
107 | box-shadow: 0 3px 3px black;
108 | transition: all 300ms ease;
109 | .File-Icon{
110 | width : 25px;
111 | height : 25px;
112 | margin-right: 5px;
113 | }
114 |
115 | }
116 | .File-Btn:hover{
117 | background-color: #00E677;
118 | }
119 |
120 | }
121 | }
122 |
123 | }
--------------------------------------------------------------------------------
/Components/src/Login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Cookies from 'js-cookie'
3 | import { connect } from 'react-redux'
4 | import { withRouter } from 'react-router-dom'
5 |
6 | import * as loginActions from '../redux/LoginAction.js'
7 | import * as registerActions from '../redux/RegisterAction.js'
8 |
9 | import { RegisterBox } from './RegisterBox.jsx'
10 | import LoginBox from './LoginBox.jsx'
11 |
12 | import './Login.scss'
13 |
14 | let USERNAME_REGEX = /^[가-힣a-zA-Z0-9\_\-]{2,12}$/
15 | let EMAIL_REGEX = /^[a-z0-9\_\-]+\@[a-z]+(\.[a-z]{2,3}){1,2}$/
16 | let PW_REGEX = /^[a-zA-Z0-9\!\@\#\$\%\^\&\*]{8,12}$/
17 |
18 | const VALIDATOR = (REGEX,VALUE) => {
19 |
20 | return REGEX.test(VALUE)
21 |
22 | }
23 |
24 | class Login extends React.Component{
25 |
26 | componentWillMount(){
27 | let { history } = this.props
28 | window.sessionStorage.clear()
29 | return
30 | }
31 |
32 | render(){
33 |
34 | const { loginState , loginDispatch } = this.props
35 |
36 | return(
37 |
38 | {RegisterBox(this.props)}
39 |
40 |
41 | )
42 | }
43 |
44 | }
45 |
46 | const mapStateToProps = (state) => {
47 |
48 | return{
49 | loginState : state.login,
50 | registerState : state.register
51 | }
52 |
53 | }
54 |
55 | const mapDispatchToProps = (dispatch) => {
56 | return{
57 | loginDispatch : {
58 | modal(bool = false){
59 | if(bool){
60 | dispatch(loginActions.AC_TOGGLE_MODAL())
61 | dispatch(registerActions.AC_CLEAR_REDUCER());
62 | }
63 | else{
64 | dispatch(loginActions.AC_TOGGLE_MODAL())
65 | }
66 | },
67 | remember(event){
68 | dispatch(loginActions.AC_IS_REMEMBER_CHECKED(event.target.checked))
69 | },
70 | loginTyping(event){
71 |
72 | if(event.target.value.length > 0){
73 | event.target.classList.add('--ShadowBorder');
74 | }
75 | else{
76 | event.target.classList.remove('--ShadowBorder')
77 | }
78 |
79 | dispatch(loginActions.AC_TYPING_LOGIN_FORM(event.target.name,event.target.value))
80 | }
81 |
82 | },
83 | registerDispatch : {
84 |
85 | registerTyping(event){
86 |
87 | let NAME = event.target.name
88 | let VALUE = event.target.value
89 |
90 | switch(NAME){
91 |
92 | case "USERNAME" :
93 |
94 | return dispatch(
95 | registerActions.AC_TYPING_REGISTER_FORM(
96 | registerActions.A_TYPING_USERNAME,
97 | VALUE,
98 | VALIDATOR(USERNAME_REGEX,VALUE)
99 | )
100 | )
101 |
102 | case "EMAIL" :
103 |
104 | return dispatch(
105 | registerActions.AC_TYPING_REGISTER_FORM(
106 | registerActions.A_TYPING_EMAIL,
107 | VALUE,
108 | VALIDATOR(EMAIL_REGEX,VALUE)
109 | )
110 | )
111 |
112 | case "PASSWORD" :
113 |
114 | return dispatch(
115 | registerActions.AC_TYPING_REGISTER_FORM(
116 | registerActions.A_TYPING_PASSWORD,
117 | VALUE,
118 | VALIDATOR(PW_REGEX,VALUE)
119 | )
120 | )
121 |
122 | }
123 |
124 | },
125 | registerSubmit(){
126 | dispatch(registerActions.AC_SUBMIT_REGISTER_FORM())
127 |
128 | },
129 | imageFlush(bool = false , file = {}){
130 | dispatch(registerActions.AC_ASSIGN_PROFILE_IMAGE(false , {}))
131 | },
132 | imageAlloc(bool = true , FILE_INFO){
133 | dispatch(registerActions.AC_ASSIGN_PROFILE_IMAGE(bool , FILE_INFO))
134 | }
135 |
136 | }
137 | }
138 |
139 | }
140 |
141 | Login = withRouter(connect(mapStateToProps , mapDispatchToProps)(Login))
142 |
143 | export default Login
--------------------------------------------------------------------------------
/Components/src/Login.scss:
--------------------------------------------------------------------------------
1 | $material_tomato : hsl(1, 83%, 63%);
2 | $material_skyblue : hsl(207, 89%, 68%);
3 | $material_deepblue : hsl(212, 80%, 42%);
4 | $material_deepblue2 : hsl(200, 97%, 45%);
5 | $material_shadow : hsl(200, 19%, 18%);
6 | $material_gray : hsl(0, 0%, 88%);
7 | $material_deeppurple : hsl(267, 75%, 31%);
8 | $material_bluegrey : hsl(200, 18%, 46%);
9 | $material_lightgrey : hsl(200, 15%, 73%);
10 | $material_lightred : hsl(339, 100%, 48%);
11 | $material_grey : hsl(200, 18%, 26%);
12 | $material_palegreen : hsl(151, 100%, 45%);
13 | $border-width : 3px;
14 |
15 | .--Login{
16 | display: flex;
17 | justify-content: center;
18 | align-items: center;
19 | }
20 |
21 | .--Modal-hide {
22 | display: none;
23 | }
24 | .--Modal-show {
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 |
29 | }
30 |
31 | .LoginBox{
32 | // margin-bottom: 150px;
33 | width : 350px;
34 | border-radius: 6px;
35 | // border : $border-width solid $material_shadow;
36 | height : 410px;
37 | background-color: hsla(0,0%,98%,0.85);
38 | box-shadow: 6px 6px 6px $material_shadow;
39 | .User{
40 | margin-top : -64px;
41 | text-align: center;
42 | }
43 | .circle{
44 | border-radius: 50%;
45 | background-color: white;
46 | color : $material_shadow;
47 | box-shadow: 0 6px 6px $material_shadow;
48 | z-index: 2;
49 | }
50 | .Header{
51 | text-align: center;
52 | padding : 20px;
53 | }
54 | .Header__Text{
55 | font-weight: bold;
56 | font-size: 26px;
57 | letter-spacing: 2px;
58 | color : $material_shadow;
59 | }
60 |
61 | .Input-Row{
62 | text-align: center;
63 | }
64 | .Input-Row:nth-child(1){
65 | margin-bottom : 7px;
66 | }
67 | .Input-Row__Input{
68 | width : 240px;
69 | height : 40px;
70 | border-radius: 8px;
71 | border : 2px solid #ccc;
72 | text-align: center;
73 | font-size: 16px;
74 | letter-spacing: 2px;
75 | font-weight: bold;
76 |
77 | }
78 | .Input-Row__Input:focus{
79 | outline : none;
80 | border : 2px solid $material_shadow;
81 | }
82 | .--ShadowBorder{
83 | border : 2px solid $material_shadow;
84 | }
85 | .Input-Row__Left{
86 | margin-left: 54px;
87 | margin-top : 14px;
88 | }
89 | .Input-Row__Right{
90 | margin-left : 75px;
91 |
92 | }
93 | .Input-Row__Text{
94 | font-weight : bold;
95 | font-size : 14px;
96 | }
97 | .Input-Row__Text a{
98 | color : black;
99 | text-decoration: none;
100 | }
101 | .Input-Row__Text a:hover{
102 | color : $material_deepblue;
103 | }
104 |
105 | .Button-Row{
106 | text-align: center;
107 | padding : 30px 0;
108 | }
109 | .Button-Row__Button{
110 | cursor: pointer;
111 | border: none;
112 | width: 240px;
113 | height : 40px;
114 | border-radius: 8px;
115 | font-size: 20px;
116 | font-weight: bold;
117 | color : white;
118 | transition: color 0.25s ease-out;
119 | }
120 | .Button-Row__Button:focus{
121 | outline : none;
122 | }
123 | .--Login-Button{
124 | background-color: $material_deepblue2;
125 | }
126 | .--Login-Button:hover{
127 | color: $material_shadow;
128 | }
129 | .--Register-Button{
130 | margin-top : 10px;
131 | background-color: $material_shadow;
132 | }
133 | .--Register-Button:hover{
134 | color : $material_deepblue2;
135 | }
136 | .Loader-Box--Login{
137 | height : 205px;
138 | display: flex;
139 | align-items: center;
140 | justify-content: center;
141 | }
142 |
143 | }
144 |
145 | .Modal-Wrapper{
146 | position: fixed;
147 | z-index: 1;
148 | width : 100%;
149 | height : 100%;
150 | top : 0;
151 | left : 0;
152 | overflow: auto; /* Enable scroll if needed */
153 | background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
154 |
155 | .Modal-Content {
156 | background-color: white;
157 | // margin: 130px auto 0 auto;
158 | padding: 20px;
159 | border: 1px solid #888;
160 | width: 400px;
161 | height : auto;
162 | border-radius: 12px;
163 | box-shadow: 5px 5px 5px $material_shadow;
164 | }
165 |
166 | .Modal-Header__Block{
167 | cursor : pointer;
168 | background-color : white;
169 | display: inline-flex;
170 | justify-content: center;
171 | align-items: center;
172 | padding: 20px;
173 | width : calc(50% - 20px);
174 | height : 25px;
175 | margin-top : -20px;
176 | transition: color 0.25s ease-out;
177 | transition: background-color 0.25s ease-out;
178 |
179 | }
180 |
181 | .Modal-Header__Text{
182 | font-size: 22px;
183 | }
184 |
185 | .--Top-Left-Block{
186 | margin-left : -20px;
187 | border-top-left-radius: 12px;
188 | color : $material_shadow;
189 | box-shadow: inset 0 0 1px $material_shadow;
190 |
191 | }
192 |
193 | .--UnAvailable{
194 | opacity: 0.5;
195 | }
196 | .--Input-UnAvaliable{
197 | background-color: white;
198 | }
199 | .--Available{
200 | opacity: 1;
201 | color : $material_shadow;
202 | background-color: $material_palegreen;
203 | }
204 |
205 | .--Top-Right-Block{
206 | margin-right : -20px;
207 | border-top-right-radius: 12px;
208 | color : white;
209 | box-shadow: inset 0 0 1px $material_lightred;
210 | background-color: $material_lightred;
211 | }
212 |
213 | .Modal-Body__Row{
214 | text-align: center;
215 | }
216 | .User_Img{
217 | margin-top : 20px;
218 | display: inline-block;
219 | text-align: center;
220 | overflow: hidden;
221 | width : 150px;
222 | height : 150px;
223 | border : 3px solid #ccc ;
224 | border-radius: 50%;
225 | }
226 | .User_Img img{
227 | margin-top : 20px;
228 | width: 100%;
229 | height : 100%;
230 | }
231 | .Form-Grid{
232 | display: grid;
233 | height : auto;
234 | grid-template-columns: 1fr;
235 | grid-template-rows: 1fr auto;
236 | grid-row-gap: 15px;
237 | padding : 0 20px;
238 | margin-top : 20px;
239 | }
240 | .Form-Input-Row{
241 | margin-bottom: 5px;
242 | }
243 | .Form-Label{
244 | color : $material_grey;
245 | display: block;
246 | }
247 | .Form-Input{
248 | display: block;
249 | height : 30px;
250 | width : 100%;
251 | text-align: center;
252 | outline: none;
253 | border-radius: 12px;
254 | border : 2px solid $material_grey;
255 | color : $material_shadow;
256 | transition : background-color 0.25s ease-out
257 | }
258 |
259 | .Loader-Box--Register{
260 | display: flex;
261 | align-items: center;
262 | justify-content: center;
263 | height : 500px;
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/Components/src/LoginBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { BeatLoader } from 'react-spinners'
3 | import { MdAccountCircle } from 'react-icons/md';
4 | import { Link } from 'react-router-dom'
5 |
6 | import { SERVER_URL , CLIENT_URL } from '../redux/GlobalURL.js'
7 | import { connect } from 'react-redux'
8 | import { withRouter } from 'react-router-dom'
9 | import { refreshSessionStroage } from '../redux/LoginAction.js'
10 | import Cookies from 'js-cookie'
11 | import { sha3_256 } from 'js-sha3'
12 |
13 | class LoginBox extends React.Component{
14 |
15 | constructor(props){
16 | super(props)
17 |
18 | this.state = {
19 | LOGIN_ID : "",
20 | LOGIN_PW : "",
21 | IS_AUTHORIZING : false,
22 | REMEMBER : false
23 | }
24 |
25 | this.handleLoginSubmit = this.handleLoginSubmit.bind(this)
26 | this.handleFormInput = this.handleFormInput.bind(this)
27 |
28 | }
29 |
30 | componentWillMount(){
31 | if(Cookies.get('user')){
32 | this.setState({
33 | LOGIN_ID : Cookies.get('user')
34 | })
35 | }
36 | }
37 |
38 | handleLoginSubmit(event){
39 |
40 | event.preventDefault()
41 |
42 | if(event.target.id === "LOGIN_BTN" || event.keyCode === 13){
43 |
44 | this.setState({
45 | IS_AUTHORIZING : true
46 | })
47 |
48 | fetch(`${SERVER_URL}/login`,{
49 | method : 'POST',
50 | credentials : `include`,
51 | headers: {
52 | 'Accept': 'application/json',
53 | 'Content-Type': 'application/json'
54 | },
55 | body : JSON.stringify({
56 | EMAIL : document.getElementById('INPUT_ID__LOGIN').value,
57 | PW : document.getElementById('INPUT_PW__LOGIN').value
58 | })
59 | }).then((response) => (response.json())).then((Jres) => {
60 | if(Jres.status === 1){
61 |
62 | console.log(`로그인 성공 : ${Jres}`)
63 | refreshSessionStroage(Jres)
64 |
65 | this.props.history.push(`/home?user=${window.sessionStorage.getItem('EMAIL').split("@")[0]}`)
66 |
67 | if(this.state.REMEMBER){
68 |
69 | Cookies.set('user',Jres.EMAIL,{
70 | expires : 7,
71 | path : CLIENT_URL
72 | })
73 |
74 | }
75 |
76 | Cookies.set('SID',Jres.SID,{
77 | expires : 7,
78 | path : CLIENT_URL
79 | })
80 |
81 | }
82 | else{
83 | alert('로그인 실패')
84 | this.setState({
85 | IS_AUTHORIZING : false
86 | })
87 | }
88 |
89 | })
90 |
91 |
92 |
93 | }
94 |
95 | }
96 |
97 | handleFormInput(event){
98 |
99 | if(event.target.id === "INPUT_ID__LOGIN"){
100 | this.setState({
101 | LOGIN_ID : event.target.value
102 | })
103 | }
104 | else if(event.target.id === "INPUT_PW__LOGIN"){
105 | this.setState({
106 | LOGIN_PW : event.target.value
107 | })
108 | }
109 | else if(event.target.id === "REMEMBER"){
110 | this.setState({
111 | REMEMBER : !this.state.REMEMBER
112 | },()=>{
113 | console.log(this.state)
114 | })
115 | }
116 |
117 | }
118 |
119 | render(){
120 |
121 | let {loginState , loginDispatch} = this.props
122 |
123 | return(
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | {!this.state.IS_AUTHORIZING?
134 | (
135 |
136 |
137 | Member Login
138 |
139 |
140 |
173 |
174 | )
175 | :
176 | (
177 |
178 |
179 |
180 | )
181 | }
182 |
183 |
184 |
185 |
186 |
187 |
190 |
191 |
192 |
193 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | )
204 | }
205 |
206 | }
207 |
208 | LoginBox = withRouter(LoginBox)
209 |
210 | export default LoginBox
211 |
--------------------------------------------------------------------------------
/Components/src/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Link } from 'react-router-dom'
4 |
5 | class NotFound extends React.Component{
6 |
7 | render(){
8 | return (
9 |
10 |
Page Not Found.
11 | Home
12 |
13 | )
14 | }
15 |
16 |
17 | }
18 |
19 | export default NotFound
--------------------------------------------------------------------------------
/Components/src/RegisterBox.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import {
4 | FilePond,
5 | File,
6 | registerPlugin
7 | } from 'react-filepond';
8 |
9 | import FilePondPluginFileRename from 'filepond-plugin-file-rename'
10 | import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size'
11 | import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
12 | import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
13 | import './filepond.min.css';
14 | import './filepond-plugin-image-preview.min.css'
15 |
16 | import { Redirect } from 'react-router-dom'
17 | import { ScaleLoader } from 'react-spinners'
18 | import { SERVER_URL } from '../redux/GlobalURL';
19 | import AlertBox from './AlertBox.jsx'
20 | registerPlugin(
21 | FilePondPluginImagePreview,
22 | FilePondPluginFileValidateSize,
23 | FilePondPluginFileValidateType,
24 | FilePondPluginFileRename
25 | )
26 |
27 | export const RegisterBox = (props) => {
28 |
29 | return(
30 |
31 |
32 |
33 |
34 |
35 |
36 |
{
37 | if(props.registerState.IS_VALID_EMAIL && props.registerState.IS_VALID_NAME && props.registerState.IS_VALID_PW && props.registerState.IS_IMG_ASSIGNED && !(props.registerState.IS_SUBMIT_SUCCESS)){
38 | props.registerDispatch.registerSubmit()
39 | }
40 | }} className={"Modal-Header__Block --Top-Left-Block "+((props.registerState.IS_VALID_EMAIL && props.registerState.IS_VALID_NAME && props.registerState.IS_VALID_PW && props.registerState.IS_IMG_ASSIGNED && !(props.registerState.IS_SUBMIT_SUCCESS))?"--Available":"--Unavailable")}>
41 |
42 | Submit
43 |
44 |
{
45 | props.loginDispatch.modal(props.registerState.IS_SUBMIT_SUCCESS || props.registerState.IS_SUBMIT_ERROR)
46 | }} className="Modal-Header__Block --Top-Right-Block">
47 |
48 | Close
49 |
50 |
51 |
52 |
53 |
54 | {
55 | props.registerState.IS_SUBMIT_ERROR?
56 | (
57 | ErrorDrawer(props)
58 | )
59 | :
60 | props.registerState.IS_SUBMIT_SUCCESS?
61 | (
62 | VerifyDrawer(props)
63 | )
64 | :
65 | props.registerState.IS_SUBMITTING === false?
66 | (
67 | BodyDrawer(props)
68 | )
69 | :
70 | (
71 | LoaderDrawer(props)
72 | )
73 | }
74 |
75 |
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
83 |
84 |
85 |
86 | const BodyDrawer = (props) => (
87 |
88 | {AlertDrawer(props.registerState)}
89 |
90 |
91 |
109 |
110 |
111 |
112 | {
131 | props.registerDispatch.imageFlush(false,{})
132 | }}
133 | onprocessfile={(error,file)=>{
134 |
135 | let FILE_INFO = `${file.serverId}.${file.fileExtension}`
136 |
137 | props.registerDispatch.imageAlloc(true,FILE_INFO)
138 |
139 | }}
140 | onprocessfilerevert={()=>{
141 | props.registerDispatch.imageFlush(false,{})
142 | }}
143 |
144 | />
145 |
146 |
147 |
148 |
149 |
150 | )
151 |
152 | const AlertDrawer = (props) => {
153 |
154 | let AlertBoxes = [];
155 |
156 | let index = 0;
157 | if(props.IS_VALID_NAME === false){
158 | AlertBoxes.push()
159 | }
160 | if(props.IS_VALID_EMAIL === false){
161 | AlertBoxes.push()
162 | }
163 | if(props.IS_VALID_PW === false){
164 | AlertBoxes.push()
165 | }
166 | if((props.IS_VALID_PW && props.IS_VALID_EMAIL && props.IS_VALID_NAME) === true && props.IS_IMG_ASSIGNED === false){
167 | AlertBoxes.push()
168 | }
169 |
170 | return AlertBoxes
171 |
172 | }
173 |
174 | const LoaderDrawer = () => (
175 |
176 |
177 |
178 | )
179 |
180 |
181 | const VerifyDrawer = (props) => (
182 |
187 | )
188 |
189 | const ErrorDrawer = (props) => (
190 |
195 | )
196 |
--------------------------------------------------------------------------------
/Components/src/ResizeBullets.js:
--------------------------------------------------------------------------------
1 | export const DECISION_BULLETS_POSITION = (Bullets,BulletSize) => {
2 |
3 | let BulletSizeHalf = `${parseInt(BulletSize)/2}px`
4 | let MinusBulletSize = `-${BulletSize}`
5 |
6 | for (let index = 0; index < Bullets.length; index++) {
7 |
8 | if(index === 0){
9 | Bullets[index].style.top = MinusBulletSize
10 | Bullets[index].style.left = MinusBulletSize
11 | Bullets[index].style.cursor = 'nw-resize'
12 | }
13 |
14 | if(index === 1){
15 | Bullets[index].style.top = MinusBulletSize
16 | Bullets[index].style.left = `calc(50% - ${BulletSizeHalf})`
17 | Bullets[index].style.cursor = 'n-resize'
18 | }
19 |
20 | if(index === 2){
21 | Bullets[index].style.top = MinusBulletSize
22 | Bullets[index].style.right = MinusBulletSize
23 | Bullets[index].style.cursor = 'ne-resize'
24 | }
25 |
26 | if(index === 3){
27 | Bullets[index].style.top = `calc(50% - ${BulletSizeHalf})`
28 | Bullets[index].style.left = MinusBulletSize
29 | Bullets[index].style.cursor = 'w-resize'
30 | }
31 |
32 | if(index === 4){
33 | Bullets[index].style.top = `calc(50% - ${BulletSizeHalf})`
34 | Bullets[index].style.right = MinusBulletSize
35 | Bullets[index].style.cursor = 'e-resize'
36 | }
37 |
38 | if(index === 5){
39 | Bullets[index].style.bottom = MinusBulletSize
40 | Bullets[index].style.left = MinusBulletSize
41 | Bullets[index].style.cursor = 'sw-resize'
42 | }
43 |
44 | if(index === 6){
45 | Bullets[index].style.bottom = MinusBulletSize
46 | Bullets[index].style.left = `calc(50% - ${BulletSizeHalf})`
47 | Bullets[index].style.cursor = 's-resize'
48 | }
49 |
50 | if(index === 7){
51 | Bullets[index].style.bottom = MinusBulletSize
52 | Bullets[index].style.right = MinusBulletSize
53 | Bullets[index].style.cursor = 'se-resize'
54 | }
55 |
56 | }
57 |
58 |
59 | }
60 |
61 | export const ATTACH_RESIZE_HANDLER = (Bullets) => {
62 |
63 | Bullets.forEach((el,index) => {
64 |
65 | if(index === 0){
66 |
67 | el.addEventListener('mousedown',(event)=>{
68 | handleMouseDown(event,'NW')
69 | })
70 |
71 | el.addEventListener('mouseleave',(event)=>{
72 | event.target.removeEventListener('mousedown',handleMouseDown)
73 | event.target.removeEventListener('mousemove',handleMouseMove_NW)
74 | })
75 |
76 | el.addEventListener('mouseup',(event)=>{
77 | event.target.removeEventListener('mousedown',handleMouseDown)
78 | event.target.removeEventListener('mousemove',handleMouseMove_NW)
79 | })
80 |
81 | }
82 |
83 | if(index === 1){
84 |
85 | el.addEventListener('mousedown',(event)=>{
86 | handleMouseDown(event,'N')
87 | })
88 |
89 | el.addEventListener('mouseleave',(event)=>{
90 | event.target.removeEventListener('mousedown',handleMouseDown)
91 | event.target.removeEventListener('mousemove',handleMouseMove_N)
92 | })
93 |
94 | el.addEventListener('mouseup',(event)=>{
95 | event.target.removeEventListener('mousedown',handleMouseDown)
96 | event.target.removeEventListener('mousemove',handleMouseMove_N)
97 | })
98 |
99 | }
100 |
101 | if(index === 2){
102 |
103 | el.addEventListener('mousedown',(event)=>{
104 | handleMouseDown(event,'NE')
105 | })
106 |
107 | el.addEventListener('mouseleave',(event)=>{
108 | event.target.removeEventListener('mousedown',handleMouseDown)
109 | event.target.removeEventListener('mousemove',handleMouseMove_NE)
110 | })
111 |
112 | el.addEventListener('mouseup',(event)=>{
113 | event.target.removeEventListener('mousedown',handleMouseDown)
114 | event.target.removeEventListener('mousemove',handleMouseMove_NE)
115 | })
116 |
117 | }
118 |
119 | if(index === 3){
120 |
121 | el.addEventListener('mousedown',(event)=>{
122 | handleMouseDown(event,'W')
123 | })
124 |
125 | el.addEventListener('mouseleave',(event)=>{
126 | event.target.removeEventListener('mousedown',handleMouseDown)
127 | event.target.removeEventListener('mousemove',handleMouseMove_W)
128 | })
129 |
130 | el.addEventListener('mouseup',(event)=>{
131 | event.target.removeEventListener('mousedown',handleMouseDown)
132 | event.target.removeEventListener('mousemove',handleMouseMove_W)
133 | })
134 |
135 | }
136 |
137 | if(index === 4){
138 |
139 | el.addEventListener('mousedown',(event)=>{
140 | handleMouseDown(event,'E')
141 | })
142 |
143 | el.addEventListener('mouseleave',(event)=>{
144 | event.target.removeEventListener('mousedown',handleMouseDown)
145 | event.target.removeEventListener('mousemove',handleMouseMove_E)
146 | })
147 |
148 | el.addEventListener('mouseup',(event)=>{
149 | event.target.removeEventListener('mousedown',handleMouseDown)
150 | event.target.removeEventListener('mousemove',handleMouseMove_E)
151 | })
152 |
153 | }
154 |
155 | if(index === 5){
156 |
157 | el.addEventListener('mousedown',(event)=>{
158 | handleMouseDown(event,'SW')
159 | })
160 |
161 | el.addEventListener('mouseleave',(event)=>{
162 | event.target.removeEventListener('mousedown',handleMouseDown)
163 | event.target.removeEventListener('mousemove',handleMouseMove_SW)
164 | })
165 |
166 | el.addEventListener('mouseup',(event)=>{
167 | event.target.removeEventListener('mousedown',handleMouseDown)
168 | event.target.removeEventListener('mousemove',handleMouseMove_SW)
169 | })
170 |
171 | }
172 |
173 | if(index === 6){
174 |
175 | el.addEventListener('mousedown',(event)=>{
176 | handleMouseDown(event,'S')
177 | })
178 |
179 | el.addEventListener('mouseleave',(event)=>{
180 | event.target.removeEventListener('mousedown',handleMouseDown)
181 | event.target.removeEventListener('mousemove',handleMouseMove_S)
182 | })
183 |
184 | el.addEventListener('mouseup',(event)=>{
185 | event.target.removeEventListener('mousedown',handleMouseDown)
186 | event.target.removeEventListener('mousemove',handleMouseMove_S)
187 | })
188 |
189 | }
190 |
191 | if(index === 7){
192 |
193 | el.addEventListener('mousedown',(event)=>{
194 | handleMouseDown(event,'SE')
195 | })
196 |
197 | el.addEventListener('mouseleave',(event)=>{
198 | event.target.removeEventListener('mousedown',handleMouseDown)
199 | event.target.removeEventListener('mousemove',handleMouseMove_SE)
200 | })
201 |
202 | el.addEventListener('mouseup',(event)=>{
203 | event.target.removeEventListener('mousedown',handleMouseDown)
204 | event.target.removeEventListener('mousemove',handleMouseMove_SE)
205 | })
206 |
207 | }
208 |
209 | })
210 |
211 | }
212 |
213 | const handleMouseDown = (event,direction) => {
214 |
215 | let initialMouseClientX = event.clientX
216 | let initialMouseClientY = event.clientY
217 |
218 | switch(direction){
219 |
220 | case 'NW':
221 | return event.target.addEventListener('mousemove',(event)=>{
222 | handleMouseMove_NW(event,initialMouseClientX,initialMouseClientY)
223 | })
224 | case 'N':
225 | return event.target.addEventListener('mousemove',(event)=>{
226 | handleMouseMove_N(event,initialMouseClientX,initialMouseClientY)
227 | })
228 | case 'NE':
229 | return event.target.addEventListener('mousemove',(event)=>{
230 | handleMouseMove_NE(event,initialMouseClientX,initialMouseClientY)
231 | })
232 | case 'W':
233 | return event.target.addEventListener('mousemove',(event)=>{
234 | handleMouseMove_W(event,initialMouseClientX,initialMouseClientY)
235 | })
236 | case 'E':
237 | return event.target.addEventListener('mousemove',(event)=>{
238 | handleMouseMove_E(event,initialMouseClientX,initialMouseClientY)
239 | })
240 | case 'SW':
241 | return event.target.addEventListener('mousemove',(event)=>{
242 | handleMouseMove_SW(event,initialMouseClientX,initialMouseClientY)
243 | })
244 | case 'S':
245 | return event.target.addEventListener('mousemove',(event)=>{
246 | handleMouseMove_S(event,initialMouseClientX,initialMouseClientY)
247 | })
248 | case 'SE':
249 | return event.target.addEventListener('mousemove',(event)=>{
250 | handleMouseMove_SE(event,initialMouseClientX,initialMouseClientY)
251 | })
252 |
253 | default :
254 | return
255 |
256 | }
257 |
258 | }
259 |
260 | const handleMouseMove_NW = (event,ICX,ICY) => {
261 |
262 | // let targetResizeImage = event.target.parentNode.querySelector('img')
263 | // targetResizeImage.style.transformOrigin = '100% 100%'
264 | // targetResizeImage.width = targetResizeImage.width + event.movementX
265 | // targetResizeImage.height = targetResizeImage.height + event.movementY
266 |
267 | }
268 |
269 | const handleMouseMove_N = (event,ICX,ICY) => {
270 |
271 | // let targetResizeImage = event.target.parentNode.querySelector('img')
272 |
273 | // targetResizeImage.height = targetResizeImage.height + event.movementY
274 |
275 | }
276 |
277 | const handleMouseMove_NE = (event,ICX,ICY) => {
278 |
279 | // let targetResizeImage = event.target.parentNode.querySelector('img')
280 |
281 | // targetResizeImage.width = targetResizeImage.width + event.movementX
282 | // targetResizeImage.height = targetResizeImage.height + event.movementY
283 |
284 | }
285 |
286 | const handleMouseMove_W = (event,ICX,ICY) => {
287 |
288 | // let targetResizeImage = event.target.parentNode.querySelector('img')
289 |
290 | // targetResizeImage.width = targetResizeImage.width + event.movementX
291 |
292 | }
293 |
294 | const handleMouseMove_E = (event,ICX,ICY) => {
295 |
296 | let targetResizeImage = event.target.parentNode.querySelector('img')
297 |
298 | targetResizeImage.width = targetResizeImage.width + event.movementX
299 |
300 | }
301 |
302 | const handleMouseMove_SW = (event,ICX,ICY) => {
303 |
304 | // let targetResizeImage = event.target.parentNode.querySelector('img')
305 |
306 | // targetResizeImage.width = targetResizeImage.width + event.movementX
307 | // targetResizeImage.height = targetResizeImage.height + event.movementY
308 |
309 | }
310 |
311 | const handleMouseMove_S = (event,ICX,ICY) => {
312 |
313 | let targetResizeImage = event.target.parentNode.querySelector('img')
314 |
315 | targetResizeImage.height = targetResizeImage.height + event.movementY
316 |
317 | }
318 |
319 | const handleMouseMove_SE = (event,ICX,ICY) => {
320 |
321 | let targetResizeImage = event.target.parentNode.querySelector('img')
322 |
323 | targetResizeImage.width = targetResizeImage.width + event.movementX
324 | targetResizeImage.height = targetResizeImage.height + event.movementY
325 |
326 | }
--------------------------------------------------------------------------------
/Components/src/Root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom'
3 | import { BrowserRouter as Router , Route , Switch } from 'react-router-dom'
4 | import { Provider } from 'react-redux'
5 | import { AC_GET_SESSION_DATA } from '../redux/UserAction.js'
6 | import { Redirect } from 'react-router-dom'
7 |
8 | import _store from '../redux/_store.js'
9 |
10 | import Login from './Login.jsx'
11 | import Home from './Home.jsx'
12 | import Write from './Write.jsx'
13 | import ArticleView from './ArticleView.jsx'
14 | import NotFound from './NotFound.jsx'
15 |
16 | import '../Styles/Share.scss'
17 | import Cookies from "js-cookie";
18 |
19 | class Root extends React.Component{
20 |
21 | render(){
22 | return(
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {
32 | return RouteDecision(Home)
33 | }}>
34 | {
35 | return RouteDecision(Write)
36 | }}>
37 | {
38 | return RouteDecision(ArticleView)
39 | }}>
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | }
52 |
53 | const RouteDecision = (component) => {
54 |
55 | return window.sessionStorage.getItem('EMAIL') !== null ? () : ()
56 |
57 | }
58 |
59 | ReactDOM.render(,document.getElementById('root'))
60 |
--------------------------------------------------------------------------------
/Components/src/TempDocLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import './TempDocLoader.scss'
4 | import {SERVER_URL} from '../redux/GlobalURL.js'
5 |
6 | class TempDocLoader extends React.Component{
7 |
8 | constructor(props){
9 | super(props)
10 | this.handleGetTempDocContent = this.handleGetTempDocContent.bind(this)
11 | }
12 |
13 | handleGetTempDocContent(_id){
14 |
15 | return fetch(`${SERVER_URL}/write/getTempDocContent?_id=${_id}`,{
16 | method : 'GET',
17 | headers: {
18 | 'Accept': 'application/json',
19 | 'Content-Type': 'application/json'
20 | }
21 | }).then(res=>res.json())
22 | .then((res) => {
23 | console.log(res)
24 | if(confirm('불러올까요?')){
25 | RICH_TEXT_AREA.document.body.innerHTML = ""
26 | RICH_TEXT_AREA.document.body.innerHTML = res["payload"][0]["TEMP_SAVE_CONTENT"]
27 | this.props.tempDocToggler()
28 | }
29 |
30 | })
31 |
32 | }
33 |
34 | render(){
35 | return(
36 |
37 | {renderTempDocs(this.props.docs,this.handleGetTempDocContent)}
38 |
39 | )
40 | }
41 | }
42 |
43 | const renderTempDocs = (docs,handler) => {
44 |
45 | docs.sort((a,b) => {
46 | return new Date(b.TEMP_SAVE_DATE) - new Date(a.TEMP_SAVE_DATE)
47 | })
48 |
49 | return docs.map((el,index) => {
50 | return (
51 | {handler(el._id)}} className='TempDocLoader-Container__Item' key={index}>
52 |
53 | {el.TEMP_SAVE_TITLE}
54 |
55 |
56 |
57 |
58 | {(el._id).substr(0,Math.floor(el._id.length/2))}
59 |
60 |
61 |
62 | {new Date(el.TEMP_SAVE_DATE).toJSON().substr(0,10)}
63 |
64 |
65 |
66 |
67 | )
68 | })
69 |
70 | }
71 |
72 | export default TempDocLoader
--------------------------------------------------------------------------------
/Components/src/TempDocLoader.scss:
--------------------------------------------------------------------------------
1 | .TempDocLoader-Container{
2 |
3 | top : 15px;
4 | left : 15px;
5 | width : 535px;
6 | height : 300px;
7 | overflow-y : auto;
8 | border-radius: 4px;
9 | background-color: white;
10 | box-shadow: 2px 2px 5px #ccc;
11 | position : absolute;
12 |
13 | }
14 | .TempDocLoader-Container__Item{
15 | padding : 10px 10px 5px 10px;
16 | border-bottom : 1px solid #ccc;
17 | height : 50px;
18 | cursor: pointer;
19 | }
20 |
21 | .TempDocLoader-Container__Item__Title{
22 | height : 20px;
23 | margin-bottom : 10px;
24 | }
25 | .TempDocLoader-Container__Item__Title span{
26 | font-size : 13pt;
27 | }
28 | .TempDocLoader-Container__Item__Sub{
29 | display: grid;
30 | height : 20px;
31 | grid-template-columns: 1fr 1fr;
32 | }
33 |
34 | .TempDocLoader-Container__Item__Sub div{
35 | display: flex;
36 | align-items: flex-end;
37 | }
38 | .TempDocLoader-Container__Item__Sub div span{
39 | color : #ccc;
40 | font-size : 10pt;
41 | letter-spacing: 0.05em;
42 | }
43 | .TempDocLoader-Container__Item__Sub div:nth-child(1){
44 | justify-content: flex-start;
45 | }
46 | .TempDocLoader-Container__Item__Sub div:nth-child(2){
47 | justify-content: flex-end;
48 | }
--------------------------------------------------------------------------------
/Components/src/TopBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { SERVER_URL } from '../redux/GlobalURL.js'
3 | import Cookies from 'js-cookie'
4 |
5 | import * as LoginActions from '../redux/LoginAction.js'
6 | import * as UserActions from '../redux/UserAction.js'
7 |
8 | import './TopBar.scss'
9 |
10 | import Header_Icon from '../../public/Images/post-it.svg'
11 | import { MdCreate, MdSearch } from 'react-icons/md'
12 | import { MdAccountCircle } from 'react-icons/md'
13 | import { MdMenu } from 'react-icons/md'
14 | import { IoMdLogOut } from 'react-icons/io'
15 | import { MdPublic } from 'react-icons/md'
16 | import { connect } from 'react-redux';
17 | import { withRouter , Redirect } from 'react-router-dom'
18 | import { sha3_256 } from 'js-sha3'
19 | import {AC_TITLE_SEARCH} from '../redux/ArticleLoaderAction.js'
20 |
21 | class TopBar extends React.Component{
22 |
23 | constructor(props){
24 | super(props)
25 | this.handleLogout = this.handleLogout.bind(this)
26 | this.handleRouteToWrite = this.handleRouteToWrite.bind(this)
27 | this.handleRouteToHome = this.handleRouteToHome.bind(this)
28 | this.handleTitleSearch = this.handleTitleSearch.bind(this)
29 | }
30 |
31 | handleLogout(){
32 | window.sessionStorage.clear()
33 | let { history } = this.props
34 | history.go(-(history.length - 2))
35 | // history.push('/',null)
36 | }
37 |
38 | handleRouteToWrite(){
39 | let { history } = this.props
40 | history.push(`/write?user=${window.sessionStorage.getItem('EMAIL').split("@")[0]}`,null)
41 | }
42 | handleRouteToHome(){
43 | let { history } = this.props
44 | history.push(`/home?user=${window.sessionStorage.getItem('EMAIL').split("@")[0]}`,null)
45 | }
46 | handleTitleSearch(event){
47 | return this.props.articleLoaderDispatch.titleSearch(event.target.value)
48 | }
49 |
50 | render(){
51 |
52 | let { loginState } = this.props
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |

63 |
64 |
65 | React-Board
66 |
67 |
68 |
69 |
75 |
76 |
77 |
78 |
79 |
80 | {
81 | loginState.IS_AUTH_SUCCESS || window.sessionStorage.USERNAME?
82 | (
83 |
}`})
84 |
85 | )
86 | :
87 | (
88 |
89 | )
90 |
91 | }
92 |
93 |
94 | {`${window.sessionStorage.USERNAME}님`}
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | )
115 |
116 | }
117 |
118 | }
119 |
120 | const mapStateToProps = (state) => {
121 |
122 | return {
123 |
124 | loginState : state.login,
125 | userState : state.user
126 |
127 | }
128 |
129 | }
130 |
131 | const mapDispatchToProps = (dispatch) => {
132 |
133 | return {
134 | articleLoaderDispatch : {
135 | titleSearch(searchText){
136 | dispatch(AC_TITLE_SEARCH(searchText))
137 | }
138 | }
139 | }
140 |
141 | }
142 |
143 | const PARSE_U_IMG_PATH = (path) => {
144 |
145 | let U_IMG_PATH = new String(path)
146 | U_IMG_PATH = U_IMG_PATH.substring(1)
147 | return U_IMG_PATH
148 |
149 | }
150 |
151 | TopBar = withRouter(connect(mapStateToProps,mapDispatchToProps)(TopBar))
152 |
153 | export default TopBar
154 |
--------------------------------------------------------------------------------
/Components/src/TopBar.scss:
--------------------------------------------------------------------------------
1 | $material_tomato : hsl(1, 83%, 63%);
2 | $material_skyblue : hsl(207, 89%, 68%);
3 | $material_deepblue : hsl(212, 80%, 42%);
4 | $material_deepblue2 : hsl(200, 97%, 45%);
5 | $material_shadow : hsl(200, 19%, 18%);
6 | $material_gray : hsl(0, 0%, 88%);
7 | $material_deeppurple : hsl(267, 75%, 31%);
8 | $material_bluegrey : hsl(200, 18%, 46%);
9 | $material_lightgrey : hsl(200, 15%, 73%);
10 | $material_lightred : hsl(339, 100%, 48%);
11 | $material_grey : hsl(200, 18%, 26%);
12 | $material_palegreen : hsl(151, 100%, 45%);
13 | $material_deepblack : hsl(225, 6%, 13%);
14 |
15 | .TopBar{
16 |
17 | height : 60px;
18 | background-color: $material_deepblack;
19 | width: 100%;
20 | box-shadow: 0px 5px 3px black;
21 |
22 | .TopBar__Grid-Container{
23 |
24 | display: grid;
25 | grid-template-columns: 300px 1fr 350px;
26 | height : 100%;
27 |
28 | .TopBar__Grid-Container__Item:nth-child(1){
29 |
30 | display: grid;
31 | grid-template-columns: 70px 230px;
32 |
33 | .TopBar__Grid-Container__Item__Item:nth-child(1){
34 | display: flex;
35 | align-items: center;
36 | justify-content: flex-end;
37 |
38 | .TopBar__Grid-Container__Item__Item__Img{
39 | width: 40px;
40 | }
41 | }
42 |
43 | .TopBar__Grid-Container__Item__Item:nth-child(2){
44 | display: flex;
45 | align-items: center;
46 | justify-content: center;
47 | padding-top : 5px;
48 |
49 | .TopBar__Grid-Container__Item__Item__Text{
50 | color : $material_palegreen;
51 | font-size: 30px;
52 | letter-spacing: 2px;
53 | }
54 |
55 | }
56 |
57 |
58 |
59 |
60 | }
61 |
62 | .TopBar__Grid-Container__Item:nth-child(2){
63 | display: flex;
64 | align-items: center;
65 | justify-content: flex-end;
66 | padding-top : 5px;
67 | padding-right : 25px;
68 | .TopBar__Grid-Container__Item__Search{
69 |
70 | height : 35px;
71 | border-radius: 4px;
72 | width : 260px;
73 | position: relative;
74 |
75 | }
76 | .TopBar__Grid-Container__Item__Search input{
77 |
78 | border-radius: 4px;
79 | box-sizing: border-box;
80 | border : none;
81 | outline : none;
82 | height : 100%;
83 | width : 100%;
84 | padding : 5px 10px;
85 | background-color: transparent;
86 | color : white;
87 | transition: background-color 300ms ease;
88 | font-size: 13px;
89 |
90 | }
91 | .TopBar__Grid-Container__Item__Search input:focus{
92 | background-color: rgb(55, 57, 64);
93 | }
94 | #SEARCH{
95 | color : white;
96 | position: absolute;
97 | top : 7px;
98 | right : 7px;
99 | }
100 | }
101 |
102 | .TopBar__Grid-Container__Item:nth-child(3){
103 | display: grid;
104 | grid-template-columns: 170px 60px 60px 60px;
105 |
106 | .TopBar__Grid-Container__Item__Item{
107 | display: flex;
108 | align-items: center;
109 | justify-content: center;
110 |
111 | .Icon-Size {
112 | font-size: 30px;
113 | cursor: pointer;
114 | }
115 |
116 | #MdCreate{
117 | color : white;
118 | }
119 | #IoMdLogOut{
120 | color : white;
121 | }
122 | #MdAccountCircle{
123 | color : white;
124 | }
125 | #MdPublic{
126 | color : white;
127 | }
128 |
129 | }
130 | .TopBar__Grid-Container__Item__Item:hover{
131 | border-bottom: 2px solid white;
132 | }
133 |
134 | .TopBar__Grid-Container__Item__Profile{
135 | display: grid;
136 | grid-template-columns: 50px 120px;
137 |
138 | .TopBar__Grid-Container__Item__Profile__Item{
139 | display: flex;
140 | align-items: center;
141 | justify-content: center;
142 | padding-top : 5px;
143 | }
144 |
145 | .TopBar__Grid-Container__Item__Profile__Item-Text{
146 |
147 | color : white;
148 | letter-spacing: 2px;
149 |
150 | }
151 |
152 | .Profile-Image-Size{
153 |
154 | width : 45px;
155 | height : 45px;
156 | border-radius: 50%;
157 |
158 | }
159 |
160 | .Profile-Icon-Size{
161 |
162 | font-size : 60px;
163 | color : white;
164 |
165 | }
166 | }
167 | }
168 |
169 | }
170 |
171 |
172 | }
173 |
174 | .--Fixed{
175 | position: fixed;
176 | }
177 |
178 | /* #Media Queries
179 | ================================================== */
180 | /* Large Desktop */
181 | @media only screen and (min-width: 1441px) {
182 |
183 | }
184 | /* Smaller that standard */
185 | @media only screen and (min-width: 959px) and (max-width: 1440px) {
186 |
187 | }
188 | /* Smaller than standard 960 (devices and browsers) */
189 | @media only screen and (max-width: 959px) {
190 |
191 | }
192 |
193 | /* Tablet Portrait size to standard 960 (devices and browsers) */
194 | @media only screen and (min-width: 768px) and (max-width: 959px) {
195 |
196 | }
197 |
198 | /* All Mobile Sizes (devices and browser) */
199 | @media only screen and (max-width: 767px) {
200 |
201 | }
202 |
203 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
204 | @media only screen and (min-width: 480px) and (max-width: 767px) {
205 |
206 | }
207 |
208 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
209 | @media only screen and (max-width: 479px) {
210 |
211 | }
--------------------------------------------------------------------------------
/Components/src/VideoLoader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { connect } from 'react-redux'
4 |
5 | class VideoLoader extends React.Component{
6 |
7 | constructor(props){
8 | super(props)
9 | this.state = {
10 | VideoBasket : []
11 | }
12 |
13 | }
14 |
15 | render(){
16 |
17 | const { writeState } = this.props
18 |
19 | return (
20 |
21 |
22 |
비디오 로더
23 |
24 |
25 | )
26 | }
27 |
28 | }
29 |
30 | const mapStateToProps = (state) => {
31 | return {
32 | writeState : state.write
33 | }
34 | }
35 |
36 | VideoLoader = connect(mapStateToProps , null)(VideoLoader)
37 |
38 | export default VideoLoader
--------------------------------------------------------------------------------
/Components/src/Write.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import TopBar from '../src/TopBar.jsx'
4 | import Editor from './Editor.jsx'
5 | import ImageLoader from './ImageLoader.jsx'
6 | import VideoLoader from './VideoLoader.jsx'
7 | import '../src/Write.scss'
8 |
9 | import { withRouter } from 'react-router-dom'
10 | import { connect } from 'react-redux'
11 |
12 | import * as WriteActions from '../redux/WriteAction.js'
13 |
14 | class Write extends React.Component{
15 |
16 | render(){
17 |
18 | let { writeState } = this.props
19 |
20 | return(
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | )
41 |
42 | }
43 |
44 | }
45 |
46 | const mapStateToProps = (state) => {
47 |
48 | return{
49 | writeState : state.write
50 | }
51 |
52 | }
53 |
54 | Write = withRouter(connect(mapStateToProps,null)(Write))
55 |
56 | export default Write
57 |
--------------------------------------------------------------------------------
/Components/src/Write.scss:
--------------------------------------------------------------------------------
1 | .--Bold{
2 | font-weight: bold;
3 | }
4 |
5 | .Write-Grid-Container{
6 |
7 | display: grid;
8 | height : 100vh;
9 |
10 | .Write-Grid-Container__Item:nth-child(1){
11 |
12 | background-color: white;
13 | height : 100%;
14 |
15 | }
16 | .Write-Grid-Container__Item:nth-child(2){
17 |
18 | background-color: hsl(225, 6%, 13%);
19 | height : 100%;
20 |
21 | }
22 |
23 | }
24 |
25 | /* #Media Queries
26 | ================================================== */
27 | /* Large Desktop */
28 | @media only screen and (min-width: 1441px) {
29 |
30 | .Write-Grid-Container{
31 |
32 | grid-template-columns: 1fr 350px;
33 |
34 | .Write-Grid-Container__Item:nth-child(1){
35 |
36 | padding-top : 65px;
37 | background-color: white;
38 | min-height: calc(100vh - 65px);
39 | height : calc(100vh - 65px);
40 |
41 |
42 | }
43 | .Write-Grid-Container__Item:nth-child(2){
44 |
45 | background-color: hsl(225, 6%, 13%);
46 | padding-top : 65px;
47 | min-height: calc(100vh - 65px);
48 | height : calc(100vh - 65px);
49 |
50 | }
51 |
52 | }
53 |
54 | }
55 | /* Smaller that standard */
56 | @media only screen and (min-width: 959px) and (max-width: 1440px) {
57 |
58 | .Write-Grid-Container{
59 |
60 | grid-template-columns: 1fr 350px;
61 |
62 | .Write-Grid-Container__Item:nth-child(1){
63 |
64 | padding-top : 65px;
65 | background-color: white;
66 | min-height: calc(100vh - 65px);
67 | height : calc(100vh - 65px);
68 |
69 |
70 | }
71 | .Write-Grid-Container__Item:nth-child(2){
72 |
73 | background-color: hsl(225, 6%, 13%);
74 | height : 100%;
75 |
76 | }
77 |
78 | }
79 |
80 | }
81 | /* Smaller than standard 960 (devices and browsers) */
82 | @media only screen and (max-width: 959px) {
83 |
84 | }
85 |
86 | /* Tablet Portrait size to standard 960 (devices and browsers) */
87 | @media only screen and (min-width: 768px) and (max-width: 959px) {
88 |
89 | }
90 |
91 | /* All Mobile Sizes (devices and browser) */
92 | @media only screen and (max-width: 767px) {
93 |
94 | }
95 |
96 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
97 | @media only screen and (min-width: 480px) and (max-width: 767px) {
98 |
99 | }
100 |
101 | /* Mobile Portrait Size to Mobile Landscape Size (devices and browsers) */
102 | @media only screen and (max-width: 479px) {
103 |
104 | }
--------------------------------------------------------------------------------
/Components/src/app.js:
--------------------------------------------------------------------------------
1 | let app = require('express')()
2 |
3 | app.get('*',(req,res) => {
4 | res.sendFile('./index.html');
5 | })
6 |
7 | app.listen(80,()=>{
8 | console.log(80);
9 | })
--------------------------------------------------------------------------------
/Components/src/filepond-plugin-image-preview.min.css:
--------------------------------------------------------------------------------
1 | /*
2 | * FilePondPluginImagePreview 3.1.4
3 | * Licensed under MIT, https://opensource.org/licenses/MIT
4 | * Please visit https://pqina.nl/filepond for details.
5 | */
6 |
7 | /* eslint-disable */
8 | .filepond--image-preview-wrapper{z-index:2}.filepond--image-preview-overlay{display:block;position:absolute;left:0;top:0;width:100%;min-height:5rem;max-height:7rem;margin:0;opacity:0;z-index:2;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.filepond--image-preview-overlay svg{width:100%;height:auto;color:inherit;max-height:inherit}.filepond--image-preview-overlay-idle{mix-blend-mode:multiply;color:rgba(40,40,40,.85)}.filepond--image-preview-overlay-success{mix-blend-mode:normal;color:#369763}.filepond--image-preview-overlay-failure{mix-blend-mode:normal;color:#c44e47}@supports (-webkit-marquee-repetition:infinite) and (object-fit:fill){.filepond--image-preview-overlay-idle{mix-blend-mode:normal}}.filepond--image-preview-wrapper{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:absolute;left:0;top:0;right:0;margin:0;border-radius:.45em;overflow:hidden;background:rgba(0,0,0,.01)}.filepond--image-preview{position:absolute;left:0;top:0;z-index:1;display:block;width:100%;height:auto;pointer-events:none;-webkit-transform-origin:center center;transform-origin:center center;background:#222;will-change:transform,opacity}.filepond--image-preview[data-transparency-indicator=grid] canvas,.filepond--image-preview[data-transparency-indicator=grid] img{background-color:#fff;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg' fill='%23eee'%3E%3Cpath d='M0 0h50v50H0M50 50h50v50H50'/%3E%3C/svg%3E");background-size:1.25em 1.25em}.filepond--image-clip{position:relative;overflow:hidden;margin:0 auto}.filepond--image-bitmap,.filepond--image-vector{position:absolute;left:0;top:0;will-change:transform}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview-wrapper{border-radius:0}.filepond--root[data-style-panel-layout~=integrated] .filepond--image-preview{height:100%;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-wrapper{border-radius:99999rem}.filepond--root[data-style-panel-layout~=circle] .filepond--image-preview-overlay{top:auto;bottom:0;-webkit-transform:scaleY(-1);transform:scaleY(-1)}.filepond--root[data-style-panel-layout~=circle] .filepond--file .filepond--file-action-button[data-align*=bottom]:not([data-align*=center]){margin-bottom:.325em}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=left]{left:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--file [data-align*=right]{right:calc(50% - 3em)}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=left],.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=right]{margin-bottom:0.5125em}.filepond--root[data-style-panel-layout~=circle] .filepond--progress-indicator[data-align*=bottom][data-align*=center]{margin-top:0;margin-bottom:.1875em;margin-left:.1875em}
--------------------------------------------------------------------------------
/EditorImplement/rich-text-area.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Editor
7 |
8 |
112 |
151 |
152 |
153 |
192 |
193 |
--------------------------------------------------------------------------------
/ScreenShots/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/.DS_Store
--------------------------------------------------------------------------------
/ScreenShots/2019-03-05_Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-03-05_Home.png
--------------------------------------------------------------------------------
/ScreenShots/2019-06-25_Write.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-25_Write.PNG
--------------------------------------------------------------------------------
/ScreenShots/2019-06-25_Write_Color.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-25_Write_Color.PNG
--------------------------------------------------------------------------------
/ScreenShots/2019-06-25_Write_Size.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-25_Write_Size.PNG
--------------------------------------------------------------------------------
/ScreenShots/2019-06-26_imgDragandDrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-26_imgDragandDrop.png
--------------------------------------------------------------------------------
/ScreenShots/2019-06-26_imgLoad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-26_imgLoad.png
--------------------------------------------------------------------------------
/ScreenShots/2019-06-26_imgLocateAnyWhere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-06-26_imgLocateAnyWhere.png
--------------------------------------------------------------------------------
/ScreenShots/2019-07-30_articleListing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-07-30_articleListing.png
--------------------------------------------------------------------------------
/ScreenShots/2019-07-30_articleRead.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-07-30_articleRead.png
--------------------------------------------------------------------------------
/ScreenShots/2019-07-30_articleTempSaveLoad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-07-30_articleTempSaveLoad.png
--------------------------------------------------------------------------------
/ScreenShots/2019-08-31_Reply.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-08-31_Reply.png
--------------------------------------------------------------------------------
/ScreenShots/2019-09-08_ArticleFilter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/2019-09-08_ArticleFilter.png
--------------------------------------------------------------------------------
/ScreenShots/HomePlan.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/HomePlan.PNG
--------------------------------------------------------------------------------
/ScreenShots/ethereal.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/ethereal.PNG
--------------------------------------------------------------------------------
/ScreenShots/formaluser.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/formaluser.PNG
--------------------------------------------------------------------------------
/ScreenShots/login.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/login.PNG
--------------------------------------------------------------------------------
/ScreenShots/login_2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/login_2.PNG
--------------------------------------------------------------------------------
/ScreenShots/register.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/register.PNG
--------------------------------------------------------------------------------
/ScreenShots/register_2.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/register_2.PNG
--------------------------------------------------------------------------------
/ScreenShots/register_3.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/register_3.PNG
--------------------------------------------------------------------------------
/ScreenShots/unformaluser.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/unformaluser.PNG
--------------------------------------------------------------------------------
/ScreenShots/verify_1.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/ScreenShots/verify_1.PNG
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 |
2 | let express = require('express')
3 | let session = require('express-session')
4 | let app = express()
5 | let readRouter = require('./routes/readRouter.js')
6 | let loginRouter = require('./routes/loginRouter.js')
7 | let registerRouter = require('./routes/registerRouter.js')
8 | let writeRouter = require('./routes/writeRouter.js')
9 | let path = require('path')
10 |
11 | let cors = require('cors');
12 | const corsOptions = {
13 | origin: true,
14 | credentials: true
15 | };
16 |
17 | app.use(cors(corsOptions));
18 | app.set('PORT',process.env.PORT || 3000)
19 |
20 | app.use(session({
21 |
22 | secret: 'HSJPRIME',
23 |
24 | resave: false,
25 |
26 | saveUninitialized: true,
27 |
28 | cookie: {
29 | httpOnly : false,
30 | secure: false,
31 | maxAge: 24 * 24 * 60 * 60 * 1000,
32 | }
33 |
34 | }));
35 |
36 | app.use(express.static(path.join(__dirname, 'public')));
37 |
38 | app.use('/read',readRouter)
39 | app.use('/write',writeRouter)
40 | app.use('/login',loginRouter)
41 | app.use('/register',registerRouter)
42 |
43 | app.use('*', (req,res) =>{
44 | res.sendFile(path.join(__dirname+'/public/dist/index.html'));
45 | });
46 |
47 | app.listen(3000,()=>{
48 | console.log(`익스프레스 서버는 리스닝 합니다.`)
49 | })
50 |
--------------------------------------------------------------------------------
/clear.sh:
--------------------------------------------------------------------------------
1 | rm -f /Users/hsjprime/Desktop/Development/Web/React-Board/public/SharedImages/*
2 |
--------------------------------------------------------------------------------
/db_command.txt:
--------------------------------------------------------------------------------
1 | // 컬렉션 생성
2 | db.createcollection('formalUsers');
3 | db.createcollection('nonformalUsers');
4 | // TTL 설정
5 | db.unformalUsers.ensureIndex({expire:1},{expireAfterSeconds : 600});
6 | db.unformalUsers.insertOne({a:'a',expire: new Date()})
7 |
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-board",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "webpack.config.js",
6 | "scripts": {
7 | "lint": "./node_modules/.bin/eslint ./",
8 | "dev": "webpack-dev-server --hot",
9 | "production": "webpack -p",
10 | "bundle": "webpack"
11 | },
12 | "dependencies": {
13 | "body-parser": "^1.18.3",
14 | "bson-ext": "^2.0.1",
15 | "clean-webpack-plugin": "^3.0.0",
16 | "cors": "^2.8.5",
17 | "express": "^4.16.3",
18 | "express-session": "^1.15.6",
19 | "filepond": "^3.3.2",
20 | "filepond-plugin-file-rename": "^1.1.0",
21 | "filepond-plugin-file-validate-size": "^2.0.0",
22 | "filepond-plugin-file-validate-type": "^1.2.0",
23 | "filepond-plugin-image-preview": "^3.1.4",
24 | "formidable": "^1.2.1",
25 | "history": "^4.7.2",
26 | "html-webpack-plugin": "^3.2.0",
27 | "js-cookie": "^2.2.0",
28 | "js-sha3": "^0.8.0",
29 | "kerberos": "^1.1.2",
30 | "mongodb": "^3.1.10",
31 | "multer": "^1.4.1",
32 | "node-sass": "^4.9.3",
33 | "nodemailer": "^4.7.0",
34 | "nodemailer-smtp-pool": "^2.8.3",
35 | "nodemailer-smtp-transport": "^2.7.4",
36 | "prop-types": "^15.6.2",
37 | "react": "^16.5.0",
38 | "react-color": "^2.17.3",
39 | "react-dom": "^16.5.0",
40 | "react-filepond": "^5.0.0",
41 | "react-icons": "^3.4.0",
42 | "react-redux": "^5.0.7",
43 | "react-router-dom": "^4.3.1",
44 | "react-spinners": "^0.4.7",
45 | "redux": "^4.0.0",
46 | "redux-cookie": "^0.5.9",
47 | "redux-logger": "^3.0.6",
48 | "redux-thunk": "^2.3.0",
49 | "session-file-store": "^1.2.0",
50 | "sha.js": "^2.4.11"
51 | },
52 | "devDependencies": {
53 | "babel-core": "^6.26.3",
54 | "babel-loader": "^7.1.5",
55 | "babel-polyfill": "^6.26.0",
56 | "babel-preset-env": "^1.7.0",
57 | "babel-preset-react": "^6.24.1",
58 | "babel-preset-stage-0": "^6.24.1",
59 | "css-loader": "^1.0.0",
60 | "eslint": "^6.0.1",
61 | "eslint-plugin-react": "^7.14.2",
62 | "file-loader": "^2.0.0",
63 | "img-loader": "^3.0.1",
64 | "redux-devtools": "^3.4.1",
65 | "sass-loader": "^7.1.0",
66 | "style-loader": "^0.22.1",
67 | "url-loader": "^1.1.2",
68 | "webpack": "^4.16.5",
69 | "webpack-cli": "^3.1.0",
70 | "webpack-dev-server": "^3.1.5"
71 | },
72 | "repository": {
73 | "type": "git",
74 | "url": "git+https://github.com/HamSungJun/React-Board.git"
75 | },
76 | "author": "HSJPRIME",
77 | "license": "ISC",
78 | "bugs": {
79 | "url": "https://github.com/HamSungJun/React-Board/issues"
80 | },
81 | "homepage": "https://github.com/HamSungJun/React-Board#readme"
82 | }
83 |
--------------------------------------------------------------------------------
/public/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/.DS_Store
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Black-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Black-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Bold-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Bold-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-DemiLight-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-DemiLight-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Light-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Light-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Medium-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Medium-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Regular-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Regular-Hestia.woff
--------------------------------------------------------------------------------
/public/Fonts/NotoSansKR-Thin-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Fonts/NotoSansKR-Thin-Hestia.woff
--------------------------------------------------------------------------------
/public/Images/no-img.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/Images/no-img.jpg
--------------------------------------------------------------------------------
/public/Images/post-it.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
47 |
--------------------------------------------------------------------------------
/public/UserImages/1562566761370.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/UserImages/1562566761370.jpeg
--------------------------------------------------------------------------------
/public/UserImages/1562633547385.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/UserImages/1562633547385.jpeg
--------------------------------------------------------------------------------
/public/UserImages/1573381871614.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/UserImages/1573381871614.png
--------------------------------------------------------------------------------
/public/UserImages/no-user-img.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
48 |
--------------------------------------------------------------------------------
/public/assets/NotoSansKR-Bold-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/assets/NotoSansKR-Bold-Hestia.woff
--------------------------------------------------------------------------------
/public/assets/NotoSansKR-Regular-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/assets/NotoSansKR-Regular-Hestia.woff
--------------------------------------------------------------------------------
/public/assets/post-it.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
47 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/.DS_Store
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/README.md:
--------------------------------------------------------------------------------
1 | # NotoSansKR-Hestia
2 | ----------
3 |
4 | ##### 노토 산스 쓰고 발할라로 갑시다
5 |
6 | [GitHub Pages][1]를 참고해주세요.
7 |
8 | ##### 사용법
9 |
10 | HTML에서
11 |
12 | ```html
13 |
14 | ```
15 |
16 | 혹은 CSS에서
17 |
18 | ```css
19 | @import url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/stylesheets/NotoSansKR-Hestia.css);
20 | ```
21 |
22 | 후 다음과 같이 사용
23 |
24 | ```css
25 | .noto {
26 | font-family: 'Noto Sans Korean', sans-serif;
27 | }
28 | ```
29 |
30 | [1]: http://theeluwin.github.io/NotoSansKR-Hestia
31 |
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/.DS_Store
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Black-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Black-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Bold-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Bold-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-DemiLight-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-DemiLight-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Light-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Light-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Medium-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Medium-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Regular-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Regular-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Thin-Hestia.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/eot/NotoSansKR-Thin-Hestia.eot
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Black-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Black-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Bold-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Bold-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-DemiLight-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-DemiLight-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Light-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Light-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Medium-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Medium-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Regular-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Regular-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Thin-Hestia.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/otf/NotoSansKR-Thin-Hestia.otf
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/.DS_Store
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Black-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Black-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Bold-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Bold-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-DemiLight-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-DemiLight-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Light-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Light-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Medium-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Medium-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Regular-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Regular-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Thin-Hestia.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-fc0616a/fonts/woff/NotoSansKR-Thin-Hestia.woff
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/ksx1001.txt:
--------------------------------------------------------------------------------
1 | 가각간갇갈갉갊감갑값갓갔강갖갗같갚갛개객갠갤갬갭갯갰갱갸갹갼걀걋걍걔걘걜거걱건걷걸걺검겁것겄겅겆겉겊겋게겐겔겜겝겟겠겡겨격겪견겯결겸겹겻겼경곁계곈곌곕곗고곡곤곧골곪곬곯곰곱곳공곶과곽관괄괆괌괍괏광괘괜괠괩괬괭괴괵괸괼굄굅굇굉교굔굘굡굣구국군굳굴굵굶굻굼굽굿궁궂궈궉권궐궜궝궤궷귀귁귄귈귐귑귓규균귤그극근귿글긁금급긋긍긔기긱긴긷길긺김깁깃깅깆깊까깍깎깐깔깖깜깝깟깠깡깥깨깩깬깰깸깹깻깼깽꺄꺅꺌꺼꺽꺾껀껄껌껍껏껐껑께껙껜껨껫껭껴껸껼꼇꼈꼍꼐꼬꼭꼰꼲꼴꼼꼽꼿꽁꽂꽃꽈꽉꽐꽜꽝꽤꽥꽹꾀꾄꾈꾐꾑꾕꾜꾸꾹꾼꿀꿇꿈꿉꿋꿍꿎꿔꿜꿨꿩꿰꿱꿴꿸뀀뀁뀄뀌뀐뀔뀜뀝뀨끄끅끈끊끌끎끓끔끕끗끙끝끼끽낀낄낌낍낏낑나낙낚난낟날낡낢남납낫났낭낮낯낱낳내낵낸낼냄냅냇냈냉냐냑냔냘냠냥너넉넋넌널넒넓넘넙넛넜넝넣네넥넨넬넴넵넷넸넹녀녁년녈념녑녔녕녘녜녠노녹논놀놂놈놉놋농높놓놔놘놜놨뇌뇐뇔뇜뇝뇟뇨뇩뇬뇰뇹뇻뇽누눅눈눋눌눔눕눗눙눠눴눼뉘뉜뉠뉨뉩뉴뉵뉼늄늅늉느늑는늘늙늚늠늡늣능늦늪늬늰늴니닉닌닐닒님닙닛닝닢다닥닦단닫달닭닮닯닳담답닷닸당닺닻닿대댁댄댈댐댑댓댔댕댜더덕덖던덛덜덞덟덤덥덧덩덫덮데덱덴델뎀뎁뎃뎄뎅뎌뎐뎔뎠뎡뎨뎬도독돈돋돌돎돐돔돕돗동돛돝돠돤돨돼됐되된될됨됩됫됴두둑둔둘둠둡둣둥둬뒀뒈뒝뒤뒨뒬뒵뒷뒹듀듄듈듐듕드득든듣들듦듬듭듯등듸디딕딘딛딜딤딥딧딨딩딪따딱딴딸땀땁땃땄땅땋때땍땐땔땜땝땟땠땡떠떡떤떨떪떫떰떱떳떴떵떻떼떽뗀뗄뗌뗍뗏뗐뗑뗘뗬또똑똔똘똥똬똴뙈뙤뙨뚜뚝뚠뚤뚫뚬뚱뛔뛰뛴뛸뜀뜁뜅뜨뜩뜬뜯뜰뜸뜹뜻띄띈띌띔띕띠띤띨띰띱띳띵라락란랄람랍랏랐랑랒랖랗래랙랜랠램랩랫랬랭랴략랸럇량러럭런럴럼럽럿렀렁렇레렉렌렐렘렙렛렝려력련렬렴렵렷렸령례롄롑롓로록론롤롬롭롯롱롸롼뢍뢨뢰뢴뢸룀룁룃룅료룐룔룝룟룡루룩룬룰룸룹룻룽뤄뤘뤠뤼뤽륀륄륌륏륑류륙륜률륨륩륫륭르륵른를름릅릇릉릊릍릎리릭린릴림립릿링마막만많맏말맑맒맘맙맛망맞맡맣매맥맨맬맴맵맷맸맹맺먀먁먈먕머먹먼멀멂멈멉멋멍멎멓메멕멘멜멤멥멧멨멩며멱면멸몃몄명몇몌모목몫몬몰몲몸몹못몽뫄뫈뫘뫙뫼묀묄묍묏묑묘묜묠묩묫무묵묶문묻물묽묾뭄뭅뭇뭉뭍뭏뭐뭔뭘뭡뭣뭬뮈뮌뮐뮤뮨뮬뮴뮷므믄믈믐믓미믹민믿밀밂밈밉밋밌밍및밑바박밖밗반받발밝밞밟밤밥밧방밭배백밴밸뱀뱁뱃뱄뱅뱉뱌뱍뱐뱝버벅번벋벌벎범법벗벙벚베벡벤벧벨벰벱벳벴벵벼벽변별볍볏볐병볕볘볜보복볶본볼봄봅봇봉봐봔봤봬뵀뵈뵉뵌뵐뵘뵙뵤뵨부북분붇불붉붊붐붑붓붕붙붚붜붤붰붸뷔뷕뷘뷜뷩뷰뷴뷸븀븃븅브븍븐블븜븝븟비빅빈빌빎빔빕빗빙빚빛빠빡빤빨빪빰빱빳빴빵빻빼빽뺀뺄뺌뺍뺏뺐뺑뺘뺙뺨뻐뻑뻔뻗뻘뻠뻣뻤뻥뻬뼁뼈뼉뼘뼙뼛뼜뼝뽀뽁뽄뽈뽐뽑뽕뾔뾰뿅뿌뿍뿐뿔뿜뿟뿡쀼쁑쁘쁜쁠쁨쁩삐삑삔삘삠삡삣삥사삭삯산삳살삵삶삼삽삿샀상샅새색샌샐샘샙샛샜생샤샥샨샬샴샵샷샹섀섄섈섐섕서석섞섟선섣설섦섧섬섭섯섰성섶세섹센셀셈셉셋셌셍셔셕션셜셤셥셧셨셩셰셴셸솅소속솎손솔솖솜솝솟송솥솨솩솬솰솽쇄쇈쇌쇔쇗쇘쇠쇤쇨쇰쇱쇳쇼쇽숀숄숌숍숏숑수숙순숟술숨숩숫숭숯숱숲숴쉈쉐쉑쉔쉘쉠쉥쉬쉭쉰쉴쉼쉽쉿슁슈슉슐슘슛슝스슥슨슬슭슴습슷승시식신싣실싫심십싯싱싶싸싹싻싼쌀쌈쌉쌌쌍쌓쌔쌕쌘쌜쌤쌥쌨쌩썅써썩썬썰썲썸썹썼썽쎄쎈쎌쏀쏘쏙쏜쏟쏠쏢쏨쏩쏭쏴쏵쏸쐈쐐쐤쐬쐰쐴쐼쐽쑈쑤쑥쑨쑬쑴쑵쑹쒀쒔쒜쒸쒼쓩쓰쓱쓴쓸쓺쓿씀씁씌씐씔씜씨씩씬씰씸씹씻씽아악안앉않알앍앎앓암압앗았앙앝앞애액앤앨앰앱앳앴앵야약얀얄얇얌얍얏양얕얗얘얜얠얩어억언얹얻얼얽얾엄업없엇었엉엊엌엎에엑엔엘엠엡엣엥여역엮연열엶엷염엽엾엿였영옅옆옇예옌옐옘옙옛옜오옥온올옭옮옰옳옴옵옷옹옻와왁완왈왐왑왓왔왕왜왝왠왬왯왱외왹왼욀욈욉욋욍요욕욘욜욤욥욧용우욱운울욹욺움웁웃웅워웍원월웜웝웠웡웨웩웬웰웸웹웽위윅윈윌윔윕윗윙유육윤율윰윱윳융윷으윽은을읊음읍읏응읒읔읕읖읗의읜읠읨읫이익인일읽읾잃임입잇있잉잊잎자작잔잖잗잘잚잠잡잣잤장잦재잭잰잴잼잽잿쟀쟁쟈쟉쟌쟎쟐쟘쟝쟤쟨쟬저적전절젊점접젓정젖제젝젠젤젬젭젯젱져젼졀졈졉졌졍졔조족존졸졺좀좁좃종좆좇좋좌좍좔좝좟좡좨좼좽죄죈죌죔죕죗죙죠죡죤죵주죽준줄줅줆줌줍줏중줘줬줴쥐쥑쥔쥘쥠쥡쥣쥬쥰쥴쥼즈즉즌즐즘즙즛증지직진짇질짊짐집짓징짖짙짚짜짝짠짢짤짧짬짭짯짰짱째짹짼쨀쨈쨉쨋쨌쨍쨔쨘쨩쩌쩍쩐쩔쩜쩝쩟쩠쩡쩨쩽쪄쪘쪼쪽쫀쫄쫌쫍쫏쫑쫓쫘쫙쫠쫬쫴쬈쬐쬔쬘쬠쬡쭁쭈쭉쭌쭐쭘쭙쭝쭤쭸쭹쮜쮸쯔쯤쯧쯩찌찍찐찔찜찝찡찢찧차착찬찮찰참찹찻찼창찾채책챈챌챔챕챗챘챙챠챤챦챨챰챵처척천철첨첩첫첬청체첵첸첼쳄쳅쳇쳉쳐쳔쳤쳬쳰촁초촉촌촐촘촙촛총촤촨촬촹최쵠쵤쵬쵭쵯쵱쵸춈추축춘출춤춥춧충춰췄췌췐취췬췰췸췹췻췽츄츈츌츔츙츠측츤츨츰츱츳층치칙친칟칠칡침칩칫칭카칵칸칼캄캅캇캉캐캑캔캘캠캡캣캤캥캬캭컁커컥컨컫컬컴컵컷컸컹케켁켄켈켐켑켓켕켜켠켤켬켭켯켰켱켸코콕콘콜콤콥콧콩콰콱콴콸쾀쾅쾌쾡쾨쾰쿄쿠쿡쿤쿨쿰쿱쿳쿵쿼퀀퀄퀑퀘퀭퀴퀵퀸퀼큄큅큇큉큐큔큘큠크큭큰클큼큽킁키킥킨킬킴킵킷킹타탁탄탈탉탐탑탓탔탕태택탠탤탬탭탯탰탱탸턍터턱턴털턺텀텁텃텄텅테텍텐텔템텝텟텡텨텬텼톄톈토톡톤톨톰톱톳통톺톼퇀퇘퇴퇸툇툉툐투툭툰툴툼툽툿퉁퉈퉜퉤튀튁튄튈튐튑튕튜튠튤튬튱트특튼튿틀틂틈틉틋틔틘틜틤틥티틱틴틸팀팁팃팅파팍팎판팔팖팜팝팟팠팡팥패팩팬팰팸팹팻팼팽퍄퍅퍼퍽펀펄펌펍펏펐펑페펙펜펠펨펩펫펭펴편펼폄폅폈평폐폘폡폣포폭폰폴폼폽폿퐁퐈퐝푀푄표푠푤푭푯푸푹푼푿풀풂품풉풋풍풔풩퓌퓐퓔퓜퓟퓨퓬퓰퓸퓻퓽프픈플픔픕픗피픽핀필핌핍핏핑하학한할핥함합핫항해핵핸핼햄햅햇했행햐향허헉헌헐헒험헙헛헝헤헥헨헬헴헵헷헹혀혁현혈혐협혓혔형혜혠혤혭호혹혼홀홅홈홉홋홍홑화확환활홧황홰홱홴횃횅회획횐횔횝횟횡효횬횰횹횻후훅훈훌훑훔훗훙훠훤훨훰훵훼훽휀휄휑휘휙휜휠휨휩휫휭휴휵휸휼흄흇흉흐흑흔흖흗흘흙흠흡흣흥흩희흰흴흼흽힁히힉힌힐힘힙힛힝abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890、。·‥…¨〃―∥\∼‘’“”〔〕〈〉《》「」『』【】±×÷≠≤≥∞∴°′″℃Å¢£¥♂♀∠⊥⌒∂∇≡≒§※☆★○●◎◇◆□■△▲▽▼→←↑↓↔〓≪≫√∽∝∵∫∬∈∋⊆⊇⊂⊃∪∩∧∨¬⇒⇔∀∃´~ˇ˘˝˚˙¸˛¡¿ː∮∑∏¤℉‰◁◀▷▶♤♠♡♥♧♣⊙◈▣◐◑▒▤▥▨▧▦▩♨☏☎☜☞¶†‡↕↗↙↖↘♭♩♪♬㉿㈜№㏇™㏂㏘℡€®㉾!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[₩]^_`abcdefghijklmnopqrstuvwxyz{|} ̄ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿㆀㆁㆂㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌㆍㆎⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂┒┑┚┙┖┕┎┍┞┟┡┢┦┧┩┪┭┮┱┲┵┶┹┺┽┾╀╁╃╄╅╆╇╈╉╊㎕㎖㎗ℓ㎘㏄㎣㎤㎥㎦㎙㎚㎛㎜㎝㎞㎟㎠㎡㎢㏊㎍㎎㎏㏏㎈㎉㏈㎧㎨㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎀㎁㎂㎃㎄㎺㎻㎼㎽㎾㎿㎐㎑㎒㎓㎔Ω㏀㏁㎊㎋㎌㏖㏅㎭㎮㎯㏛㎩㎪㎫㎬㏝㏐㏓㏃㏉㏜㏆ÆÐªĦIJĿŁØŒºÞŦŊ㉠㉡㉢㉣㉤㉥㉦㉧㉨㉩㉪㉫㉬㉭㉮㉯㉰㉱㉲㉳㉴㉵㉶㉷㉸㉹㉺㉻ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮½⅓⅔¼¾⅛⅜⅝⅞æđðħıijĸŀłøœßþŧŋʼn㈀㈁㈂㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌㈍㈎㈏㈐㈑㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂¹²³⁴ⁿ₁₂₃₄ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя`~!@#$%^&*()_-+=|\{[]}"':;?/>.<,
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-fc0616a/stylesheets/NotoSansKR-Hestia.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Noto Sans Korean';
3 | font-style: normal;
4 | font-weight: 100;
5 | src: local('Noto Sans Thin'), local('NotoSans-Thin'),
6 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Thin-Hestia.eot),
7 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Thin-Hestia.eot?#iefix) format('embedded-opentype'),
8 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Thin-Hestia.woff) format('woff'),
9 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Thin-Hestia.otf) format('opentype');
10 | }
11 |
12 | @font-face {
13 | font-family: 'Noto Sans Korean';
14 | font-style: normal;
15 | font-weight: 300;
16 | src: local('Noto Sans Light'), local('NotoSans-Light'),
17 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Light-Hestia.eot),
18 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Light-Hestia.eot?#iefix) format('embedded-opentype'),
19 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Light-Hestia.woff) format('woff'),
20 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Light-Hestia.otf) format('opentype');
21 | }
22 |
23 | @font-face {
24 | font-family: 'Noto Sans Korean';
25 | font-style: normal;
26 | font-weight: 350;
27 | src: local('Noto Sans DemiLight'), local('NotoSans-DemiLight'),
28 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-DemiLight-Hestia.eot),
29 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-DemiLight-Hestia.eot?#iefix) format('embedded-opentype'),
30 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-DemiLight-Hestia.woff) format('woff'),
31 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-DemiLight-Hestia.otf) format('opentype');
32 | }
33 |
34 | @font-face {
35 | font-family: 'Noto Sans Korean';
36 | font-style: normal;
37 | font-weight: 400;
38 | src: local('Noto Sans Regular'), local('NotoSans-Regular'),
39 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Regular-Hestia.eot),
40 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Regular-Hestia.eot?#iefix) format('embedded-opentype'),
41 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Regular-Hestia.woff) format('woff'),
42 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Regular-Hestia.otf) format('opentype');
43 | }
44 |
45 | @font-face {
46 | font-family: 'Noto Sans Korean';
47 | font-style: normal;
48 | font-weight: 500;
49 | src: local('Noto Sans Medium'), local('NotoSans-Medium'),
50 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Medium-Hestia.eot),
51 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Medium-Hestia.eot?#iefix) format('embedded-opentype'),
52 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Medium-Hestia.woff) format('woff'),
53 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Medium-Hestia.otf) format('opentype');
54 | }
55 |
56 | @font-face {
57 | font-family: 'Noto Sans Korean';
58 | font-style: normal;
59 | font-weight: 700;
60 | src: local('Noto Sans Bold'), local('NotoSans-Bold'),
61 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Bold-Hestia.eot),
62 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Bold-Hestia.eot?#iefix) format('embedded-opentype'),
63 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Bold-Hestia.woff) format('woff'),
64 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Bold-Hestia.otf) format('opentype');
65 | }
66 |
67 | @font-face {
68 | font-family: 'Noto Sans Korean';
69 | font-style: normal;
70 | font-weight: 900;
71 | src: local('Noto Sans Black'), local('NotoSans-Black'),
72 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Black-Hestia.eot),
73 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/eot/NotoSansKR-Black-Hestia.eot?#iefix) format('embedded-opentype'),
74 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/woff/NotoSansKR-Black-Hestia.woff) format('woff'),
75 | url(https://cdn.jsdelivr.net/gh/theeluwin/NotoSansKR-Hestia@master/fonts/otf/NotoSansKR-Black-Hestia.otf) format('opentype');
76 | }
77 |
--------------------------------------------------------------------------------
/public/theeluwin-NotoSansKR-Hestia-v0.1-4-gfc0616a.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/public/theeluwin-NotoSansKR-Hestia-v0.1-4-gfc0616a.zip
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Community Board developed by React.js
2 |
3 | ## Installation
4 |
5 | ~~~
6 | (!Client Side)
7 |
8 | ./Project Root Diretory
9 |
10 | $git clone https://github.com/HamSungJun/React-Board.git
11 | $npm install
12 | $npm run dev ---> localhost:9000
13 |
14 | (!Server Side <= This is local REST)
15 |
16 | $node app.js ---> localhost:3000
17 |
18 | After then, all of Client's Http Requests wiil be sent to localhost:3000/(Some Routes)
19 | ~~~
20 |
21 | ## 개발 내역
22 |
23 | #### 2018-11-24
24 | >불금에 Login.jsx 만들었습니다. Redux 로그인 리듀서에서 입력하는 로그인 Form에 대한 모든 상태 데이터를 관리하고 로그인 로직을 구현해 볼 생각입니다. redux-logger 미들웨어를 적용해 두었기 때문에 개발자 도구 Console에서 데이터 변동 확인가능합니다.
25 |
26 | 
27 |
28 | #### 2018-11-28
29 |
30 | >화요일 심심한 저녁에 회원가입 폼을 제작하였습니다. 회원가입 모달 스크린은 로그인 박스에 추가된 Register 버튼을 클릭하면 렌더링 됩니다. 자신의 프로필 이미지를 설정할 수 있도록 filepond 파일 업로드 라이브러리를 이용하였습니다.
31 |
32 | 
33 |
34 | 
35 |
36 | #### 2018-11-29
37 |
38 | >수요일 시간 많은 날에 회원가입 폼을 리덕스 스토어와 연결하였습니다. 입력 인풋 요소들에게는 각각의 정규식을 통해 입력된 값에 대한 검증을 수행하고 유효한 입력이면 State는 true 상태로 변경됩니다. 입력이 유효하지 않을 경우 AlertBox가 Live Append 됩니다. 모든 필요 Boolean State가 만족하게 될때 Submit 버튼이 밝은 초록색으로 이용 가능해 집니다.
39 |
40 | 
41 |
42 | 
43 |
44 | #### 2018-12-05
45 |
46 | >폼을 작성하게 되면 Submit 버튼을 클릭하여 유저가 입력한 이메일로 가입 인증을 위한 메일을 전송합니다. 메일을 보내는 것은 nodemailer모듈과 ethereal fake SMTP 서비스를 이용하였습니다. 받은 메일에서 유저를 구분하기 위한 Unique Key는 가입한 시각의 Timestamp값을 이용하였습니다. 링크를 클릭하게 되면 서버 고유의 가입 인증을 위한 라우팅 주소로 타임스탬프 값이 전해지며 이것을 MongoDB 다큐먼트의 식별값으로 이용하여 비인증된유저 컬렉션에서 인증된 유저컬렉션으로 다큐먼트를 이동시킵니다.
47 |
48 | #### 인증메일 발송
49 | 
50 |
51 | #### 비인증 유저 컬렉션
52 | 
53 |
54 | #### FAKE SMTP
55 | 
56 |
57 | #### 인증 유저 컬렉션
58 | 
59 |
60 |
61 |
62 | #### 2019-03-05
63 |
64 | >모비젠이라는 회사에서 현장실습을 하느라 개발 재개가 늦어졌네요. 로그인 성공시 /Home으로 진입하며 현재 TopBar만 구성되어 있습니다. 현재 이슈는 새로고침시 Redux Store 데이터를 어떻게 직전의 Store값으로 복원 시키느냐와 세션 유지 및 파기 시간 설정입니다. 메뉴 아이콘은 ` react-icons` 를 이용하여 구성하였습니다.
65 |
66 | #### Home__TopBar
67 |
68 | 
69 |
70 |
71 |
72 | #### 2019-06-25
73 |
74 | >현재 `HTML` 내부 `iframe`을 통해서 에디트 기능을 개발하려 하고 있습니다.
75 |
76 | 
77 |
78 | 
79 |
80 | 
81 |
82 | - 개발완료된 내용
83 | - 텍스트 굵게 처리하는 기능
84 | - 텍스트 기울임 기능
85 | - 텍스트 밑줄 기능
86 | - 텍스트 정렬 기능
87 | - 하이퍼 링크 기능
88 | - 텍스트 색상 조절 기능
89 | - 텍스트 크기 지정 기능
90 |
91 | - 개발중인 내용
92 | - 미디어 로더 기능
93 | > 해당 기능은 파일 브라우저를 통해서 이미지를 선택하면 사이드 패널의 프리뷰 아이콘에 등록되어 마우스 호버시 확대된 이미지 화면을 볼 수 있도록 지원하고 에디트 프레임에 드래그 앤 드랍을 통해서 필요한 곳에 바로 이미지를 삽입할 수 있도록 만들어볼 계획입니다.
94 |
95 |
96 |
97 | #### 2019-06-26
98 |
99 | > 이미지를 낱개 혹은 다중으로 `Photo Library` 사이드 바에 프리뷰 상태로 로드할 수 있게 되었습니다. 프리뷰는 서버쪽에 이미지 저장이 성공적으로만 이루어 졌을 경우에 등장하므로 안정성이 높습니다. `Edit Frame`에 로드 되는 상황에서는 서버쪽 경로를 src로 이용합니다.
100 |
101 | > 이미지는 라이브러리에 로드시켜 놓는데에 성공하였다면 `Drag & Drop` 유저 액션을 통해 `Edit Frame`에 로드시킬 수 있습니다. 원래 이미지 크기의 1/5 상태로 입력됩니다. 유저의 입맛에 따라 이미지 크기를 수정할 수 있도록 `Resizer`를 구현해 볼 계획 입니다.
102 |
103 | 
104 |
105 | 
106 |
107 | 
108 |
109 | - 개발 하려는 기능
110 | - `Edit Frame` 내 이미지 사이즈 조절 기능
111 |
112 |
113 |
114 | #### 2019-07-11
115 |
116 | - React-Board 리팩토링
117 |
118 | - 불필요한 React-Redux 커넥션 제거.
119 |
120 | - 간단한 State 데이터는 컴포넌트 생성자 내부 this.state = {...} 으로 컨트롤
121 |
122 | - React-Router의 history 로직 추가 (비 로그인시 접근 제어 , 로그아웃 Redirecting)
123 |
124 | - 유저 정보를 리덕스 스토어에 저장하는 방식에서 `sessionStorage`에 저장하는 방식으로 변경(새로고침시 데이터가 증발하는 문제. Logout 이벤트에서 Clear())
125 |
126 | - 개발 완료된 내용
127 |
128 | - 작성 완료 버튼 클릭시 내용을 서버쪽으로 `POST`
129 |
130 | - SharedPostings 컬렉션에 POST_THUMBNAIL key 추가
131 |
132 | - 로그아웃 버튼 클릭시 세션 스토리지 내용 Clear 및 기본 주소로 이동
133 |
134 | - 개발 예정인 내용
135 |
136 | - 유효 세션 인증을 위한 `JSON WEB TOKEN` 도입을 할건데.. (이건좀 나중에)
137 |
138 | - 저장된 포스트를 불러와 렌더링 하는 기능 (READ)
139 |
140 | - 포스트중 하나를 클릭하면 내용을 렌더링 하는 기능 (READ)
141 |
142 | - 저장된 포스르를 수정 및 삭제할 수 있는 기능 (UPDATE or DELETE)
143 |
144 |
145 |
146 | #### 2019-07-30
147 |
148 | - 개발 완료된 내용
149 |
150 | - 게시글을 중간 작성 할 수 있도록 제작.
151 |
152 | - 중간 저장된 글을 불러와서 이어 작성 할 수 있도록 제작.
153 |
154 | - Home에서 작성된 포스트들을 살펴볼 수 있도록 리스팅.
155 |
156 | - 개발 예정 내용
157 |
158 | - 글을 읽을 때 접근하는 장치에 따라 최적화된 뷰를 제공할 수 있어야 함.
159 |
160 | - 반응형 레이아웃 제공 필요.
161 |
162 | - 게시글에 대한 댓글 기능 구현 예정.
163 |
164 | #### 글 임시저장 및 불러 오기
165 |
166 | 
167 |
168 | #### 게시글 리스팅
169 |
170 | 
171 |
172 | #### 게시글 클릭 -> 읽기
173 |
174 | 
175 |
176 | > 게시글을 불러올 때 서버에 'GET' 요청을 통해 서버에 존재하는 포스팅을 20개씩 쿼리하여 가져옵니다. 스크롤바가 하단에 닿게되면 추가로 20개씩 요청하는 식으로 페이지네이션을 구현하고 데이터베이스 쿼리할 때 게시글 내용 전체를 가져올까 생각하다가 리스팅에 필요한 내용만 프로젝션하여 가져온후 리스트 중 하나를 클릭하면 해당 포스트의 내용만 쿼리하여 가져옵니다. 사용자가 모든 내용을 읽어보지 않을 것이라 생각하였고 내용까지 가져오면 클라이언트 측이 메모리 용량을 필요치 않게 많이 차지 할 것이라 생각함.
177 |
178 | > 게시글 리스팅에 필요한 데이터는 리액트 컴포넌트 State에 저장하지 않고 Redux Store에 저장하였습니다. History 이동시 컴포넌트 Mount , Unmount가 발생하는데 이때 State가 유실되어 한번 요청했던 데이터를 다시 요청하게 되는 상황이 발생하기 때문입니다. Store에 저장하게 되면 다시 요청하고 렌더링하는 작업없이 요청했던 내용을 윈도우를 리로드 하지 않는한 유지 할 수 있음.
179 |
180 |
181 |
182 | #### 2019-08-31
183 |
184 | - 개발 완료된 내용
185 |
186 | - 게시글에 대한 댓글 작성기능
187 |
188 | - 자신이 작성한 댓글이면 댓글 Row 오른편에 붉은 휴지통 아이콘을 통해 자체 삭제할 수 있도록 개발
189 |
190 | - 댓글 작성 및 삭제시 Mongo DB findAndUpdate() 메소드를 통해 타겟 게시물의 변동된 내용을 반영하여 새로운 다큐먼트를 리턴하도록 설정 -> 리액트 State 변동을 통한 DOM Refresh
191 |
192 | - 개발 예정인 내용
193 |
194 | - 좋아요 버튼기능 활성화
195 |
196 | - 마이페이지
197 |
198 | #### 게시글 댓글 수정 및 삭제
199 |
200 | 
201 |
202 | #### 2019-09-08
203 |
204 | - 개발 완료된 내용
205 |
206 | - 게시글 리스트 정렬기능
207 |
208 | - 게시글 리스트 추천수, 조회수, 댓글수 나타나도록 추가
209 |
210 | - 글을 작성한후 홈으로 돌아오는 경우 'refresh' 글을 읽고 홈으로 돌아오는 경우는 유지
211 |
212 | - 개발 예정인 내용
213 |
214 | - 마이페이지
215 |
216 | #### 게시글 요소별 정렬기능 구현
217 |
218 | 
219 |
--------------------------------------------------------------------------------
/routes/loginRouter.js:
--------------------------------------------------------------------------------
1 | let express = require('express')
2 |
3 | let loginRouter = express.Router()
4 | let bodyParser = require('body-parser')
5 | let sha256 = require('js-sha3').sha3_256
6 | let MonDB = require('../DB/MongoTransactions.js')
7 |
8 | loginRouter.use(bodyParser.urlencoded({ extended: false }))
9 | loginRouter.use(bodyParser.json())
10 |
11 | loginRouter.post('/',(req,res) => {
12 | // 받은 Auth 데이터를 몽고DB에 쿼리
13 | // verification되면 세션에 등록
14 |
15 | let POSTED_EMAIL = req.body.EMAIL
16 | let POSTED_PW = sha256(req.body.PW)
17 | let REMEMBER = req.body.REMEMBER
18 |
19 | console.log(`${POSTED_EMAIL}와 ${POSTED_PW} 와 ${REMEMBER}`)
20 |
21 | let DB_Machine = new MonDB()
22 |
23 | DB_Machine.FORMAL_USER_AUTHENTICATION(POSTED_EMAIL,POSTED_PW).then((response)=>{
24 |
25 | // 로그인이 성공된 순간.
26 |
27 | console.log(req.session.EAMIL)
28 |
29 | if(req.session.EMAIL === undefined){
30 | req.session.EMAIL = response.EMAIL
31 | response.SID = req.sessionID
32 |
33 | }
34 |
35 |
36 | res.json(response)
37 |
38 |
39 | }).catch((response)=>{
40 |
41 | res.json(response)
42 |
43 | })
44 |
45 | })
46 |
47 | loginRouter.post('/getSessionData',(req,res) => {
48 |
49 | // 서버상의 세션아이디와 클라이언트의 쿠키에 저장된 세션아이디가 동일하다면
50 | // DB_Machine을 통해서 유저 정보를 재전송 해준다.
51 |
52 | const POSTED_SID = req.body.SID
53 |
54 | console.log(`SID : ${POSTED_SID}`)
55 | console.log(`req.sessionID : ${JSON.stringify(req.sessionID)}`)
56 |
57 | if(req.sessionID === POSTED_SID){
58 |
59 | let DB_Machine = new MonDB()
60 | const SESSION_EMAIL = req.session.EMAIL
61 | DB_Machine.GET_USER_DATA_FROM_FORMALUSERS(SESSION_EMAIL).then((response) => {
62 |
63 | res.json(response)
64 |
65 | }).catch((response) => {
66 |
67 | res.json(response)
68 |
69 | })
70 |
71 | }
72 |
73 | })
74 |
75 | module.exports = loginRouter
76 |
77 |
--------------------------------------------------------------------------------
/routes/mailer.js:
--------------------------------------------------------------------------------
1 | let nodemailer = require('nodemailer')
2 | let secret = require('../DB/secret.js')
3 |
4 | module.exports = function VerifyMailProcess(to,timeid) {
5 | nodemailer.createTestAccount((err, account) => {
6 | if (err) {
7 | console.error('Failed to create a testing account. ' + err.message);
8 | return process.exit(1);
9 | }
10 |
11 | console.log('Credentials obtained, sending message...');
12 |
13 | // Create a SMTP transporter object
14 | let transporter = nodemailer.createTransport({
15 | host: secret.mailerConfig.host,
16 | port: secret.mailerConfig.port,
17 | secure: secret.mailerConfig.secure,
18 | auth: {
19 | user: secret.mailerConfig.user,
20 | pass: secret.mailerConfig.password
21 | }
22 | });
23 |
24 | // Message object
25 | let message = {
26 | from: `ReactBoard `,
27 | to: to,
28 | subject: '게시판 가입인증 메일입니다.',
29 | html:
30 | `
31 |
32 |
35 |
36 |
37 | 해당 링크를 클릭하여 가입을 완료하여 주십시오.
38 |
39 |
40 |
41 | `
42 | };
43 |
44 | transporter.sendMail(message, (err, info) => {
45 | if (err) {
46 | console.log('Error occurred. ' + err.message);
47 | return process.exit(1);
48 | }
49 |
50 | console.log('Message sent: %s', info.messageId);
51 | // Preview only available when sending through an Ethereal account
52 | console.log('Preview URL: %s', nodemailer.getTestMessageUrl(info));
53 | });
54 |
55 | });
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/routes/readRouter.js:
--------------------------------------------------------------------------------
1 | let express = require("express");
2 |
3 | let readRouter = express.Router();
4 | let MonDB = require("../DB/MongoTransactions.js");
5 | let bodyParser = require("body-parser");
6 | let path = require("path");
7 | let fs = require("fs");
8 |
9 | readRouter.use(bodyParser.urlencoded({ extended: false }));
10 | readRouter.use(bodyParser.json());
11 | readRouter.use(bodyParser.text());
12 |
13 | readRouter.get("/", (req, res) => {
14 | let skip = parseInt(req.query.skip);
15 | let limit = parseInt(req.query.limit);
16 | console.log(skip, limit);
17 | let DB_Machine = new MonDB();
18 | DB_Machine.GET_READABLE_DOCS(skip, limit).then(response => {
19 | if (response.status === 1) {
20 | return res.json(response).end();
21 | }
22 | });
23 | });
24 |
25 | readRouter.get("/getContentById", (req, res) => {
26 | let DB_Machine = new MonDB();
27 | DB_Machine.GET_CONTENT_BY_ID(req.query._id).then(response => {
28 | if (response.status === 1) {
29 | return res.json(response).end();
30 | }
31 | });
32 | });
33 |
34 | readRouter.post("/postReply", (req, res) => {
35 | let DB_Machine = new MonDB();
36 | let doc = {
37 | REPLY_AUTHOR: req.body.REPLY_AUTHOR,
38 | REPLY_AUTHOR_IMAGE: req.body.REPLY_AUTHOR_IMAGE,
39 | REPLY_AUTHOR_EMAIL: req.body.REPLY_AUTHOR_EMAIL,
40 | REPLY_CONTENT: req.body.REPLY_CONTENT,
41 | REPLY_DATE: parseInt(req.body.REPLY_DATE)
42 | };
43 | DB_Machine.POST_REPLY_BY_ID(req.body._id, doc).then(response => {
44 | if (response.status === 1) {
45 | return res.json(response).end();
46 | }
47 | });
48 | });
49 |
50 | readRouter.post("/deleteReply", (req, res) => {
51 | let DB_Machine = new MonDB();
52 | DB_Machine.DELETE_REPLY_BY_ID(
53 | req.body.targetArticleId,
54 | req.body.targetReplyId
55 | ).then(response => {
56 | if (response.status === 1) {
57 | return res.json(response).end();
58 | }
59 | });
60 | });
61 |
62 | readRouter.post("/recommendUp", (req, res) => {
63 | let DB_Machine = new MonDB();
64 | console.log(req.body);
65 | DB_Machine.RECOMMEND_UP_BY_EMAIL(
66 | req.body.targetArticleId,
67 | req.body.EMAIL
68 | ).then(response => {
69 | if (response.status === 1) {
70 | return res.json(response).end();
71 | }
72 | });
73 | });
74 |
75 | readRouter.put("/postEyeUp", (req, res) => {
76 | let DB_Machine = new MonDB();
77 | DB_Machine.POST_EYE_UP_BY_ID(req.body.targetArticleId).then(response => {
78 | if (response.status === 1) {
79 | return res.json(response).end();
80 | }
81 | });
82 | });
83 |
84 | module.exports = readRouter;
85 |
--------------------------------------------------------------------------------
/routes/registerRouter.js:
--------------------------------------------------------------------------------
1 | let express = require('express')
2 | let registerRouter = express.Router()
3 | let bodyParser = require('body-parser')
4 | let fs = require('fs')
5 | let formidable = require('formidable')
6 | let sha256 = require('js-sha3').sha3_256
7 | let MonDB = require('../DB/MongoTransactions.js')
8 | let secret = require('../DB/secret')
9 | let mailer = require('../routes/mailer.js')
10 |
11 | registerRouter.use(bodyParser.urlencoded({ extended: false }))
12 | registerRouter.use(bodyParser.json())
13 | registerRouter.use(bodyParser.text())
14 |
15 | registerRouter.post('/nonformalRegisterFile',(req,res)=>{
16 |
17 | console.log(__dirname)
18 |
19 | if(!fs.existsSync('tmp/')){
20 | fs.mkdirSync('tmp')
21 | }
22 | if(!fs.existsSync('./public/UserImages/')){
23 | fs.mkdirSync('./public/UserImages/')
24 | }
25 | let form = new formidable.IncomingForm()
26 | let unique = new Date().getTime()
27 | let tmpPath = `./tmp/${unique}`
28 |
29 | process.chdir('./tmp')
30 | fs.mkdirSync(`./${unique}`)
31 | process.chdir('../')
32 |
33 | form.encoding = 'utf-8'
34 | form.type = true
35 | form.uploadDir = tmpPath
36 |
37 | form.parse(req,(err,field,file)=>{
38 |
39 | if(err){throw err}
40 |
41 | let oldPath = file.filepond.path
42 | let newPath ='./public/UserImages/'
43 |
44 | fs.rename(oldPath,`${newPath}${unique}.${getExt(file.filepond.type)}`,(err)=>{
45 | if(err){throw err}
46 |
47 | fs.rmdir(`./tmp/${unique}`,(err)=>{
48 | if(err){throw err}
49 | })
50 | })
51 |
52 |
53 | res.end(unique.toString())
54 | })
55 |
56 | })
57 |
58 | registerRouter.delete('/nonformalRegisterFile',(req)=> {
59 |
60 | let uniqueID = req.body
61 |
62 | fs.readdir('./public/UserImages',(err,files) => {
63 |
64 | if(err){throw err}
65 |
66 | files.forEach((el) => {
67 |
68 | let nameonly = el.split('.')
69 |
70 | if(nameonly[0] === uniqueID){
71 |
72 | fs.unlink(`./public/UserImages/${el}`,(err)=>{
73 | if(err){throw err}
74 | })
75 |
76 | }
77 | })
78 | })
79 |
80 | })
81 |
82 | registerRouter.post('/nonformalRegisterSubmit',(req,res) => {
83 |
84 | let NF_USER_EMAIL = req.body.EMAIL
85 | let NF_USER_USERNAME = req.body.USERNAME
86 | let NF_FILE_NAME = req.body.FILENAME
87 | let NF_USER_PW = req.body.PW
88 | let NF_TIME_ID = NF_FILE_NAME.substr(0,NF_FILE_NAME.length-4)
89 | let createdAt = new Date()
90 |
91 | let DB_Machine = new MonDB();
92 |
93 | DB_Machine.INSERT_NONFORMAL({
94 | TIMEID : NF_TIME_ID,
95 | EMAIL : NF_USER_EMAIL,
96 | USERNAME : NF_USER_USERNAME,
97 | PW : sha256(NF_USER_PW),
98 | U_IMG_PATH : `/UserImages/${NF_FILE_NAME}`,
99 | createdAt : createdAt
100 | },secret.MongoCollections.nonformalUsers).then((response)=>{
101 | res.json(response)
102 | mailer(NF_USER_EMAIL,NF_TIME_ID)
103 | }).catch((response)=>{
104 | res.json(response)
105 | })
106 |
107 | })
108 |
109 | registerRouter.get('/verifyEmail',(req,res)=>{
110 |
111 | // 앱 초기화면으로 이동.
112 | // nonformalUsers 컬렉션 다큐먼트를 formalUsers 컬렉션으로 이동.
113 | let TIMEID = req.query.timeid
114 |
115 | let DB_Machine = new MonDB()
116 | DB_Machine.MOVE_NONFORMAL_TO_FORMAL(TIMEID,secret.MongoCollections.nonformalUsers,secret.MongoCollections.formalUsers).then(()=>{
117 | console.log("이메일 인증에 성공.")
118 | res.redirect('http://localhost:9000/')
119 | }).catch((res)=>{
120 | console.log(res.mesg)
121 | })
122 |
123 | })
124 |
125 | const getExt = (mime) => {
126 | let text = mime.split('/')
127 | return text[1]
128 | }
129 |
130 |
131 | module.exports = registerRouter
--------------------------------------------------------------------------------
/routes/test.js:
--------------------------------------------------------------------------------
1 | let path = require('path')
2 |
3 |
4 |
5 | process.chdir('../')
6 | console.log(path.join(__dirname,'public/','SharedImages/'))
--------------------------------------------------------------------------------
/routes/writeRouter.js:
--------------------------------------------------------------------------------
1 | let express = require('express')
2 |
3 | let writeRouter = express.Router()
4 | let bodyParser = require('body-parser')
5 | let MonDB = require('../DB/MongoTransactions.js')
6 | let formidable = require('formidable')
7 | let path = require('path')
8 | let fs = require('fs')
9 |
10 | writeRouter.use(bodyParser.urlencoded({ extended: false }))
11 | writeRouter.use(bodyParser.json())
12 | writeRouter.use(bodyParser.text())
13 |
14 | writeRouter.post('/imageUpload',(req,res) => {
15 |
16 | const uploadPath = path.resolve(process.cwd(),'public/','SharedImages/')
17 |
18 | if(!fs.existsSync(uploadPath)){
19 | fs.mkdirSync(uploadPath)
20 | }
21 |
22 | let form = new formidable.IncomingForm()
23 | form.uploadDir = uploadPath
24 | form.encoding = 'utf-8'
25 | form.multiples = true
26 | form.type = true
27 | form.keepExtensions = true
28 |
29 | form.parse(req)
30 |
31 | GET_FILE_RENAMED(form,res)
32 |
33 | })
34 |
35 | writeRouter.post('/writeComplete',(req,res)=>{
36 |
37 | let doc = {
38 | POST_TITLE : req.body.POST_TITLE,
39 | POST_CONTENT : req.body.POST_CONTENT,
40 | POST_THUMBNAIL : req.body.POST_THUMBNAIL,
41 | POST_REPLY : [],
42 | AUTHOR : req.body.AUTHOR,
43 | U_IMG_PATH : req.body.U_IMG_PATH,
44 | EMAIL : req.body.EMAIL,
45 | POST_DATE : req.body.POST_DATE,
46 | RECOMMEND : [],
47 | EYE : 0
48 | }
49 |
50 | let DB_Machine = new MonDB();
51 |
52 | DB_Machine.SAVE_USER_SHARED_POSTING(doc).then((response)=>{
53 | if(response.status === 1){
54 | res.json(response)
55 | res.end()
56 | }
57 | })
58 |
59 | })
60 |
61 | writeRouter.post('/tempDocSave',(req,res)=>{
62 |
63 | // let uniqDocName = `TempDoc_${new Date().getTime()}`
64 |
65 | let doc = {
66 | TEMP_SAVE_TITLE : req.body.TEMP_SAVE_TITLE,
67 | TEMP_SAVE_CONTENT : req.body.TEMP_SAVE_CONTENT,
68 | TEMP_SAVE_DATE : new Date().getTime(),
69 | AUTHOR : req.body.EMAIL
70 | }
71 |
72 | let DB_Machine = new MonDB()
73 |
74 | DB_Machine.SAVE_USER_TEMP_DOCUMENT(doc).then((response) => {
75 | if(response.status === 1){
76 | res.json(response)
77 | res.end()
78 | }
79 | })
80 |
81 | })
82 |
83 | writeRouter.get('/getUserTempDocs',(req,res) => {
84 | // console.log(req.query.user)
85 |
86 | let DB_Machine = new MonDB()
87 |
88 | DB_Machine.GET_USER_TEMP_DOCUMENT_WITHOUT_CONTENT(req.query.user).then((response) => {
89 | if(response.status === 1){
90 | console.log(response)
91 | res.json(response)
92 | res.end()
93 | }
94 | })
95 |
96 | })
97 |
98 | writeRouter.get('/getTempDocContent',(req,res) => {
99 |
100 | let DB_Machine = new MonDB()
101 | console.log(req.query._id)
102 | DB_Machine.GET_TEMP_DOC_CONTENT_BY_ID(req.query._id).then((response) => {
103 | if(response.status === 1){
104 | console.log(response)
105 | res.json(response)
106 | res.end()
107 | }
108 | })
109 |
110 | })
111 |
112 | async function GET_FILE_RENAMED(form,res){
113 |
114 | let fileRenamed = await RENAME_FILE(form)
115 |
116 | res.json({
117 | loc : fileRenamed
118 | })
119 | res.end()
120 |
121 | }
122 |
123 | function RENAME_FILE(form){
124 |
125 | return new Promise((resolve) => {
126 |
127 | form.on('file',(name,file)=>{
128 |
129 | // console.log(file.path)
130 | // console.log(file.name)
131 | // console.log(form.uploadDir)
132 | // console.log(file.lastModifiedDate)
133 |
134 | const newFileName = `React-Board_${new Date(file.lastModifiedDate).toJSON().substr(0,10).replace(/-/g,"")}_${new Date().getTime()}.${file.type.split("/")[1]}`
135 | const newPath = `${form.uploadDir}/${newFileName}`
136 |
137 | fs.rename(file.path,newPath,(err)=>{
138 | if(err){throw err}
139 | resolve(newFileName)
140 | })
141 |
142 | })
143 |
144 | })
145 |
146 | }
147 |
148 | module.exports = writeRouter
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HamSungJun/React-Board/9706b77d5545a41f69c14913122c816eeebed34f/test.js
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | let webpack = require('webpack');
2 | let path = require('path');
3 | let HtmlWebpackPlugin = require('html-webpack-plugin')
4 |
5 | module.exports = {
6 | entry : path.join(__dirname,"/Components/src/Root.jsx"),
7 | output : {
8 |
9 | filename : "bundle.js",
10 | path : path.join(__dirname,"public","dist"),
11 |
12 | },
13 | devServer: {
14 | hot : true,
15 | contentBase: [
16 | path.join(__dirname, "public",'dist'),
17 | path.join(__dirname, 'Components')],
18 | watchContentBase: true,
19 | historyApiFallback: true,
20 | disableHostCheck: true,
21 | host: `0.0.0.0`,
22 | compress: true,
23 | port: 9000
24 | },
25 | mode : "development",
26 | module : {
27 | rules : [
28 | {
29 | test : /\.(css|scss|sass)$/,
30 | exclude : /node_modules/,
31 | use : [
32 | {loader : 'style-loader'},
33 | {loader : 'css-loader'},
34 | {loader : 'sass-loader'},
35 | ]
36 | },
37 | {
38 | test : /\.(js|jsx)$/,
39 | exclude: /node_modules/,
40 | use : 'babel-loader'
41 | },
42 | {
43 | test : /\.(woff|jpe?g|png|gif|svg)$/,
44 | exclude: /node_modules/,
45 | use: [{
46 | loader: 'file-loader',
47 | options: {
48 | name: '[name].[ext]',
49 | outputPath: 'assets/'
50 | }
51 | }]
52 | }
53 | ]
54 | },
55 | plugins : [
56 | new webpack.ProgressPlugin(),
57 | new webpack.NamedModulesPlugin(),
58 | new webpack.HotModuleReplacementPlugin(),
59 | ],
60 | target : "web"
61 | }
--------------------------------------------------------------------------------