docula 1.9.1 → 1.10.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/dist/docula.d.ts CHANGED
@@ -139,6 +139,7 @@ type DoculaChangelogEntry = {
139
139
  content: string;
140
140
  generatedHtml: string;
141
141
  preview: string;
142
+ draft?: boolean;
142
143
  previewImage?: string;
143
144
  urlPath: string;
144
145
  lastModified: string;
@@ -180,6 +181,7 @@ type DoculaData = {
180
181
  enableLlmsTxt?: boolean;
181
182
  hasFeed?: boolean;
182
183
  lastModified?: string;
184
+ homeUrl?: string;
183
185
  baseUrl: string;
184
186
  docsPath: string;
185
187
  apiPath: string;
@@ -334,6 +336,11 @@ declare class DoculaOptions {
334
336
  * When true, suppresses all non-error console output during the build.
335
337
  */
336
338
  quiet: boolean;
339
+ /**
340
+ * URL for the logo/home link in the header. Defaults to baseUrl or "/".
341
+ * Useful when hosting docs under a subpath but the logo should link to the parent site.
342
+ */
343
+ homeUrl?: string;
337
344
  /**
338
345
  * Base URL path prefix for all generated paths (e.g., "/docs").
339
346
  * When set, all asset and navigation URLs are prefixed with this path.
package/dist/docula.js CHANGED
@@ -1330,6 +1330,9 @@ function getChangelogEntries(changelogPath, options, hash, cachedEntries, previo
1330
1330
  }
1331
1331
  }
1332
1332
  const entry = parseChangelogEntry(filePath, options);
1333
+ if (entry.draft) {
1334
+ continue;
1335
+ }
1333
1336
  entries.push(entry);
1334
1337
  }
1335
1338
  }
@@ -1375,6 +1378,7 @@ function parseChangelogEntry(filePath, options) {
1375
1378
  });
1376
1379
  }
1377
1380
  const previewImage = matterData.previewImage;
1381
+ const draft = matterData.draft === true;
1378
1382
  return {
1379
1383
  title: matterData.title ?? fileName,
1380
1384
  date: dateString,
@@ -1387,6 +1391,7 @@ function parseChangelogEntry(filePath, options) {
1387
1391
  mdx: isMdx
1388
1392
  }),
1389
1393
  preview: generateChangelogPreview(markdownContent, 500, isMdx),
1394
+ draft,
1390
1395
  previewImage,
1391
1396
  urlPath: `${buildUrlPath(options.baseUrl, options.changelogPath, slug)}/index.html`,
1392
1397
  lastModified: fs5.statSync(filePath).mtime.toISOString().split("T")[0]
@@ -2918,6 +2923,11 @@ var DoculaOptions = class {
2918
2923
  * When true, suppresses all non-error console output during the build.
2919
2924
  */
2920
2925
  quiet = false;
2926
+ /**
2927
+ * URL for the logo/home link in the header. Defaults to baseUrl or "/".
2928
+ * Useful when hosting docs under a subpath but the logo should link to the parent site.
2929
+ */
2930
+ homeUrl;
2921
2931
  /**
2922
2932
  * Base URL path prefix for all generated paths (e.g., "/docs").
2923
2933
  * When set, all asset and navigation URLs are prefixed with this path.
@@ -3074,6 +3084,9 @@ var DoculaOptions = class {
3074
3084
  if (options.cache && typeof options.cache === "object" && options.cache.github !== null && typeof options.cache.github === "object" && typeof options.cache.github.ttl === "number") {
3075
3085
  this.cache = options.cache;
3076
3086
  }
3087
+ if (options.homeUrl !== void 0 && typeof options.homeUrl === "string") {
3088
+ this.homeUrl = options.homeUrl === "/" ? "/" : trimTrailingSlashes(options.homeUrl);
3089
+ }
3077
3090
  if (options.baseUrl !== void 0 && typeof options.baseUrl === "string") {
3078
3091
  this.baseUrl = trimTrailingSlashes(options.baseUrl);
3079
3092
  }
@@ -3229,6 +3242,7 @@ var DoculaBuilder = class {
3229
3242
  cookieAuth: this.options.cookieAuth,
3230
3243
  headerLinks: this.options.headerLinks,
3231
3244
  enableLlmsTxt: this.options.enableLlmsTxt,
3245
+ homeUrl: this.options.homeUrl,
3232
3246
  baseUrl: this.options.baseUrl,
3233
3247
  docsPath: this.options.docsPath,
3234
3248
  apiPath: this.options.apiPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docula",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Beautiful Website for Your Projects",
5
5
  "type": "module",
6
6
  "main": "./dist/docula.js",
@@ -48,14 +48,14 @@
48
48
  "colorette": "^2.0.20",
49
49
  "ecto": "^4.8.3",
50
50
  "feed": "^5.2.0",
51
- "hashery": "^1.5.0",
51
+ "hashery": "^1.5.1",
52
52
  "jiti": "^2.6.1",
53
53
  "serve-handler": "^6.1.7",
54
54
  "update-notifier": "^7.3.1",
55
- "writr": "^6.0.1"
55
+ "writr": "^6.1.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@biomejs/biome": "^2.4.7",
58
+ "@biomejs/biome": "^2.4.8",
59
59
  "@playwright/test": "^1.58.2",
60
60
  "@types/express": "^5.0.6",
61
61
  "@types/js-yaml": "^4.0.9",
@@ -68,10 +68,12 @@ body {
68
68
  gap: 12px;
69
69
  }
70
70
 
71
+ .logo-link { display: flex; align-items: center; gap: 8px; text-decoration: none; color: var(--fg); }
71
72
  .logo__img {
72
73
  height: 75px;
73
74
  width: auto;
74
75
  }
76
+ .logo__text { font-size: 18px; font-weight: 600; }
75
77
 
76
78
  .theme-button {
77
79
  border: 1px solid transparent;
@@ -354,8 +356,17 @@ body {
354
356
  .article__main li, .release-body li, .changelog-entry-body li, .home-content li { margin-bottom: 6px; list-style: disc; }
355
357
  .article__main ol li, .release-body ol li, .changelog-entry-body ol li, .home-content ol li { list-style: decimal; }
356
358
  .article__main a, .release-body a, .changelog-entry-body a, .home-content a { color: var(--link); text-decoration: underline; }
357
- .article__main pre, .release-body pre, .changelog-entry-body pre, .home-content pre { background: var(--pre-bg); border-radius: 6px; padding: 12px 16px; margin-bottom: 20px; overflow-x: auto; }
359
+ .article__main pre, .release-body pre, .changelog-entry-body pre, .home-content pre { background: var(--pre-bg); border-radius: 6px; padding: 12px 16px; margin-bottom: 20px; overflow-x: auto; position: relative; }
358
360
  .article__main pre code, .release-body pre code, .changelog-entry-body pre code, .home-content pre code { background: none; padding: 0; font-size: 13.5px; line-height: 1.5; }
361
+ .copy-code-btn { position: absolute; top: 8px; right: 8px; padding: 4px; line-height: 0; border-radius: 4px; background: transparent; color: var(--muted); border: none; cursor: pointer; opacity: 0; transition: opacity 0.15s; }
362
+ pre:hover .copy-code-btn { opacity: 1; }
363
+ .copy-code-btn:hover { color: var(--fg); }
364
+ .article__main img, .changelog-entry-body img { cursor: zoom-in; }
365
+ .lightbox-overlay { display: none; position: fixed; inset: 0; z-index: 200; background: rgba(0, 0, 0, 0.8); justify-content: center; align-items: center; cursor: pointer; }
366
+ .lightbox-overlay--visible { display: flex !important; }
367
+ .lightbox-overlay img { max-width: 90vw; max-height: 90vh; border-radius: 8px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4); cursor: default; }
368
+ .lightbox-close { position: absolute; top: 16px; right: 16px; background: none; border: none; color: #fff; cursor: pointer; padding: 4px; line-height: 0; }
369
+ .lightbox-close:hover { opacity: 0.7; }
359
370
  .article__main blockquote, .release-body blockquote, .changelog-entry-body blockquote, .home-content blockquote { border-left: 3px solid var(--border-strong); padding: 10px 16px; margin-bottom: 15px; color: var(--muted); }
360
371
  .article__main img, .release-body img, .changelog-entry-body img, .home-content img { max-width: 100%; border-radius: 6px; }
361
372
  .article__main table, .release-body table, .changelog-entry-body table, .home-content table { width: 100%; border-collapse: collapse; margin-bottom: 15px; }
@@ -4,10 +4,17 @@
4
4
  <button class="mobile-menu-toggle" id="mobile-menu-toggle" aria-label="Toggle navigation menu">
5
5
  <svg 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"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
6
6
  </button>
7
- <a href="{{baseUrl}}/">
7
+ <a href="{{baseUrl}}/" class="logo-link">
8
8
  <img alt="{{siteTitle}}" class="logo__img" src="{{baseUrl}}/logo.svg">
9
+ <span class="logo__text">{{siteTitle}}</span>
9
10
  </a>
10
11
  <nav class="header-bottom__nav">
12
+ {{#if homeUrl}}
13
+ <a class="header-bottom__item header-bottom__item--home" href="{{homeUrl}}">
14
+ <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"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
15
+ <span>Back to Home</span>
16
+ </a>
17
+ {{/if}}
11
18
  {{#if hasDocuments}}
12
19
  <a class="header-bottom__item" href="{{docsUrl}}/" id="nav-docs">
13
20
  <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"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
@@ -67,6 +74,12 @@
67
74
  </header>
68
75
  <aside class="mobile-sidebar" id="mobile-sidebar">
69
76
  <nav class="mobile-nav">
77
+ {{#if homeUrl}}
78
+ <a class="mobile-nav__item" href="{{homeUrl}}">
79
+ <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"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
80
+ <span>Back to Home</span>
81
+ </a>
82
+ {{/if}}
70
83
  {{#if hasDocuments}}
71
84
  <a class="mobile-nav__item" href="{{docsUrl}}/">
72
85
  <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"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg>
@@ -5,6 +5,25 @@
5
5
  hljs.highlightAll();
6
6
  var kbd = document.querySelector('.search-button__shortcut');
7
7
  if (kbd) kbd.textContent = navigator.platform.indexOf('Mac') > -1 ? '⌘K' : 'Ctrl K';
8
+
9
+ // Copy code buttons
10
+ const copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
11
+ const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
12
+ document.querySelectorAll('pre').forEach(function(pre) {
13
+ const btn = document.createElement('button');
14
+ btn.className = 'copy-code-btn';
15
+ btn.innerHTML = copyIcon;
16
+ btn.setAttribute('aria-label', 'Copy code');
17
+ btn.addEventListener('click', function() {
18
+ const code = pre.querySelector('code');
19
+ const text = code ? code.textContent : pre.textContent;
20
+ navigator.clipboard.writeText(text || '').then(function() {
21
+ btn.innerHTML = checkIcon;
22
+ setTimeout(function() { btn.innerHTML = copyIcon; }, 2000);
23
+ });
24
+ });
25
+ pre.appendChild(btn);
26
+ });
8
27
  });
9
28
  </script>
10
29
  <script>
@@ -243,5 +262,29 @@
243
262
  const aside = tocSidebar.closest('.content-aside');
244
263
  if (aside) { aside.style.display = 'none'; }
245
264
  }
265
+
266
+ // Image lightbox
267
+ const lightboxOverlay = document.createElement('div');
268
+ lightboxOverlay.className = 'lightbox-overlay';
269
+ lightboxOverlay.addEventListener('click', function(e) {
270
+ if (e.target !== lightboxImg) lightboxOverlay.classList.remove('lightbox-overlay--visible');
271
+ });
272
+ const lightboxClose = document.createElement('button');
273
+ lightboxClose.className = 'lightbox-close';
274
+ lightboxClose.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" 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>';
275
+ lightboxClose.addEventListener('click', function() {
276
+ lightboxOverlay.classList.remove('lightbox-overlay--visible');
277
+ });
278
+ const lightboxImg = document.createElement('img');
279
+ lightboxOverlay.appendChild(lightboxClose);
280
+ lightboxOverlay.appendChild(lightboxImg);
281
+ document.body.appendChild(lightboxOverlay);
282
+
283
+ document.querySelectorAll('.article__main img, .changelog-entry-body img').forEach(function(img) {
284
+ img.addEventListener('click', function() {
285
+ lightboxImg.src = img.src;
286
+ lightboxOverlay.classList.add('lightbox-overlay--visible');
287
+ });
288
+ });
246
289
  });
247
290
  </script>