<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Book Keeper</title>
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css"/>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- Open Modal -->
    <h1 id="show-modal">북마크 추가.</h1>
    <!-- Bookmarks Container -->
    <div class="container" id="bookmarks-container"></div>
    <!-- Modal -->
    <div class="modal-container" id="modal">
        <div class="modal">
            <i class="fas fa-times close-icon" id="close-modal"></i>
            <div class="modal-header">
                <h3>북마크 추가하기.</h3>
            </div>
            <div class="modal-content">
                <form id="bookmark-form">
                    <!-- Website Name -->
                    <div class="form-group">
                        <label class="form-label" for="website-name">Website Name</label>
                        <input class="form-input" type="text" id="website-name">                      
                    </div>
                    <!-- Website URL -->
                    <div class="form-group">
                        <label class="form-label" for="website-url">Website URL</label>
                        <input class="form-input" type="text" id="website-url">          
                    </div>
                    <button type="submit">저장하기.</button>
                </form>
            </div>
        </div>
    </div>
    <!-- Script -->
    <script src="script.js"></script>
</body>
</html>
@import url("https://fonts.googleapis.com/css?family=Karla&display=swap");

:root {
  --primary-color: #7c59b0;
  --border-radius: 5px;
}

html {
  box-sizing: border-box;
}

body {
  background: var(--primary-color);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 80 80' width='80' height='80'%3E%3Cpath fill='%23b8b8b8' fill-opacity='0.4' d='M14 16H9v-2h5V9.87a4 4 0 1 1 2 0V14h5v2h-5v15.95A10 10 0 0 0 23.66 27l-3.46-2 8.2-2.2-2.9 5a12 12 0 0 1-21 0l-2.89-5 8.2 2.2-3.47 2A10 10 0 0 0 14 31.95V16zm40 40h-5v-2h5v-4.13a4 4 0 1 1 2 0V54h5v2h-5v15.95A10 10 0 0 0 63.66 67l-3.47-2 8.2-2.2-2.88 5a12 12 0 0 1-21.02 0l-2.88-5 8.2 2.2-3.47 2A10 10 0 0 0 54 71.95V56zm-39 6a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm40-40a2 2 0 1 1 0-4 2 2 0 0 1 0 4zM15 8a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm40 40a2 2 0 1 0 0-4 2 2 0 0 0 0 4z'%3E%3C/path%3E%3C/svg%3E");
  font-family: Karla, sans-serif;
}

h1 {
  color: white;
  padding: 20px;
  background: rgba(0, 0, 0, 0.5);
  border-radius: var(--border-radius);
  cursor: pointer;
  text-align: center;
  text-transform: uppercase;
  width: 275px;
  margin: 20px auto 10px;
  user-select: none;
}

h1:hover {
  background: rgba(0, 0, 0, 0.8);
}

/* Bookmarks */
.container {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}

.item {
  background: rgba(0, 0, 0, 0.5);
  color: white;
  border-radius: var(--border-radius);
  padding: 20px;
  margin: 10px;
}

.item:hover {
  background: rgba(0, 0, 0, 0.6);
}

a {
  font-size: 20px;
  font-weight: bold;
  text-transform: uppercase;
}

a:link,
a:visited {
  color: white;
  text-decoration: none;
}

a:hover,
a:active {
  text-decoration: underline;
}

.fa-times {
  float: right;
  cursor: pointer;
  z-index: 2;
}

.name {
  margin-top: 20px;
  margin-right: 20px;
}

.name img {
  height: 20px;
  width: 20px;
  vertical-align: sub;
  margin-right: 5px;
}

/* Modal */
.modal-container {
  background: rgba(0, 0, 0, 0.6);
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}

