Skip to content

Commit eeaeb9f

Browse files
committed
Implements convert to markdown button
1 parent 3bb4dd9 commit eeaeb9f

File tree

8 files changed

+134
-48
lines changed

8 files changed

+134
-48
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@types/redux-mock-store": "^1.0.0",
3535
"@types/snakecase-keys": "^2.1.0",
3636
"@types/styled-components": "^4.1.8",
37+
"@types/turndown": "^5.0.0",
3738
"apollo-boost": "^0.1.28",
3839
"axios": "^0.18.0",
3940
"babel-core": "7.0.0-bridge.0",

Diff for: samples/mdConvertSample.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@ const TurndownService = require('turndown');
66
// }
77
// </pre><p><br></p><p>asdfasdf</p><p><br></p><blockquote>asdfasdf</blockquote><blockquote><br></blockquote><blockquote>asdf</blockquote><blockquote><br></blockquote><blockquote>asdf</blockquote><blockquote>asdfasfasdfasd</blockquote><blockquote>fasfasdfasd</blockquote><blockquote>fasdfasdf</blockquote><p><br></p><p>이정도면 <a href="https://google.com/" target="_blank">있는거</a> 다 사용한거죠? </p><p><br></p><p><br></p><p><br></p></div><div class="ql-clipboard" contenteditable="true" tabindex="-1"></div>`;
88

9-
let html = `<div class="ql-editor" data-gramm="false" contenteditable="true" data-placeholder="당신의 이야기를 적어보세요..."><pre class="ql-syntax" spellcheck="false"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">a</span>() </span>{
10-
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'hello world!'</span>);
11-
}
12-
</pre></div><div class="ql-clipboard" contenteditable="true" tabindex="-1"></div>`;
9+
let html = `<del>Hi there</del>`;
1310
const replaceMultiBlockquote = text =>
1411
text.replace(/<\/blockquote><blockquote>/g, '<br/>'); // credits to Hyeseong kim
1512

1613
html = replaceMultiBlockquote(html);
1714
const turndownService = new TurndownService();
1815

19-
turndownService.keep('del');
16+
turndownService.addRule('linethrough', {
17+
filter: ['del'],
18+
replacement: content => `~${content}~`,
19+
});
2020

2121
turndownService.addRule('codeblock', {
2222
filter: ['pre'],
2323
replacement: content => {
2424
return `\`\`\`
2525
${content}
26-
\`\`\``;
26+
\`\`\`(${content})`;
2727
},
2828
});
2929

@@ -49,7 +49,7 @@ Array.from({ length: 8 }).forEach((_, i) => {
4949
});
5050
});
5151

