sourcey 3.4.1 → 3.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,208 +1,215 @@
1
1
  // Search — client-side search dialog with keyboard navigation
2
2
  (function () {
3
- var dialog = document.getElementById('search-dialog');
4
- var input = document.getElementById('search-input');
5
- var results = document.getElementById('search-results');
6
- var openBtn = document.getElementById('search-open');
7
- if (!dialog || !input || !results) return;
8
-
9
- var entries = [];
10
- var activeIndex = -1;
11
- var filtered = [];
12
- var indexLoaded = false;
13
-
14
- // Always load from JSON search index
15
- var searchMeta = document.querySelector('meta[name="sourcey-search"]');
16
-
17
- function loadJsonIndex(callback) {
18
- if (indexLoaded) { callback(); return; }
19
- if (!searchMeta) { indexLoaded = true; callback(); return; }
20
- var url = searchMeta.getAttribute('content');
21
- fetch(url).then(function (r) { return r.json(); }).then(function (data) {
22
- entries = data.map(function (e) {
23
- return {
24
- url: e.url,
25
- method: e.method || '',
26
- path: e.path || '',
27
- summary: e.title || '',
28
- tag: e.tab || '',
29
- content: e.content || '',
30
- category: e.category || '',
31
- featured: !!e.featured,
32
- searchText: [e.method || '', e.path || '', e.title || '', e.content || '', e.tab || ''].join(' ').toLowerCase()
33
- };
3
+ function init() {
4
+ var dialog = document.getElementById('search-dialog');
5
+ var input = document.getElementById('search-input');
6
+ var results = document.getElementById('search-results');
7
+ var openBtn = document.getElementById('search-open');
8
+ if (!dialog || !input || !results) return;
9
+
10
+ var entries = [];
11
+ var activeIndex = -1;
12
+ var filtered = [];
13
+ var indexLoaded = false;
14
+
15
+ // Always load from JSON search index
16
+ var searchMeta = document.querySelector('meta[name="sourcey-search"]');
17
+
18
+ function loadJsonIndex(callback) {
19
+ if (indexLoaded) { callback(); return; }
20
+ if (!searchMeta) { indexLoaded = true; callback(); return; }
21
+ var url = searchMeta.getAttribute('content');
22
+ fetch(url).then(function (r) { return r.json(); }).then(function (data) {
23
+ entries = data.map(function (e) {
24
+ return {
25
+ url: e.url,
26
+ method: e.method || '',
27
+ path: e.path || '',
28
+ summary: e.title || '',
29
+ tag: e.tab || '',
30
+ content: e.content || '',
31
+ category: e.category || '',
32
+ featured: !!e.featured,
33
+ searchText: [e.method || '', e.path || '', e.title || '', e.content || '', e.tab || ''].join(' ').toLowerCase()
34
+ };
35
+ });
36
+ indexLoaded = true;
37
+ callback();
38
+ }).catch(function () {
39
+ indexLoaded = true;
40
+ callback();
34
41
  });
35
- indexLoaded = true;
36
- callback();
37
- }).catch(function () {
38
- indexLoaded = true;
39
- callback();
40
- });
41
- }
42
-
43
- var dialogInner = dialog.querySelector('.search-dialog-inner');
44
-
45
- function positionDialog() {
46
- if (!openBtn || !dialogInner) return;
47
- var rect = openBtn.getBoundingClientRect();
48
- dialogInner.style.position = 'absolute';
49
- dialogInner.style.top = (rect.top - 4) + 'px';
50
- var extraWidth = Math.min(rect.width * 0.5, 200);
51
- dialogInner.style.left = (rect.left - extraWidth / 2) + 'px';
52
- dialogInner.style.width = (rect.width + extraWidth) + 'px';
53
- dialogInner.style.maxWidth = 'none';
54
- dialogInner.style.transform = 'none';
55
- dialogInner.style.margin = '0';
56
- }
57
-
58
- function open() {
59
- positionDialog();
60
- dialog.classList.add('open');
61
- input.value = '';
62
- input.focus();
63
- if (!indexLoaded) {
64
- results.innerHTML = '<div class="search-loading">Loading…</div>';
65
- loadJsonIndex(function () { showResults(''); });
66
- } else {
67
- showResults('');
68
42
  }
69
- document.addEventListener('keydown', onDialogKey);
70
- }
71
43
 
72
- function close() {
73
- dialog.classList.remove('open');
74
- document.removeEventListener('keydown', onDialogKey);
75
- }
76
-
77
- function showResults(query) {
78
- var q = query.toLowerCase().trim();
79
- if (!q) {
80
- // Show featured pages first, then endpoints
81
- var featured = entries.filter(function (e) { return e.featured; });
82
- var rest = entries.filter(function (e) { return !e.featured && e.category !== 'Sections'; });
83
- filtered = featured.concat(rest).slice(0, 30);
84
- } else {
85
- var terms = q.split(/\s+/);
86
- filtered = entries.filter(function (e) {
87
- return terms.every(function (t) { return e.searchText.indexOf(t) !== -1; });
88
- });
89
- // Sort by category so groups stay together (only for search results)
90
- var categoryOrder = { Pages: 0, Sections: 1, Endpoints: 2, Models: 3 };
91
- filtered.sort(function (a, b) {
92
- return (categoryOrder[a.category] || 9) - (categoryOrder[b.category] || 9);
93
- });
44
+ var dialogInner = dialog.querySelector('.search-dialog-inner');
45
+
46
+ function positionDialog() {
47
+ if (!openBtn || !dialogInner) return;
48
+ var rect = openBtn.getBoundingClientRect();
49
+ dialogInner.style.position = 'absolute';
50
+ dialogInner.style.top = (rect.top - 4) + 'px';
51
+ var extraWidth = Math.min(rect.width * 0.5, 200);
52
+ dialogInner.style.left = (rect.left - extraWidth / 2) + 'px';
53
+ dialogInner.style.width = (rect.width + extraWidth) + 'px';
54
+ dialogInner.style.maxWidth = 'none';
55
+ dialogInner.style.transform = 'none';
56
+ dialogInner.style.margin = '0';
94
57
  }
95
58
 
96
- activeIndex = filtered.length ? 0 : -1;
97
- render();
98
- }
99
-
100
- function render() {
101
- var html = '';
102
- var lastCategory = '';
59
+ function open() {
60
+ positionDialog();
61
+ dialog.classList.add('open');
62
+ input.value = '';
63
+ input.focus();
64
+ if (!indexLoaded) {
65
+ results.innerHTML = '<div class="search-loading">Loading…</div>';
66
+ loadJsonIndex(function () { showResults(''); });
67
+ } else {
68
+ showResults('');
69
+ }
70
+ document.addEventListener('keydown', onDialogKey);
71
+ }
103
72
 
104
- for (var i = 0; i < filtered.length; i++) {
105
- var e = filtered[i];
106
- var cat = e.category || 'Results';
73
+ function close() {
74
+ dialog.classList.remove('open');
75
+ document.removeEventListener('keydown', onDialogKey);
76
+ }
107
77
 
108
- if (cat !== lastCategory) {
109
- html += '<div class="search-category">' + escapeHtml(cat) + '</div>';
110
- lastCategory = cat;
78
+ function showResults(query) {
79
+ var q = query.toLowerCase().trim();
80
+ if (!q) {
81
+ // Show featured pages first, then endpoints
82
+ var featured = entries.filter(function (e) { return e.featured; });
83
+ var rest = entries.filter(function (e) { return !e.featured && e.category !== 'Sections'; });
84
+ filtered = featured.concat(rest).slice(0, 30);
85
+ } else {
86
+ var terms = q.split(/\s+/);
87
+ filtered = entries.filter(function (e) {
88
+ return terms.every(function (t) { return e.searchText.indexOf(t) !== -1; });
89
+ });
90
+ // Sort by category so groups stay together (only for search results)
91
+ var categoryOrder = { Pages: 0, Sections: 1, Endpoints: 2, Models: 3 };
92
+ filtered.sort(function (a, b) {
93
+ return (categoryOrder[a.category] || 9) - (categoryOrder[b.category] || 9);
94
+ });
111
95
  }
112
96
 
113
- var cls = 'search-result' + (i === activeIndex ? ' active' : '');
114
- var label = e.method
115
- ? '<span class="search-result-method method-' + e.method.toLowerCase() + '">' + e.method + '</span> ' +
116
- '<span class="search-result-path">' + escapeHtml(e.path) + '</span>'
117
- : '<span class="search-result-path">' + escapeHtml(e.summary) + '</span>';
118
- var tagLine = e.tag ? '<span class="search-result-tag">' + escapeHtml(e.tag) + '</span>' : '';
119
- var summaryLine = e.method && e.summary ? '<span class="search-result-summary">' + escapeHtml(e.summary) + '</span>' : '';
120
-
121
- html += '<a href="' + e.url + '" class="' + cls + '" data-index="' + i + '">' +
122
- '<div class="search-result-main">' + label + summaryLine + '</div>' +
123
- tagLine + '</a>';
97
+ activeIndex = filtered.length ? 0 : -1;
98
+ render();
124
99
  }
125
100
 
126
- results.innerHTML = html;
101
+ function render() {
102
+ var html = '';
103
+ var lastCategory = '';
104
+
105
+ for (var i = 0; i < filtered.length; i++) {
106
+ var e = filtered[i];
107
+ var cat = e.category || 'Results';
108
+
109
+ if (cat !== lastCategory) {
110
+ html += '<div class="search-category">' + escapeHtml(cat) + '</div>';
111
+ lastCategory = cat;
112
+ }
113
+
114
+ var cls = 'search-result' + (i === activeIndex ? ' active' : '');
115
+ var label = e.method
116
+ ? '<span class="search-result-method method-' + e.method.toLowerCase() + '">' + e.method + '</span> ' +
117
+ '<span class="search-result-path">' + escapeHtml(e.path) + '</span>'
118
+ : '<span class="search-result-path">' + escapeHtml(e.summary) + '</span>';
119
+ var tagLine = e.tag ? '<span class="search-result-tag">' + escapeHtml(e.tag) + '</span>' : '';
120
+ var summaryLine = e.method && e.summary ? '<span class="search-result-summary">' + escapeHtml(e.summary) + '</span>' : '';
121
+
122
+ html += '<a href="' + e.url + '" class="' + cls + '" data-index="' + i + '">' +
123
+ '<div class="search-result-main">' + label + summaryLine + '</div>' +
124
+ tagLine + '</a>';
125
+ }
127
126
 
128
- // Scroll active result into view
129
- var activeEl = results.querySelector('.search-result.active');
130
- if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
131
- }
127
+ results.innerHTML = html;
132
128
 
133
- function escapeHtml(s) {
134
- var el = document.createElement('span');
135
- el.textContent = s;
136
- return el.innerHTML;
137
- }
138
-
139
- function navigate(index) {
140
- if (index < 0 || index >= filtered.length) return;
141
- var entry = filtered[index];
142
- close();
143
- window.location.href = entry.url;
144
- }
129
+ // Scroll active result into view
130
+ var activeEl = results.querySelector('.search-result.active');
131
+ if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
132
+ }
145
133
 
146
- function onDialogKey(e) {
147
- if (e.key === 'Escape') { close(); e.preventDefault(); return; }
148
- if (e.key === 'ArrowDown') {
149
- e.preventDefault();
150
- activeIndex = Math.min(activeIndex + 1, filtered.length - 1);
151
- render();
152
- return;
134
+ function escapeHtml(s) {
135
+ var el = document.createElement('span');
136
+ el.textContent = s;
137
+ return el.innerHTML;
153
138
  }
154
- if (e.key === 'ArrowUp') {
155
- e.preventDefault();
156
- activeIndex = Math.max(activeIndex - 1, 0);
157
- render();
158
- return;
139
+
140
+ function navigate(index) {
141
+ if (index < 0 || index >= filtered.length) return;
142
+ var entry = filtered[index];
143
+ close();
144
+ window.location.href = entry.url;
159
145
  }
160
- if (e.key === 'Enter') {
161
- e.preventDefault();
162
- if (activeIndex >= 0) navigate(activeIndex);
163
- return;
146
+
147
+ function onDialogKey(e) {
148
+ if (e.key === 'Escape') { close(); e.preventDefault(); return; }
149
+ if (e.key === 'ArrowDown') {
150
+ e.preventDefault();
151
+ activeIndex = Math.min(activeIndex + 1, filtered.length - 1);
152
+ render();
153
+ return;
154
+ }
155
+ if (e.key === 'ArrowUp') {
156
+ e.preventDefault();
157
+ activeIndex = Math.max(activeIndex - 1, 0);
158
+ render();
159
+ return;
160
+ }
161
+ if (e.key === 'Enter') {
162
+ e.preventDefault();
163
+ if (activeIndex >= 0) navigate(activeIndex);
164
+ return;
165
+ }
164
166
  }
165
- }
166
167
 
167
- input.addEventListener('input', function () {
168
- showResults(input.value);
169
- });
168
+ input.addEventListener('input', function () {
169
+ showResults(input.value);
170
+ });
170
171
 
171
- results.addEventListener('click', function (e) {
172
- var result = e.target.closest('.search-result');
173
- if (result) {
174
- e.preventDefault();
175
- navigate(parseInt(result.getAttribute('data-index'), 10));
176
- }
177
- });
172
+ results.addEventListener('click', function (e) {
173
+ var result = e.target.closest('.search-result');
174
+ if (result) {
175
+ e.preventDefault();
176
+ navigate(parseInt(result.getAttribute('data-index'), 10));
177
+ }
178
+ });
178
179
 
179
- // Open search
180
- if (openBtn) openBtn.addEventListener('click', open);
180
+ // Open search
181
+ if (openBtn) openBtn.addEventListener('click', open);
181
182
 
182
- // Also bind mobile search button
183
- var mobileBtn = document.getElementById('search-open-mobile');
184
- if (mobileBtn) mobileBtn.addEventListener('click', open);
183
+ // Also bind mobile search button
184
+ var mobileBtn = document.getElementById('search-open-mobile');
185
+ if (mobileBtn) mobileBtn.addEventListener('click', open);
185
186
 
186
- // Keyboard shortcut: Ctrl+K or /
187
- document.addEventListener('keydown', function (e) {
188
- if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
189
- e.preventDefault();
190
- open();
191
- }
192
- if (e.key === '/' && !isEditable(e.target)) {
193
- e.preventDefault();
194
- open();
195
- }
196
- });
187
+ // Keyboard shortcut: Ctrl+K or /
188
+ document.addEventListener('keydown', function (e) {
189
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
190
+ e.preventDefault();
191
+ open();
192
+ }
193
+ if (e.key === '/' && !isEditable(e.target)) {
194
+ e.preventDefault();
195
+ open();
196
+ }
197
+ });
197
198
 
198
- // Close on backdrop click
199
- dialog.addEventListener('click', function (e) {
200
- if (e.target === dialog) close();
201
- });
199
+ // Close on backdrop click
200
+ dialog.addEventListener('click', function (e) {
201
+ if (e.target === dialog) close();
202
+ });
202
203
 
203
- function isEditable(el) {
204
- var tag = el.tagName;
205
- return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
204
+ function isEditable(el) {
205
+ var tag = el.tagName;
206
+ return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
207
+ }
206
208
  }
207
209
 
210
+ if ('requestIdleCallback' in window) {
211
+ window.requestIdleCallback(init, { timeout: 250 });
212
+ } else {
213
+ window.addEventListener('load', init, { once: true });
214
+ }
208
215
  })();
@@ -4,63 +4,71 @@
4
4
  // backdrop click, nav link click, Escape (native dialog behavior).
5
5
  // Dropdown toggles list visibility for tab selection.
6
6
  (function () {
7
- var dialog = document.getElementById('mobile-nav');
8
- var openBtns = document.querySelectorAll('[data-drawer-slide]');
9
- if (!dialog) return;
7
+ function init() {
8
+ var dialog = document.getElementById('mobile-nav');
9
+ var openBtns = document.querySelectorAll('[data-drawer-slide]');
10
+ if (!dialog) return;
10
11
 
11
- // Sync active state from desktop sidebar to drawer on open
12
- function syncActiveState() {
13
- var activeDesktop = document.querySelector('#nav .nav-link.active');
14
- if (!activeDesktop) return;
15
- var activeHref = activeDesktop.getAttribute('href');
16
- if (!activeHref) return;
12
+ // Sync active state from desktop sidebar to drawer on open
13
+ function syncActiveState() {
14
+ var activeDesktop = document.querySelector('#nav .nav-link.active');
15
+ if (!activeDesktop) return;
16
+ var activeHref = activeDesktop.getAttribute('href');
17
+ if (!activeHref) return;
17
18
 
18
- dialog.querySelectorAll('.nav-link').forEach(function (link) {
19
- link.classList.toggle('active', link.getAttribute('href') === activeHref);
19
+ dialog.querySelectorAll('.nav-link').forEach(function (link) {
20
+ link.classList.toggle('active', link.getAttribute('href') === activeHref);
21
+ });
22
+ }
23
+
24
+ openBtns.forEach(function (btn) {
25
+ btn.addEventListener('click', function () {
26
+ syncActiveState();
27
+ dialog.showModal();
28
+ });
20
29
  });
21
- }
22
30
 
23
- openBtns.forEach(function (btn) {
24
- btn.addEventListener('click', function () {
25
- syncActiveState();
26
- dialog.showModal();
31
+ // Close on backdrop click (click on dialog element itself, not its children)
32
+ dialog.addEventListener('click', function (e) {
33
+ if (e.target === dialog) dialog.close();
27
34
  });
28
- });
29
35
 
30
- // Close on backdrop click (click on dialog element itself, not its children)
31
- dialog.addEventListener('click', function (e) {
32
- if (e.target === dialog) dialog.close();
33
- });
36
+ // Close on nav link or close button click
37
+ dialog.addEventListener('click', function (e) {
38
+ if (e.target.closest('a') || e.target.closest('[data-close-drawer]')) dialog.close();
39
+ });
34
40
 
35
- // Close on nav link or close button click
36
- dialog.addEventListener('click', function (e) {
37
- if (e.target.closest('a') || e.target.closest('[data-close-drawer]')) dialog.close();
38
- });
41
+ // Dropdown: toggle list visibility
42
+ var toggle = document.getElementById('drawer-group-toggle');
43
+ var list = document.getElementById('drawer-group-list');
44
+ if (!toggle || !list) return;
39
45
 
40
- // Dropdown: toggle list visibility
41
- var toggle = document.getElementById('drawer-group-toggle');
42
- var list = document.getElementById('drawer-group-list');
43
- if (!toggle || !list) return;
46
+ function closeDropdown() {
47
+ list.style.display = 'none';
48
+ toggle.setAttribute('aria-expanded', 'false');
49
+ }
44
50
 
45
- function closeDropdown() {
46
- list.style.display = 'none';
47
- toggle.setAttribute('aria-expanded', 'false');
48
- }
51
+ toggle.addEventListener('click', function () {
52
+ var open = list.style.display !== 'none';
53
+ if (open) {
54
+ closeDropdown();
55
+ } else {
56
+ list.style.display = '';
57
+ toggle.setAttribute('aria-expanded', 'true');
58
+ }
59
+ });
49
60
 
50
- toggle.addEventListener('click', function () {
51
- var open = list.style.display !== 'none';
52
- if (open) {
53
- closeDropdown();
54
- } else {
55
- list.style.display = '';
56
- toggle.setAttribute('aria-expanded', 'true');
57
- }
58
- });
61
+ // Close dropdown when clicking outside it
62
+ dialog.addEventListener('click', function (e) {
63
+ if (list.style.display !== 'none' && !e.target.closest('.drawer-dropdown')) {
64
+ closeDropdown();
65
+ }
66
+ });
67
+ }
59
68
 
60
- // Close dropdown when clicking outside it
61
- dialog.addEventListener('click', function (e) {
62
- if (list.style.display !== 'none' && !e.target.closest('.drawer-dropdown')) {
63
- closeDropdown();
64
- }
65
- });
69
+ if ('requestIdleCallback' in window) {
70
+ window.requestIdleCallback(init, { timeout: 250 });
71
+ } else {
72
+ window.addEventListener('load', init, { once: true });
73
+ }
66
74
  })();