-
Notifications
You must be signed in to change notification settings - Fork 10
[1주차] 장자윤 과제 제출합니다. #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "semi": true, | ||
| "signleQuote": true, | ||
| "trailingComma": "es5", | ||
| "tabWidth": 2 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="ko"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Todo</title> | ||
| <link rel="stylesheet" href="style.css" /> | ||
| </head> | ||
| <body> | ||
| <header><h1 id="goToday">Todo-List</h1></header> | ||
| <nav> | ||
| <button class="dateButton" id="prevButton"><</button> | ||
| <input type="date" id="currentDate" /> | ||
| <button class="dateButton" id="nextButton">></button> | ||
| <span class="todoCount">0 개</span> | ||
| </nav> | ||
| <main> | ||
| <!-- 입력 목록 --> | ||
| <div class="inputContainer"> | ||
| <input placeholder="할 일을 입력해주세요" id="todoInput" required /> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. required 속성을 넣어주신 점이 인상깊네용 😲 |
||
| <span class="appendTodo">+</span> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. button 태그를 사용해도 좋을 거 같아용 |
||
| </div> | ||
| <!-- 투두리스트 목록 --> | ||
| </main> | ||
| <script src="script.js"></script> | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| document.addEventListener("DOMContentLoaded", () => { | ||
| const goToday = document.getElementById("goToday"); | ||
| const prevButton = document.getElementById("prevButton"); | ||
| const currentDate = document.getElementById("currentDate"); | ||
| const nextButton = document.getElementById("nextButton"); | ||
| const todoCount = document.querySelector(".todoCount"); | ||
| const todoInput = document.getElementById("todoInput"); | ||
| const appendTodo = document.querySelector(".appendTodo"); | ||
| const mainContainer = document.querySelector("main"); | ||
|
|
||
| // todo 리스트 컨테이너 div 태그 추가 | ||
| const todoListContainer = document.createElement("div"); | ||
| mainContainer.appendChild(todoListContainer); | ||
|
|
||
| // 날짜별 투두리스트 저장 | ||
| let todos = {}; | ||
|
|
||
| // 마지막으로 선택된 날짜 | ||
| const savedDate = localStorage.getItem("lastSelectedDate"); | ||
|
|
||
| // 날짜 디폴트값 : 당일 | ||
| function setToday() { | ||
| const today = new Date(); | ||
| const yyyy = today.getFullYear(); | ||
| const mm = String(today.getMonth() + 1).padStart(2, "0"); // 1~9월 앞에 0 붙임 | ||
| const dd = String(today.getDate()).padStart(2, "0"); // 1~9일 앞에 0 붙임 | ||
|
|
||
| const defaultDate = `${yyyy}-${mm}-${dd}`; | ||
| currentDate.value = defaultDate; | ||
| localStorage.setItem("lastSelectedDate", defaultDate); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 페이지 새로고침 시 마지막으로 선택했던 날짜가 유지되도록 구현해 주신 점이 인상 깊었습니다! |
||
| loadTodos(); | ||
| renderTodos(defaultDate); | ||
| } | ||
|
|
||
| // 페이지 새로고침 : 마지막에 본 날짜 유지 | ||
| function setInitialDate() { | ||
| loadTodos(); | ||
|
|
||
| if (savedDate) { | ||
| currentDate.value = savedDate; | ||
| } else { | ||
| setToday(); | ||
| return; | ||
| } | ||
| renderTodos(currentDate.value); | ||
| } | ||
|
|
||
| setInitialDate(); // 페이지 로딩 시 날짜 설정 | ||
|
|
||
| // 날짜 변경 | ||
| function changeDate(days) { | ||
| // value를 연도, 월, 일로 분리 | ||
| const [year, month, day] = currentDate.value.split("-").map(Number); | ||
| const selectedDate = new Date(year, month - 1, day); // JS에서 month는 0~11 | ||
| selectedDate.setDate(selectedDate.getDate() + days); | ||
|
|
||
| const yyyy = selectedDate.getFullYear(); | ||
| const mm = String(selectedDate.getMonth() + 1).padStart(2, "0"); | ||
| const dd = String(selectedDate.getDate()).padStart(2, "0"); | ||
| currentDate.value = `${yyyy}-${mm}-${dd}`; | ||
|
|
||
| localStorage.setItem("lastSelectedDate", currentDate.value); | ||
| renderTodos(currentDate.value); | ||
| } | ||
|
|
||
| // 버튼 클릭 시 날짜 변경 | ||
| prevButton.addEventListener("click", () => { | ||
| changeDate(-1); | ||
| console.log("날짜:", currentDate.value); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| }); | ||
| nextButton.addEventListener("click", () => { | ||
| changeDate(1); | ||
| console.log("날짜:", currentDate.value); | ||
| }); | ||
|
|
||
| // 달력에서 날짜 변경 | ||
| currentDate.addEventListener("change", () => { | ||
| localStorage.setItem("lastSelectedDate", currentDate.value); // 선택한 날짜 저장 | ||
| renderTodos(currentDate.value); | ||
| console.log("날짜:", currentDate.value); | ||
| }); | ||
|
|
||
| // Todo 추가 | ||
| appendTodo.addEventListener("click", () => { | ||
| const text = todoInput.value.trim(); | ||
| if (!text) return; | ||
|
|
||
| if (!todos[currentDate.value]) todos[currentDate.value] = []; | ||
| todos[currentDate.value].push({ text, checked: false }); // todo 객체 형태로 저장 | ||
|
|
||
| todoInput.value = ""; // 입력값 초기화 | ||
| saveTodos(); | ||
| renderTodos(currentDate.value); | ||
| }); | ||
| todoInput.addEventListener("keydown", (event) => { | ||
| if (event.key === "Enter") { | ||
| appendTodo.click(); | ||
| } | ||
| }); | ||
|
Comment on lines
+95
to
+99
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
승선님 리뷰에도 남겨놨었는데, 한글 입력 후 엔터로 등록을 할 경우 마지막 글자가 두 번 입력되는 현상이 있습니다! 이 문제는 IME 조합형 입력 중 keydown 이벤트가 조합이 완료되기 전에 먼저 실행되면서 발생하는 이슈인데요. 아래 참고 자료들에서 안내하는 것처럼 KeyboardEvent의 isComposing 속성을 활용하면 https://developer.mozilla.org/ko/docs/Web/API/KeyboardEvent/isComposing |
||
|
|
||
| // Todo 렌더링 | ||
| function renderTodos(date) { | ||
| todoListContainer.innerHTML = ""; // 기존 목록 초기화 | ||
| const list = todos[date] || []; | ||
|
|
||
| list.forEach((todo, index) => { | ||
| const div = document.createElement("div"); | ||
| div.className = "listContainer"; | ||
|
|
||
| div.innerHTML = ` | ||
| <input type="checkbox" class="checkboxButton" id="checkbox-${index}" ${ | ||
| todo.checked ? "checked" : "" | ||
| } /> | ||
| <span class="todoContent">${todo.text}</span> | ||
| <button class="deleteButton">x</button> | ||
| `; | ||
|
Comment on lines
+110
to
+116
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 renderTodos 함수에서 |
||
|
|
||
| const checkbox = div.querySelector(`#checkbox-${index}`); | ||
| const todoContent = div.querySelector(".todoContent"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
현재 코드에서 선언만 되고 실제로는 사용되지 않고 있는데요, 불필요한 코드는 제거해 주시면 더 좋을 거 같습니다! |
||
| const deleteButton = div.querySelector(".deleteButton"); | ||
|
|
||
| // 체크박스 | ||
| checkbox.addEventListener("change", () => { | ||
| todo.checked = checkbox.checked; | ||
| saveTodos(); | ||
| renderTodos(date); // 상태 반영 위해 다시 렌더링 | ||
| }); | ||
|
|
||
| // 투두 삭제 | ||
| deleteButton.addEventListener("click", () => { | ||
| todos[date].splice(index, 1); | ||
| saveTodos(); | ||
| renderTodos(date); | ||
| }); | ||
|
|
||
| todoListContainer.appendChild(div); | ||
| }); | ||
|
|
||
| // 투두 개수 | ||
| todoCount.textContent = `${list.filter((todo) => !todo.checked).length} 개`; | ||
| } | ||
|
|
||
| // 투두 리스트 로컬 스토리지에 저장 | ||
| function saveTodos() { | ||
| localStorage.setItem("todos", JSON.stringify(todos)); | ||
| } | ||
| // 투두 리스트 로드 | ||
| function loadTodos() { | ||
| const data = localStorage.getItem("todos"); | ||
| if (data) { | ||
| todos = JSON.parse(data); | ||
| } | ||
| } | ||
|
|
||
| // 페이지 로드 시 투두리스트 로드 | ||
| loadTodos(); | ||
|
|
||
| // Todo-List 클릭 -> 오늘 날짜로 | ||
| goToday.addEventListener("click", () => { | ||
| setToday(); | ||
| }); | ||
| }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 전반적으로 rem단위를 사용하신 것이 인상깊네요! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| * { | ||
| text-align: center; | ||
| font-family: Pretendard; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretendard는 기본 제공 폰트가 아니기 때문에 CDN, @import, @font-face 등의 방식으로 폰트를 불러와야 실제로 적용됩니다! |
||
| margin: 0; | ||
| padding: 0; | ||
| box-sizing: border-box; | ||
| } | ||
|
|
||
| header { | ||
| display: flex; | ||
| justify-content: center; | ||
| padding: 2rem; | ||
| } | ||
|
|
||
| h1 { | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| /* ------------- */ | ||
|
|
||
| nav { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| padding-bottom: 0.5rem; | ||
| gap: 0.5rem; | ||
| } | ||
|
|
||
| .dateButton { | ||
| border: none; | ||
| background: none; | ||
|
|
||
| font-weight: 900; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| #currentDate { | ||
| border: none; | ||
| font-size: 1rem; | ||
| } | ||
| /* 달력 아이콘 스타일 */ | ||
| #currentDate::-webkit-calendar-picker-indicator { | ||
| padding: 0; | ||
| margin: 0; | ||
| cursor: pointer; | ||
|
|
||
| margin-left: -0.2rem; | ||
| margin-right: 0.6rem; | ||
| } | ||
|
|
||
| .todoCount { | ||
| margin-left: 1rem; | ||
| } | ||
|
|
||
| /* ------------- */ | ||
|
|
||
| main { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
|
|
||
| flex-direction: column; | ||
| } | ||
|
|
||
| .inputContainer { | ||
| display: flex; | ||
| flex-direction: row; | ||
|
|
||
| padding: 0.5rem; | ||
| margin-bottom: 0.8rem; | ||
| width: 100%; | ||
| max-width: 20.8rem; | ||
| } | ||
|
|
||
| #todoInput { | ||
| text-align: start; | ||
| padding: 0.6rem; | ||
| padding-left: 0.7rem; | ||
| flex: 1; | ||
|
|
||
| border: 1px solid grey; | ||
| border-radius: 5px; | ||
| } | ||
| #todoInput::placeholder { | ||
| color: #b8b8b8; | ||
| } | ||
|
|
||
| .appendTodo { | ||
| position: relative; | ||
| top: 2px; | ||
|
|
||
| margin-left: 0.9rem; | ||
| color: grey; | ||
| font-size: 1.5rem; | ||
| } | ||
| .appendTodo:hover { | ||
| color: black; | ||
| cursor: pointer; | ||
| } | ||
|
|
||
| #todoInput:valid + .appendTodo { | ||
| color: black; | ||
| } | ||
| #todoInput:invalid + .appendTodo { | ||
| color: grey; | ||
| cursor: not-allowed; | ||
| } | ||
|
|
||
| /* ------------- */ | ||
|
|
||
| .listContainer { | ||
| display: flex; | ||
| align-items: center; | ||
|
|
||
| padding: 0.1rem 0.3rem; | ||
| margin-bottom: 10px; | ||
| margin-left: 1px; | ||
| width: 20rem; | ||
| } | ||
|
|
||
| .checkboxButton { | ||
| margin-top: 1.5px; | ||
| margin-right: 13px; | ||
| transform: scale(1.4); | ||
| accent-color: #808080; /* 체크 시 배경색 기본 파랑->회색 */ | ||
|
|
||
| cursor: pointer; | ||
| } | ||
|
|
||
| .todoContent { | ||
| flex: 1; | ||
| text-align: left; | ||
| word-break: break-all; /* 문자열 길어지면 강제 줄바꿈*/ | ||
| } | ||
|
|
||
| /* 체크되었을 때 글자 색 변경 */ | ||
| .checkboxButton:checked ~ .todoContent, | ||
| .checkboxButton:checked ~ .deleteButton { | ||
| color: #8b8b8b; | ||
| } | ||
| /* 체크되었을 때 취소선 */ | ||
| .checkboxButton:checked ~ .todoContent { | ||
| text-decoration: line-through; | ||
| color: #8b8b8b; | ||
| } | ||
|
|
||
| .deleteButton { | ||
| position: relative; | ||
| top: -2px; | ||
| margin-left: 0.9rem; | ||
|
|
||
| font-size: 1.3rem; | ||
| border: none; | ||
| background: none; | ||
|
|
||
| cursor: pointer; | ||
| } | ||
| .deleteButton:hover, | ||
| .checkboxButton:checked ~ .deleteButton:hover { | ||
| color: #d1a29f; | ||
| } | ||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 렌더링에는 문제가 없지만, 부등호는 잠재적으로 태그 시작/종료 기호이므로, 상황에 따라 오동작할 수 있습니다!
따라서 안전하게 표시하려면 < →
<, > →>와 같이 HTML 엔티티로 대체해줘도 좋을 거 같아용