Skip to content

Commit d6335c4

Browse files
authored
feat/src
fix: router 및 경로 수정, 검색기능 추가
2 parents d1bf8a3 + 5c25f79 commit d6335c4

File tree

6 files changed

+181
-27
lines changed

6 files changed

+181
-27
lines changed

.vscode/settings.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
{
22
"liveServer.settings.port": 5502,
3-
"cSpell.words": [
4-
"nygkshpdh",
5-
"resizer"
6-
]
7-
}
3+
"cSpell.words": ["nygkshpdh", "resizer"]
4+
}

index.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta property="og:title" content="Notion Clone" />
7+
<meta
8+
property="og:description"
9+
content="Notion Clone 웹앱으로 쉽게 문서를 관리하세요."
10+
/>
611
<title>Notion Clone</title>
712
<!-- 노션은 새 페이지 제목이 타이틀로 들어감 -->
813
<link rel="stylesheet" href="./src/styles/main.css" />
@@ -26,11 +31,11 @@ <h2 class="sidebar-title">전공무관</h2>
2631
</div>
2732
</div>
2833
<div class="sidebar-features">
29-
<div class="sidebar-feature">
34+
<div class="sidebar-feature search">
3035
<div>검색</div>
3136
</div>
3237
<div class="sidebar-feature">
33-
<a href="./"></a>
38+
<a href="../"></a>
3439
</div>
3540
</div>
3641
<h3 class="myPage">내 페이지</h3>
@@ -43,6 +48,7 @@ <h3 class="myPage">내 페이지</h3>
4348
<!-- 편집기 영역 -->
4449
<main id="content"></main>
4550
</div>
51+
<div class="search-popup"></div>
4652
<script type="module" src="./src/index.js"></script>
4753
</body>
4854
</html>