52-
turndownService.addRule('listItem', {
52+
turndownService.addRule('ordered listItem', {
5353
filter: el => {
5454
return el.tagName === 'LI' && el.parentElement.tagName === 'OL';
5555
},

Diff for: src/components/write/MarkdownEditor.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,6 @@ ${selected}
534534
/>
535535
</PaddingWrapper>
536536
<Toolbar
537-
visible
538537
shadow={shadow}
539538
mode="MARKDOWN"
540539
onClick={this.handleToolbarClick}

Diff for: src/components/write/FullPageEditor.tsx renamed to src/components/write/QuillEditor.tsx

+46-14
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import AddLink from './AddLink';
1212
import postStyles from '../../lib/styles/postStyles';
1313
import TitleTextarea from './TitleTextarea';
1414
import { getScrollTop } from '../../lib/utils';
15+
import convertToMarkdown from '../../lib/convertToMarkdown';
1516

1617
Quill.register('modules/markdownShortcuts', MarkdownShortcuts);
1718

18-
export interface FullPageEditorProps {}
19-
export interface FullPageEditorState {
19+
export interface QuillEditorProps {}
20+
export interface QuillEditorState {
2021
titleFocus: boolean;
2122
editorFocus: boolean;
2223
addLink: boolean;
@@ -28,8 +29,12 @@ export interface FullPageEditorState {
2829
shadow: boolean;
2930
}
3031

31-
const FullPageEditorWrapper = styled.div`
32-
padding-top: 1.5rem;
32+
const StyledTitleTextarea = styled(TitleTextarea)`
33+
margin-bottom: 2rem;
34+
`;
35+
36+
const QuillEditorWrapper = styled.div`
37+
padding-top: 5rem;
3338
position: relative;
3439
/* display: flex;
3540
flex-direction: column;
@@ -46,7 +51,7 @@ const FullPageEditorWrapper = styled.div`
4651
`;
4752

4853
const Editor = styled.div`
49-
margin-top: 2rem;
54+
margin-top: 1rem;
5055
position: relative;
5156
.ql-container {
5257
font-family: inherit;
@@ -112,9 +117,9 @@ const Editor = styled.div`
112117
}
113118
`;
114119

115-
export default class FullPageEditor extends React.Component<
116-
FullPageEditorProps,
117-
FullPageEditorState
120+
export default class QuillEditor extends React.Component<
121+
QuillEditorProps,
122+
QuillEditorState
118123
> {
119124
editor = React.createRef<HTMLDivElement>();
120125
titleTextarea: HTMLTextAreaElement | null = null;
@@ -185,6 +190,22 @@ export default class FullPageEditor extends React.Component<
185190
quill.format('code-block', false);
186191
},
187192
},
193+
removeQuote: {
194+
key: 'enter',
195+
empty: true,
196+
format: ['blockquote'],
197+
handler: (range: RangeStatic, context: any) => {
198+
quill.format('blockquote', false);
199+
},
200+
},
201+
removeQuoteWithBackspace: {
202+
key: 'backspace',
203+
empty: true,
204+
format: ['blockquote'],
205+
handler: (range: RangeStatic, context: any) => {
206+
quill.format('blockquote', false);
207+
},
208+
},
188209
};
189210

190211
const quill = new Quill(this.editor.current as Element, {
@@ -328,7 +349,7 @@ export default class FullPageEditor extends React.Component<
328349

329350
// blocks [Enter] key
330351
handleTitleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
331-
if (e.keyCode === 13) {
352+
if ([9, 13].includes(e.keyCode)) {
332353
e.preventDefault();
333354
if (this.quill) {
334355
this.quill.focus();
@@ -354,6 +375,12 @@ export default class FullPageEditor extends React.Component<
354375
this.setState({ addLink: false });
355376
};
356377

378+
handleConvertToMarkdown = () => {
379+
if (!this.quill) return;
380+
const html = this.quill.root.innerHTML;
381+
console.log(convertToMarkdown(html));
382+
};
383+
357384
public render() {
358385
const {
359386
addLink,
@@ -363,19 +390,24 @@ export default class FullPageEditor extends React.Component<
363390
shadow,
364391
} = this.state;
365392
return (
366-
<FullPageEditorWrapper>
367-
<Toolbar visible={!titleFocus} shadow={shadow} mode="WYSIWYG" />
368-
<TitleTextarea
393+
<QuillEditorWrapper>
394+
<StyledTitleTextarea
369395
placeholder="제목을 입력하세요"
370396
onKeyDown={this.handleTitleKeyDown}
371397
inputRef={ref => {
372398
this.titleTextarea = ref;
373399
}}
374400
onFocus={this.handleTitleFocus}
375401
onBlur={this.handleTitleBlur}
402+
tabIndex={1}
403+
/>
404+
<Toolbar
405+
shadow={shadow}
406+
mode="WYSIWYG"
407+
onConvertToMarkdown={this.handleConvertToMarkdown}
376408
/>
377409
<Editor>
378-
<div ref={this.editor} />
410+
<div ref={this.editor} tabIndex={2} />
379411
{addLink && (
380412
<AddLink
381413
{...addLinkPosition}
@@ -386,7 +418,7 @@ export default class FullPageEditor extends React.Component<
386418
/>
387419
)}
388420
</Editor>
389-
</FullPageEditorWrapper>
421+
</QuillEditorWrapper>
390422
);
391423
}
392424
}

Diff for: src/components/write/Toolbar.tsx

+20-22
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import {
1212
} from 'react-icons/md';
1313
import palette from '../../lib/styles/palette';
1414
import zIndexes from '../../lib/styles/zIndexes';
15+
import { FaMarkdown } from 'react-icons/fa';
1516

1617
// box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.09);
1718
const ToolbarBlock = styled.div<{
18-
visible: boolean;
1919
shadow: boolean;
2020
forMarkdown: boolean;
2121
}>`
@@ -25,7 +25,7 @@ const ToolbarBlock = styled.div<{
2525
height: 3rem;
2626
display: flex;
2727
align-items: center;
28-
margin-bottom: 3rem;
28+
margin-bottom: 1rem;
2929
position: sticky;
3030
width: 100%;
3131
background: white;
@@ -36,11 +36,6 @@ const ToolbarBlock = styled.div<{
3636
css`
3737
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.09);
3838
`}
39-
${props =>
40-
!props.visible &&
41-
css`
42-
visibility: hidden;
43-
`};
4439
${props =>
4540
props.forMarkdown &&
4641
css`
@@ -52,7 +47,7 @@ const ToolbarBlock = styled.div<{
5247
`}
5348
`;
5449

55-
const TooblarGroup = styled.div`
50+
const ToolbarGroup = styled.div`
5651
display: flex;
5752
height: 100%;
5853
`;
@@ -96,28 +91,23 @@ const Separator = styled.div`
9691
background: ${palette.gray4};
9792
`;
9893
export interface ToolbarProps {
99-
visible: boolean;
10094
shadow: boolean;
10195
mode: 'MARKDOWN' | 'WYSIWYG';
10296
onClick?: Function;
97+
onConvertToMarkdown?: () => void;
10398
}
10499

105100
const { useEffect, useState, useCallback } = React;
106101
const Toolbar: React.SFC<ToolbarProps> = ({
107-
visible,
108102
shadow,
109103
mode,
110104
onClick = () => {},
105+
onConvertToMarkdown,
111106
}) => {
112107
const forMarkdown = mode === 'MARKDOWN';
113108
return (
114-
<ToolbarBlock
115-
visible={visible}
116-
id="toolbar"
117-
shadow={shadow}
118-
forMarkdown={forMarkdown}
119-
>
120-
<TooblarGroup>
109+
<ToolbarBlock id="toolbar" shadow={shadow} forMarkdown={forMarkdown}>
110+
<ToolbarGroup>
121111
<ToolbarItem
122112
className="ql-header"
123113
value={1}
@@ -154,9 +144,9 @@ const Toolbar: React.SFC<ToolbarProps> = ({
154144
H<span>4</span>
155145
</Heading>
156146
</ToolbarItem>
157-
</TooblarGroup>
147+
</ToolbarGroup>
158148
<Separator />
159-
<TooblarGroup>
149+
<ToolbarGroup>
160150
<ToolbarItem className="ql-bold" onClick={() => onClick('bold')}>
161151
<MdFormatBold />
162152
</ToolbarItem>
@@ -171,9 +161,9 @@ const Toolbar: React.SFC<ToolbarProps> = ({
171161
<ToolbarItem className="ql-strike" onClick={() => onClick('strike')}>
172162
<MdFormatStrikethrough />
173163
</ToolbarItem>
174-
</TooblarGroup>
164+
</ToolbarGroup>
175165
<Separator />
176-
<TooblarGroup>
166+
<ToolbarGroup>
177167
<ToolbarItem
178168
className="ql-blockquote"
179169
onClick={() => onClick('blockquote')}
@@ -192,7 +182,15 @@ const Toolbar: React.SFC<ToolbarProps> = ({
192182
>
193183
<MdCode />
194184
</ToolbarItem>
195-
</TooblarGroup>
185+
</ToolbarGroup>
186+
<Separator />
187+
<ToolbarGroup>
188+
{forMarkdown ? null : (
189+
<ToolbarItem onClick={onConvertToMarkdown}>
190+
<FaMarkdown />
191+
</ToolbarItem>
192+
)}
193+
</ToolbarGroup>
196194
</ToolbarBlock>
197195
);
198196
};

Diff for: src/lib/convertToMarkdown.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import TurndownService from 'turndown';
2+
3+
let turndownService: TurndownService | null = null;
4+
5+
export default function convertToMarkdown(html: string): string {
6+
// initialize turndownService
7+
if (!turndownService) {
8+
turndownService = new TurndownService({
9+
headingStyle: 'atx',
10+
});
11+
}
12+
if (!turndownService) return '';
13+
14+
turndownService.addRule('linethrough', {
15+
filter: ['del'],
16+
replacement: content => `~${content}~`,
17+
});
18+
turndownService.addRule('codeblock', {
19+
filter: ['pre'],
20+
replacement: content => {
21+
return `\`\`\`
22+
${content}
23+
\`\`\``;
24+
},
25+
});
26+
turndownService.addRule('underline', {
27+
filter: ['u'],
28+
replacement: content => `<u>${content}</u>`,
29+
});
30+
turndownService.addRule('ordered listItem', {
31+
filter: el => {
32+
return !!(
33+
el.tagName === 'LI' &&
34+
el.parentElement &&
35+
el.parentElement.tagName === 'OL'
36+
);
37+
},
38+
replacement: (content, node, options) => {
39+
const indent = ~~(node as any).className.replace('ql-indent-', '');
40+
const spaces = ' '.repeat(indent * 2);
41+
const parent = node.parentNode;
42+
const siblings = [...(parent!.children as any)].filter(
43+
el => el.className === (node as any).className,
44+
);
45+
const index = siblings.indexOf(node);
46+
return `\n${spaces} ${index + 1}. ${content}`;
47+
},
48+
});
49+
50+
return turndownService.turndown(html);
51+
}

Diff for: src/pages/WritePage.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import styled from 'styled-components';
3-
// import FullPageEditor from '../components/write/FullPageEditor';
3+
import QuillEditor from '../components/write/QuillEditor';
44
import EditorPanes from '../components/write/EditorPanes';
55

66
import MarkdownEditorContainer from '../containers/write/MarkdownEditorContainer';
@@ -16,11 +16,11 @@ interface WritePageProps {}
1616
const WritePage: React.SFC<WritePageProps> = props => {
1717
return (
1818
<WritePageBlock>
19-
{/* <FullPageEditor /> */}
20-
<EditorPanes
19+
<QuillEditor />
20+
{/* <EditorPanes
2121
left={<MarkdownEditorContainer />}
2222
right={<MarkdownPreviewContainer />}
23-
/>
23+
/> */}
2424
</WritePageBlock>
2525
);
2626
};

Diff for: yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,11 @@
12971297
dependencies:
12981298
"@types/estree" "*"
12991299

1300+
"@types/turndown@^5.0.0":
1301+
version "5.0.0"
1302+
resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.0.tgz#2b763b36f783e4e237cea62cdc8f8592b72b9285"
1303+
integrity sha512-Y7KZn6SfSv1vzjByJGijrM9w99HoUbLiAgYwe+sGH6RYi5diIGLYOcr4Z1YqR2biFs9K5PnxKv/Fbkmh57pvzg==
1304+
13001305
"@types/unist@*", "@types/unist@^2.0.0":
13011306
version "2.0.2"
13021307
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6"

0 commit comments

Comments
 (0)