DongyoungKwon

Add WebSpeech API && Sentence-Similarity API

1 # Teleprompter-SST 1 # Teleprompter-SST
2 +<!-- TABLE OF CONTENTS -->
3 +## Table of Contents
4 + - [ν”„λ‘œμ νŠΈ μ†Œκ°œ](#ν”„λ‘œμ νŠΈ-μ†Œκ°œ)
5 + - [μ£Όμš”κΈ°λŠ₯](#μ£Όμš”κΈ°λŠ₯)
6 + - [Directory ꡬ쑰](#directory-ꡬ쑰)
7 + - [μ„€μΉ˜ 방법](#μ„€μΉ˜-방법)
8 + - [νŒ€μ›](#νŒ€μ›)
9 + - [Reference](#reference)
10 + - [License](#license)
2 11
3 - 12 +<!-- ν”„λ‘œμ νŠΈ μ†Œκ°œ -->
4 -### ν”„λ‘œμ νŠΈ μ†Œκ°œ 13 +## πŸŽ™ ν”„λ‘œμ νŠΈ μ†Œκ°œ
5 - - πŸŽ™ μŒμ„±μ— 맞좰 λŒ€λ³Έμ„ 화면에 μ‹€μ‹œκ°„μœΌλ‘œ 좜λ ₯ν•˜λŠ” 프둬프터 μ„œλΉ„μŠ€ πŸ“œ 14 +---
15 + - μŒμ„±μ— 맞좰 λŒ€λ³Έμ„ 화면에 μ‹€μ‹œκ°„μœΌλ‘œ 좜λ ₯ν•˜λŠ” 프둬프터 μ„œλΉ„μŠ€
6 - Untactμ‹œλŒ€μ— ν™”μƒνšŒμ˜μ—μ„œ νŽΈν•˜κ²Œ λ°œν‘œν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” μ„œλΉ„μŠ€ 16 - Untactμ‹œλŒ€μ— ν™”μƒνšŒμ˜μ—μ„œ νŽΈν•˜κ²Œ λ°œν‘œν•  수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” μ„œλΉ„μŠ€
17 +<br />
18 +<!-- μ£Όμš”κΈ°λŠ₯ -->
19 +## πŸ“œ μ£Όμš”κΈ°λŠ₯
20 +---
21 + - [**Web Speech API**](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API) && [**string-similarity API**](https://www.npmjs.com/package/) μ‚¬μš©
22 + - Real-time Script Output
23 +<br />
24 +<!-- Directory ꡬ쑰 -->
25 +## πŸ—‚ Directory ꡬ쑰
26 +---
27 +```bash
28 +Teleprompter-SST
29 +β”œβ”€β”€ client ---> Front-end
30 +β”‚ β”œβ”€β”€ public ---> 정적 파일 보관
31 +β”‚ β”‚ β”œβ”€β”€ favicon.ico
32 +β”‚ β”‚ β”œβ”€β”€ index.html
33 +β”‚ β”‚ β”œβ”€β”€ manifest.json
34 +β”‚ β”‚ └── robots.txt
35 +β”‚ β”œβ”€β”€ src
36 +β”‚ β”‚ β”œβ”€β”€ components
37 +β”‚ β”‚ β”‚ └── Teleprompter.js ---> μŒμ„±μΈμ‹ && λ¬Έμž₯μœ μ‚¬λ„ μˆ˜ν–‰
38 +β”‚ β”‚ β”œβ”€β”€ pages
39 +β”‚ β”‚ β”‚ β”œβ”€β”€ MainPage.js ---> 메인 ν™”λ©΄
40 +β”‚ β”‚ β”‚ └── PrompterPage.js ---> λŒ€λ³Έ 좜λ ₯ ν™”λ©΄
41 +β”‚ β”‚ β”œβ”€β”€ App.css
42 +β”‚ β”‚ β”œβ”€β”€ App.js ---> Router
43 +β”‚ β”‚ β”œβ”€β”€ App.test.js
44 +β”‚ β”‚ β”œβ”€β”€ index.css
45 +β”‚ β”‚ β”œβ”€β”€ index.js
46 +β”‚ β”‚ β”œβ”€β”€ reportWebVitals.js
47 +β”‚ β”‚ β”œβ”€β”€ serviceWorker.js
48 +β”‚ β”‚ β”œβ”€β”€ setupTests.js
49 +β”‚ β”‚ └── styles.js ---> PrompterPage.js Style
50 +β”‚ β”œβ”€β”€ .gitignore
51 +β”‚ β”œβ”€β”€ package-lock.json
52 +β”‚ β”œβ”€β”€ package.json
53 +β”‚ └── yarn.lock
54 +β”‚
55 +β”œβ”€β”€ .gitignore
56 +β”œβ”€β”€ LICENSE ---> MIT License
57 +β”œβ”€β”€ package-lock.json
58 +β”œβ”€β”€ package.json
59 +β”œβ”€β”€ README.md
60 +β”œβ”€β”€ server.js ---> Back-end
61 +└── yarn.lock
62 +```
63 +<br />
64 +<!-- μ„€μΉ˜ 방법 -->
7 65
66 +## ⌨️ μ„€μΉ˜ 방법
67 +---
8 68
9 -### μ£Όμš”κΈ°λŠ₯
10 - - Google Speech-to-Text API && ADAMS.ai λ¬Έμž₯ μœ μ‚¬λ„ API μ‚¬μš©
11 - - Real-time Script Output
12 69
70 +#### client 폴더 이동
71 +`$ cd Teleprompter-SST/client`
13 72
14 -### νŒ€μ› 73 +#### package.json에 λͺ…μ‹œλœ λͺ¨λ“ˆ μ„€μΉ˜
15 -- κΆŒλ™μ˜ (2016110307) 74 +`$ npm install`
75 +
76 +#### Teleprompter-SST 폴더 이동
77 +`$ cd ..`
78 +#### package.json에 λͺ…μ‹œλœ λͺ¨λ“ˆ μ„€μΉ˜
79 +`$ npm install`
80 +
81 +#### μ‹œμž‘
82 +`$ npm run dev`
16 83
84 +#### Local Address 접속
85 +`http://localhost:3000`
86 +
87 +
88 +<!-- νŒ€μ› -->
89 +## πŸ§‘β€πŸ’» νŒ€μ›
90 +---
91 +- κΆŒλ™μ˜ (2016110307)
17 - 김닀솔 (2017110268) 92 - 김닀솔 (2017110268)
18 93
19 -#### Start
20 -- `$ yarn global add create-react-app`
21 -- `$ create-react-app teleprompt-sst`
22 -- `$ yarn add @material-ui/core`
...\ No newline at end of file ...\ No newline at end of file
94 +
95 +<!-- document -->
96 +## πŸ“‹ Reference
97 +---
98 +- [**Web Speech API**](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API)
99 +- [**string-similarity API**](https://www.npmjs.com/package/)
100 +
101 +
102 +<!-- license -->
103 +## πŸ“‹ License
104 +---
105 +Teleprompter-SST is [MIT licensed](./LICENSE).
106 +<br></br>
107 +[πŸ‘†Back To The Top](#Teleprompter-SST)
...\ No newline at end of file ...\ No newline at end of file
......
This diff could not be displayed because it is too large.
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
11 "react": "^17.0.1", 11 "react": "^17.0.1",
12 "react-dom": "^17.0.1", 12 "react-dom": "^17.0.1",
13 "react-router-dom": "^5.2.0", 13 "react-router-dom": "^5.2.0",
14 - "react-scripts": "4.0.1", 14 + "react-scripts": "^4.0.1",
15 + "string-similarity": "^4.0.3",
16 + "styled-components": "^5.2.1",
15 "web-vitals": "^0.2.4" 17 "web-vitals": "^0.2.4"
16 }, 18 },
17 "scripts": { 19 "scripts": {
......
...@@ -9,35 +9,12 @@ ...@@ -9,35 +9,12 @@
9 name="description" 9 name="description"
10 content="Web site created using create-react-app" 10 content="Web site created using create-react-app"
11 /> 11 />
12 - <!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
13 - <!--
14 - manifest.json provides metadata used when your web app is installed on a
15 - user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16 - -->
17 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> 12 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18 - <!--
19 - Notice the use of %PUBLIC_URL% in the tags above.
20 - It will be replaced with the URL of the `public` folder during the build.
21 - Only files inside the `public` folder can be referenced from the HTML.
22 13
23 - Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24 - work correctly both with client-side routing and a non-root public URL.
25 - Learn how to configure a non-root public URL by running `npm run build`.
26 - -->
27 <title>프둬프터 μ„œλΉ„μŠ€</title> 14 <title>프둬프터 μ„œλΉ„μŠ€</title>
28 </head> 15 </head>
29 <body> 16 <body>
30 <noscript>You need to enable JavaScript to run this app.</noscript> 17 <noscript>You need to enable JavaScript to run this app.</noscript>
31 <div id="root"></div> 18 <div id="root"></div>
32 - <!--
33 - This HTML file is a template.
34 - If you open it directly in the browser, you will see an empty page.
35 -
36 - You can add webfonts, meta tags, or analytics to this file.
37 - The build step will place the bundled scripts into the <body> tag.
38 -
39 - To begin the development, run `npm start` or `yarn start`.
40 - To create a production bundle, use `npm run build` or `yarn build`.
41 - -->
42 </body> 19 </body>
43 </html> 20 </html>
......
...@@ -2,6 +2,7 @@ import React, { Component } from 'react'; ...@@ -2,6 +2,7 @@ import React, { Component } from 'react';
2 import { Route } from 'react-router-dom'; 2 import { Route } from 'react-router-dom';
3 import MainPage from './pages/MainPage'; 3 import MainPage from './pages/MainPage';
4 import PrompterPage from './pages/PrompterPage'; 4 import PrompterPage from './pages/PrompterPage';
5 +// import test from './pages/test';
5 6
6 class App extends Component { 7 class App extends Component {
7 render() { 8 render() {
...@@ -9,6 +10,7 @@ class App extends Component { ...@@ -9,6 +10,7 @@ class App extends Component {
9 <> 10 <>
10 <Route path="/" component={MainPage} exact={true} /> 11 <Route path="/" component={MainPage} exact={true} />
11 <Route path="/prompter" component={PrompterPage} exact={true} /> 12 <Route path="/prompter" component={PrompterPage} exact={true} />
13 + {/* <Route path="/prompter" component={test} exact={true} /> */}
12 </> 14 </>
13 ); 15 );
14 } 16 }
......
1 +import React from 'react'
2 +import styled from 'styled-components'
3 +import stringSimilarity from 'string-similarity'
4 +
5 +const StyledTeleprompter = styled.div`
6 + font-size: 9rem;
7 + width: 100%;
8 + height: 56rem;
9 + scroll-behavior: smooth;
10 + overflow: scroll;
11 + display: block;
12 + margin-bottom: 1rem;
13 +`
14 +// Userκ°€ μ§€κΈˆ λ§ν•˜κ³  μžˆλŠ” 단어 style
15 +const Interim = styled.div`
16 + background: rgb(0, 0, 0, 0.25);
17 + color: white;
18 + flex: 0 0 auto;
19 + padding: 0.5rem;
20 + border-radius: 1rem;
21 + display: inline-block;
22 +`
23 +
24 +// Script λ¬Έμžμ—΄ 처리 ["I", "am", "happy"] -> "iamhappy"
25 +const onlyWord = (word) =>
26 + word
27 + .trim() // λ¬Έμžμ—΄ μ’Œμš°μ—μ„œ 곡백 제거
28 + .toLocaleLowerCase() // μ•ŒνŒŒλ²³ μ†Œλ¬Έμžλ‘œ λ³€ν™˜
29 + .replace(/[^κ°€-νž£γ„±-γ…Žγ…-γ…£a-z]/gi, '') // μ •κ·œμ‹μ„ μ΄μš©ν•΄ ν•œκΈ€ λ˜λŠ” μ•ŒνŒŒλ²³μ΄ μ•„λ‹Œ 문자 빈칸으둜 λ³€ν™˜
30 +
31 +export default function Teleprompter({ words, progress, listening, onChange }) {
32 + const recog = React.useRef(null)
33 + const scrollRef = React.useRef(null)
34 + const [ results, setResults ] = React.useState('')
35 +
36 + React.useEffect(() => {
37 + const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
38 + recog.current = new SpeechRecognition()
39 + recog.current.continuous = true
40 + recog.current.interimResults = true
41 + }, [])
42 +
43 + React.useEffect(() => {
44 + if (listening) {
45 + recog.current.start()
46 + }
47 + else {
48 + recog.current.stop()
49 + }
50 + }, [listening])
51 +
52 + React.useEffect(() => {
53 + const handleResult = ({ results }) => {
54 + const interim = Array.from(results)
55 + .filter(r => !r.isFinal)
56 + .map(r => r[0].transcript)
57 + .join(' ')
58 + setResults(interim)
59 +
60 + const newIndex = interim
61 + .split(' ')
62 + .reduce((memo, word) => {
63 + if ( memo >= words.length) {
64 + return memo
65 + }
66 + const similarity = stringSimilarity.compareTwoStrings(
67 + onlyWord(word),
68 + onlyWord(words[memo])
69 + )
70 + memo +=
71 + similarity > 0.3 // μœ μ‚¬λ„ 민감도 μ„€μ •
72 + ? 1
73 + : 0
74 + return memo
75 + }, progress)
76 + if ( newIndex > progress && newIndex <= words.length ) {
77 + onChange(newIndex)
78 + }
79 + }
80 + recog.current.addEventListener(
81 + 'result',
82 + handleResult
83 + )
84 + return () => {
85 + recog.current.removeEventListener(
86 + 'result',
87 + handleResult
88 + )
89 + }
90 + }, [onChange, progress, words])
91 +
92 + React.useEffect(() => {
93 + /* eslint-disable no-unused-expressions */
94 + scrollRef.current
95 + .querySelector(
96 + `[data-index='${
97 + progress + 8 // ν˜„μž¬ μ§„ν–‰ μƒνƒœμ— 따라 Scroll μ„€μ •
98 + }']`
99 + )
100 + ?.scrollIntoView({
101 + behavior: 'smooth',
102 + block: 'nearest',
103 + inline: 'start'
104 + })
105 + }, [progress])
106 +
107 + return (
108 + <>
109 + <StyledTeleprompter ref={scrollRef}>
110 + {words.map((word, i) => (
111 + <span
112 + key={`${word}:${i}`}
113 + data-index={i}
114 + style={{
115 + color:
116 + i < progress
117 + ? '#000' // 아직 읽지 μ•Šμ€ wordλŠ” 흰색
118 + : '#ccc' // 읽은 wordλŠ” κ²€μ€μƒ‰μœΌλ‘œ λ³€κ²½
119 + }}
120 + >
121 + {word}{' '}
122 + </span>
123 + ))}
124 + </StyledTeleprompter>
125 + {results && ( <Interim>{results}</Interim> )}
126 + </>
127 + )
128 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -13,18 +13,4 @@ body { ...@@ -13,18 +13,4 @@ body {
13 code { 13 code {
14 font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 14 font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
15 monospace; 15 monospace;
16 -} 16 +}
17 -
18 -/* body {
19 - margin: 0;
20 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
21 - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
22 - sans-serif;
23 - -webkit-font-smoothing: antialiased;
24 - -moz-osx-font-smoothing: grayscale;
25 -}
26 -
27 -code {
28 - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
29 - monospace;
30 -} */
...\ No newline at end of file ...\ No newline at end of file
......
1 -import React, { Component, Fragment } from 'react'; 1 +import React, { Fragment } from 'react';
2 +import axios from 'axios'
3 +import {
4 + GlobalStyles,
5 + StyledApp,
6 + StyledTeleprompter as Teleprompter,
7 + Controls,
8 + Buttons,
9 + Button,
10 +} from "../styles";
2 // import { withStyles, Typography, Paper, Button, TextField} from '@material-ui/core'; 11 // import { withStyles, Typography, Paper, Button, TextField} from '@material-ui/core';
3 12
4 // const styles = theme => ({ 13 // const styles = theme => ({
...@@ -30,17 +39,57 @@ import React, { Component, Fragment } from 'react'; ...@@ -30,17 +39,57 @@ import React, { Component, Fragment } from 'react';
30 // }, 39 // },
31 // }); 40 // });
32 41
33 -class PrompterPage extends Component { 42 +// const INITIAL_TEXT = ``;
34 43
35 - render() { 44 +function PrompterPage() {
36 - // const { classes } = this.props; 45 + const [listening, setListening] = React.useState(false);
37 - return ( 46 + const [words, setWords] = React.useState("".split(" "));
38 - <Fragment> 47 + // const [words, setWords] = React.useState("");
39 - <h1>{this.props.script}</h1> 48 + const [progress, setProgress] = React.useState(0);
40 - </Fragment> 49 +
41 - ); 50 + // Serverλ‘œλΆ€ν„° Script λ°›μ•„μ˜΄
51 + axios.get("api/script")
52 + .then(res => { // .then : 응닡(μƒνƒœμ½”λ“œ200~300미만)μ„±κ³΅μ‹œ
53 + console.log(res.data);
54 + setWords(res.data.split(" ")); // λ°›μ•„μ˜¨ Script λ¬Έμžμ—΄ 처리
55 + })
56 + .catch(error => {
57 + console.log(error);
58 + });
59 +
60 +
61 + // const handleInput = (e) => {
62 + // setWords(e.target.value.split(" "));
63 + // progress && setProgress(0);
64 + // };
65 +
66 + const handleListening = () => {
67 + if (listening) {
68 + setListening(false);
69 + } else {
70 + setProgress(0);
71 + setListening(true);
42 } 72 }
73 + };
74 +
75 + const handleReset = () => setProgress(0);
76 +
77 + const handleChange = (progress) => setProgress(progress);
78 +
79 + return (
80 + <>
81 + <GlobalStyles />
82 + <StyledApp>
83 + <Controls>
84 + <Buttons>
85 + <Button onClick={handleListening}>{listening ? "Stop" : "Start"}</Button>
86 + <Button onClick={handleReset} disabled={listening} secondary>Reset</Button>
87 + </Buttons>
88 + </Controls>
89 + <Teleprompter words={words} listening={listening} progress={progress} onChange={handleChange} />
90 + </StyledApp>
91 + </>
92 + );
43 } 93 }
44 94
45 -// export default withStyles(styles)(PrompterPage);
46 export default PrompterPage; 95 export default PrompterPage;
...\ No newline at end of file ...\ No newline at end of file
......
1 +import styled, { createGlobalStyle, css } from "styled-components";
2 +import Teleprompter from "./components/Teleprompter";
3 +
4 +export const GlobalStyles = createGlobalStyle`
5 + * {
6 + box-sizing: border-box;
7 + }
8 + html,
9 + body,
10 + #root {
11 + height: 100%;
12 + font-family: sans-serif;
13 + margin: 0;
14 + overflow: hidden;
15 + background: #282828;
16 + }
17 +`;
18 +
19 +export const StyledApp = styled.div`
20 + font-family: sans-serif;
21 + text-align: center;
22 + height: 100%;
23 + height: 100vh;
24 + margin: 1rem;
25 +`;
26 +
27 +export const StyledTeleprompter = styled(Teleprompter)`
28 + display: flex;
29 + flex-direction: column;
30 + justify-content: space-around;
31 + height: 100%;
32 +`;
33 +
34 +export const Controls = styled.div`
35 + display: flex;
36 + flex-direction: column;
37 + margin-bottom: 1rem;
38 +`;
39 +
40 +export const Buttons = styled.div`
41 + display: flex;
42 + justify-content: flex-start;
43 +`;
44 +
45 +export const Input = styled.textarea`
46 + height: 5rem;
47 + border: 1px solid rgb(0, 0, 0, 0.25);
48 + padding: 0.5rem;
49 + font-family: Tahoma, sans-serif;
50 + background: transparent;
51 + margin-bottom: 1rem;
52 + width: 100%;
53 +`;
54 +
55 +export const Button = styled.button`
56 + display: inline-block;
57 + border: none;
58 + padding: 0.5rem 1rem;
59 + margin: 0;
60 + text-decoration: none;
61 + background: #666ba5;
62 + border: 1px solid rgb(0, 0, 0, 0.25);
63 + color: white;
64 + font-size: 1rem;
65 + cursor: pointer;
66 + text-align: center;
67 + transition: background 250ms ease-in-out, transform 150ms ease;
68 + margin-right: 1rem;
69 + min-width: 5rem;
70 + ${(p) =>
71 + p.secondary &&
72 + css`
73 + background: #666;
74 + `}
75 + &:last-child {
76 + margin-right: 0;
77 + }
78 + &:hover,
79 + &:focus {
80 + transform: scale(1.1);
81 + }
82 + &:focus {
83 + outline: 1px solid #fff;
84 + }
85 + &:active {
86 + transform: scale(0.99);
87 + }
88 +`;
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
67 jsesc "^2.5.1" 67 jsesc "^2.5.1"
68 source-map "^0.5.0" 68 source-map "^0.5.0"
69 69
70 -"@babel/helper-annotate-as-pure@^7.10.4": 70 +"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4":
71 version "7.10.4" 71 version "7.10.4"
72 resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" 72 resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3"
73 integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== 73 integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==
...@@ -1116,6 +1116,21 @@ ...@@ -1116,6 +1116,21 @@
1116 globals "^11.1.0" 1116 globals "^11.1.0"
1117 lodash "^4.17.19" 1117 lodash "^4.17.19"
1118 1118
1119 +"@babel/traverse@^7.4.5":
1120 + version "7.12.9"
1121 + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f"
1122 + integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==
1123 + dependencies:
1124 + "@babel/code-frame" "^7.10.4"
1125 + "@babel/generator" "^7.12.5"
1126 + "@babel/helper-function-name" "^7.10.4"
1127 + "@babel/helper-split-export-declaration" "^7.11.0"
1128 + "@babel/parser" "^7.12.7"
1129 + "@babel/types" "^7.12.7"
1130 + debug "^4.1.0"
1131 + globals "^11.1.0"
1132 + lodash "^4.17.19"
1133 +
1119 "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": 1134 "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
1120 version "7.12.7" 1135 version "7.12.7"
1121 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" 1136 resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13"
...@@ -1153,6 +1168,28 @@ ...@@ -1153,6 +1168,28 @@
1153 resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" 1168 resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
1154 integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== 1169 integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
1155 1170
1171 +"@emotion/is-prop-valid@^0.8.8":
1172 + version "0.8.8"
1173 + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
1174 + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
1175 + dependencies:
1176 + "@emotion/memoize" "0.7.4"
1177 +
1178 +"@emotion/memoize@0.7.4":
1179 + version "0.7.4"
1180 + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
1181 + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
1182 +
1183 +"@emotion/stylis@^0.8.4":
1184 + version "0.8.5"
1185 + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
1186 + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
1187 +
1188 +"@emotion/unitless@^0.7.4":
1189 + version "0.7.5"
1190 + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
1191 + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
1192 +
1156 "@eslint/eslintrc@^0.2.1": 1193 "@eslint/eslintrc@^0.2.1":
1157 version "0.2.1" 1194 version "0.2.1"
1158 resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c" 1195 resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.1.tgz#f72069c330461a06684d119384435e12a5d76e3c"
...@@ -2654,6 +2691,21 @@ babel-plugin-named-asset-import@^0.3.7: ...@@ -2654,6 +2691,21 @@ babel-plugin-named-asset-import@^0.3.7:
2654 resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" 2691 resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd"
2655 integrity sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw== 2692 integrity sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==
2656 2693
2694 +"babel-plugin-styled-components@>= 1":
2695 + version "1.12.0"
2696 + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz#1dec1676512177de6b827211e9eda5a30db4f9b9"
2697 + integrity sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==
2698 + dependencies:
2699 + "@babel/helper-annotate-as-pure" "^7.0.0"
2700 + "@babel/helper-module-imports" "^7.0.0"
2701 + babel-plugin-syntax-jsx "^6.18.0"
2702 + lodash "^4.17.11"
2703 +
2704 +babel-plugin-syntax-jsx@^6.18.0:
2705 + version "6.18.0"
2706 + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
2707 + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=
2708 +
2657 babel-plugin-syntax-object-rest-spread@^6.8.0: 2709 babel-plugin-syntax-object-rest-spread@^6.8.0:
2658 version "6.13.0" 2710 version "6.13.0"
2659 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" 2711 resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
...@@ -3130,6 +3182,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: ...@@ -3130,6 +3182,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0:
3130 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" 3182 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
3131 integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== 3183 integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
3132 3184
3185 +camelize@^1.0.0:
3186 + version "1.0.0"
3187 + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
3188 + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
3189 +
3133 caniuse-api@^3.0.0: 3190 caniuse-api@^3.0.0:
3134 version "3.0.0" 3191 version "3.0.0"
3135 resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" 3192 resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
...@@ -3682,6 +3739,11 @@ css-blank-pseudo@^0.1.4: ...@@ -3682,6 +3739,11 @@ css-blank-pseudo@^0.1.4:
3682 dependencies: 3739 dependencies:
3683 postcss "^7.0.5" 3740 postcss "^7.0.5"
3684 3741
3742 +css-color-keywords@^1.0.0:
3743 + version "1.0.0"
3744 + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
3745 + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
3746 +
3685 css-color-names@0.0.4, css-color-names@^0.0.4: 3747 css-color-names@0.0.4, css-color-names@^0.0.4:
3686 version "0.0.4" 3748 version "0.0.4"
3687 resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" 3749 resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
...@@ -3753,6 +3815,15 @@ css-select@^2.0.0: ...@@ -3753,6 +3815,15 @@ css-select@^2.0.0:
3753 domutils "^1.7.0" 3815 domutils "^1.7.0"
3754 nth-check "^1.0.2" 3816 nth-check "^1.0.2"
3755 3817
3818 +css-to-react-native@^3.0.0:
3819 + version "3.0.0"
3820 + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
3821 + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
3822 + dependencies:
3823 + camelize "^1.0.0"
3824 + css-color-keywords "^1.0.0"
3825 + postcss-value-parser "^4.0.2"
3826 +
3756 css-tree@1.0.0-alpha.37: 3827 css-tree@1.0.0-alpha.37:
3757 version "1.0.0-alpha.37" 3828 version "1.0.0-alpha.37"
3758 resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" 3829 resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
...@@ -5539,7 +5610,7 @@ hmac-drbg@^1.0.0: ...@@ -5539,7 +5610,7 @@ hmac-drbg@^1.0.0:
5539 minimalistic-assert "^1.0.0" 5610 minimalistic-assert "^1.0.0"
5540 minimalistic-crypto-utils "^1.0.1" 5611 minimalistic-crypto-utils "^1.0.1"
5541 5612
5542 -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: 5613 +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2:
5543 version "3.3.2" 5614 version "3.3.2"
5544 resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" 5615 resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
5545 integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== 5616 integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
...@@ -9235,7 +9306,7 @@ react-router@5.2.0: ...@@ -9235,7 +9306,7 @@ react-router@5.2.0:
9235 tiny-invariant "^1.0.2" 9306 tiny-invariant "^1.0.2"
9236 tiny-warning "^1.0.0" 9307 tiny-warning "^1.0.0"
9237 9308
9238 -react-scripts@4.0.1: 9309 +react-scripts@^4.0.1:
9239 version "4.0.1" 9310 version "4.0.1"
9240 resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.1.tgz#34974c0f4cfdf1655906c95df6a04d80db8b88f0" 9311 resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.1.tgz#34974c0f4cfdf1655906c95df6a04d80db8b88f0"
9241 integrity sha512-NnniMSC/wjwhcJAyPJCWtxx6CWONqgvGgV9+QXj1bwoW/JI++YF1eEf3Upf/mQ9KmP57IBdjzWs1XvnPq7qMTQ== 9312 integrity sha512-NnniMSC/wjwhcJAyPJCWtxx6CWONqgvGgV9+QXj1bwoW/JI++YF1eEf3Upf/mQ9KmP57IBdjzWs1XvnPq7qMTQ==
...@@ -9998,6 +10069,11 @@ shallow-clone@^3.0.0: ...@@ -9998,6 +10069,11 @@ shallow-clone@^3.0.0:
9998 dependencies: 10069 dependencies:
9999 kind-of "^6.0.2" 10070 kind-of "^6.0.2"
10000 10071
10072 +shallowequal@^1.1.0:
10073 + version "1.1.0"
10074 + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
10075 + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
10076 +
10001 shebang-command@^1.2.0: 10077 shebang-command@^1.2.0:
10002 version "1.2.0" 10078 version "1.2.0"
10003 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" 10079 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
...@@ -10361,6 +10437,11 @@ string-natural-compare@^3.0.1: ...@@ -10361,6 +10437,11 @@ string-natural-compare@^3.0.1:
10361 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" 10437 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
10362 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== 10438 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
10363 10439
10440 +string-similarity@^4.0.3:
10441 + version "4.0.3"
10442 + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.3.tgz#ef52d6fc59c8a0fc93b6307fbbc08cc6e18cde21"
10443 + integrity sha512-QEwJzNFCqq+5AGImk5z4vbsEPTN/+gtyKfXBVLBcbPBRPNganZGfQnIuf9yJ+GiwSnD65sT8xrw/uwU1Q1WmfQ==
10444 +
10364 string-width@^3.0.0, string-width@^3.1.0: 10445 string-width@^3.0.0, string-width@^3.1.0:
10365 version "3.1.0" 10446 version "3.1.0"
10366 resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 10447 resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
...@@ -10500,6 +10581,22 @@ style-loader@1.3.0: ...@@ -10500,6 +10581,22 @@ style-loader@1.3.0:
10500 loader-utils "^2.0.0" 10581 loader-utils "^2.0.0"
10501 schema-utils "^2.7.0" 10582 schema-utils "^2.7.0"
10502 10583
10584 +styled-components@^5.2.1:
10585 + version "5.2.1"
10586 + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a"
10587 + integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==
10588 + dependencies:
10589 + "@babel/helper-module-imports" "^7.0.0"
10590 + "@babel/traverse" "^7.4.5"
10591 + "@emotion/is-prop-valid" "^0.8.8"
10592 + "@emotion/stylis" "^0.8.4"
10593 + "@emotion/unitless" "^0.7.4"
10594 + babel-plugin-styled-components ">= 1"
10595 + css-to-react-native "^3.0.0"
10596 + hoist-non-react-statics "^3.0.0"
10597 + shallowequal "^1.1.0"
10598 + supports-color "^5.5.0"
10599 +
10503 stylehacks@^4.0.0: 10600 stylehacks@^4.0.0:
10504 version "4.0.3" 10601 version "4.0.3"
10505 resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" 10602 resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
...@@ -10509,7 +10606,7 @@ stylehacks@^4.0.0: ...@@ -10509,7 +10606,7 @@ stylehacks@^4.0.0:
10509 postcss "^7.0.0" 10606 postcss "^7.0.0"
10510 postcss-selector-parser "^3.0.0" 10607 postcss-selector-parser "^3.0.0"
10511 10608
10512 -supports-color@^5.3.0: 10609 +supports-color@^5.3.0, supports-color@^5.5.0:
10513 version "5.5.0" 10610 version "5.5.0"
10514 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 10611 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
10515 integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 10612 integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
......
This diff is collapsed. Click to expand it.
...@@ -16,8 +16,7 @@ ...@@ -16,8 +16,7 @@
16 "license": "MIT", 16 "license": "MIT",
17 "dependencies": { 17 "dependencies": {
18 "body-parser": "^1.19.0", 18 "body-parser": "^1.19.0",
19 - "express": "^4.17.1", 19 + "express": "^4.17.1"
20 - "multer": "^1.4.2"
21 }, 20 },
22 "devDependencies": { 21 "devDependencies": {
23 "concurrently": "^5.3.0", 22 "concurrently": "^5.3.0",
......
...@@ -7,39 +7,14 @@ const port = process.env.PORT || 5000; ...@@ -7,39 +7,14 @@ const port = process.env.PORT || 5000;
7 app.use(bodyParser.json()); 7 app.use(bodyParser.json());
8 app.use(bodyParser.urlencoded({ extended: true})); 8 app.use(bodyParser.urlencoded({ extended: true}));
9 9
10 -// const data = fs.readFileSync('./database.json');
11 -// const conf = JSON.parse(data);
12 -// const mysql = require('mysql');
13 -
14 -
15 -const multer = require('multer');
16 -
17 -// app.get('/api/customers', (req, res) => {
18 -// connection.query(
19 -// "SELECT * FROM course",
20 -// (err, rows, fields) => {
21 -// res.send(rows);
22 -// }
23 -// );
24 -// });
25 -
26 -// app.get('/api/customers/:id', (req, res) => {
27 -// let sql = 'SELECT * FROM course WHERE id=?';
28 -// let params = [req.params.id];
29 -// connection.query(sql, params,
30 -// (err, rows, fields) => {
31 -// res.send(rows);
32 -// }
33 -// );
34 -// });
35 let scriptReceived = ""; 10 let scriptReceived = "";
11 +
36 app.get('/api/script', (req, res) => { 12 app.get('/api/script', (req, res) => {
37 res.send( 13 res.send(
38 scriptReceived 14 scriptReceived
39 ) 15 )
40 }); 16 });
41 17
42 -
43 app.post('/api/script', (req, res) => { 18 app.post('/api/script', (req, res) => {
44 scriptReceived = req.body.script; 19 scriptReceived = req.body.script;
45 }); 20 });
......
  • λ³€κ²½ 사항

    1. PrompterPage κ΅¬ν˜„ μ™„λ£Œ
      • Material-Ui 적용 μ˜ˆμ •
    2. WebSpeech API && Sentence-Similarity API μ‚¬μš©ν•˜μ—¬ μ£Όμš” κΈ°λŠ₯ μ™„μ„±
      • μ’€ 더 λ§€λ„λŸ½κ²Œ μ§„ν–‰ν•˜κ³ μž μ•Œκ³ λ¦¬μ¦˜ μˆ˜μ • μ˜ˆμ •
    3. README.md μˆ˜μ •ν–ˆμœΌλ‚˜ GitHUB와 GitLAB의 문법 차이둜 μ΄μƒν•˜κ²Œ ν‘œμ‹œ
      • λ‹€μŒ Commit에 μˆ˜μ • μ˜ˆμ •
    Edited