mirror of
https://github.com/tcsenpai/obsidiangarden_netlify.git
synced 2025-06-04 12:00:02 +00:00
Implemented search box
This commit is contained in:
parent
fd92473178
commit
eee9e9aa5f
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,3 +2,5 @@ node_modules
|
||||
dist
|
||||
netlify/functions/search/data.json
|
||||
netlify/functions/search/index.json
|
||||
# Local Netlify folder
|
||||
.netlify
|
||||
|
@ -4,5 +4,6 @@ exports.ALL_NOTE_SETTINGS= [
|
||||
"dgShowBacklinks",
|
||||
"dgShowLocalGraph",
|
||||
"dgShowInlineTitle",
|
||||
"dgShowFileTree"
|
||||
"dgShowFileTree",
|
||||
"dgEnableSearch"
|
||||
];
|
@ -19,22 +19,26 @@
|
||||
{%endif%}
|
||||
{% endmacro %}
|
||||
|
||||
<div x-init="isDesktop = window.innerWidth>=1490;"
|
||||
<div x-init="isDesktop = (window.innerWidth>=1490) ? true: false;"
|
||||
x-on:resize.window="isDesktop = (window.innerWidth>=1490) ? true : false;"
|
||||
x-data="{isDesktop: true, showFilesMobile: false}">
|
||||
|
||||
<div x-show="!isDesktop">
|
||||
<div x-show.important="!isDesktop">
|
||||
{%include "components/filetreeNavbar.njk"%}
|
||||
</div>
|
||||
|
||||
<div x-show="showFilesMobile && !isDesktop" @click="showFilesMobile = false" style="display:none;" class="fullpage-overlay"></div>
|
||||
|
||||
<nav class="filetree-sidebar" style="display:none;" x-show="isDesktop || showFilesMobile">
|
||||
<nav class="filetree-sidebar" x-show.important="isDesktop || showFilesMobile">
|
||||
|
||||
<a href="/" style="text-decoration: none;">
|
||||
<h1 style="text-align:center;">{{meta.siteName}}</h1>
|
||||
</a>
|
||||
|
||||
{% if settings.dgEnableSearch === true%}
|
||||
<div style="display: flex; justify-content: center;">
|
||||
{% include "components/searchButton.njk" %}
|
||||
</div>
|
||||
{%endif%}
|
||||
<div class="folder" x-data="{isOpen: true}">
|
||||
{%- for fileOrFolderName, fileOrFolder in filetree -%}
|
||||
{{menuItem(fileOrFolderName, fileOrFolder, 0)}}
|
||||
|
@ -5,4 +5,8 @@
|
||||
<h1>{{meta.siteName}}</h1>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if settings.dgEnableSearch === true%}
|
||||
{% include "components/searchButton.njk" %}
|
||||
{%endif%}
|
||||
</nav>
|
@ -1,5 +1,10 @@
|
||||
<nav class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a href="/" style="text-decoration: none;">
|
||||
<h1>{{meta.siteName}}</h1>
|
||||
</a>
|
||||
</div>
|
||||
{% if settings.dgEnableSearch === true%}
|
||||
{% include "components/searchButton.njk" %}
|
||||
{%endif%}
|
||||
</nav>
|
7
src/site/_includes/components/searchButton.njk
Normal file
7
src/site/_includes/components/searchButton.njk
Normal file
@ -0,0 +1,7 @@
|
||||
<div class="search-button" onclick="toggleSearch()">
|
||||
<i class="fa fa-search"></i>
|
||||
<span>Search</span>
|
||||
<div style="font-size: 0.6rem; margin:0 10px 0 12px; text-align:center;" class="search-keys">
|
||||
CTRL + K
|
||||
</div>
|
||||
</div>
|
23
src/site/_includes/components/searchContainer.njk
Normal file
23
src/site/_includes/components/searchContainer.njk
Normal file
@ -0,0 +1,23 @@
|
||||
<div class="search-container" id="globalsearch" onclick="toggleSearch()">
|
||||
<div class="search-box">
|
||||
<input type="search" id="term" placeholder="Start typing...">
|
||||
<div id="search-results"></div>
|
||||
<footer class="search-box-footer">
|
||||
<div class="navigation-hint">
|
||||
<span>Enter to select</span>
|
||||
</div>
|
||||
|
||||
<div class="navigation-hint">
|
||||
<i class="fa fa-arrow-up" aria-hidden="true"></i>
|
||||
<i class="fa fa-arrow-down" aria-hidden="true"></i>
|
||||
<span>to navigate</span>
|
||||
</div>
|
||||
|
||||
<div class="navigation-hint">
|
||||
<span>ESC to close</span>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
{%include "components/searchScript.njk"%}
|
148
src/site/_includes/components/searchScript.njk
Normal file
148
src/site/_includes/components/searchScript.njk
Normal file
@ -0,0 +1,148 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', init, false);
|
||||
document.addEventListener('DOMContentLoaded', setCorrectShortcut, false);
|
||||
|
||||
window.toggleSearch=function(){
|
||||
if(document.getElementById('globalsearch').classList.contains('active')){
|
||||
document.getElementById('globalsearch').classList.remove('active');
|
||||
}else{
|
||||
|
||||
document.getElementById('globalsearch').classList.add('active');
|
||||
document.getElementById('term').focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function debounce(func, timeout = 300) {
|
||||
let timer;
|
||||
return(...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply();
|
||||
}, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
function setCorrectShortcut(){
|
||||
if(navigator.platform.toUpperCase().indexOf('MAC')>=0){
|
||||
document.querySelectorAll(".search-keys").forEach(x=>x.innerHTML = "⌘ + K");
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
|
||||
//open searchmodal when ctrl + k is pressed, cmd + k on mac
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
toggleSearch();
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
document.getElementById('globalsearch').classList.remove('active');
|
||||
}
|
||||
|
||||
//navigate search results with arrow keys
|
||||
if (document.getElementById('globalsearch').classList.contains('active')) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
let active = document.querySelector('.searchresult.active');
|
||||
if (active) {
|
||||
active.classList.remove('active');
|
||||
if (active.nextElementSibling) {
|
||||
active.nextElementSibling.classList.add('active');
|
||||
} else {
|
||||
document.querySelector('.searchresult').classList.add('active');
|
||||
}
|
||||
} else {
|
||||
document.querySelector('.searchresult').classList.add('active');
|
||||
}
|
||||
|
||||
let currentActive = document.querySelector('.searchresult.active');
|
||||
if (currentActive) {
|
||||
currentActive .scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
let active = document.querySelector('.searchresult.active');
|
||||
if (active) {
|
||||
active.classList.remove('active');
|
||||
if (active.previousElementSibling) {
|
||||
active.previousElementSibling.classList.add('active');
|
||||
} else {
|
||||
document.querySelectorAll('.searchresult').forEach((el) => {
|
||||
if (!el.nextElementSibling) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
document.querySelectorAll('.searchresult').forEach((el) => {
|
||||
if (el.nextElementSibling) {
|
||||
el.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let currentActive = document.querySelector('.searchresult.active');
|
||||
if (currentActive) {
|
||||
currentActive .scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
let active = document.querySelector('.searchresult.active');
|
||||
if (active) {
|
||||
window.location.href = active.querySelector("a").href;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
field = document.querySelector('#term');
|
||||
field.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
|
||||
debounce(()=>search())();
|
||||
}
|
||||
});
|
||||
resultsDiv = document.querySelector('#search-results');
|
||||
|
||||
const params = new URL(location.href).searchParams;
|
||||
if (params.get('q')) {
|
||||
field.setAttribute('value', params.get('q'));
|
||||
search();
|
||||
}
|
||||
}
|
||||
async function search() {
|
||||
let search = field
|
||||
.value
|
||||
.trim();
|
||||
if (!search)
|
||||
return;
|
||||
console.log(`search for ${search}`);
|
||||
let searchRequest = await fetch(`/api/search?term=${encodeURIComponent(search)}`);
|
||||
let results = await searchRequest.json();
|
||||
let resultsHTML = '';
|
||||
if (!results.length) {
|
||||
resultsHTML += `<p>No results for "${search}"</p>`;
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
return;
|
||||
}
|
||||
resultsHTML += '<ul>';
|
||||
// we need to add title, url from ref
|
||||
results.forEach(r => {
|
||||
resultsHTML += `<li class="searchresult"><a class="search-link" href="${r.url}">${r.title}</a><span onclick="window.location='${r.url}'">${r.content}</span></li>`;
|
||||
});
|
||||
resultsHTML += '</ul>';
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
}
|
||||
</script>
|
@ -1,8 +0,0 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', init, false);
|
||||
async function init() {
|
||||
const content = document.body.querySelector('.content');
|
||||
content.innerHTML = content.innerHTML.replaceAll(/\s\#(\w+)/gm,
|
||||
`<span class="cm-formatting cm-formatting-hashtag cm-hashtag cm-hashtag-begin cm-meta">#</span><span class="cm-hashtag cm-hashtag-end cm-meta"><a href="/search?q=%23$1~1">$1</a></span>`);
|
||||
}
|
||||
</script>
|
@ -7,7 +7,6 @@ permalink: "notes/{{ page.fileSlug | slugify }}/"
|
||||
<head>
|
||||
<title>{{ page.fileSlug }}</title>
|
||||
{%include "components/pageheader.njk"%}
|
||||
{%include "components/wrapTagsScript.njk"%}
|
||||
</head>
|
||||
<body class="theme-{{meta.baseTheme}} markdown-preview-view markdown-rendered markdown-preview-section">
|
||||
{%include "components/notegrowthhistory.njk"%}
|
||||
@ -18,7 +17,11 @@ permalink: "notes/{{ page.fileSlug | slugify }}/"
|
||||
{%include "components/filetree.njk"%}
|
||||
{% endif %}
|
||||
|
||||
<div class="content">
|
||||
{% if settings.dgEnableSearch === true %}
|
||||
{%include "components/searchContainer.njk"%}
|
||||
{% endif %}
|
||||
|
||||
<div class="content cm-s-obsidian">
|
||||
{% if settings.dgShowInlineTitle === true %}
|
||||
<h1>{{ page.fileSlug }}</h1>
|
||||
{% endif %}
|
||||
|
@ -11,7 +11,10 @@
|
||||
{%else%}
|
||||
{%include "components/filetree.njk"%}
|
||||
{% endif %}
|
||||
<div class="content">
|
||||
{% if settings.dgEnableSearch === true %}
|
||||
{%include "components/searchContainer.njk"%}
|
||||
{% endif %}
|
||||
<div class="content cm-s-obsidian">
|
||||
|
||||
{% if settings.dgShowInlineTitle === true %}
|
||||
<h1>{{ noteTitle }}</h1>
|
||||
|
@ -7,6 +7,6 @@ permalinkBypassOutputDir: true
|
||||
"title": {{post.fileSlug | jsonify | safe }},
|
||||
"date":"{{ post.date }}",
|
||||
"url":"{{ post.url }}",
|
||||
"content": {{ post.templateContent | striptags(true) | jsonify | safe }}
|
||||
"content": {{ post.templateContent | striptags(true) | link | jsonify | safe }}
|
||||
}{% if not loop.last %},{% endif %}
|
||||
{% endfor %}]
|
||||
|
@ -1,71 +0,0 @@
|
||||
---
|
||||
title: "Search"
|
||||
permalink: "search/"
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ collections.gardenEntry[0].fileSlug }}</title>
|
||||
{%include "components/pageheader.njk"%}
|
||||
</head>
|
||||
<body class="theme-{{meta.baseTheme}} markdown-preview-view">
|
||||
<div class="content cm-s-obsidian">
|
||||
{% if dgHomeLink !== false%}
|
||||
<a href="/">🏡 Back Home</a>
|
||||
{% endif %}
|
||||
<h1>Search</h1>
|
||||
<p>
|
||||
<label for="term">Type search query</label>
|
||||
<br/>
|
||||
<input type="search" id="term" placeholder="Start typing...">
|
||||
</p>
|
||||
|
||||
<div id="results"></div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', init, false);
|
||||
function debounce(func, timeout = 500){
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
async function init() {
|
||||
field = document.querySelector('#term');
|
||||
field.addEventListener('keydown', debounce(() => search()));
|
||||
resultsDiv = document.querySelector('#results');
|
||||
|
||||
const params = new URL(location.href).searchParams;
|
||||
if (params.get('q')) {
|
||||
field.setAttribute('value', params.get('q'));
|
||||
search();
|
||||
}
|
||||
}
|
||||
async function search() {
|
||||
let search = field.value.trim();
|
||||
if(!search) return;
|
||||
console.log(`search for ${search}`);
|
||||
let searchRequest = await fetch(`/api/search?term=${encodeURIComponent(search)}`);
|
||||
let results = await searchRequest.json();
|
||||
let resultsHTML = '<p><strong>Search Results</strong></p>';
|
||||
if(!results.length) {
|
||||
resultsHTML += '<p>Sorry, there were no results.</p>';
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
return;
|
||||
}
|
||||
resultsHTML += '<ul>';
|
||||
// we need to add title, url from ref
|
||||
results.forEach(r => {
|
||||
resultsHTML += `<li><a href="${r.url}">${ r.title }</a> <span>${r.content}</span></li>`;
|
||||
});
|
||||
resultsHTML += '</ul>';
|
||||
resultsDiv.innerHTML = resultsHTML;
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -3,6 +3,9 @@
|
||||
* MODIFY THE custom-style.scss FILE INSTEAD.
|
||||
***/
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.content {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
@ -162,6 +165,9 @@ ul.task-list {
|
||||
right: 0;
|
||||
z-index: 3;
|
||||
padding-left: var(--file-margins);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.navbar-inner {
|
||||
display: flex;
|
||||
@ -198,6 +204,113 @@ ul.task-list {
|
||||
}
|
||||
|
||||
|
||||
.search-container {
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 15;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-container.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
transform: translateY(200px);
|
||||
background-color: var(--background-primary);
|
||||
max-width: 80%;
|
||||
width: 900px;
|
||||
border-radius: 15px;
|
||||
padding: 10px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 2rem;
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-primary);
|
||||
|
||||
}
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#search-results {
|
||||
margin-top: 20px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
#search-results ul {
|
||||
padding-inline-start: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#search-results .searchresult {
|
||||
margin-bottom: 15px;
|
||||
list-style: none;
|
||||
background-color: var(--background-secondary);
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.searchresult.active {
|
||||
border: 2px solid var(--text-accent);
|
||||
}
|
||||
|
||||
.search-box-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.navigation-hint {
|
||||
font-size: 1rem;
|
||||
color: var(--text-secondary);
|
||||
margin-right: 20px;
|
||||
}
|
||||
.search-link {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
background-color: var(--background-primary);
|
||||
border-radius: 20px;
|
||||
height: 2em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 150px;
|
||||
margin: 10px 65px;
|
||||
cursor: pointer;
|
||||
>span{
|
||||
padding: 3px 3px 3px 15px;
|
||||
}
|
||||
>i{
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
border: 1px solid var(--text-accent);
|
||||
}
|
||||
}
|
||||
|
||||
div[class*="language-ad-"],
|
||||
div[class*="callout-"] {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
Loading…
x
Reference in New Issue
Block a user