src/components/Editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class Page {
2121
this.pageHeaderUser = new Dom(
2222
"div",
2323
"page-header_user",
24-
"전공 무관 페이지",
24+
"전공무관 페이지",
2525
)
2626
const lockSvg = `<svg xmlns="http://www.w3.org/2000/svg"
2727
viewBox="0 0 20 20"

src/components/Sidebar.js

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,88 @@ function toggleChildren(ul, toggleBtn) {
8888
toggleBtn.style.fontSize = isClosed ? "1rem" : "0.8rem"
8989
}
9090

91+
/* -------------------검색--------------------- */
92+
const searchFeature = document.querySelector(".sidebar-feature.search")
93+
const searchPopup = document.querySelector(".search-popup")
94+
95+
searchFeature.addEventListener("click", (e) => {
96+
e.stopPropagation()
97+
searchPopup.style.display =
98+
searchPopup.style.display === "block" ? "none" : "block"
99+
if (searchPopup.style.display === "block") {
100+
searchInput.focus()
101+
renderSearchResults(cachedDocuments)
102+
}
103+
})
104+
105+
// 검색 팝업 내부 HTML
106+
searchPopup.innerHTML = `
107+
<input id="search-input" type="text" placeholder="제목 검색..." />
108+
<ul class="search-results"></ul>
109+
`
110+
111+
const searchInput = searchPopup.querySelector("input")
112+
const searchResults = searchPopup.querySelector(".search-results")
113+
114+
// 문서 데이터 가져오기
115+
let cachedDocuments = []
116+
117+
async function cacheDocuments() {
118+
cachedDocuments = await fetchDocuments()
119+
}
120+
121+
cacheDocuments()
122+
123+
// 결과 렌더링 함수
124+
function renderSearchResults(docs, term = "") {
125+
searchResults.innerHTML = ""
126+
127+
const results = []
128+
129+
function flatten(docs) {
130+
for (const doc of docs) {
131+
results.push(doc)
132+
if (doc.documents) flatten(doc.documents)
133+
}
134+
}
135+
136+
flatten(docs)
137+
138+
const filtered = term
139+
? results.filter((doc) =>
140+
doc.title.toLowerCase().includes(term.toLowerCase()),
141+
)
142+
: results
143+
144+
filtered.forEach((doc) => {
145+
const li = document.createElement("li")
146+
li.textContent = doc.title
147+
li.dataset.id = doc.id
148+
li.addEventListener("click", () => {
149+
searchInput.value = doc.title
150+
searchPopup.style.display = "none"
151+
openDocument(doc.id)
152+
setActiveDocumentLi(doc.id)
153+
})
154+
searchResults.appendChild(li)
155+
})
156+
}
157+
158+
// 입력 시 필터링
159+
searchInput.addEventListener("input", (e) => {
160+
const term = e.target.value.trim()
161+
renderSearchResults(cachedDocuments, term)
162+
})
163+
164+
// 외부 클릭 시 팝업 닫기
165+
document.addEventListener("mousedown", (e) => {
166+
if (!searchPopup.contains(e.target) && !searchFeature.contains(e.target)) {
167+
searchPopup.style.display = "none"
168+
}
169+
})
170+
91171
/* -------------------------------------------- */
92-
/* 2. 트리 렌더링 */
172+
/* 1. 트리 렌더링 */
93173
/* -------------------------------------------- */
94174

95175
function renderTree(documents, parentElement = sidebarTree, depth = 0) {
@@ -129,10 +209,6 @@ function renderTree(documents, parentElement = sidebarTree, depth = 0) {
129209
const toggleBtn = document.createElement("button")
130210
toggleBtn.className = "toggle-btn"
131211
toggleBtn.type = "button"
132-
toggleBtn.style.backgroundImage = "url(/src/img/toggle.svg)"
133-
toggleBtn.style.backgroundRepeat = "no-repeat"
134-
toggleBtn.style.backgroundSize = "cover"
135-
toggleBtn.style.backgroundPosition = "center"
136212
toggleBtn.style.marginRight = "6px"
137213
toggleBtn.style.fontSize = "0.8rem"
138214
toggleBtn.style.lineHeight = "1"
@@ -247,7 +323,7 @@ function renderTree(documents, parentElement = sidebarTree, depth = 0) {
247323
}
248324

249325
/* -------------------------------------------- */
250-
/* 3. 루트 Document 생성 */
326+
/* 2. 루트 Document 생성 */
251327
/* -------------------------------------------- */
252328
createRootBtn.addEventListener("click", async () => {
253329
const newDoc = await createDocument("새 문서", null)
@@ -256,7 +332,7 @@ createRootBtn.addEventListener("click", async () => {
256332
})
257333

258334
/* -------------------------------------------- */
259-
/* 4. 초기 로드 */
335+
/* 3. 초기 로드 */
260336
/* -------------------------------------------- */
261337
async function loadTree(targetDocId = null) {
262338
const documents = await fetchDocuments()

src/router.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { EditPage } from "./components/Editor.js"
22

33
const $content = document.getElementById("content")
44

5+
let BASE_PATH = ""
6+
57
// 라우팅 처리 함수
68
function matchRoute(path) {
79
if (path === "/") return routes["/"]
@@ -25,7 +27,7 @@ const routes = {
2527
$content.innerHTML = "<h1>문서 목록</h1>"
2628
// 예시: 목록을 클릭하면 특정 id 문서로 이동
2729
const link = document.createElement("a")
28-
link.href = "/documents/123"
30+
link.href = `${BASE_PATH}/documents/123`
2931
link.textContent = "문서 123 보기"
3032
link.addEventListener("click", (e) => {
3133
e.preventDefault()
@@ -44,7 +46,7 @@ const routes = {
4446
}
4547

4648
export function navigateTo(path) {
47-
history.pushState({ path }, "", path)
49+
history.pushState({ path }, "", `${BASE_PATH}${path}`)
4850
loadContent(path)
4951
}
5052

@@ -61,5 +63,27 @@ window.addEventListener("popstate", (e) => {
6163

6264
// 첫 로딩
6365
window.addEventListener("DOMContentLoaded", () => {
64-
loadContent(location.pathname)
66+
const pathSegments = location.pathname.split("/").filter(Boolean)
67+
if (location.hostname.endsWith("github.io") && pathSegments.length > 0) {
68+
BASE_PATH = `/${pathSegments[0]}`
69+
} else {
70+
BASE_PATH = ""
71+
}
72+
73+
let initialBrowserPath = location.pathname
74+
let pathForRouter = "/"
75+
76+
if (initialBrowserPath.startsWith(BASE_PATH)) {
77+
pathForRouter = initialBrowserPath.substring(BASE_PATH.length)
78+
if (pathForRouter === "") {
79+
pathForRouter = "/"
80+
}
81+
}
82+
const restoredPath = history.state?.path
83+
const finalPathToLoad =
84+
restoredPath && restoredPath.startsWith(BASE_PATH)
85+
? restoredPath.substring(BASE_PATH.length) || "/"
86+
: pathForRouter
87+
88+
loadContent(finalPathToLoad)
6589
})

src/styles/main.css

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@
77
/* 사이드바 오픈 버튼 */
88
.open-btn {
99
display: none;
10-
background: url(/src/img/sidemenu.svg) no-repeat center/cover;
10+
background: url(../img/sidemenu.svg) no-repeat center/cover;
1111
width: 30px;
1212
height: 30px;
1313
margin: 10px;
1414
}
1515

1616
.open-btn:hover {
17-
background: url(/src/img/arrowopen.svg) no-repeat center/cover;
17+
background: url(../img/arrowopen.svg) no-repeat center/cover;
1818
transition: all 0.2s;
1919
}
2020

2121
/* 사이드바 */
2222
.sidebar {
23-
width: 250px;
23+
position: relative;
24+
width: 270px;
2425
/* 최소 사이즈 제한 */
2526
min-width: 245px;
2627
/* 최대 사이즈 제한 */
@@ -61,7 +62,7 @@
6162
}
6263

6364
.create-root-btn {
64-
background: url(/src/img/headercreate.svg) no-repeat center/cover;
65+
background: url(../img/headercreate.svg) no-repeat center/cover;
6566
}
6667

6768
.create-root-btn:hover,
@@ -70,7 +71,7 @@
7071
}
7172

7273
.hide-btn {
73-
background: url(/src/img/arrowhide.svg) no-repeat center/cover;
74+
background: url(../img/arrowhide.svg) no-repeat center/cover;
7475
}
7576

7677
.sidebar-features {
@@ -101,7 +102,7 @@
101102
width: 21px;
102103
height: 21px;
103104
margin-right: 6px;
104-
background: url("/src/img/search.svg") no-repeat center/cover;
105+
background: url(../img/search.svg) no-repeat center/cover;
105106
}
106107

107108
/* 두 번째 sidebar-feature (홈 아이콘) */
@@ -111,7 +112,7 @@
111112
position: absolute;
112113
width: 21px;
113114
height: 21px;
114-
background: url("/src/img/home.svg") no-repeat center/cover;
115+
background: url(../img/home.svg) no-repeat center/cover;
115116
}
116117

117118
.sidebar-feature a {
@@ -211,11 +212,11 @@ textarea {
211212

212213
/* delete btn */
213214
.delete-btn {
214-
background: url(/src/img/delete.svg) no-repeat center/cover;
215+
background: url(../img/delete.svg) no-repeat center/cover;
215216
}
216217

217218
.add-btn {
218-
background: url(/src/img/plus.svg) no-repeat center/cover;
219+
background: url(../img/plus.svg) no-repeat center/cover;
219220
}
220221

221222
.delete-btn:hover,
@@ -258,9 +259,59 @@ textarea {
258259
/* toggle 버튼 hover */
259260
.toggle-btn {
260261
transition: all 0.15s ease;
262+
background: url(../img/toggle.svg) no-repeat center/cover;
261263
}
262264

263265
.toggle-btn:hover {
264266
background-color: #dddddd;
265267
border-radius: 2px;
268+
}
269+
270+
/* search-popup */
271+
.search-popup {
272+
position: absolute;
273+
top: 25%;
274+
left: 50%;
275+
transform: translate(-50%, -50%);
276+
width: 800px;
277+
min-width: 500px;
278+
max-height: 1000px;
279+
overflow-y: auto;
280+
background-color: #fff;
281+
border: 1px solid #ccc;
282+
border-radius: 6px;
283+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
284+
display: none;
285+
z-index: 1000;
286+
padding: 5px;
287+
}
288+
289+
.search-popup input {
290+
width: 100%;
291+
height: 50px;
292+
font-size: 2.4rem;
293+
padding: 5px 8px 5px 50px;
294+
background: url(../img/search.svg) no-repeat;
295+
background-position: 10px center;
296+
background-size: 30px 30px;
297+
margin-bottom: 5px;
298+
border: none;
299+
border-radius: 4px;
300+
}
301+
302+
.search-popup ul {
303+
list-style: none;
304+
margin: 0;
305+
padding: 0;
306+
}
307+
308+
.search-popup li {
309+
padding: 5px 8px;
310+
cursor: pointer;
311+
border-radius: 4px;
312+
font-size: 1.6rem;
313+
}
314+
315+
.search-popup li:hover {
316+
background-color: #eee;
266317
}

0 commit comments

Comments
 (0)