.show-modal {
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal {
  background: #fff;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
  max-width: 95%;
  width: 500px;
  animation: modalopen 1s;
}

@keyframes modalopen {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.close-icon {
  float: right;
  color: white;
  font-size: 24px;
  position: relative;
  top: 13px;
  right: 13px;
  cursor: pointer;
}

.modal-header {
  background: var(--primary-color);
  color: #fff;
  padding: 15px;
}

h3 {
  margin: 0;
}

.modal-content {
  padding: 20px;
  background: whitesmoke;
}

/* Form */
.form-group {
  height: 55px;
}

.form-input {
  width: 97%;
  padding: 5px;
  border: 2px solid var(--primary-color);
  border-radius: var(--border-radius);
  display: block;
  outline: none;
}

.form-label {
  color: var(--primary-color);
  display: block;
}

button {
  cursor: pointer;
  color: white;
  background: var(--primary-color);
  border: none;
  height: 30px;
  width: 100px;
  border-radius: var(--border-radius);
  margin-top: 10px;
}

button:hover {
  filter: brightness(110%);
}

button:focus {
  outline: none;
}

/* Media Query: Large Smartphone (Vertical) */
@media screen and (max-width: 600px) {
  .container {
    flex-direction: column;
  }
}
//북마크 추가 창 전체.
const modal = document.getElementById('modal');
//북마크 추가 버튼 창.
const modalShow = document.getElementById('show-modal');
//추가 창 닫기
const modalClose = document.getElementById('close-modal');
//북마크 추가 창 입력 전체.
const bookmarkForm = document.getElementById('bookmark-form');
const websiteNameEl = document.getElementById('website-name');
const websiteUrlEl = document.getElementById('website-url');
//북마크들 전체.
const bookmarksContainer = document.getElementById('bookmarks-container');

let bookmarks = [];

// 모달 보여주기. 입력에 집중.
function showModal() {
  modal.classList.add('show-modal');
  websiteNameEl.focus();
}

// 모달 이벤트 추가.
modalShow.addEventListener('click', showModal);
modalClose.addEventListener('click', () => modal.classList.remove('show-modal'));
window.addEventListener('click', (e) => (e.target === modal ? modal.classList.remove('show-modal') : false));

// 정상 주소인지 확인.
function validate(nameValue, urlValue) {
  const expression = /(https)?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
  const regex = new RegExp(expression);
  if (!nameValue || !urlValue) {
    //주소,제목 둘중 하나라도 입력이 안되면.
    alert('주소와 제목을 입력해 주세요.');
    return false;
  }
  if (!urlValue.match(regex)) {
    //주소가 정규식으로 파악한 정상 주소와 맞지 않으면.
    alert('정상적인 주소를 입력해 주세요.');
    return false;
  }
  // 정상.
  return true;
}

// 문서에 북마크 생성.
function buildBookmarks() {
  // 일단 북마크 요소들을 청소.
  bookmarksContainer.textContent = '';
  // Build items
  bookmarks.forEach((bookmark) => {
    //구조분해 할당.
    const { name, url } = bookmark;
    // Item 생성
    const item = document.createElement('div');
    item.classList.add('item');
    // Icon 생성
    const closeIcon = document.createElement('i');
    closeIcon.classList.add('fas', 'fa-times');
    closeIcon.setAttribute('title', 'Delete Bookmark');
    closeIcon.setAttribute('onclick', `deleteBookmark('${url}')`);
    // 파비콘 / 링크 생성
    const linkInfo = document.createElement('div');
    linkInfo.classList.add('name');
    // 파비콘--- 웹상에서 그 창을 간단히 표현하는 아이콘
    const favicon = document.createElement('img');
    favicon.setAttribute('src', `https://s2.googleusercontent.com/s2/favicons?domain=${url}`);
    favicon.setAttribute('alt', 'Favicon');
    // 링크
    const link = document.createElement('a');
    link.setAttribute('href', `${url}`);
    link.setAttribute('target', '_blank');
    link.textContent = name;
    // 북마크 컨테이너에 할당하기.
    linkInfo.append(favicon, link);
    item.append(closeIcon, linkInfo);
    bookmarksContainer.appendChild(item);
  });
}

// Fetch 북마크
function fetchBookmarks() {
  // 값이 있으면 북마크를 저장소에서 가져오기.
  if (localStorage.getItem('bookmarks')) {
    bookmarks = JSON.parse(localStorage.getItem('bookmarks'));
  } else {
    // 값이 없으면 저장소에 북마크 기본 배열 생성.
    bookmarks = [
      {
        name: 'Jacinto Design',
        url: 'http://jacinto.design',
      },
    ];
    localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
  }
  buildBookmarks();
}

// 북마크 삭제.
function deleteBookmark(url) {
  // 북마크 배열 돌기.
  bookmarks.forEach((bookmark, i) => {
    if (bookmark.url === url) {
      bookmarks.splice(i, 1);
      //선택한 북마크 삭제.
    }
  });
  // 로컬 저장소의 북마크 배열 업데이트,  DOM 문서 재구성.
  localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
  fetchBookmarks();
}

function storeBookmark(e){
  e.preventDefault();
  const nameValue = websiteNameEl.value;
  let urlValue = websiteUrlEl.value;
 if (!urlValue.includes('https://') && !urlValue.includes('http://')) {  //주소코드를 가지고 있지 않을때. 즉 입력받은 값이 정상 사이트 주소가 아니면.
    urlValue = `https://${urlValue}`;
    //대신 주소 형식을 맞춰준다.
  }
  if(!validate(nameValue, urlValue)){
    //적합의 역인 경우이므로 만약 주소값이 적합하지 않으면 거짓 반환.
    return false;
  }
  //북마크 저장하기.
  const bookmark = {
    name: nameValue,
    url: urlValue,
  };
  //push로 저장.
  bookmark.push(bookmark);
  //북마크 입력창은 리셋.
  bookmarkForm.reset();
  //다시 입력창에 커서 포커스
  websiteNameEl.focus();
  fetchBookmarks();
  localStorage.setItem('bookmarks', JSON.stringify(bookmarks));
}


// 제출 이벤트
bookmarkForm.addEventListener('submit', storeBookmark);

// 로드 될때 기본으로 fetch 실행해서 기본 북마크 생성.
fetchBookmarks();
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.