diff --git a/index.html b/index.html new file mode 100644 index 0000000..0aa31ca --- /dev/null +++ b/index.html @@ -0,0 +1,47 @@ + + + + + vanilla-todo 백승선 + + + + + + +
+ +

To Do

+
+ + + +
+ + + +
+
+ + +
+
+
+ +
To-do: 0
+
+ +
+ + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..dbd106a --- /dev/null +++ b/style.css @@ -0,0 +1,287 @@ + + +/*reset*/ +body { + margin: 0; + font-family: arial, sans-serif; + background: #e2e8f0; +} + +/* Header layout with flexbox */ +.header { + display: flex; + align-items: center; + justify-content: center; + background: #0f172a; + color: white; + padding: 14px 16px; +} + +.menu { + position: absolute; + left: 16px; + background: none; + border: none; + cursor: pointer; + padding: 8px; +} + +.menuIcon, +.menuIcon::before, +.menuIcon::after { + display: block; + width: 24px; + height: 2px; + background: white; + border-radius: 2px; + content: ""; + position: relative; +} + +.menuIcon::before, +.menuIcon::after { + position: absolute; + left: 0; +} + +.menuIcon::before { top: -10px; } +.menuIcon::after { top: 10px; } + +.menuTab { + position: fixed; + background: #e2e8f0; + width: 300px; + top: 0; left: 0; bottom: 0; + transform: translateX(-100%); + transition: transform .25s ease; +} + +.menuTab.active { + transform: translateX(0); +} + +.menuContent { + display: flex; + height: 100%; + flex-direction: column; + gap: 8px; + font-size: 15px; +} + +.options { + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 80px; + align-items: center; + gap: 110px; + padding: 20px; + +} + +.nWeek { + width: 120px; + height: 30px; + font-size: 16px; + border-radius: 10px; +} + +.lWeek { + width: 120px; + height: 30px; + font-size: 16px; + border-radius: 10px; +} + +.menuDatePicker{ + margin-top: 80px; + margin-left: 88px; + font-size: 16px; + width: 120px; + height: 30px; + border-radius: 10px; + +} + +#closeMenu { + position: absolute; + font-size: 30px; + width: 22px; + margin-top: 15px; + margin-left: 20px; + background: none; + border: none; + cursor: pointer; +} +/* Centered title */ +.title { + margin: 0; + font-size: 35px; + font-weight: 600; +} + +.dateSelector { + display: flex; + align-items: center; + justify-content: center; + background: none; + margin: 30px; + gap: 20px; +} + +.date { + font-size: 20px; + background: none; + border: none; +} + +.today { + display: flex; + color: #0f172a; + font-size: 20px; + background: none; + border: none; +} + +.inputContainer { + display: flex; + justify-content: center; + gap: 20px; + margin: 40px auto; + background: whitesmoke; + border-radius: 20px; + padding-left: 10px; + width: 400px; + height: 50px; +} + +.input { + font-size: 23px; + border: none; + outline: none; + background: none; + color: #0f172a; +} + +.register { + font-size: 15px; + width: 60px; + height: 25px; + border-radius: 13px; + border: 2px solid black; + cursor: pointer; + background: #0f172a; + color: white; + margin-right: 1px; + margin-top: auto; + margin-bottom: auto; +} + +.list { + display: flex; + flex-direction: column; + gap: 6px; + height: 425px; + width: 600px; + margin: 0 auto; + background: whitesmoke; + border: 6px solid #0f172a; + border-radius: 20px; +} + +.event { + display: flex; + flex-direction: column; + align-items: center; + gap: 14px; + font-size: 26px; + padding: 0; + margin: 5px; + max-height: 350px; + overflow-y: auto; +} + +.topRowList { + display: flex; +} + +.clearAll{ + color: #0f172a; + font-size: 20px; + border-radius: 20px; + width: 120px; + margin-top: 5px; + margin-left: 10px; + background: #e2e8f0; +} + +.numEvent { + line-height: 1.5; + color: #0f172a; + font-size: 18px; + border-radius: 20px; + width: 120px; + margin-top: 5px; + margin-left: auto; + margin-right: 10px; + background: #e2e8f0; + border: 2px solid #0f172a; + padding-left: 20px; +} +.event li { + line-height: 1.5; + display: flex; + padding: 10px 5px; + border: 3px solid #0f172a; + border-radius: 15px; + height: 30px; + width: 550px; + background: #e2e8f0; +} + +.delEvent { + background: #0f172a; + color: white; + border: none; + font-size: 16px; + border-radius: 20px; +} + +.text { + margin-left: 10px; + margin-right: auto; + font-size: 20px; +} + +.pinEvent { + background: none; + color: white; + border: black solid 1px; + font-size: 15px; + border-radius: 20px; + margin-right: 8px; +} + +.doneEvent { + background: #0f172a; + color: white; + border: none; + font-size: 15px; + border-radius: 20px; +} + +.markDone { + text-decoration: line-through; + text-decoration-color: red; + color: grey; +} + +.markPin { + background: red; +} + +body.invert { + filter: invert(1); +} + + diff --git a/todolist.js b/todolist.js new file mode 100644 index 0000000..23641f7 --- /dev/null +++ b/todolist.js @@ -0,0 +1,233 @@ +document.addEventListener('DOMContentLoaded', () => { + + //menu tab + const menuTab = document.getElementById('menuTab'); + const openBtn = document.querySelector('.menu'); + const closeBtn = document.getElementById('closeMenu'); + + //open and close menu drawer + const openDrawer = () => { + menuTab.classList.add('active'); + }; + const closeDrawer = () => { + menuTab.classList.remove('active'); + }; + + openBtn.addEventListener('click', openDrawer); + closeBtn.addEventListener('click', closeDrawer); + + //close menu when clicked outside of menu tab + document.addEventListener('click', (e) => { + if (menuTab.classList.contains('active') && !menuTab.contains(e.target) && !openBtn.contains(e.target)) { + closeDrawer(); + } + }); + + //close menu when user presses esc + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && menuTab.classList.contains('active')) closeDrawer(); + }); + + //define date manipulating buttons + const today = document.querySelector('.today'); + const prevBtn = document.getElementById('yesterday'); + const nextBtn = document.getElementById('tomorrow'); + + //get date + let current = new Date(); + const fmt = { year: 'numeric', month: 'long', day: 'numeric' }; + + //Date manipulation + function render() { + today.textContent = current.toLocaleDateString(undefined, fmt); + } + render(); + + //define next and last week buttons in menu + const nWeek = document.querySelector('.nWeek'); + const lWeek = document.querySelector('.lWeek'); + + nWeek.addEventListener('click', e => { + saveList(current); + current.setDate(current.getDate() + 7); + render(); + loadList(current); + }) + + lWeek.addEventListener('click', e => { + saveList(current); + current.setDate(current.getDate() - 7); + render(); + loadList(current); + }) + + const dateKey = (d) => new Date(d).toLocaleDateString('en-CA'); + // "YYYY-MM-DD" in local time + const storageKey = (d) => `${dateKey(d)}`; + + const numEvent = document.querySelector('.numEvent'); + + //saves the events for a date + function saveList(date) { + sessionStorage.setItem(storageKey(date), event.innerHTML); + } + + //loads the events for a date + function loadList(date) { + event.innerHTML = sessionStorage.getItem(storageKey(date) || ''); + getNumEvent(); + } + + //save current events when prevBtn is pressed + prevBtn.addEventListener('click', () => { + if (event.firstChild){ + saveList(current); + } + current.setDate(current.getDate() - 1); + render(); + loadList(current); + }) + + //save current events when nextBtn is pressed + nextBtn.addEventListener('click', () => { + if (event.firstChild){ + saveList(current); + } + current.setDate(current.getDate() + 1); + render(); + loadList(current); + }) + + //when the date is clicked on, change current date to today + today.addEventListener('click', () => { + current = new Date(); + render(); + loadList(current); + }) + + + + const input = document.querySelector('.input'); + const add = document.querySelector('.register'); + const event = document.querySelector('.event'); + const clearAll = document.querySelector('.clearAll'); + + //event listener that is restored once loaded from storage + event.addEventListener('click', (e) => { + //delete button + if (e.target && e.target.classList.contains('delEvent')) { + e.target.closest('li')?.remove(); + saveList(current); + } + + //done button + if (e.target && e.target.classList.contains('doneEvent')) { + const li = e.target.closest('li'); + const span = li?.querySelector('.text'); // get the text span + span.classList.toggle('markDone'); + saveList(current); + } + + //pin and unpin events + if (e.target && e.target.classList.contains('pinEvent')) { + const li = e.target.closest('li'); + const span = li?.querySelector('.pinEvent'); + if (li.dataset.originalIndex === undefined) { + li.dataset.originalIndex = Array.from(event.children).indexOf(li); + } + //save the original index of event for unpinning + const originalIndex = parseInt(li.dataset.originalIndex, 10); + //unpin + if (li.classList.contains('pinned')) { + li.classList.remove('pinned'); + console.log(originalIndex); + span.classList.toggle('markPin'); + event.insertBefore(li, event.children[originalIndex+1]); + } + //pin + else { + console.log(originalIndex); + span.classList.toggle('markPin'); + event.insertBefore(li, event.firstChild); + li.classList.add('pinned'); + } + } + getNumEvent(); + }); + + //add event using enter and button click + add.addEventListener('click', addToList); + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + addToList(); + } + }); + + //adding event to list + function addToList() { + const li = document.createElement('li'); + + const span = document.createElement('span'); + //event as text + span.textContent = input.value; + span.className = 'text'; + + //delete event button + const delEvent = document.createElement('button'); + delEvent.className = 'delEvent'; + delEvent.textContent = 'Delete'; + //done event button + const doneEvent = document.createElement('button'); + doneEvent.className = 'doneEvent'; + doneEvent.textContent = 'Done'; + //pin event button + const pinEvent = document.createElement('button'); + pinEvent.className = 'pinEvent'; + pinEvent.textContent = 'Pin'; + + //add event to list + if (span && span.textContent !== '') { + li.appendChild(doneEvent); + li.appendChild(span); + li.appendChild(pinEvent); + li.appendChild(delEvent); + event.appendChild(li); + } + + getNumEvent(); + saveList(current); + input.value = ''; + } + + //clear all events for a date + function clearALlEvents() { + while (event.firstChild) { + event.removeChild(event.firstChild); + } + } + clearAll.addEventListener('click', () => { + clearALlEvents(); + getNumEvent(); + }) + + //get the number of Events + function getNumEvent() { + numEvent.textContent = "To-do: " + event.children.length; + } + + //calendar manipulation + const menuContent = document.getElementById('menuContent'); + datePickerEl = document.createElement('input'); + datePickerEl.type = 'date'; + datePickerEl.className = 'menuDatePicker';// make it obvious + menuContent.appendChild(datePickerEl); + datePickerEl.addEventListener('change', () => { + if (!datePickerEl.value) return; + const [y, m, d] = datePickerEl.value.split('-').map(Number); + saveList(current); + current = new Date(y, m - 1, d); + render(); + loadList(current); + + }); +}); \ No newline at end of file