diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2022-07-22 23:27:00 +0200 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2022-07-22 23:27:00 +0200 |
| commit | 45c89b984d3f2b3bf97c8c5ee52e62aa6a4af6dc (patch) | |
| tree | 098417a178a0fe4e054722a84e696d4574c98b16 /template | |
| parent | 773829d1848f1f18ef587ed5608dd72483c9be53 (diff) | |
| download | mitjafelicijan.com-45c89b984d3f2b3bf97c8c5ee52e62aa6a4af6dc.tar.gz | |
Added front end search
Diffstat (limited to 'template')
| -rwxr-xr-x | template/_includes.html | 3 | ||||
| -rwxr-xr-x | template/_navigation.html | 2 | ||||
| -rw-r--r-- | template/_search.html | 6 | ||||
| -rwxr-xr-x | template/index.html | 4 | ||||
| -rwxr-xr-x | template/post.html | 2 | ||||
| -rwxr-xr-x | template/script.js | 145 | ||||
| -rwxr-xr-x | template/style.css | 145 |
7 files changed, 217 insertions, 90 deletions
diff --git a/template/_includes.html b/template/_includes.html index d16fa3d..8102a35 100755 --- a/template/_includes.html +++ b/template/_includes.html | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | <!-- user code --> | 1 | <!-- user code --> |
| 2 | 2 | ||
| 3 | <script src="/script.js?v=2022-01-30-01" async></script> | 3 | <script src="/assets/general/elasticlunr.min.js"></script> |
| 4 | <script src="/script.js?v=2022-07-22-02" async></script> | ||
diff --git a/template/_navigation.html b/template/_navigation.html index 85290a3..45a2a9e 100755 --- a/template/_navigation.html +++ b/template/_navigation.html | |||
| @@ -13,6 +13,8 @@ | |||
| 13 | <a href="/books.html">Books</a> | 13 | <a href="/books.html">Books</a> |
| 14 | 14 | ||
| 15 | <a href="/feed.rss" itemprop="url">RSS</a> | 15 | <a href="/feed.rss" itemprop="url">RSS</a> |
| 16 | |||
| 17 | <a class="cursor search-trigger">Search</a> | ||
| 16 | </nav> | 18 | </nav> |
| 17 | </header> | 19 | </header> |
| 18 | </div> | 20 | </div> |
diff --git a/template/_search.html b/template/_search.html new file mode 100644 index 0000000..997b35c --- /dev/null +++ b/template/_search.html | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | <div class="blur hidden"></div> | ||
| 2 | |||
| 3 | <section class="search-form hidden"> | ||
| 4 | <input type="search" placeholder="Search ..."> | ||
| 5 | <ul></ul> | ||
| 6 | </section> | ||
diff --git a/template/index.html b/template/index.html index 8c1cc6e..cb334e1 100755 --- a/template/index.html +++ b/template/index.html | |||
| @@ -55,6 +55,10 @@ | |||
| 55 | 55 | ||
| 56 | {{template "_footer.html"}} | 56 | {{template "_footer.html"}} |
| 57 | 57 | ||
| 58 | {{template "_includes.html"}} | ||
| 59 | |||
| 60 | {{template "_search.html"}} | ||
| 61 | |||
| 58 | </body> | 62 | </body> |
| 59 | 63 | ||
| 60 | </html> | 64 | </html> |
diff --git a/template/post.html b/template/post.html index 564ed09..cf80b22 100755 --- a/template/post.html +++ b/template/post.html | |||
| @@ -69,6 +69,8 @@ | |||
| 69 | 69 | ||
| 70 | {{template "_includes.html"}} | 70 | {{template "_includes.html"}} |
| 71 | 71 | ||
| 72 | {{template "_search.html"}} | ||
| 73 | |||
| 72 | {{template "_libraries.html"}} | 74 | {{template "_libraries.html"}} |
| 73 | 75 | ||
| 74 | </body> | 76 | </body> |
diff --git a/template/script.js b/template/script.js index 0e1291c..3c58403 100755 --- a/template/script.js +++ b/template/script.js | |||
| @@ -25,79 +25,88 @@ window.addEventListener('load', async () => { | |||
| 25 | }); | 25 | }); |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | // comments code | 28 | // Search functionality |
| 29 | const commentsEndpoint = 'https://mitjafelicijan.com/comments-api'; | 29 | |
| 30 | const commentsPlaceholder = document.querySelector('.comments'); | 30 | window.index = null; |
| 31 | 31 | ||
| 32 | if (commentsPlaceholder) { | 32 | const response = await fetch('/feed.json'); |
| 33 | const guid = commentsPlaceholder.dataset.guid; | 33 | const feed = await response.json(); |
| 34 | const name = commentsPlaceholder.querySelector('input'); | 34 | |
| 35 | const comment = commentsPlaceholder.querySelector('textarea'); | 35 | window.index = elasticlunr(function () { |
| 36 | const submit = commentsPlaceholder.querySelector('button'); | 36 | this.addField('title'); |
| 37 | const comments = commentsPlaceholder.querySelector('ul'); | 37 | this.addField('body'); |
| 38 | 38 | this.setRef('id'); | |
| 39 | if (guid) { | 39 | }); |
| 40 | await readAndRenderComments(guid, comments); | 40 | |
| 41 | 41 | for (const item of feed.items) { | |
| 42 | submit.addEventListener('click', async() => { | 42 | item.id = item.url; |
| 43 | submit.disabled = true; | 43 | window.index.addDoc({ |
| 44 | await writeComments(guid, name.value, comment.value); | 44 | id: item.url, |
| 45 | 45 | title: item.title, | |
| 46 | submit.disabled = false; | 46 | body: item.content_html, |
| 47 | name.value = ''; | 47 | url: item.url, |
| 48 | comment.value = ''; | 48 | }); |
| 49 | |||
| 50 | await readAndRenderComments(guid, comments); | ||
| 51 | }); | ||
| 52 | } | ||
| 53 | } | 49 | } |
| 54 | 50 | ||
| 55 | async function writeComments(guid, name, comment) { | 51 | const blur = document.querySelector('.blur'); |
| 56 | const response = await fetch(commentsEndpoint, { | 52 | const searchForm = document.querySelector('.search-form'); |
| 57 | method: 'POST', | 53 | const searchResultsList = document.querySelector('.search-form ul'); |
| 58 | headers: { | 54 | |
| 59 | 'Accept': 'application/json', | 55 | function showSearchModal() { |
| 60 | 'Content-Type': 'application/json' | 56 | blur.classList.remove('hidden'); |
| 61 | }, | 57 | searchForm.classList.remove('hidden'); |
| 62 | body: JSON.stringify({ | 58 | |
| 63 | action: 'write', | 59 | // Clear search input. |
| 64 | guid, | 60 | searchForm.querySelector('input').value = ''; |
| 65 | name, | 61 | |
| 66 | comment, | 62 | // We need to clear the list before opening modal. |
| 67 | }) | 63 | searchResultsList.innerHTML = ''; |
| 68 | }); | 64 | |
| 65 | // Focus on search input. | ||
| 66 | searchForm.querySelector('input').focus(); | ||
| 69 | } | 67 | } |
| 70 | 68 | ||
| 71 | async function readAndRenderComments(guid, commentsPlaceholder) { | 69 | document.querySelector('.search-trigger').addEventListener('click', async (evt) => { |
| 72 | const response = await fetch(commentsEndpoint, { | 70 | showSearchModal(); |
| 73 | method: 'POST', | 71 | }); |
| 74 | headers: { | 72 | |
| 75 | 'Accept': 'application/json', | 73 | document.onkeydown = function (e) { |
| 76 | 'Content-Type': 'application/json' | 74 | // Show search modal on F key. |
| 77 | }, | 75 | if (blur.classList.contains('hidden')) { |
| 78 | body: JSON.stringify({ | 76 | if (e.key === 'f') { |
| 79 | action: 'read', | 77 | setTimeout(() => { |
| 80 | guid, | 78 | showSearchModal(); |
| 81 | }) | 79 | }, 100); |
| 82 | }); | 80 | } |
| 81 | } | ||
| 83 | 82 | ||
| 84 | // remove all existing comments from list | 83 | // Hide search modal on escape key. |
| 85 | commentsPlaceholder.innerHTML = ''; | 84 | if (!blur.classList.contains('hidden')) { |
| 86 | 85 | if (e.key === 'Escape') { | |
| 87 | const commentList = await response.json(); | 86 | blur.classList.add('hidden'); |
| 88 | for (const comment of commentList.reverse()) { | 87 | searchForm.classList.add('hidden'); |
| 89 | const date = new Date(comment.date).toLocaleDateString('en-US', { | 88 | } |
| 90 | year: 'numeric', | ||
| 91 | month: 'long', | ||
| 92 | day: 'numeric', | ||
| 93 | hour: 'numeric', | ||
| 94 | minute: 'numeric' | ||
| 95 | }); | ||
| 96 | |||
| 97 | const commentElement = document.createElement('li'); | ||
| 98 | commentElement.innerHTML = `<p><b>${comment.name}</b> - ${date}<p><p>${comment.comment}<p><hr>`; | ||
| 99 | commentsPlaceholder.appendChild(commentElement); | ||
| 100 | } | 89 | } |
| 101 | } | 90 | }; |
| 91 | |||
| 92 | blur.addEventListener('click', async (evt) => { | ||
| 93 | evt.target.classList.add('hidden'); | ||
| 94 | searchForm.classList.add('hidden'); | ||
| 95 | }); | ||
| 96 | |||
| 97 | document.querySelector('.search-form input').addEventListener('keyup', async (evt) => { | ||
| 98 | // Perform search. | ||
| 99 | const searchResults = window.index.search(evt.target.value); | ||
| 100 | |||
| 101 | // We need to clear the list before adding new results. | ||
| 102 | searchResultsList.innerHTML = ''; | ||
| 103 | |||
| 104 | // Loop through the results and add them to the list. | ||
| 105 | for (const result of searchResults.slice(0, 9)) { | ||
| 106 | const listItem = document.createElement('li'); | ||
| 107 | listItem.innerHTML = `<a href="${result.doc.url}">${result.doc.title}</a>`; | ||
| 108 | searchResultsList.appendChild(listItem); | ||
| 109 | } | ||
| 110 | }); | ||
| 102 | 111 | ||
| 103 | }); | 112 | }); |
diff --git a/template/style.css b/template/style.css index af0d7bc..ebb2b26 100755 --- a/template/style.css +++ b/template/style.css | |||
| @@ -29,7 +29,7 @@ body { | |||
| 29 | background: white; | 29 | background: white; |
| 30 | /*font-family: 'Times New Roman', Times, serif;*/ | 30 | /*font-family: 'Times New Roman', Times, serif;*/ |
| 31 | /*font-family: 'IBM Plex Sans', sans-serif;*/ | 31 | /*font-family: 'IBM Plex Sans', sans-serif;*/ |
| 32 | font-family: "SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif; | 32 | font-family: "SF Pro Text", "SF Pro Icons", "Helvetica Neue", "Helvetica", "Arial", sans-serif; |
| 33 | color: var(--base-color); | 33 | color: var(--base-color); |
| 34 | font-size: var(--base-font-size); | 34 | font-size: var(--base-font-size); |
| 35 | line-height: var(--base-line-heigh); | 35 | line-height: var(--base-line-heigh); |
| @@ -54,6 +54,10 @@ hr { | |||
| 54 | color: #000; | 54 | color: #000; |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | .cursor { | ||
| 58 | cursor: pointer; | ||
| 59 | } | ||
| 60 | |||
| 57 | /* width of the page */ | 61 | /* width of the page */ |
| 58 | 62 | ||
| 59 | .wrapper { | 63 | .wrapper { |
| @@ -74,12 +78,35 @@ a:hover { | |||
| 74 | 78 | ||
| 75 | /* headings */ | 79 | /* headings */ |
| 76 | 80 | ||
| 77 | h1 { font-size: 220%; line-height: 1.2em; } | 81 | h1 { |
| 78 | h2 { font-size: 180%; line-height: 1.2em; } | 82 | font-size: 220%; |
| 79 | h3 { font-size: 160%; line-height: 1.2em; } | 83 | line-height: 1.2em; |
| 80 | h4 { font-size: 140%; line-height: 1.2em; } | 84 | } |
| 81 | h5 { font-size: 120%; line-height: 1.2em; } | 85 | |
| 82 | h6 { font-size: 100%; line-height: 1.2em; } | 86 | h2 { |
| 87 | font-size: 180%; | ||
| 88 | line-height: 1.2em; | ||
| 89 | } | ||
| 90 | |||
| 91 | h3 { | ||
| 92 | font-size: 160%; | ||
| 93 | line-height: 1.2em; | ||
| 94 | } | ||
| 95 | |||
| 96 | h4 { | ||
| 97 | font-size: 140%; | ||
| 98 | line-height: 1.2em; | ||
| 99 | } | ||
| 100 | |||
| 101 | h5 { | ||
| 102 | font-size: 120%; | ||
| 103 | line-height: 1.2em; | ||
| 104 | } | ||
| 105 | |||
| 106 | h6 { | ||
| 107 | font-size: 100%; | ||
| 108 | line-height: 1.2em; | ||
| 109 | } | ||
| 83 | 110 | ||
| 84 | h1[itemtype="headline"] { | 111 | h1[itemtype="headline"] { |
| 85 | padding-bottom: 0; | 112 | padding-bottom: 0; |
| @@ -95,12 +122,15 @@ table { | |||
| 95 | width: 100%; | 122 | width: 100%; |
| 96 | } | 123 | } |
| 97 | 124 | ||
| 98 | table, th, td { | 125 | table, |
| 126 | th, | ||
| 127 | td { | ||
| 99 | border: 1px solid black; | 128 | border: 1px solid black; |
| 100 | text-align: left; | 129 | text-align: left; |
| 101 | } | 130 | } |
| 102 | 131 | ||
| 103 | th, td { | 132 | th, |
| 133 | td { | ||
| 104 | padding: 5px 10px; | 134 | padding: 5px 10px; |
| 105 | } | 135 | } |
| 106 | 136 | ||
| @@ -268,7 +298,7 @@ code { | |||
| 268 | font-weight: 500; | 298 | font-weight: 500; |
| 269 | } | 299 | } |
| 270 | 300 | ||
| 271 | pre > code { | 301 | pre>code { |
| 272 | background: unset; | 302 | background: unset; |
| 273 | padding: unset; | 303 | padding: unset; |
| 274 | 304 | ||
| @@ -285,7 +315,8 @@ pre { | |||
| 285 | margin-block-end: 40px; | 315 | margin-block-end: 40px; |
| 286 | } | 316 | } |
| 287 | 317 | ||
| 288 | img, video { | 318 | img, |
| 319 | video { | ||
| 289 | max-width: 100%; | 320 | max-width: 100%; |
| 290 | margin: 30px auto; | 321 | margin: 30px auto; |
| 291 | display: block; | 322 | display: block; |
| @@ -357,14 +388,14 @@ audio { | |||
| 357 | 388 | ||
| 358 | /* comments */ | 389 | /* comments */ |
| 359 | 390 | ||
| 360 | .comments input{ | 391 | .comments input { |
| 361 | width: 100%; | 392 | width: 100%; |
| 362 | font: var(--comment-form-font); | 393 | font: var(--comment-form-font); |
| 363 | border: 1px solid #bbb; | 394 | border: 1px solid #bbb; |
| 364 | padding: 5px; | 395 | padding: 5px; |
| 365 | } | 396 | } |
| 366 | 397 | ||
| 367 | .comments textarea{ | 398 | .comments textarea { |
| 368 | width: 100%; | 399 | width: 100%; |
| 369 | height: 100px; | 400 | height: 100px; |
| 370 | resize: vertical; | 401 | resize: vertical; |
| @@ -384,13 +415,69 @@ audio { | |||
| 384 | border-top: initial !important; | 415 | border-top: initial !important; |
| 385 | } | 416 | } |
| 386 | 417 | ||
| 418 | /* search form */ | ||
| 419 | |||
| 420 | .search-form { | ||
| 421 | position: fixed; | ||
| 422 | top: 120px; | ||
| 423 | left: 50%; | ||
| 424 | margin-left: -250px; | ||
| 425 | width: 500px; | ||
| 426 | padding: 30px; | ||
| 427 | background: #eee; | ||
| 428 | border-radius: 5px; | ||
| 429 | } | ||
| 430 | |||
| 431 | .blur { | ||
| 432 | position: fixed; | ||
| 433 | top: 0; | ||
| 434 | left: 0; | ||
| 435 | right: 0; | ||
| 436 | bottom: 0; | ||
| 437 | backdrop-filter: blur(15px); | ||
| 438 | } | ||
| 439 | |||
| 440 | .hidden { | ||
| 441 | display: none; | ||
| 442 | } | ||
| 443 | |||
| 444 | .search-form input { | ||
| 445 | width: 100%; | ||
| 446 | margin-bottom: 20px; | ||
| 447 | border: 1px solid #ffffff; | ||
| 448 | padding: 5px 10px; | ||
| 449 | border-radius: 3px; | ||
| 450 | outline: none; | ||
| 451 | } | ||
| 452 | |||
| 453 | .search-form ul { | ||
| 454 | list-style-type: none; | ||
| 455 | padding: 0; | ||
| 456 | margin: 0; | ||
| 457 | } | ||
| 458 | |||
| 459 | .search-form ul li { | ||
| 460 | margin-bottom: 5px; | ||
| 461 | } | ||
| 462 | |||
| 387 | /* responsive */ | 463 | /* responsive */ |
| 388 | 464 | ||
| 389 | @media only screen and (max-width: 960px) { | 465 | @media only screen and (max-width: 960px) { |
| 390 | main { padding: 0 20px; } | 466 | main { |
| 391 | footer { padding: 0 20px; } | 467 | padding: 0 20px; |
| 392 | h1[itemtype="headline"] { font-size: 220%; } | 468 | } |
| 393 | .navigation header { padding: 0 20px; } | 469 | |
| 470 | footer { | ||
| 471 | padding: 0 20px; | ||
| 472 | } | ||
| 473 | |||
| 474 | h1[itemtype="headline"] { | ||
| 475 | font-size: 220%; | ||
| 476 | } | ||
| 477 | |||
| 478 | .navigation header { | ||
| 479 | padding: 0 20px; | ||
| 480 | } | ||
| 394 | 481 | ||
| 395 | article img { | 482 | article img { |
| 396 | max-width: 100%; | 483 | max-width: 100%; |
| @@ -400,10 +487,26 @@ audio { | |||
| 400 | } | 487 | } |
| 401 | 488 | ||
| 402 | @media only screen and (max-width: 600px) { | 489 | @media only screen and (max-width: 600px) { |
| 403 | .navigation header { display: block; } | 490 | .navigation header { |
| 404 | .navigation header h3 { text-align: center; margin-bottom: 10px; } | 491 | display: block; |
| 405 | .navigation header nav { text-align: center; } | 492 | } |
| 406 | .post-list li a h2 { font-weight: 500; } | 493 | |
| 494 | .navigation header h3 { | ||
| 495 | text-align: center; | ||
| 496 | margin-bottom: 10px; | ||
| 497 | } | ||
| 498 | |||
| 499 | .navigation header nav { | ||
| 500 | text-align: center; | ||
| 501 | } | ||
| 502 | |||
| 503 | .post-list li a h2 { | ||
| 504 | font-weight: 500; | ||
| 505 | } | ||
| 506 | |||
| 507 | .search-trigger { | ||
| 508 | display: none; | ||
| 509 | } | ||
| 407 | } | 510 | } |
| 408 | 511 | ||
| 409 | /* light/dark mode */ | 512 | /* light/dark mode */ |
