Skip to content

Commit 94fe189

Browse files
authored
Merge pull request velopert#349 from velopert/feature/cloudflare
Feature/cloudflare
2 parents ebf0840 + 5241386 commit 94fe189

File tree

9 files changed

+238
-38
lines changed

9 files changed

+238
-38
lines changed

.env

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
REACT_APP_API_HOST=http://localhost:5000/
2-
PUBLIC_URL=/
2+
PUBLIC_URL=/
3+
REACT_APP_GRAPHQL_HOST=https://v2cdn.velog.io/
4+
REACT_APP_GRAPHQL_HOST_NOCDN=https://v2.velog.io/

src/components/setting/SettingUserProfile.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type SettingUserProfileProps = {
1717
thumbnail: string | null;
1818
displayName: string;
1919
shortBio: string;
20+
loading: boolean;
2021
};
2122

2223
function SettingUserProfile({
@@ -26,6 +27,7 @@ function SettingUserProfile({
2627
thumbnail,
2728
displayName,
2829
shortBio,
30+
loading,
2931
}: SettingUserProfileProps) {
3032
const [edit, onToggleEdit] = useToggle(false);
3133
const [inputs, onChange] = useInputs({
@@ -45,7 +47,9 @@ function SettingUserProfile({
4547
src={optimizeImage(thumbnail || userThumbnail, 400)}
4648
alt="profile"
4749
/>
48-
<Button onClick={onUpload}>이미지 업로드</Button>
50+
<Button onClick={onUpload} disabled={loading}>
51+
{loading ? '업로드중...' : '이미지 업로드'}
52+
</Button>
4953
<Button color="transparent" onClick={onClearThumbnail}>
5054
이미지 제거
5155
</Button>

src/components/write/WriteMarkdownEditor.tsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface MarkdownEditorProps {
3434
lastUploadedImage: string | null;
3535
initialBody: string;
3636
theme: 'light' | 'dark';
37+
tempBlobImage: string | null;
3738
}
3839

3940
type MarkdownEditorState = {
@@ -239,7 +240,29 @@ export default class WriteMarkdownEditor extends React.Component<
239240
return;
240241
}
241242
if (!this.codemirror) return;
242-
this.codemirror.getDoc().replaceSelection(`![](${encodeURI(image)})`);
243+
const lines = this.codemirror.getValue().split('\n');
244+
const lineIndex = lines.findIndex((l) => l.includes('![업로드중..]'));
245+
if (lineIndex === -1) return;
246+
247+
const startCh = lines[lineIndex].indexOf('![업로드중..]');
248+
this.codemirror
249+
.getDoc()
250+
.replaceRange(
251+
`![](${encodeURI(image)})`,
252+
{ line: lineIndex, ch: startCh },
253+
{ line: lineIndex, ch: lines[lineIndex].length },
254+
);
255+
// this.codemirror.getDoc().replaceSelection(`![](${encodeURI(image)})`);
256+
};
257+
258+
addTempImageBlobToEditor = (blobUrl: string) => {
259+
const imageMarkdown = `![업로드중..](${blobUrl})\n`;
260+
261+
if (this.isIOS) {
262+
return;
263+
}
264+
if (!this.codemirror) return;
265+
this.codemirror.getDoc().replaceSelection(imageMarkdown);
243266
};
244267

245268
componentDidUpdate(prevProps: MarkdownEditorProps) {
@@ -255,6 +278,13 @@ export default class WriteMarkdownEditor extends React.Component<
255278
) {
256279
this.addImageToEditor(lastUploadedImage);
257280
}
281+
282+
if (
283+
this.props.tempBlobImage &&
284+
this.props.tempBlobImage !== prevProps.tempBlobImage
285+
) {
286+
this.addTempImageBlobToEditor(this.props.tempBlobImage);
287+
}
258288
}
259289

260290
componentDidMount() {

src/containers/setting/SettingUserProfileContainer.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import SettingUserProfile from '../../components/setting/SettingUserProfile';
33
import useUpload from '../../lib/hooks/useUpload';
4-
import useS3Upload from '../../lib/hooks/useS3Upload';
54
import useUserProfile from './hooks/useUserProfile';
65
import useUpdateThumbnail from './hooks/useUpdateThumbnail';
76
import useUser from '../../lib/hooks/useUser';
87
import RequireLogin from '../../components/common/RequireLogin';
8+
import { useCFUpload } from '../../lib/hooks/useCFUpload';
99

1010
export type SettingUserProfileContainerProps = {};
1111

1212
function SettingUserProfileContainer(props: SettingUserProfileContainerProps) {
1313
const user = useUser();
14-
const { profile, update } = useUserProfile();
14+
const { profile, update } = useUserProfile();
1515
const [upload] = useUpload();
16-
const [s3Upload] = useS3Upload();
16+
const { upload: cfUpload } = useCFUpload();
1717
const updateThumbnail = useUpdateThumbnail();
18+
const [imageBlobUrl, setImageBlobUrl] = React.useState<string | null>(null);
19+
const [loading, setLoading] = useState(false);
1820

1921
const uploadThumbnail = async () => {
2022
const file = await upload();
2123
if (!file) return;
22-
const image = await s3Upload(file, { type: 'profile' });
24+
setLoading(true);
25+
setImageBlobUrl(URL.createObjectURL(file));
26+
const image = await cfUpload(file, { type: 'profile' });
27+
setLoading(false);
2328
if (!image) return;
2429
updateThumbnail(image);
2530
};
@@ -45,7 +50,8 @@ function SettingUserProfileContainer(props: SettingUserProfileContainerProps) {
4550
onClearThumbnail={clearThumbnail}
4651
displayName={profile.display_name}
4752
shortBio={profile.short_bio}
48-
thumbnail={profile.thumbnail}
53+
thumbnail={imageBlobUrl || profile.thumbnail}
54+
loading={loading}
4955
/>
5056
);
5157
}

src/containers/write/MarkdownEditorContainer.tsx

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useState } from 'react';
1+
import React, { useMemo, useRef, useState } from 'react';
22
import MarkdownEditor from '../../components/write/WriteMarkdownEditor';
33
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
44
import { RootState } from '../../modules';
@@ -21,7 +21,6 @@ import strip from 'strip-markdown';
2121
import TagInputContainer from './TagInputContainer';
2222
import WriteFooter from '../../components/write/WriteFooter';
2323
import useUpload from '../../lib/hooks/useUpload';
24-
import useS3Upload from '../../lib/hooks/useS3Upload';
2524
import DragDropUpload from '../../components/common/DragDropUpload';
2625
import PasteUpload from '../../components/common/PasteUpload';
2726
import { bindActionCreators } from 'redux';
@@ -43,6 +42,7 @@ import { toast } from 'react-toastify';
4342
import { usePrevious } from 'react-use';
4443
import { useTheme } from '../../lib/hooks/useTheme';
4544
import { useUncachedApolloClient } from '../../lib/graphql/UncachedApolloContext';
45+
import { useCFUpload } from '../../lib/hooks/useCFUpload';
4646

4747
export type MarkdownEditorContainerProps = {};
4848

@@ -64,6 +64,9 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
6464
const [writePost] = useMutation<WritePostResponse>(WRITE_POST, {
6565
client: uncachedClient,
6666
});
67+
68+
const bodyRef = useRef(initialBody);
69+
const titleRef = useRef(title);
6770
const [createPostHistory] =
6871
useMutation<CreatePostHistoryResponse>(CREATE_POST_HISTORY);
6972
const [editPost] = useMutation<EditPostResult>(EDIT_POST, {
@@ -132,31 +135,17 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
132135
}, [actionCreators, markdown]);
133136

134137
const [upload, file] = useUpload();
135-
const [s3Upload, image] = useS3Upload();
136-
const prevImage = usePrevious(image);
137138

138-
useEffect(() => {
139-
if (!file) return;
140-
s3Upload(file, {
141-
type: 'post',
142-
});
143-
}, [file, s3Upload]);
139+
const { upload: cfUpload, image } = useCFUpload();
140+
const prevImage = usePrevious(image);
141+
const [imageBlobUrl, setImageBlobUrl] = useState<string | null>(null);
144142

145143
useEffect(() => {
146144
if (image !== prevImage && !thumbnail && image) {
147145
actionCreators.setThumbnail(image);
148146
}
149147
}, [actionCreators, image, prevImage, thumbnail]);
150148

151-
const onDragDropUpload = useCallback(
152-
(file: File) => {
153-
s3Upload(file, {
154-
type: 'post',
155-
});
156-
},
157-
[s3Upload],
158-
);
159-
160149
const onTempSave = useCallback(
161150
async (notify?: boolean) => {
162151
if (!title || !markdown) {
@@ -189,7 +178,6 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
189178
dispatch(setWritePostId(id));
190179
history.replace(`/write?id=${id}`);
191180
notifySuccess();
192-
return;
193181
}
194182
// tempsaving unreleased post:
195183
if (isTemp) {
@@ -246,6 +234,57 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
246234
],
247235
);
248236

237+
const uploadWithPostId = useCallback(
238+
async (file: File) => {
239+
if (!file) return;
240+
let id = postId;
241+
if (!id) {
242+
const title = titleRef.current || 'Temp Title';
243+
const body = bodyRef.current || 'Temp Body';
244+
245+
const response = await writePost({
246+
variables: {
247+
title,
248+
body,
249+
tags: [],
250+
is_markdown: true,
251+
is_temp: true,
252+
is_private: false,
253+
url_slug: escapeForUrl(title),
254+
thumbnail: null,
255+
meta: {},
256+
series_id: null,
257+
},
258+
});
259+
if (!response || !response.data) return;
260+
id = response.data.writePost.id;
261+
dispatch(setWritePostId(id));
262+
history.replace(`/write?id=${id}`);
263+
}
264+
if (!id) return;
265+
266+
const url = URL.createObjectURL(file);
267+
setImageBlobUrl(url);
268+
269+
cfUpload(file, { type: 'post', refId: id }).catch((e) => {
270+
toast.error('이미지 업로드 실패! 잠시 후 다시 시도하세요.');
271+
});
272+
},
273+
[postId, cfUpload, writePost, dispatch, history],
274+
);
275+
276+
const onDragDropUpload = useCallback(
277+
(file: File) => {
278+
uploadWithPostId(file);
279+
},
280+
[uploadWithPostId],
281+
);
282+
283+
useEffect(() => {
284+
if (!file) return;
285+
uploadWithPostId(file);
286+
}, [file, uploadWithPostId]);
287+
249288
useEffect(() => {
250289
const changed = !shallowEqual(lastSavedData, { title, body: markdown });
251290
if (changed) {
@@ -267,6 +306,11 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
267306
);
268307
const theme = useTheme();
269308

309+
useEffect(() => {
310+
bodyRef.current = markdown;
311+
titleRef.current = title;
312+
}, [markdown, title]);
313+
270314
if (!isReady) return null;
271315

272316
return (
@@ -276,6 +320,7 @@ const MarkdownEditorContainer: React.FC<MarkdownEditorContainerProps> = () => {
276320
</Helmet>
277321
<MarkdownEditor
278322
onUpload={upload}
323+
tempBlobImage={imageBlobUrl}
279324
lastUploadedImage={image}
280325
initialBody={initialBody}
281326
title={title}

src/containers/write/PublishPreviewContainer.tsx

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import React, { useEffect, useCallback, useRef } from 'react';
2-
import { connect } from 'react-redux';
2+
import { connect, useDispatch, useSelector } from 'react-redux';
33
import { RootState } from '../../modules';
44
import PublishPreview from '../../components/write/PublishPreview';
5-
import { changeDescription, setThumbnail } from '../../modules/write';
5+
import {
6+
changeDescription,
7+
setThumbnail,
8+
setWritePostId,
9+
} from '../../modules/write';
610
import useUpload from '../../lib/hooks/useUpload';
7-
import useS3Upload from '../../lib/hooks/useS3Upload';
11+
import { useCFUpload } from '../../lib/hooks/useCFUpload';
12+
import { useMutation } from '@apollo/react-hooks';
13+
import { WritePostResponse, WRITE_POST } from '../../lib/graphql/post';
14+
import { useUncachedApolloClient } from '../../lib/graphql/UncachedApolloContext';
15+
import { escapeForUrl } from '../../lib/utils';
816

917
const mapStateToProps = (state: RootState) => ({
1018
title: state.write.title,
@@ -38,9 +46,16 @@ const PublishPreviewContainer: React.FC<PublishPreviewContainerProps> = ({
3846
(description: string) => changeDescription(description),
3947
[changeDescription],
4048
);
41-
49+
const uncachedClient = useUncachedApolloClient();
50+
const [writePost] = useMutation<WritePostResponse>(WRITE_POST, {
51+
client: uncachedClient,
52+
});
4253
const [upload, file] = useUpload();
43-
const [s3Upload, image] = useS3Upload();
54+
const { upload: cfUpload, image } = useCFUpload();
55+
56+
const { markdown, postId, tags } = useSelector(
57+
(state: RootState) => state.write,
58+
);
4459

4560
// fills description with defaultDescription when it is empty
4661
useEffect(() => {
@@ -57,12 +72,48 @@ const PublishPreviewContainer: React.FC<PublishPreviewContainerProps> = ({
5772
upload();
5873
};
5974

75+
const dispatch = useDispatch();
76+
77+
const getValidPostId = useCallback(async () => {
78+
if (postId) return postId;
79+
const validTitle = title || 'Temp Title';
80+
const validBody = markdown || 'Temp Body';
81+
82+
const response = await writePost({
83+
variables: {
84+
title: validTitle,
85+
body: validBody,
86+
tags,
87+
is_markdown: true,
88+
is_temp: true,
89+
is_private: false,
90+
url_slug: escapeForUrl(validTitle),
91+
thumbnail: null,
92+
meta: {},
93+
series_id: null,
94+
},
95+
});
96+
97+
if (!response || !response.data) return null;
98+
const id = response.data.writePost.id;
99+
dispatch(setWritePostId(id));
100+
return id;
101+
}, [postId, writePost, markdown, tags, title, dispatch]);
102+
103+
const uploadWithPostId = useCallback(
104+
async (file: File) => {
105+
const id = await getValidPostId();
106+
if (!id) return;
107+
cfUpload(file, { type: 'post', refId: id });
108+
},
109+
[cfUpload, getValidPostId],
110+
);
111+
60112
useEffect(() => {
61113
if (!file) return;
62-
s3Upload(file, {
63-
type: 'post',
64-
});
65-
}, [file, s3Upload]);
114+
uploadWithPostId(file);
115+
// eslint-disable-next-line react-hooks/exhaustive-deps
116+
}, [file]);
66117

67118
useEffect(() => {
68119
if (!image) return;

0 commit comments

Comments
 (0)