<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();
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.