반응형
깃허브 데스크탑으로 프로젝트 관리하기 강의 오픈!! (인프런 바로가기)
참고
아래의 토스트 UI 에디터는 메인에서 직접 commit하기 때문에 code owners에게 메일 알림이 가지 않는다.
import React, { useEffect, useRef, useState } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
// GitHub RESTful API
import { Octokit } from "@octokit/rest";
// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import "@toast-ui/editor/dist/toastui-editor-viewer.css"; // Viewer css
import { Editor } from "@toast-ui/react-editor";
import Viewer from "@toast-ui/editor/dist/toastui-editor-viewer";
// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";
// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";
const colorSyntaxOptions = {
preset: [
"#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
"#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
],
};
//const CONTENT_KEY = "CONTENT_KEY";
let myKey = "...";
const App = () => {
const editorRef = useRef(null);
const [editMode, setEditMode] = useState(false);
const repo = "auto-test";
const path = "README.md";
const getSHA = async (octokit) => {
const result = await octokit.request(
`GET /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
}
);
return result.data.sha;
};
const fileWrite = async (contents) => {
const octokit = new Octokit({
auth: myKey,
});
const currentSHA = await getSHA(octokit);
const result = await octokit.request(
`PUT /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
message: "commit message!",
sha: currentSHA,
committer: {
name: "bloodstrawberry",
email: "bloodstrawberry@github.com",
},
content: `${btoa(unescape(encodeURIComponent(`${contents}`)))}`,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
console.log(result.status);
};
const fileRead = async () => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.request(
`GET /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
encoding: "utf-8",
decoding: "utf-8",
}
);
return result;
};
const handleSave = () => {
let markDownContent = editorRef.current.getInstance().getMarkdown();
//let htmlContent = editorRef.current.getInstance().getHTML();
console.log(markDownContent);
//localStorage.setItem(CONTENT_KEY, markDownContent);
fileWrite(markDownContent);
};
const initReadMe = async () => {
// let item = localStorage.getItem(CONTENT_KEY);
let result = await fileRead();
let contents = decodeURIComponent(escape(window.atob(result.data.content)));
console.log(contents);
if (editMode === false) {
const viewer = new Viewer({
el: document.querySelector(".toast-editor-viewer"),
viewer: true,
height: "400px",
usageStatistics: false, // 통계 수집 거부
plugins: [tableMergedCell],
});
viewer.setMarkdown(contents);
}
if (editorRef.current)
editorRef.current.getInstance().setMarkdown(contents);
};
useEffect(() => {
initReadMe();
}, [editMode]);
return (
<div>
<Box sx={{ m: 2 }}>
<h1>Toast UI Editor</h1>
<Button
variant="outlined"
color="secondary"
sx={{ m: 1 }}
onClick={() => setEditMode(!editMode)}
>
{editMode ? "취소하기" : "편집하기"}
</Button>
<Button
variant="outlined"
color="primary"
sx={{ m: 1 }}
onClick={handleSave}
disabled={editMode === false}
>
저장하기
</Button>
{editMode === false && <div className="toast-editor-viewer"></div>}
{editMode === true && (
<Editor
ref={editorRef}
// initialValue={initContents}
height="400px"
placeholder="Please Enter Text."
previewStyle="tab" // or vertical
initialEditType="wysiwyg" // or markdown
// hideModeSwitch={true} // 하단 숨기기
toolbarItems={[
// 툴바 옵션 설정
["heading", "bold", "italic", "strike"],
["hr", "quote"],
["ul", "ol", "task", "indent", "outdent"],
["table", /* "image", */ "link"],
["code", "codeblock"],
]}
//theme="dark"
//useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
usageStatistics={false} // 통계 수집 거부
plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
/>
)}
</Box>
</div>
);
};
export default App;
따라서 fileWrite의 flow를 수정한다.
최신 브랜치 생성 → fileWrite → PR 생성 및 Merge → 브랜치 삭제
브랜치 생성, 삭제 함수는 다음과 같다.
const makeBranch = async (branchName) => {
const octokit = new Octokit({
auth: myKey,
});
const currentSHA = await getSHAforMain(octokit);
console.log(currentSHA);
const result = await octokit.git.createRef({
owner: "bloodstrawberry",
repo: `${repo}`,
ref: `refs/heads/${branchName}`, // 새로운 브랜치 이름
sha: currentSHA, // 기반 커밋의 SHA
});
console.log(result);
return result;
};
const deleteBranch = async (branchName) => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.git.deleteRef({
owner: "bloodstrawberry",
repo: `${repo}`,
ref: `heads/${branchName}`, // 새로운 브랜치 이름
});
console.log("delete!!", result);
};
PR 요청 및 Merge는 다음과 같다.
const createPullRequest = async (branchName, title, body) => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.pulls.create({
owner: "bloodstrawberry",
repo: `${repo}`,
title,
body,
head: branchName, // 현재 브랜치
base: "main",
});
console.log(result);
console.log("Pull Request Created:", result.data);
const mergeResult = await octokit.pulls.merge({
owner: "bloodstrawberry",
repo: `${repo}`,
pull_number: result.data.number, // 생성된 PR의 번호를 사용
});
console.log(mergeResult);
console.log("Pull Request Merged:", mergeResult.data);
return mergeResult;
};
fileWrite는 flow대로 수정하면 된다.
여기서는 브랜치 이름을 test_branch로 명시하였다.
const fileWrite = async (contents) => {
const octokit = new Octokit({
auth: myKey,
});
// 브랜치 생성
const branchName = "test_branch";
const createResult = await makeBranch(branchName);
console.log(createResult);
// const currentSHA = await getSHA(octokit);
// 브랜치 파일에 대한 SHA 획득
const currentSHA = await getSHAforBranchFile(octokit, branchName);
const result = await octokit.request(
`PUT /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
message: "commit message!",
sha: currentSHA,
committer: {
name: "bloodstrawberry",
email: "bloodstrawberry@github.com",
},
content: `${btoa(unescape(encodeURIComponent(`${contents}`)))}`,
branch: branchName, // 해당 branch의 파일을 수정
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
console.log(result.status);
// PR + Merge
const prResult = await createPullRequest(branchName, "PR TITLE", "PR TEST");
console.log(prResult);
// 브랜치 삭제
deleteBranch(branchName);
};
각 함수에 대해 SHA를 구하는 방법은 링크 또는 전체 코드를 참고하자.
fileWrite에서 생성한 브랜치의 이름이 "test_branch"이기 때문에 commit에도 남아있는 것을 알 수 있다.
그리고 CODEOWNERS에 등록된 계정에 메일도 정상적으로 도착하였다.
전체 코드는 다음과 같다.
import React, { useEffect, useRef, useState } from "react";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
// GitHub RESTful API
import { Octokit } from "@octokit/rest";
// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import "@toast-ui/editor/dist/toastui-editor-viewer.css"; // Viewer css
import { Editor } from "@toast-ui/react-editor";
import Viewer from "@toast-ui/editor/dist/toastui-editor-viewer";
// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';
// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";
// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";
const colorSyntaxOptions = {
preset: [
"#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
"#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
],
};
//const CONTENT_KEY = "CONTENT_KEY";
let myKey = process.env.REACT_APP_MY_TOKEN;
const App = () => {
const editorRef = useRef(null);
const [editMode, setEditMode] = useState(false);
const repo = "auto-test";
const path = "README.md";
const getSHAforMain = async (octokit) => {
const response = await octokit.git.getRef({
owner: "bloodstrawberry",
repo: `${repo}`,
ref: "heads/main", // main 브랜치의 이름
});
return response.data.object.sha;
};
const getSHAforBranchFile = async (octokit, branchName) => {
const result = await octokit.request(
`GET /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
ref: branchName, // 브랜치 이름을 ref에 지정
}
);
return result.data.sha;
};
const makeBranch = async (branchName) => {
const octokit = new Octokit({
auth: myKey,
});
const currentSHA = await getSHAforMain(octokit);
console.log(currentSHA);
const result = await octokit.git.createRef({
owner: "bloodstrawberry",
repo: `${repo}`,
ref: `refs/heads/${branchName}`, // 새로운 브랜치 이름
sha: currentSHA, // 기반 커밋의 SHA
});
console.log(result);
return result;
};
const deleteBranch = async (branchName) => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.git.deleteRef({
owner: "bloodstrawberry",
repo: `${repo}`,
ref: `heads/${branchName}`, // 새로운 브랜치 이름
});
console.log("delete!!", result);
};
const createPullRequest = async (branchName, title, body) => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.pulls.create({
owner: "bloodstrawberry",
repo: `${repo}`,
title,
body,
head: branchName, // 현재 브랜치
base: "main",
});
console.log(result);
console.log("Pull Request Created:", result.data);
const mergeResult = await octokit.pulls.merge({
owner: "bloodstrawberry",
repo: `${repo}`,
pull_number: result.data.number, // 생성된 PR의 번호를 사용
});
console.log(mergeResult);
console.log("Pull Request Merged:", mergeResult.data);
return mergeResult;
};
const fileWrite = async (contents) => {
const octokit = new Octokit({
auth: myKey,
});
// 브랜치 생성
const branchName = "test_branch";
const createResult = await makeBranch(branchName);
console.log(createResult);
// const currentSHA = await getSHA(octokit);
// 브랜치 파일에 대한 SHA 획득
const currentSHA = await getSHAforBranchFile(octokit, branchName);
const result = await octokit.request(
`PUT /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
message: "commit message!",
sha: currentSHA,
committer: {
name: "bloodstrawberry",
email: "bloodstrawberry@github.com",
},
content: `${btoa(unescape(encodeURIComponent(`${contents}`)))}`,
branch: branchName, // 해당 branch의 파일을 수정
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
console.log(result.status);
// PR + Merge
const prResult = await createPullRequest(branchName, "PR TITLE", "PR TEST");
console.log(prResult);
// 브랜치 삭제
deleteBranch(branchName);
};
const fileRead = async () => {
const octokit = new Octokit({
auth: myKey,
});
const result = await octokit.request(
`GET /repos/bloodstrawberry/${repo}/contents/${path}`,
{
owner: "bloodstrawberry",
repo: `${repo}`,
path: `${path}`,
encoding: "utf-8",
decoding: "utf-8",
}
);
return result;
};
const handleSave = () => {
let markDownContent = editorRef.current.getInstance().getMarkdown();
//let htmlContent = editorRef.current.getInstance().getHTML();
console.log(markDownContent);
//localStorage.setItem(CONTENT_KEY, markDownContent);
fileWrite(markDownContent);
};
const initReadMe = async () => {
// let item = localStorage.getItem(CONTENT_KEY);
let result = await fileRead();
let contents = decodeURIComponent(escape(window.atob(result.data.content)));
console.log(contents);
if (editMode === false) {
const viewer = new Viewer({
el: document.querySelector(".toast-editor-viewer"),
viewer: true,
height: "400px",
usageStatistics: false, // 통계 수집 거부
plugins: [tableMergedCell],
});
viewer.setMarkdown(contents);
}
if (editorRef.current)
editorRef.current.getInstance().setMarkdown(contents);
};
useEffect(() => {
initReadMe();
}, [editMode]);
return (
<div>
<Box sx={{ m: 2 }}>
<h1>Toast UI Editor</h1>
<Button
variant="outlined"
color="secondary"
sx={{ m: 1 }}
onClick={() => setEditMode(!editMode)}
>
{editMode ? "취소하기" : "편집하기"}
</Button>
<Button
variant="outlined"
color="primary"
sx={{ m: 1 }}
onClick={handleSave}
disabled={editMode === false}
>
저장하기
</Button>
{editMode === false && <div className="toast-editor-viewer"></div>}
{editMode === true && (
<Editor
ref={editorRef}
// initialValue={initContents}
height="400px"
placeholder="Please Enter Text."
previewStyle="tab" // or vertical
initialEditType="wysiwyg" // or markdown
// hideModeSwitch={true} // 하단 숨기기
toolbarItems={[
// 툴바 옵션 설정
["heading", "bold", "italic", "strike"],
["hr", "quote"],
["ul", "ol", "task", "indent", "outdent"],
["table", /* "image", */ "link"],
["code", "codeblock"],
]}
//theme="dark"
//useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
usageStatistics={false} // 통계 수집 거부
plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
/>
)}
</Box>
</div>
);
};
export default App;
반응형
'개발 > React' 카테고리의 다른 글
리액트 - Hansontable Customizing with GitHub (0) | 2023.09.29 |
---|---|
리액트 - Mui Toggle Button으로 편집기 버튼 만들기 with React Color Picker (0) | 2023.09.27 |
리액트 - 로컬 스토리지 유효기간 설정하기 (Setting Local Storage Expiration Time) (0) | 2023.08.21 |
리액트 - 새 창으로 로그인해서 현재 상태 유지하기 (0) | 2023.08.19 |
리액트 - GitHub OAuth 로그인 정보를 활용하여 Commit Message 남기기 (0) | 2023.08.19 |
댓글