aboutsummaryrefslogtreecommitdiff
path: root/themes/simple/layouts/partials/search.html
blob: 3e317536f7301666d8ac551e5e670a79b6431f9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<section class="search-modal mb-10 hidden">
  <input class="bg-gray-100 w-full px-3 py-2 rounded outline-none" type="search" placeholder="Search here...">
  <section class="results bg-white border border-gray-200 shadow-md p-2 flex flex-col gap-2 rounded mt-4 hidden"></section>
</section>

<script src="https://unpkg.com/lunr/lunr.js"></script>
<script>
  (async function() {
      const debounceDelay = 700;

      // Fetch search index generated by Hugo.
      const req = await fetch('/index.json');
      window.searchDocuments = await req.json();

      window.searchIndex = lunr(function() {
          this.ref('permalink');
          this.field('title', { boost: 20 });
          this.field('tags', { boost: 10 });
          this.field('summary', { boost: 1 });

          window.searchDocuments.forEach(function(doc) {
              this.add(doc);
          }, this);

          console.log('Search index processed...');
      })

      // Connect DOM and search the index.
      let cachedSearchTerm = null;
	    const searchModal = document.querySelector('.search-modal');
	    const searchInput = document.querySelector('.search-modal input');
	    const searchResults = document.querySelector('.search-modal .results');

      // Display search modal.
      window.showSearchModal = function() {
          searchModal.classList.remove('hidden');
          searchInput.focus();
      }

      // Detect OS and sets proper search button text.
      const searchButtonElement = document.querySelector('.search-button');
      const searchButtonTextElement = document.querySelector('.search-button-text');
      if (searchButtonElement) {
          let searchButtonText = 'ctrl+k';
          if (navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
              searchButtonText = 'cmd+k';
          }
          searchButtonTextElement.innerText = searchButtonText;
          searchButtonElement.classList.remove('hidden');
      }

      // On keyboard shortcut shows search modal.
      document.addEventListener('keydown', function(event) {
          // Handles macOS CMD+k.
          if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
              event.preventDefault();
              showSearchModal();
          }

          // Handles Windows/Linux Ctrl+k.
          if (event.ctrlKey && event.key === 'k') {
              event.preventDefault();
              showSearchModal();
          }
      });

      // Debounce magic.
      function debounce(func, delay) {
		      let timerId;
		      return function (...args) {
			        clearTimeout(timerId);
			        timerId = setTimeout(() => {
				          func.apply(this, args);
			        }, delay);
		      };
	    }

      // Do the actual search.
      searchInput.addEventListener('keyup', debounce((evt)=> {
          const query = evt.target.value.trim().toLowerCase();
		      if (query.length && query != cachedSearchTerm) {
			        const results = searchIndex.search(query);

              if (results.length == 0) {
	                searchResults.classList.add('hidden');
              } else {
                  searchResults.innerText = '';
	                searchResults.classList.remove('hidden');
	                cachedSearchTerm = query;

	                results.forEach(resultItem => {
                      const item = window.searchDocuments.find(doc => doc.permalink === resultItem.ref);

		                  const link = document.createElement('a');
		                  link.href = item.permalink;
		                  link.classList.add('hover:bg-gray-100', 'cursor-pointer', 'py-2', 'px-3', 'rounded');

                      const title = document.createElement('div');
		                  title.classList.add('text-gray-800', 'font-medium');
		                  title.innerHTML = item.title;
                      link.appendChild(title);

                      const meta = document.createElement('div');
                      meta.classList.add('text-gray-500', 'flex', 'items-center', 'gap-2');

                      const section = document.createElement('span');
                      section.classList.add('uppercase', 'text-xs', 'font-medium', 'bg-gray-200', 'px-1', 'rounded')
		                  section.innerText = item.type;
                      meta.appendChild(section);

                      const summary = document.createElement('span');
                      const summaryText = item.summary.length > 80 ? `${item.summary.substring(0, 80)}...` : item.summary;
		                  summary.innerHTML = summaryText;
                      meta.appendChild(summary);

                      link.appendChild(meta);
		                  searchResults.appendChild(link);
		              });
		          }
          }
	    }, debounceDelay));
  })();
</script>