일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Three js
- Raycasting
- Flutter
- Completer
- code editor
- FirebaseAnalytics
- node
- Image Resize typescript
- KakaoMap
- Prism.js
- Babel standalone
- Three-fiber
- RouteObserver
- swagger-typescript-api
- REST API
- react
- web track
- methodChannel
- userevent_tracker
- Game js
- babel
- Flutter 3.0
- typescript
- Redux
- addPostFrameCallback
- identifierForVender
- androidId
- webrtc
- redux toolkit
- Dart 2.17.0
- Today
- Total
Never give up
React - html to doc (feat.quill2) 본문
이전에 html을 pdf로 만드는 간단한 예제는 해봤는데
(https://devmemory.tistory.com/98)
수정 가능한 doc file을 만들어야 될 필요가 있어서 한번 만들어 봤습니다
App.tsx
const Main = () => {
let text: string
const downloadDoc = (e: React.MouseEvent<HTMLElement>, isDownload: boolean) => {
e.preventDefault()
if (text === undefined) {
const quill = document.querySelector('#div_quill > .ql-editor')
text = `${quill?.innerHTML}`
}
const doc = makeDoc(text, 'test')
if (isDownload) {
doc.download()
} else {
doc.getFile().then((file) => {
console.log({ file })
})
}
}
const btnStyles = defaultStyles;
btnStyles.backgroundColor = 'white';
btnStyles.color = 'black';
btnStyles.border = '1px solid grey'
return (
<div className={styles.div_main}>
<div>
<QuillComponent onChange={(value: string) => text = value}>
<TestComponent />
</QuillComponent>
<div className={styles.div_btn}>
<CommonBtn styles={btnStyles} onClick={(e) => downloadDoc(e, true)}>다운로드</CommonBtn>
<CommonBtn styles={btnStyles} onClick={(e) => downloadDoc(e, false)}>파일 확인</CommonBtn>
</div>
</div>
</div>
)
}
export default Main
뷰는 간단하게 quill, test componenet 그리고 다운로드용 버튼 이렇게 구현을 해놨고
quill의 데이터가 변경될때마다 콜백으로 text를 가져옵니다
(해당 예제에서는 querySelector로 가져오는 부분으로만 처리해도 됩니다)
그리고 가져온 데이터를 makeDoc 클로져 함수를 이용해서 다운로드를 합니다
make_doc.ts
const makeDoc = (htmlText: string, fileName: string) => {
const makeDataUrl = () => {
const html =
`
<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word'
>
<head>
<meta charset='utf-8'>
<style>
table {
width: 100%;
border: 1px solid black;
border-collapse: collapse;
}
table th, table td {
padding: 8px;
border: 1px solid black;
}
</style>
</head>
<body>
${htmlText}
</body>
</html>
`;
const url =
"data:application/vnd.ms-word;charset=utf-8," +
encodeURIComponent(html);
return url
}
const url = makeDataUrl()
const getFile = async () => {
const res = await fetch(url)
const blob = await res.blob()
return new File([blob], getFileName())
}
const download = () => {
const a = document.createElement('a')
a.style.display = 'none'
a.download = getFileName()
a.href = url
a.click()
a.remove()
}
const getFileName = () => {
return fileName.endsWith('.doc') ? fileName : `${fileName}.doc`
}
return {
getFile,
download
}
};
export default makeDoc
html scheme부분 보면 조금 독특한 부분이 보일텐데
html을 ms office XML 형태로 사용하는 attributes인거 같습니다
추가로 테이블 부분 스타일을 지정해줬는데, 이 부분이 정의가 안되어 있으면
테이블 형태로 저장은 되는데 border가 보이지 않습니다
해당 부분을 data url로 변환후 a tag를 이용해서 다운로드 처리를 합니다
주의할 점은 확장자를 .doc으로 설정하지 않으면 내용에 html이 들어가있는 다른 형식의 파일이 나옵니다
quill_componenet.jsx
const QuillComponent = ({ children, onChange }) => {
let quillEditor
useEffect(() => {
initQuill()
return () => {
quillEditor?.off("text-change", textChangeListener);
}
}, [])
const initQuill = async () => {
const { default: Quill } = await import("quill2");
const color = Quill.import("attributors/class/color");
const fontSize = Quill.import("attributors/style/size");
const align = Quill.import("attributors/style/align");
const fontStyle = Quill.import("attributors/class/font");
// fontStyle.whitelist = ["Arial", "Helvetica", "sans-serif"];
Quill.register(color, true);
Quill.register(fontSize, true);
Quill.register(align, true);
Quill.register(fontStyle, true);
const editor = document.getElementById('div_quill')
quillEditor = new Quill(editor, {
modules: {
toolbar: getToolbarOptions(),
},
theme: "snow",
placeholder: "내용을 입력해주세요...",
});
quillEditor.on("text-change", textChangeListener);
}
const getToolbarOptions = () => {
let toolbarOptions = [
[
"image",
"video",
"blockquote",
"link",
{ header: [1, 2, 3, 4, 5, 6, false] },
],
[{ table: "append" }],
[{ align: [] }, { indent: "-1" }, { indent: "+1" }],
["bold", "italic", "underline", "strike"],
[{ list: "ordered" }, { list: "bullet" }],
[{ color: [] }, { background: [] }],
];
return toolbarOptions;
};
const textChangeListener = () => {
let text = quillEditor.container.firstChild.innerHTML;
if (text.includes("script")) {
text = text.replaceAll("script", "");
alert("script는 사용하실 수 없습니다.");
return;
} else if (text.includes("http")) {
text = text.replaceAll("http", "");
alert("http는 사용하실 수 없습니다.");
return;
}
onChange(text)
};
return (
<div id='div_quill'>{children}</div>
)
}
export default QuillComponent
아쉽게도 quill2는 typescript 지원이 안되어서 jsx로 만들어봤습니다
test_componenet.tsx
const TestComponent = () => {
return (
<div>
<div style={{ 'textAlign': 'center' }}>test</div>
<p style={{ 'fontWeight': 'bold' }}>thead is not working in quill2</p>
<table>
<thead>
<tr>
<th>h1</th>
<th>h2</th>
</tr>
</thead>
<tbody>
<tr>
<td>d1</td>
<td>d2</td>
</tr>
</tbody>
</table>
<br />
<p style={{ 'fontWeight': 'bold' }}>only tbody is working in quill2</p>
<table>
<tbody>
<tr>
<td>h1</td>
<td>22</td>
</tr>
<tr>
<td>d1</td>
<td>d2</td>
</tr>
</tbody>
</table>
</div>
)
}
export default TestComponent
텍스트 편집기에 사용할 테스트 컴포넌트입니다
common_btn.tsx
type BtnProps = {
children: JSX.Element | string,
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined
styles?: any,
disabled?: boolean
}
export const defaultStyles = {
backgroundColor: '#64b5f6',
color: 'white',
width: "120px",
height: "40px",
margin: 'unset',
border: 'unset'
}
const CommonBtn: React.FC<BtnProps> = ({ children, onClick, styles = defaultStyles, disabled = false }) => {
return (
<button className='btn_common' onClick={onClick} style={{
'--background-color': styles.backgroundColor,
'--color': styles.color,
'--width': styles.width,
'--height': styles.height,
'--margin': styles.margin,
'--border': styles.border
} as React.CSSProperties} disabled={disabled}>{children}</button>
)
}
export default CommonBtn
.btn_common {
background-color: var(--background-color);
color: var(--color);
width: var(--width);
height: var(--height);
margin: var(--margin);
border: var(--border);
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
}
.btn_common:hover {
animation: btn 0.8s ease forwards;
}
@keyframes btn {
from {
outline: grey solid;
outline-offset: 10px;
transform: scale(1);
}
to {
outline: transparent solid;
outline-offset: 0px;
transform: scale(1.05);
box-shadow: 0 0 5px grey;
}
}
svelte에서 사용하던 방식과 비슷하게 만들어 봤는데
svelte에 비해 조금 더 귀찮은 부분이 있는거 같습니다
동일한부분을 svelte로 만들어 보면
<script>
export let width = "120px";
export let height = "40px";
export let margin = "0";
export let backgroundColor = "var(--point-navy)";
export let color = "white";
export let disabled = false;
export let border = 'unset'
export let onClick;
</script>
<button
class="btn_custom"
type="button"
style="--width: {width}; --height: {height}; --margin: {margin}; --background-color: {backgroundColor}; --color: {color}; --border: {border};"
{disabled}
on:click|preventDefault={onClick}
>
<slot />
</button>
<style>
.btn_common {
background-color: var(--background-color);
color: var(--color);
width: var(--width);
height: var(--height);
margin: var(--margin);
border: var(--border);
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
}
.btn_common:hover {
animation: btn 0.8s ease forwards;
}
@keyframes btn {
from {
outline: grey solid;
outline-offset: 10px;
transform: scale(1);
}
to {
outline: transparent solid;
outline-offset: 0px;
transform: scale(1.05);
box-shadow: 0 0 5px grey;
}
}
</style>
js라 조금 더 간단한것도 있지만
한 파일에 모든 부분이 나와서 조금 더 가독성도 좋고, boilerplate가 없어서 훨씬 편합니다
마지막으로 결과 부분을 보면 다음과 같습니다
일부 스타일링은 작동이 안될 가능성이 있고, 테스트를 조금 해봐야될거 같습니다
추가로 이미지 파일 적용시에는 base64로 처리를 해야됩니다
전체 소스코드 링크 : https://github.com/devmemory/react_make_doc
'WEB' 카테고리의 다른 글
React, Node - SSE example (0) | 2023.03.04 |
---|---|
React - Axios common settings (0) | 2022.12.21 |
React - Drag & Drop file (0) | 2022.08.20 |
React - Social login(Kakao, Naver , Facebook, Google) (18) | 2022.07.16 |
React - make html element to pdf(Feat. jspdf, html2canvas) (4) | 2022.07.02 |