@mgks/docmd 0.2.9 → 0.3.1

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.
@@ -3,6 +3,7 @@
3
3
  /*
4
4
  * Main CSS file for docmd
5
5
  */
6
+
6
7
  .docmd-container.steps-reset .docmd-container.steps-reset ol.steps-list>li.step-item::before,
7
8
  .docmd-container.steps-reset ol.steps-list>li.step-item::before,
8
9
  .docmd-container.steps-reset ol.steps-list[start]>li.step-item::before {
@@ -44,7 +45,9 @@ img {
44
45
  --image-caption-bg: #f8f8f8;
45
46
  --image-caption-text: #666;
46
47
  --lightbox-bg: rgba(0, 0, 0, 0.9);
47
- --lightbox-text: #fff
48
+ --lightbox-text: #fff;
49
+ --accent-color: #858585;
50
+
48
51
  }
49
52
 
50
53
  [data-theme=dark] {
@@ -244,9 +247,11 @@ body.sidebar-collapsed .main-content-wrapper {
244
247
  transition: .2s
245
248
  }
246
249
 
250
+ .docmd-search-trigger:hover,
247
251
  .theme-toggle-header:hover {
248
252
  background: var(--sidebar-bg);
249
- border-color: var(--accent-color)
253
+ border-color: var(--accent-color);
254
+
250
255
  }
251
256
 
252
257
  .card .card-title,
@@ -373,18 +378,20 @@ html[data-theme=dark] .theme-toggle-button .icon-sun,
373
378
  opacity: .7
374
379
  }
375
380
 
381
+ .docmd-search-trigger,
376
382
  .theme-toggle-button {
377
- background: 0 0;
383
+ background-color: var(--sidebar-bg);
378
384
  border: 1px solid var(--border-color);
379
385
  color: var(--sidebar-text);
380
386
  padding: 8px;
381
387
  border-radius: 4px;
382
388
  cursor: pointer;
383
- margin-top: .5em;
389
+ /* margin-top: .5em; */
384
390
  display: flex;
385
391
  align-items: center;
386
392
  justify-content: center;
387
- width: 100%
393
+ width: 100%;
394
+
388
395
  }
389
396
 
390
397
  .next-page,
@@ -640,13 +647,11 @@ details[open]>.collapsible-summary .collapsible-arrow svg {
640
647
  padding: 0;
641
648
  margin-top: 2rem;
642
649
  box-shadow: none;
643
- /* Remove default container shadow */
644
650
  }
645
651
 
646
652
  .changelog-entry {
647
653
  display: grid;
648
654
  grid-template-columns: 180px 1fr;
649
- /* Date width | Content width */
650
655
  gap: 2rem;
651
656
  margin-bottom: 3rem;
652
657
  position: relative;
@@ -675,7 +680,6 @@ details[open]>.collapsible-summary .collapsible-arrow svg {
675
680
  padding-bottom: 1rem;
676
681
  }
677
682
 
678
- /* Reset top margin for the first element in the body */
679
683
  .changelog-body> :first-child {
680
684
  margin-top: 0;
681
685
  }
@@ -1126,13 +1130,13 @@ img.lightbox {
1126
1130
 
1127
1131
  .sponsor-ribbon {
1128
1132
  position: fixed;
1129
- bottom: 75px;
1130
- right: 10px;
1133
+ bottom: 4.5em;
1134
+ right: 0;
1131
1135
  z-index: 1000;
1132
1136
  transform: rotate(0);
1133
1137
  transform-origin: center center;
1134
- opacity: .75;
1135
- transition: .5s
1138
+ opacity: .85;
1139
+ transition: .5s;
1136
1140
  }
1137
1141
 
1138
1142
  .sponsor-ribbon:hover {
@@ -1143,19 +1147,18 @@ img.lightbox {
1143
1147
  display: flex;
1144
1148
  align-items: center;
1145
1149
  gap: .5rem;
1146
- background: linear-gradient(135deg, #ff6b6b, #ee5a24);
1147
- padding: .75rem 1.5rem;
1148
- border-radius: 5px;
1149
- box-shadow: rgba(255, 107, 107, .3) 0 4px 12px;
1150
- font-weight: 600;
1151
- font-size: .875rem;
1150
+ background: linear-gradient(to bottom right, #ff6b6b, #ee5a24);
1151
+ padding: .5rem .5em .5em .75rem;
1152
+ border-radius: 20px 0 0 20px;
1153
+ font-weight: 700;
1154
+ font-size: .85em;
1152
1155
  transition: .3s;
1153
- white-space: nowrap
1156
+ white-space: nowrap;
1154
1157
  }
1155
1158
 
1156
1159
  .sponsor-link:hover {
1157
1160
  transform: scale(1.02);
1158
- box-shadow: rgba(255, 107, 107, .4) 0 6px 20px
1161
+ box-shadow: rgba(255, 107, 107, .4) 0 0 25px
1159
1162
  }
1160
1163
 
1161
1164
  .sponsor-icon {
@@ -1253,6 +1256,214 @@ hr {
1253
1256
  height: 1em;
1254
1257
  }
1255
1258
 
1259
+ /* --- Search UI Styles --- */
1260
+
1261
+ /* Trigger Button in Header */
1262
+ .docmd-search-trigger {
1263
+ display: flex;
1264
+ align-items: center;
1265
+ gap: 0.5rem;
1266
+ background-color: var(--sidebar-bg);
1267
+ border: 1px solid var(--border-color);
1268
+ border-radius: 6px;
1269
+ padding: 0.4rem 0.6rem;
1270
+ color: var(--text-muted, #666);
1271
+ cursor: pointer;
1272
+ font-size: 0.9rem;
1273
+ transition: all 0.2s ease;
1274
+ margin-right: 0.75rem;
1275
+ }
1276
+
1277
+ .search-label {
1278
+ margin-right: 1rem;
1279
+ }
1280
+
1281
+ .search-keys {
1282
+ display: flex;
1283
+ gap: 2px;
1284
+ }
1285
+
1286
+ .docmd-kbd {
1287
+ background-color: var(--bg-color);
1288
+ border: 1px solid var(--border-color);
1289
+ border-radius: 3px;
1290
+ font-family: var(--font-family-mono);
1291
+ font-size: 0.7rem;
1292
+ padding: 0 4px;
1293
+ box-shadow: 0 1px 0 var(--border-color);
1294
+ color: var(--text-muted, var(--accent-color));
1295
+ min-width: 1.2em;
1296
+ text-align: center;
1297
+
1298
+ }
1299
+
1300
+ /* Mobile adjustment for trigger */
1301
+ @media (max-width: 768px) {
1302
+
1303
+ .search-label,
1304
+ .search-keys {
1305
+ display: none;
1306
+ }
1307
+
1308
+ .docmd-search-trigger {
1309
+ padding: 0.5rem;
1310
+ margin-right: 0.5rem;
1311
+ }
1312
+ }
1313
+
1314
+ /* Modal Overlay */
1315
+ .docmd-search-modal {
1316
+ position: fixed;
1317
+ top: 0;
1318
+ left: 0;
1319
+ right: 0;
1320
+ bottom: 0;
1321
+ background-color: rgba(0, 0, 0, 0.5);
1322
+ backdrop-filter: blur(2px);
1323
+ z-index: 1000;
1324
+ display: flex;
1325
+ justify-content: center;
1326
+ align-items: flex-start;
1327
+ padding-top: 10vh;
1328
+ }
1329
+
1330
+ /* Search Box Container */
1331
+ .docmd-search-box {
1332
+ width: 100%;
1333
+ max-width: 600px;
1334
+ background-color: var(--bg-color);
1335
+ border: 1px solid var(--border-color);
1336
+ border-radius: 12px;
1337
+ box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.3);
1338
+ display: flex;
1339
+ flex-direction: column;
1340
+ overflow: hidden;
1341
+ animation: searchSlideIn 0.2s ease-out;
1342
+ }
1343
+
1344
+ @keyframes searchSlideIn {
1345
+ from {
1346
+ opacity: 0;
1347
+ transform: scale(0.98) translateY(10px);
1348
+ }
1349
+
1350
+ to {
1351
+ opacity: 1;
1352
+ transform: scale(1) translateY(0);
1353
+ }
1354
+ }
1355
+
1356
+ /* Header & Input */
1357
+ .docmd-search-header {
1358
+ display: flex;
1359
+ align-items: center;
1360
+ padding: 1rem 1.5rem;
1361
+ border-bottom: 1px solid var(--border-color);
1362
+ gap: 1rem;
1363
+ }
1364
+
1365
+ .docmd-search-header svg {
1366
+ color: var(--link-color);
1367
+ }
1368
+
1369
+ #docmd-search-input {
1370
+ flex: 1;
1371
+ border: none;
1372
+ background: transparent;
1373
+ font-size: 1.1rem;
1374
+ color: var(--text-color);
1375
+ outline: none;
1376
+ }
1377
+
1378
+ .docmd-search-close {
1379
+ background: none;
1380
+ border: none;
1381
+ cursor: pointer;
1382
+ color: var(--text-muted, #888);
1383
+ padding: 0;
1384
+ display: flex;
1385
+ }
1386
+
1387
+ /* Results List */
1388
+ .docmd-search-results {
1389
+ max-height: 60vh;
1390
+ overflow-y: auto;
1391
+ padding: 0.5rem;
1392
+ }
1393
+
1394
+ .search-result-item {
1395
+ display: block;
1396
+ padding: 0.75rem 1rem;
1397
+ border-radius: 6px;
1398
+ text-decoration: none;
1399
+ color: var(--text-color);
1400
+ margin-bottom: 2px;
1401
+ border-left: 3px solid transparent;
1402
+ }
1403
+
1404
+ .search-result-item:hover,
1405
+ .search-result-item:focus,
1406
+ .search-result-item.selected {
1407
+ background-color: var(--sidebar-bg);
1408
+ border-left-color: var(--link-color);
1409
+ text-decoration: none;
1410
+ cursor: pointer;
1411
+ }
1412
+
1413
+ .search-result-title {
1414
+ font-weight: 600;
1415
+ font-size: 0.95rem;
1416
+ margin-bottom: 0.2rem;
1417
+ color: var(--link-color);
1418
+ }
1419
+
1420
+ .search-result-preview {
1421
+ font-size: 0.8rem;
1422
+ color: var(--text-muted, #666);
1423
+ white-space: nowrap;
1424
+ overflow: hidden;
1425
+ text-overflow: ellipsis;
1426
+ }
1427
+
1428
+ .search-no-results,
1429
+ .search-error {
1430
+ padding: 2rem;
1431
+ text-align: center;
1432
+ color: var(--text-muted, #666);
1433
+ }
1434
+
1435
+ .search-initial,
1436
+ .search-no-results,
1437
+ .search-error {
1438
+ padding: 3rem 2rem;
1439
+ text-align: center;
1440
+ color: var(--text-muted, #666);
1441
+ font-size: 0.95rem;
1442
+ }
1443
+
1444
+ .search-result-preview mark {
1445
+ background-color: rgba(255, 235, 59, 0.4);
1446
+ color: inherit;
1447
+ padding: 0 2px;
1448
+ border-radius: 2px;
1449
+ }
1450
+
1451
+ [data-theme="dark"] .search-result-preview mark {
1452
+ background-color: rgba(255, 235, 59, 0.25);
1453
+ color: #fff;
1454
+ }
1455
+
1456
+ .docmd-search-footer {
1457
+ padding: 0.75rem 1.5rem;
1458
+ background-color: var(--sidebar-bg);
1459
+ border-top: 1px solid var(--border-color);
1460
+ font-size: 0.75rem;
1461
+ color: var(--text-muted, #888);
1462
+ display: flex;
1463
+ gap: 1rem;
1464
+ justify-content: flex-end;
1465
+ }
1466
+
1256
1467
  @keyframes heartbeat {
1257
1468
 
1258
1469
  0%,
@@ -1333,6 +1544,37 @@ hr {
1333
1544
  grid-template-columns: 1fr
1334
1545
  }
1335
1546
 
1547
+ .docmd-search-modal {
1548
+ padding-top: 0;
1549
+ background-color: var(--bg-color);
1550
+ align-items: flex-start;
1551
+ }
1552
+
1553
+ .docmd-search-box {
1554
+ max-width: 100%;
1555
+ height: 100%;
1556
+ border-radius: 0;
1557
+ border: none;
1558
+ box-shadow: none;
1559
+ }
1560
+
1561
+ .docmd-search-header {
1562
+ padding: 1rem;
1563
+ }
1564
+
1565
+ #docmd-search-input {
1566
+ font-size: 1rem;
1567
+ }
1568
+
1569
+ .docmd-search-footer {
1570
+ display: none;
1571
+ }
1572
+
1573
+ .docmd-search-results {
1574
+ max-height: none;
1575
+ flex: 1;
1576
+ }
1577
+
1336
1578
  .sponsor-ribbon {
1337
1579
  bottom: 10px;
1338
1580
  right: 10px
@@ -60,6 +60,7 @@ figure img {
60
60
  :root[data-theme=light] {
61
61
  --font-family-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
62
62
  --font-family-mono: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
63
+ --accent-color: #0097ff;
63
64
  --sky-primary: #0097ff;
64
65
  --sky-primary-light: #e5f4ff;
65
66
  --sky-primary-dark: #0078cc;
@@ -92,7 +93,8 @@ figure img {
92
93
  --image-hover-transform: translateY(-2px);
93
94
  --image-hover-shadow: var(--shadow-lg);
94
95
  --image-border-radius: 8px;
95
- --image-transition: all 0.3s ease
96
+ --image-transition: all 0.3s ease;
97
+
96
98
  }
97
99
 
98
100
  :root[data-theme=dark] {
@@ -469,6 +471,7 @@ button:hover {
469
471
  background-color: var(--sky-primary-dark)
470
472
  }
471
473
 
474
+ .docmd-search-trigger,
472
475
  .theme-toggle-button {
473
476
  background-color: var(--sky-background-alt);
474
477
  border-color: var(--sky-border-light);
@@ -476,6 +479,7 @@ button:hover {
476
479
  transition: .2s
477
480
  }
478
481
 
482
+ .docmd-search-trigger:hover,
479
483
  .theme-toggle-button:hover {
480
484
  background-color: var(--sky-primary-light)
481
485
  }
@@ -3,6 +3,7 @@
3
3
  /*
4
4
  * Main client-side script for docmd UI interactions
5
5
  */
6
+
6
7
  // --- Collapsible Navigation Logic ---
7
8
  function initializeCollapsibleNav() {
8
9
  const nav = document.querySelector('.sidebar-nav');
@@ -1,5 +1,8 @@
1
1
  // Source file from the docmd project — https://github.com/mgks/docmd
2
- // Mermaid diagram integration with theme support
2
+
3
+ /*
4
+ * Mermaid diagram integration with theme support
5
+ */
3
6
 
4
7
  (function () {
5
8
  'use strict';
@@ -0,0 +1,218 @@
1
+ // Source file from the docmd project — https://github.com/mgks/docmd
2
+
3
+ /*
4
+ * Client-side search functionality for docmd
5
+ */
6
+
7
+ (function() {
8
+ let miniSearch = null;
9
+ let isIndexLoaded = false;
10
+ let selectedIndex = -1; // Track keyboard selection
11
+
12
+ const searchModal = document.getElementById('docmd-search-modal');
13
+ const searchInput = document.getElementById('docmd-search-input');
14
+ const searchResults = document.getElementById('docmd-search-results');
15
+
16
+ const ROOT_PATH = window.DOCMD_ROOT || './';
17
+
18
+ if (!searchModal) return;
19
+
20
+ const emptyStateHtml = '<div class="search-initial">Type to start searching...</div>';
21
+
22
+ // 1. Open/Close Logic
23
+ function openSearch() {
24
+ searchModal.style.display = 'flex';
25
+ searchInput.focus();
26
+
27
+ if (!searchInput.value.trim()) {
28
+ searchResults.innerHTML = emptyStateHtml;
29
+ selectedIndex = -1;
30
+ }
31
+
32
+ if (!isIndexLoaded) loadIndex();
33
+ }
34
+
35
+ function closeSearch() {
36
+ searchModal.style.display = 'none';
37
+ selectedIndex = -1; // Reset selection
38
+ }
39
+
40
+ // 2. Keyboard Navigation & Shortcuts
41
+ document.addEventListener('keydown', (e) => {
42
+ // Open: Cmd+K / Ctrl+K
43
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
44
+ e.preventDefault();
45
+ if (searchModal.style.display === 'flex') {
46
+ closeSearch();
47
+ } else {
48
+ openSearch();
49
+ }
50
+ }
51
+
52
+ // Context: Only handle these if search is open
53
+ if (searchModal.style.display === 'flex') {
54
+ const items = searchResults.querySelectorAll('.search-result-item');
55
+
56
+ if (e.key === 'Escape') {
57
+ e.preventDefault();
58
+ closeSearch();
59
+ }
60
+ else if (e.key === 'ArrowDown') {
61
+ e.preventDefault();
62
+ if (items.length === 0) return;
63
+ selectedIndex = (selectedIndex + 1) % items.length;
64
+ updateSelection(items);
65
+ }
66
+ else if (e.key === 'ArrowUp') {
67
+ e.preventDefault();
68
+ if (items.length === 0) return;
69
+ selectedIndex = (selectedIndex - 1 + items.length) % items.length;
70
+ updateSelection(items);
71
+ }
72
+ else if (e.key === 'Enter') {
73
+ e.preventDefault();
74
+ if (selectedIndex >= 0 && items[selectedIndex]) {
75
+ items[selectedIndex].click();
76
+ } else if (items.length > 0) {
77
+ // If nothing selected but results exist, click the first one on Enter
78
+ items[0].click();
79
+ }
80
+ }
81
+ }
82
+ });
83
+
84
+ function updateSelection(items) {
85
+ items.forEach((item, index) => {
86
+ if (index === selectedIndex) {
87
+ item.classList.add('selected');
88
+ item.scrollIntoView({ block: 'nearest' });
89
+ } else {
90
+ item.classList.remove('selected');
91
+ }
92
+ });
93
+ }
94
+
95
+ // Click handlers
96
+ document.querySelectorAll('.docmd-search-trigger').forEach(btn => {
97
+ btn.addEventListener('click', openSearch);
98
+ });
99
+
100
+ searchModal.addEventListener('click', (e) => {
101
+ if (e.target === searchModal) closeSearch();
102
+ });
103
+
104
+ // 3. Index Loading Logic
105
+ async function loadIndex() {
106
+ try {
107
+
108
+ const indexUrl = `${ROOT_PATH}search-index.json`;
109
+ const response = await fetch(indexUrl);
110
+ if (!response.ok) throw new Error(response.status);
111
+
112
+ const jsonString = await response.text();
113
+
114
+ miniSearch = MiniSearch.loadJSON(jsonString, {
115
+ fields: ['title', 'headings', 'text'],
116
+ storeFields: ['title', 'id', 'text'],
117
+ searchOptions: {
118
+ fuzzy: 0.2,
119
+ prefix: true,
120
+ boost: { title: 2, headings: 1.5 }
121
+ }
122
+ });
123
+
124
+ isIndexLoaded = true;
125
+ // console.log('Search index loaded');
126
+
127
+ if (searchInput.value.trim()) {
128
+ searchInput.dispatchEvent(new Event('input'));
129
+ }
130
+
131
+ } catch (e) {
132
+ console.error('Failed to load search index', e);
133
+ searchResults.innerHTML = '<div class="search-error">Failed to load search index.</div>';
134
+ }
135
+ }
136
+
137
+ // Helper: Snippets (Same as before)
138
+ function getSnippet(text, query) {
139
+ if (!text) return '';
140
+ const terms = query.split(/\s+/).filter(t => t.length > 2);
141
+ const lowerText = text.toLowerCase();
142
+ let bestIndex = -1;
143
+
144
+ for (const term of terms) {
145
+ const idx = lowerText.indexOf(term.toLowerCase());
146
+ if (idx >= 0) { bestIndex = idx; break; }
147
+ }
148
+
149
+ const contextSize = 60;
150
+ let start = 0;
151
+ let end = 120;
152
+
153
+ if (bestIndex >= 0) {
154
+ start = Math.max(0, bestIndex - contextSize);
155
+ end = Math.min(text.length, bestIndex + contextSize);
156
+ }
157
+
158
+ let snippet = text.substring(start, end);
159
+ if (start > 0) snippet = '...' + snippet;
160
+ if (end < text.length) snippet = snippet + '...';
161
+
162
+ const safeTerms = terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
163
+ if (safeTerms) {
164
+ const highlightRegex = new RegExp(`(${safeTerms})`, 'gi');
165
+ snippet = snippet.replace(highlightRegex, '<mark>$1</mark>');
166
+ }
167
+ return snippet;
168
+ }
169
+
170
+ // 4. Search Execution
171
+ searchInput.addEventListener('input', (e) => {
172
+ const query = e.target.value.trim();
173
+ selectedIndex = -1; // Reset selection on new input
174
+
175
+ if (!query) {
176
+ searchResults.innerHTML = emptyStateHtml;
177
+ return;
178
+ }
179
+
180
+ if (!isIndexLoaded) return;
181
+
182
+ const results = miniSearch.search(query);
183
+ renderResults(results, query);
184
+ });
185
+
186
+ function renderResults(results, query) {
187
+ if (results.length === 0) {
188
+ searchResults.innerHTML = '<div class="search-no-results">No results found.</div>';
189
+ return;
190
+ }
191
+
192
+ const html = results.slice(0, 10).map((result, index) => {
193
+ const snippet = getSnippet(result.text, query);
194
+
195
+ const linkHref = `${ROOT_PATH}${result.id}`;
196
+
197
+ // Add data-index for mouse interaction tracking if needed
198
+ return `
199
+ <a href="${linkHref}" class="search-result-item" data-index="${index}" onclick="window.closeDocmdSearch()">
200
+ <div class="search-result-title">${result.title}</div>
201
+ <div class="search-result-preview">${snippet}</div>
202
+ </a>
203
+ `;
204
+ }).join('');
205
+
206
+ searchResults.innerHTML = html;
207
+
208
+ // Optional: Allow mouse hover to update selectedIndex
209
+ searchResults.querySelectorAll('.search-result-item').forEach((item, idx) => {
210
+ item.addEventListener('mouseenter', () => {
211
+ selectedIndex = idx;
212
+ updateSelection(searchResults.querySelectorAll('.search-result-item'));
213
+ });
214
+ });
215
+ }
216
+
217
+ window.closeDocmdSearch = closeSearch;
218
+ })();