@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.
- package/.github/workflows/{publish.yml → npm-publish.yml} +2 -2
- package/README.md +3 -1
- package/assets/images/preview-dark-welcome.png +0 -0
- package/config.js +133 -165
- package/docs/comparison.md +5 -0
- package/docs/configuration.md +7 -1
- package/docs/content/search.md +68 -0
- package/docs/overview.md +1 -0
- package/package.json +5 -2
- package/src/assets/css/docmd-main.css +262 -20
- package/src/assets/css/docmd-theme-sky.css +5 -1
- package/src/assets/js/docmd-main.js +1 -0
- package/src/assets/js/docmd-mermaid.js +4 -1
- package/src/assets/js/docmd-search.js +218 -0
- package/src/commands/build.js +116 -1
- package/src/commands/init.js +5 -0
- package/src/core/config-validator.js +2 -1
- package/src/core/file-processor.js +12 -1
- package/src/templates/layout.ejs +44 -3
|
@@ -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:
|
|
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:
|
|
1130
|
-
right:
|
|
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: .
|
|
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(
|
|
1147
|
-
padding: .
|
|
1148
|
-
border-radius:
|
|
1149
|
-
|
|
1150
|
-
font-
|
|
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
|
|
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
|
}
|
|
@@ -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
|
+
})();
|