docula 2.0.0 → 2.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docula",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Beautiful Website for Your Projects",
5
5
  "type": "module",
6
6
  "main": "./dist/docula.js",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "author": "Jared Wray <me@jaredwray.com>",
19
19
  "engines": {
20
- "node": "^22.18.0 || >=24.0.0"
20
+ "node": "^22.19.0 || >=24.0.0"
21
21
  },
22
22
  "license": "MIT",
23
23
  "keywords": [
@@ -40,32 +40,34 @@
40
40
  "docula": "./bin/docula.js"
41
41
  },
42
42
  "dependencies": {
43
- "@ai-sdk/anthropic": "^3.0.77",
44
- "@ai-sdk/google": "^3.0.72",
45
- "@ai-sdk/openai": "^3.0.63",
46
- "@cacheable/net": "^2.0.7",
47
- "ai": "^6.0.178",
43
+ "@ai-sdk/anthropic": "^3.0.83",
44
+ "@ai-sdk/google": "^3.0.81",
45
+ "@ai-sdk/openai": "^3.0.70",
46
+ "@cacheable/net": "^2.0.8",
47
+ "ai": "^6.0.202",
48
48
  "colorette": "^2.0.20",
49
- "ecto": "^4.8.5",
50
- "hashery": "^2.0.0",
49
+ "ecto": "^4.8.7",
50
+ "hashery": "^3.0.0",
51
+ "ipaddr.js": "2.4.0",
51
52
  "jiti": "^2.7.0",
52
53
  "serve-handler": "^6.1.7",
54
+ "undici": "8.4.1",
53
55
  "update-notifier": "^7.3.1",
54
56
  "writr": "^6.1.2"
55
57
  },
56
58
  "devDependencies": {
57
- "@biomejs/biome": "^2.4.15",
59
+ "@biomejs/biome": "^2.4.16",
58
60
  "@playwright/test": "^1.60.0",
59
- "@types/node": "^24.12.4",
61
+ "@types/node": "^24.13.2",
60
62
  "@types/serve-handler": "^6.1.4",
61
63
  "@types/update-notifier": "^6.0.8",
62
- "@vitest/coverage-v8": "^4.1.6",
64
+ "@vitest/coverage-v8": "^4.1.8",
63
65
  "dotenv": "^17.4.2",
64
66
  "rimraf": "^6.1.3",
65
- "tsdown": "^0.22.0",
66
- "tsx": "^4.21.0",
67
+ "tsdown": "^0.22.2",
68
+ "tsx": "^4.22.4",
67
69
  "typescript": "^6.0.3",
68
- "vitest": "^4.1.6"
70
+ "vitest": "^4.1.8"
69
71
  },
70
72
  "files": [
71
73
  "dist",
@@ -221,16 +221,27 @@ body {
221
221
  }
222
222
 
223
223
  .search-button {
224
- border: none;
225
- border-radius: 4px;
224
+ border: 1px solid var(--border);
225
+ border-radius: 6px;
226
226
  padding: 0px 10px;
227
- height: 30px;
228
- width: 150px;
227
+ height: 32px;
228
+ width: 200px;
229
229
  display: flex;
230
230
  align-items: center;
231
- gap: 6px;
231
+ gap: 8px;
232
232
  color: var(--muted-fg);
233
233
  background: var(--surface);
234
+ cursor: pointer;
235
+ transition: border-color 0.15s ease, color 0.15s ease;
236
+ }
237
+
238
+ .search-button:hover {
239
+ border-color: var(--border-hover);
240
+ color: var(--fg);
241
+ }
242
+
243
+ .search-button svg {
244
+ flex-shrink: 0;
234
245
  }
235
246
 
236
247
  .search-button__text {
@@ -241,9 +252,254 @@ body {
241
252
  }
242
253
 
243
254
  .search-button__shortcut {
244
- font-size: 11px;
255
+ font-size: 12px;
245
256
  font-weight: 500;
246
- opacity: 0.6;
257
+ padding: 1px 6px;
258
+ border: 1px solid var(--border);
259
+ border-radius: 4px;
260
+ opacity: 0.7;
261
+ }
262
+
263
+ /* Search modal */
264
+ body.search-open {
265
+ overflow: hidden;
266
+ }
267
+
268
+ .search-modal {
269
+ position: fixed;
270
+ inset: 0;
271
+ z-index: 200;
272
+ display: flex;
273
+ align-items: flex-start;
274
+ justify-content: center;
275
+ padding: 12vh 16px 16px;
276
+ }
277
+
278
+ .search-modal[hidden] {
279
+ display: none;
280
+ }
281
+
282
+ .search-modal__backdrop {
283
+ position: absolute;
284
+ inset: 0;
285
+ background: rgba(0, 0, 0, 0.6);
286
+ backdrop-filter: blur(2px);
287
+ }
288
+
289
+ .search-modal__panel {
290
+ position: relative;
291
+ display: flex;
292
+ flex-direction: column;
293
+ width: min(680px, 100%);
294
+ max-height: 76vh;
295
+ background: var(--bg);
296
+ border: 1px solid var(--border-strong);
297
+ border-radius: 12px;
298
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
299
+ overflow: hidden;
300
+ }
301
+
302
+ .search-modal__panel:focus-within {
303
+ border-color: var(--link);
304
+ }
305
+
306
+ .search-modal__header {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 10px;
310
+ padding: 14px 16px;
311
+ border-bottom: 1px solid var(--border);
312
+ }
313
+
314
+ .search-modal__icon {
315
+ color: var(--muted-fg);
316
+ flex-shrink: 0;
317
+ }
318
+
319
+ .search-modal__input {
320
+ flex: 1;
321
+ min-width: 0;
322
+ border: none;
323
+ background: transparent;
324
+ color: var(--fg);
325
+ font-size: 16px;
326
+ font-family: inherit;
327
+ outline: none;
328
+ }
329
+
330
+ .search-modal__input::placeholder {
331
+ color: var(--muted-fg);
332
+ }
333
+
334
+ .search-modal__input::-webkit-search-cancel-button {
335
+ display: none;
336
+ }
337
+
338
+ .search-modal__clear {
339
+ display: flex;
340
+ align-items: center;
341
+ justify-content: center;
342
+ border: none;
343
+ background: transparent;
344
+ color: var(--muted-fg);
345
+ cursor: pointer;
346
+ padding: 2px;
347
+ border-radius: 4px;
348
+ }
349
+
350
+ .search-modal__clear:hover {
351
+ color: var(--fg);
352
+ background: var(--surface-hover);
353
+ }
354
+
355
+ .search-modal__clear[hidden] {
356
+ display: none;
357
+ }
358
+
359
+ .search-modal__body {
360
+ flex: 1;
361
+ overflow-y: auto;
362
+ padding: 8px;
363
+ }
364
+
365
+ .search-results {
366
+ list-style: none;
367
+ margin: 0;
368
+ padding: 0;
369
+ display: flex;
370
+ flex-direction: column;
371
+ gap: 4px;
372
+ }
373
+
374
+ .search-results[hidden] {
375
+ display: none;
376
+ }
377
+
378
+ .search-result__link {
379
+ display: flex;
380
+ align-items: flex-start;
381
+ gap: 12px;
382
+ padding: 10px 12px;
383
+ border-radius: 8px;
384
+ border: 1px solid transparent;
385
+ color: var(--fg);
386
+ text-decoration: none;
387
+ }
388
+
389
+ .search-result--active .search-result__link {
390
+ background: var(--surface-hover);
391
+ border-color: var(--border-strong);
392
+ }
393
+
394
+ .search-result__icon {
395
+ display: flex;
396
+ align-items: center;
397
+ color: var(--muted-fg);
398
+ margin-top: 2px;
399
+ flex-shrink: 0;
400
+ }
401
+
402
+ .search-result--active .search-result__icon {
403
+ color: var(--link);
404
+ }
405
+
406
+ .search-result__content {
407
+ min-width: 0;
408
+ flex: 1;
409
+ }
410
+
411
+ .search-result__breadcrumb {
412
+ font-size: 12px;
413
+ color: var(--muted-fg);
414
+ margin-bottom: 2px;
415
+ white-space: nowrap;
416
+ overflow: hidden;
417
+ text-overflow: ellipsis;
418
+ }
419
+
420
+ .search-result__title {
421
+ font-size: 15px;
422
+ font-weight: 600;
423
+ line-height: 1.3;
424
+ }
425
+
426
+ .search-result__text {
427
+ font-size: 13px;
428
+ color: var(--muted-fg);
429
+ margin-top: 3px;
430
+ display: -webkit-box;
431
+ -webkit-line-clamp: 2;
432
+ -webkit-box-orient: vertical;
433
+ overflow: hidden;
434
+ }
435
+
436
+ .search-result__mark {
437
+ background: transparent;
438
+ color: var(--link);
439
+ font-weight: 700;
440
+ padding: 0;
441
+ }
442
+
443
+ .search-modal__empty,
444
+ .search-modal__initial {
445
+ padding: 28px 16px;
446
+ text-align: center;
447
+ color: var(--muted-fg);
448
+ font-size: 14px;
449
+ }
450
+
451
+ .search-modal__empty[hidden],
452
+ .search-modal__initial[hidden] {
453
+ display: none;
454
+ }
455
+
456
+ .search-modal__footer {
457
+ display: flex;
458
+ flex-wrap: wrap;
459
+ gap: 16px;
460
+ padding: 10px 16px;
461
+ border-top: 1px solid var(--border);
462
+ font-size: 12px;
463
+ color: var(--muted-fg);
464
+ }
465
+
466
+ .search-modal__hint {
467
+ display: flex;
468
+ align-items: center;
469
+ gap: 4px;
470
+ }
471
+
472
+ .search-modal__kbd {
473
+ display: inline-flex;
474
+ align-items: center;
475
+ justify-content: center;
476
+ min-width: 18px;
477
+ height: 18px;
478
+ padding: 0 4px;
479
+ border: 1px solid var(--border);
480
+ border-radius: 4px;
481
+ background: var(--surface);
482
+ font-size: 11px;
483
+ font-family: inherit;
484
+ }
485
+
486
+ @media (max-width: 720px) {
487
+ .search-button {
488
+ width: auto;
489
+ }
490
+
491
+ .search-button__text,
492
+ .search-button__shortcut {
493
+ display: none;
494
+ }
495
+
496
+ .search-modal {
497
+ padding: 8vh 10px 10px;
498
+ }
499
+
500
+ .search-modal__footer {
501
+ display: none;
502
+ }
247
503
  }
248
504
 
249
505
  .content {
@@ -40,6 +40,13 @@
40
40
  {{/if}}
41
41
  </nav>
42
42
  <div class="header-actions">
43
+ {{#if enableSearch}}
44
+ <button class="search-button" id="search-button" type="button" aria-label="Search" aria-keyshortcuts="Meta+K Control+K">
45
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
46
+ <span class="search-button__text">Search</span>
47
+ <span class="search-button__shortcut">⌘K</span>
48
+ </button>
49
+ {{/if}}
43
50
  {{#if cookieAuth.loginUrl}}
44
51
  <div class="cookie-auth">
45
52
  <a href="{{cookieAuth.loginUrl}}" class="cookie-auth-btn" id="cookie-auth-login">
@@ -119,3 +126,4 @@
119
126
  <div class="mobile-sidebar__docs" id="mobile-sidebar-docs"></div>
120
127
  </aside>
121
128
  <div class="sidebar-backdrop" id="sidebar-backdrop"></div>
129
+ {{> search }}
@@ -0,0 +1,46 @@
1
+ {{#if enableSearch}}
2
+ <div class="search-modal" id="search-modal" hidden>
3
+ <div class="search-modal__backdrop" data-search-close></div>
4
+ <div class="search-modal__panel" role="dialog" aria-modal="true" aria-label="Search">
5
+ <div class="search-modal__header">
6
+ <svg class="search-modal__icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
7
+ <input
8
+ type="search"
9
+ id="search-input"
10
+ class="search-modal__input"
11
+ placeholder="Search"
12
+ autocomplete="off"
13
+ autocorrect="off"
14
+ autocapitalize="off"
15
+ spellcheck="false"
16
+ aria-label="Search"
17
+ aria-controls="search-results"
18
+ aria-activedescendant=""
19
+ role="combobox"
20
+ aria-expanded="true"
21
+ />
22
+ <button class="search-modal__clear" id="search-clear" type="button" aria-label="Clear search" hidden>
23
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
24
+ </button>
25
+ </div>
26
+ <div class="search-modal__body">
27
+ <ul class="search-results" id="search-results" role="listbox" aria-label="Search results"></ul>
28
+ <div class="search-modal__empty" id="search-empty" hidden>
29
+ No results for "<span class="search-modal__empty-query" id="search-empty-query"></span>"
30
+ </div>
31
+ <div class="search-modal__initial" id="search-initial">
32
+ Type to search the documentation.
33
+ </div>
34
+ </div>
35
+ <div class="search-modal__footer">
36
+ <span class="search-modal__hint"><kbd class="search-modal__kbd">↑</kbd><kbd class="search-modal__kbd">↓</kbd> to navigate</span>
37
+ <span class="search-modal__hint"><kbd class="search-modal__kbd">↵</kbd> to select</span>
38
+ <span class="search-modal__hint"><kbd class="search-modal__kbd">esc</kbd> to close</span>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ <script>
43
+ window.__doculaSearch = { indexUrl: "{{baseUrl}}/search-index.json" };
44
+ </script>
45
+ <script src="{{baseUrl}}/js/search.js" defer></script>
46
+ {{/if}}