blume 0.7.0 → 1.0.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.
Files changed (185) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/cli/index.js +1852 -380
  5. package/dist/cli/index.js.map +98 -91
  6. package/dist/types/ai/component-markdown.d.ts +34 -0
  7. package/dist/types/components/content/youtube.d.ts +18 -0
  8. package/dist/types/core/base-path.d.ts +9 -0
  9. package/dist/types/core/config-input.d.ts +47 -2
  10. package/dist/types/core/config.d.ts +3 -2
  11. package/dist/types/core/data.d.ts +7 -0
  12. package/dist/types/core/i18n-ui.d.ts +526 -132
  13. package/dist/types/core/schema.d.ts +293 -146
  14. package/dist/types/index.d.ts +1 -0
  15. package/dist/types/openapi/references.d.ts +60 -0
  16. package/dist/types/seo/x-handle.d.ts +12 -0
  17. package/docs/01-quickstart.mdx +5 -2
  18. package/docs/02-deployment.mdx +8 -8
  19. package/docs/03-faq.mdx +46 -16
  20. package/docs/advanced/api-reference.mdx +1 -1
  21. package/docs/advanced/changelog.mdx +1 -1
  22. package/docs/advanced/custom-pages.mdx +1 -1
  23. package/docs/advanced/skills.mdx +1 -1
  24. package/docs/configuration/ai.mdx +49 -10
  25. package/docs/configuration/customization.mdx +11 -0
  26. package/docs/configuration/export.mdx +1 -1
  27. package/docs/configuration/index.mdx +27 -3
  28. package/docs/configuration/seo.mdx +35 -5
  29. package/docs/content/components.mdx +2 -2
  30. package/docs/content/i18n.mdx +1 -1
  31. package/docs/content/navigation.mdx +3 -3
  32. package/docs/content/sources.mdx +1 -1
  33. package/docs/content/syntax.mdx +6 -4
  34. package/docs/index.mdx +2 -2
  35. package/docs/reference/cli.mdx +9 -7
  36. package/docs/reference/frontmatter.mdx +1 -1
  37. package/package.json +22 -4
  38. package/skills/blume/SKILL.md +5 -3
  39. package/skills/blume-update-docs/SKILL.md +3 -2
  40. package/src/ai/agent-readability.ts +9 -8
  41. package/src/ai/ask-context.ts +7 -2
  42. package/src/ai/ask-data.ts +3 -0
  43. package/src/ai/component-markdown.ts +461 -0
  44. package/src/ai/llms.ts +135 -26
  45. package/src/ai/markdown.ts +35 -6
  46. package/src/ai/mcp/data.ts +25 -4
  47. package/src/ai/mcp/discovery.ts +10 -3
  48. package/src/ai/mcp/server.ts +21 -7
  49. package/src/ai/mcp/tools.ts +1 -1
  50. package/src/ai/visibility.ts +74 -0
  51. package/src/astro/component-slots.ts +11 -1
  52. package/src/astro/generate.ts +77 -45
  53. package/src/astro/integration.ts +1 -1
  54. package/src/astro/markdown-negotiation.ts +1 -1
  55. package/src/astro/pages.ts +81 -19
  56. package/src/astro/templates.ts +150 -19
  57. package/src/blume-modules.d.ts +8 -0
  58. package/src/cli/commands/build.ts +120 -23
  59. package/src/cli/commands/check.ts +1 -1
  60. package/src/cli/commands/dev.ts +26 -5
  61. package/src/cli/commands/eject.ts +47 -19
  62. package/src/cli/commands/init.ts +120 -180
  63. package/src/cli/commands/preview.ts +4 -1
  64. package/src/cli/commands/validate.ts +43 -2
  65. package/src/cli/dev-lock.ts +8 -4
  66. package/src/cli/eject-scripts.ts +72 -0
  67. package/src/cli/env.ts +15 -5
  68. package/src/cli/init/questions.ts +158 -0
  69. package/src/cli/init/scaffold.ts +380 -0
  70. package/src/cli/internal-error.ts +9 -4
  71. package/src/cli/prepare.ts +3 -2
  72. package/src/components/Icon.astro +2 -1
  73. package/src/components/content/AccordionItem.astro +23 -4
  74. package/src/components/content/Badge.astro +3 -1
  75. package/src/components/content/Card.astro +4 -2
  76. package/src/components/content/Step.astro +10 -1
  77. package/src/components/content/Tabs.astro +15 -3
  78. package/src/components/content/Tile.astro +2 -1
  79. package/src/components/content/Tooltip.astro +3 -1
  80. package/src/components/content/Update.astro +9 -2
  81. package/src/components/content/auto-type-table.ts +7 -1
  82. package/src/components/content/base-href.ts +33 -0
  83. package/src/components/content/changelog-element.ts +9 -2
  84. package/src/components/content/mermaid-element.ts +7 -2
  85. package/src/components/islands/AskAI.astro +5 -2
  86. package/src/components/islands/ask-ai.tsx +86 -11
  87. package/src/components/islands/hooks.ts +28 -8
  88. package/src/components/layout/Banner.astro +10 -2
  89. package/src/components/layout/Breadcrumbs.astro +11 -2
  90. package/src/components/layout/Header.astro +13 -4
  91. package/src/components/layout/Logo.astro +11 -3
  92. package/src/components/layout/NavTree.astro +19 -5
  93. package/src/components/layout/PageActions.astro +25 -10
  94. package/src/components/layout/PageLayout.astro +85 -9
  95. package/src/components/layout/Pagination.astro +10 -4
  96. package/src/components/layout/ReferenceLayout.astro +20 -2
  97. package/src/components/layout/RootLayout.astro +142 -12
  98. package/src/components/layout/Search.astro +117 -27
  99. package/src/components/layout/search/algolia.ts +11 -2
  100. package/src/components/layout/search/endpoint.ts +11 -5
  101. package/src/components/layout/search/orama-cloud.ts +8 -2
  102. package/src/components/layout/search/types.ts +5 -1
  103. package/src/components/layout/search/typesense.ts +4 -1
  104. package/src/components/layout/toc-element.ts +1 -1
  105. package/src/components/openapi/ApiTagOperations.astro +2 -1
  106. package/src/components/openapi/Operation.astro +47 -40
  107. package/src/components/openapi/RequestPanel.astro +1 -1
  108. package/src/components/openapi/helpers.ts +71 -3
  109. package/src/components/openapi/panel.ts +1 -1
  110. package/src/core/base-path.ts +24 -0
  111. package/src/core/builtin-tags.ts +2 -0
  112. package/src/core/config-input.ts +48 -2
  113. package/src/core/config.ts +3 -2
  114. package/src/core/data.ts +4 -0
  115. package/src/core/frontmatter.ts +7 -0
  116. package/src/core/graph.ts +15 -5
  117. package/src/core/i18n-ui.ts +54 -0
  118. package/src/core/i18n.ts +16 -8
  119. package/src/core/last-modified.ts +13 -6
  120. package/src/core/links.ts +32 -8
  121. package/src/core/navigation.ts +29 -4
  122. package/src/core/package-json.ts +17 -2
  123. package/src/core/project-graph.ts +15 -6
  124. package/src/core/schema.ts +71 -2
  125. package/src/core/sources/assets.ts +6 -1
  126. package/src/core/sources/filesystem.ts +4 -0
  127. package/src/core/sources/mdx-remote.ts +23 -14
  128. package/src/core/sources/normalize.ts +152 -50
  129. package/src/core/sources/notion.ts +8 -8
  130. package/src/core/ui-packs/ar.ts +8 -0
  131. package/src/core/ui-packs/bg.ts +8 -0
  132. package/src/core/ui-packs/bn.ts +8 -0
  133. package/src/core/ui-packs/ca.ts +8 -0
  134. package/src/core/ui-packs/cs.ts +8 -0
  135. package/src/core/ui-packs/da.ts +8 -0
  136. package/src/core/ui-packs/de.ts +8 -0
  137. package/src/core/ui-packs/el.ts +8 -0
  138. package/src/core/ui-packs/es.ts +8 -0
  139. package/src/core/ui-packs/fa.ts +8 -0
  140. package/src/core/ui-packs/fi.ts +8 -0
  141. package/src/core/ui-packs/fr.ts +9 -1
  142. package/src/core/ui-packs/he.ts +8 -0
  143. package/src/core/ui-packs/hi.ts +8 -0
  144. package/src/core/ui-packs/hr.ts +8 -0
  145. package/src/core/ui-packs/hu.ts +8 -0
  146. package/src/core/ui-packs/id.ts +8 -0
  147. package/src/core/ui-packs/it.ts +8 -0
  148. package/src/core/ui-packs/ja.ts +8 -0
  149. package/src/core/ui-packs/ko.ts +8 -0
  150. package/src/core/ui-packs/nl.ts +8 -0
  151. package/src/core/ui-packs/no.ts +8 -0
  152. package/src/core/ui-packs/pl.ts +8 -0
  153. package/src/core/ui-packs/pt-br.ts +8 -0
  154. package/src/core/ui-packs/pt.ts +8 -0
  155. package/src/core/ui-packs/ro.ts +8 -0
  156. package/src/core/ui-packs/ru.ts +8 -0
  157. package/src/core/ui-packs/sk.ts +8 -0
  158. package/src/core/ui-packs/sr.ts +8 -0
  159. package/src/core/ui-packs/sv.ts +8 -0
  160. package/src/core/ui-packs/th.ts +8 -0
  161. package/src/core/ui-packs/tr.ts +8 -0
  162. package/src/core/ui-packs/uk.ts +8 -0
  163. package/src/core/ui-packs/vi.ts +8 -0
  164. package/src/core/ui-packs/zh-tw.ts +8 -0
  165. package/src/core/ui-packs/zh.ts +8 -0
  166. package/src/deploy/adapter-output.ts +18 -8
  167. package/src/deploy/redirects.ts +7 -2
  168. package/src/deploy/sitemap.ts +53 -11
  169. package/src/index.ts +5 -0
  170. package/src/markdown/base-links.ts +10 -8
  171. package/src/markdown/index.ts +15 -3
  172. package/src/markdown/inline-code.ts +7 -2
  173. package/src/markdown/package-commands.ts +10 -4
  174. package/src/og/card.ts +4 -2
  175. package/src/og/dimensions.ts +12 -0
  176. package/src/openapi/model.ts +12 -4
  177. package/src/openapi/parse.ts +21 -0
  178. package/src/openapi/references.ts +38 -8
  179. package/src/openapi/render-mdx.ts +62 -1
  180. package/src/openapi/source.ts +59 -10
  181. package/src/registry/eject.ts +184 -12
  182. package/src/registry/registry.ts +0 -3
  183. package/src/search/documents.ts +34 -2
  184. package/src/seo/jsonld.ts +20 -13
  185. package/src/seo/x-handle.ts +18 -0
@@ -90,50 +90,140 @@ const mapRoute = (
90
90
  return { groups, route, segments };
91
91
  };
92
92
 
93
- const CODE_FENCE = /^```/u;
93
+ // CommonMark allows backtick *and* tilde fences. The scanners track which
94
+ // delimiter opened the current fence (`null` when outside one) so a ``` line
95
+ // inside a ~~~ block is content, not a toggle — see `nextFenceState`.
96
+ const CODE_FENCE = /^(?<delimiter>```|~~~)/u;
97
+
98
+ /** The fence delimiter opening the current code block, or null outside one. */
99
+ type FenceState = "```" | "~~~" | null;
100
+
101
+ /**
102
+ * Advance the fenced-code state for one line: an opening fence records its
103
+ * delimiter, only the matching delimiter closes it, and any other line leaves
104
+ * the state untouched.
105
+ */
106
+ const nextFenceState = (line: string, fence: FenceState): FenceState => {
107
+ const delimiter = line.trimStart().match(CODE_FENCE)?.groups?.delimiter as
108
+ | Exclude<FenceState, null>
109
+ | undefined;
110
+ if (delimiter === undefined) {
111
+ return fence;
112
+ }
113
+ if (fence === null) {
114
+ return delimiter;
115
+ }
116
+ return fence === delimiter ? null : fence;
117
+ };
94
118
  // A closing hash sequence must be preceded by whitespace (CommonMark), so a
95
- // heading like `## What is C#` keeps its trailing `#`.
96
- const ATX_HEADING = /^(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
119
+ // heading like `## What is C#` keeps its trailing `#`. Up to 3 leading spaces
120
+ // are allowed; 4+ is an indented code block.
121
+ const ATX_HEADING = /^ {0,3}(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
122
+ // A setext underline: a run of `=` (level 1) or `-` (level 2) alone on a line,
123
+ // up to 3 leading spaces. It only forms a heading directly under paragraph
124
+ // text — see `scanHeadingLine`.
125
+ const SETEXT_UNDERLINE = /^ {0,3}(?<marker>=+|-+)\s*$/u;
126
+ // Lines that end a paragraph without being one (CommonMark): blank lines are
127
+ // checked separately; these cover list items, blockquotes, and thematic
128
+ // breaks, so a `---` after any of them stays a thematic break, not an
129
+ // underline promoting the list/quote text to a heading.
130
+ const PARAGRAPH_INTERRUPT = /^ {0,3}(?:[-+*][ \t]|\d{1,9}[.)][ \t]|>)/u;
131
+ const THEMATIC_BREAK =
132
+ /^ {0,3}(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/u;
133
+ const FRONT_MATTER_CLOSE = /^(?:-{3}|\.{3})\s*$/u;
97
134
 
98
135
  /**
99
- * Extract ATX headings from a markdown body, skipping fenced code blocks. Each
100
- * heading's anchor slug comes from a per-document `github-slugger` — the exact
101
- * slugger the renderer uses (`markdown/heading-anchors`)advanced over every
102
- * `#`–`######` in document order. Matching it (rather than a hand-rolled
103
- * slugify) keeps the manifest's anchor ids identical to the rendered ones, so
104
- * `blume validate` stops false-flagging links like `#the-read--write-fallback`
105
- * (a hand slugify collapses `--`; github-slugger keeps it) and resolves repeated
106
- * headings the same way (`setup`, `setup-1`).
136
+ * The body lines, minus a leading front matter block. Bodies from the
137
+ * normalize pipeline are already frontmatter-stripped, but `extractHeadings`
138
+ * also runs on raw documentswhere a leading `---` block (closed by `---` or
139
+ * `...`) is front matter, not a thematic break whose closing `---` would
140
+ * underline the last metadata line into a phantom setext heading.
107
141
  */
108
- /** Scan one line for an ATX heading; returns the next fenced-block state. */
142
+ const linesWithoutFrontMatter = (body: string): string[] => {
143
+ const lines = body.split("\n");
144
+ if (!/^-{3}\s*$/u.test(lines[0] ?? "")) {
145
+ return lines;
146
+ }
147
+ const close = lines.findIndex(
148
+ (line, index) => index > 0 && FRONT_MATTER_CLOSE.test(line)
149
+ );
150
+ return close === -1 ? lines : lines.slice(close + 1);
151
+ };
152
+
153
+ /** Scanner state: the open fence plus the paragraph lines accumulated so far. */
154
+ interface HeadingScanState {
155
+ fence: FenceState;
156
+ /** Consecutive paragraph lines — the candidate text for a setext underline. */
157
+ paragraph: string[];
158
+ }
159
+
160
+ /**
161
+ * Extract ATX and setext headings from a markdown body, skipping fenced code
162
+ * blocks, exactly as the renderer sees them: ATX headings may be indented up
163
+ * to 3 spaces, and a paragraph underlined with `=`/`-` is a level 1/2 setext
164
+ * heading. Each heading's anchor slug comes from a per-document
165
+ * `github-slugger` — the exact slugger the renderer uses
166
+ * (`markdown/heading-anchors`) — advanced over every heading in document
167
+ * order. Matching it (rather than a hand-rolled slugify) keeps the manifest's
168
+ * anchor ids identical to the rendered ones, so `blume validate` stops
169
+ * false-flagging links like `#the-read--write-fallback` (a hand slugify
170
+ * collapses `--`; github-slugger keeps it) and resolves repeated headings the
171
+ * same way (`setup`, `setup-1`).
172
+ */
173
+ /** Scan one line for a heading, advancing the fence/paragraph state. */
109
174
  const scanHeadingLine = (
110
175
  line: string,
111
- inFence: boolean,
176
+ state: HeadingScanState,
112
177
  slugger: GithubSlugger,
113
178
  headings: Heading[]
114
- ): boolean => {
115
- if (CODE_FENCE.test(line.trimStart())) {
116
- return !inFence;
117
- }
118
- if (inFence) {
119
- return inFence;
179
+ ): void => {
180
+ const next = nextFenceState(line, state.fence);
181
+ // Skip fence delimiter lines themselves and anything inside a fence. A fence
182
+ // also ends any open paragraph, so no underline can reach across it.
183
+ if (state.fence !== null || next !== null) {
184
+ state.fence = next;
185
+ state.paragraph = [];
186
+ return;
120
187
  }
121
- const match = line.match(ATX_HEADING);
122
- if (match?.groups) {
123
- const depth = match.groups.hashes?.length ?? 1;
124
- const text = (match.groups.text ?? "").trim();
188
+ const atx = line.match(ATX_HEADING);
189
+ if (atx?.groups) {
190
+ const depth = atx.groups.hashes?.length ?? 1;
191
+ const text = (atx.groups.text ?? "").trim();
125
192
  headings.push({ depth, slug: slugger.slug(text), text });
193
+ state.paragraph = [];
194
+ return;
195
+ }
196
+ const setext = line.match(SETEXT_UNDERLINE);
197
+ if (setext?.groups && state.paragraph.length > 0) {
198
+ // Setext wins over thematic break when it closes a paragraph (CommonMark);
199
+ // a multi-line paragraph renders as one heading, soft breaks as spaces.
200
+ const text = state.paragraph.join(" ").trim();
201
+ headings.push({
202
+ depth: setext.groups.marker?.startsWith("=") ? 1 : 2,
203
+ slug: slugger.slug(text),
204
+ text,
205
+ });
206
+ state.paragraph = [];
207
+ return;
208
+ }
209
+ if (
210
+ line.trim() === "" ||
211
+ THEMATIC_BREAK.test(line) ||
212
+ PARAGRAPH_INTERRUPT.test(line)
213
+ ) {
214
+ state.paragraph = [];
215
+ return;
126
216
  }
127
- return inFence;
217
+ state.paragraph.push(line.trim());
128
218
  };
129
219
 
130
220
  export const extractHeadings = (body: string): Heading[] => {
131
221
  const headings: Heading[] = [];
132
222
  const slugger = new GithubSlugger();
133
- let inFence = false;
223
+ const state: HeadingScanState = { fence: null, paragraph: [] };
134
224
 
135
- for (const line of body.split("\n")) {
136
- inFence = scanHeadingLine(line, inFence, slugger, headings);
225
+ for (const line of linesWithoutFrontMatter(body)) {
226
+ scanHeadingLine(line, state, slugger, headings);
137
227
  }
138
228
 
139
229
  return headings;
@@ -145,19 +235,20 @@ const INLINE_CODE = /`[^`]*`/gu;
145
235
  /**
146
236
  * Extract link targets from a markdown body for later validation, recording the
147
237
  * 1-based line/column of each target. Skips fenced code blocks and inline code.
238
+ * `lineOffset` shifts every recorded line: the body is frontmatter-stripped, so
239
+ * diagnostics that point into the raw file must add the stripped block's height.
148
240
  */
149
241
  /** Scan one line for link targets; returns the next fenced-block state. */
150
242
  const scanLinkLine = (
151
243
  line: string,
152
244
  lineNumber: number,
153
- inFence: boolean,
245
+ fence: FenceState,
154
246
  links: PageLink[]
155
- ): boolean => {
156
- if (CODE_FENCE.test(line.trimStart())) {
157
- return !inFence;
158
- }
159
- if (inFence) {
160
- return inFence;
247
+ ): FenceState => {
248
+ const next = nextFenceState(line, fence);
249
+ // Skip fence delimiter lines themselves and anything inside a fence.
250
+ if (fence !== null || next !== null) {
251
+ return next;
161
252
  }
162
253
  // Blank out inline code spans (`[label](/x)` shown as syntax, not a link)
163
254
  // with same-length padding so recorded columns stay accurate.
@@ -180,17 +271,17 @@ const scanLinkLine = (
180
271
  target,
181
272
  });
182
273
  }
183
- return inFence;
274
+ return next;
184
275
  };
185
276
 
186
- export const extractLinks = (body: string): PageLink[] => {
277
+ export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
187
278
  const links: PageLink[] = [];
188
- let inFence = false;
189
- let lineNumber = 0;
279
+ let fence: FenceState = null;
280
+ let lineNumber = lineOffset;
190
281
 
191
282
  for (const line of body.split("\n")) {
192
283
  lineNumber += 1;
193
- inFence = scanLinkLine(line, lineNumber, inFence, links);
284
+ fence = scanLinkLine(line, lineNumber, fence, links);
194
285
  }
195
286
 
196
287
  return links;
@@ -212,14 +303,13 @@ const JSX_OPEN = /<(?<tag>[A-Z][A-Za-z0-9]*)/gu;
212
303
  /** Scan one line for JSX component tags; returns the next fenced-block state. */
213
304
  const scanTagLine = (
214
305
  line: string,
215
- inFence: boolean,
306
+ fence: FenceState,
216
307
  tags: Set<string>
217
- ): boolean => {
218
- if (CODE_FENCE.test(line.trimStart())) {
219
- return !inFence;
220
- }
221
- if (inFence) {
222
- return inFence;
308
+ ): FenceState => {
309
+ const next = nextFenceState(line, fence);
310
+ // Skip fence delimiter lines themselves and anything inside a fence.
311
+ if (fence !== null || next !== null) {
312
+ return next;
223
313
  }
224
314
  const clean = line.replaceAll(INLINE_CODE, "").replaceAll(DOUBLE_QUOTED, "");
225
315
  for (const match of clean.matchAll(JSX_OPEN)) {
@@ -228,18 +318,27 @@ const scanTagLine = (
228
318
  tags.add(tag);
229
319
  }
230
320
  }
231
- return inFence;
321
+ return next;
232
322
  };
233
323
 
234
324
  export const extractComponentTags = (body: string): string[] => {
235
325
  const tags = new Set<string>();
236
- let inFence = false;
326
+ let fence: FenceState = null;
237
327
  for (const line of body.split("\n")) {
238
- inFence = scanTagLine(line, inFence, tags);
328
+ fence = scanTagLine(line, fence, tags);
239
329
  }
240
330
  return [...tags];
241
331
  };
242
332
 
333
+ /**
334
+ * Height of the frontmatter block stripped from `raw` to produce `body` (0
335
+ * when the raw text is unknown or nothing was stripped). Link positions are
336
+ * extracted from the stripped body, but diagnostics point into the raw
337
+ * document — recorded lines must shift by this offset to match it.
338
+ */
339
+ const strippedLineOffset = (raw: string | undefined, body: string): number =>
340
+ raw ? Math.max(0, raw.split("\n").length - body.split("\n").length) : 0;
341
+
243
342
  const deriveTitle = (
244
343
  meta: PageMeta,
245
344
  headings: Heading[],
@@ -348,7 +447,10 @@ export const normalizeEntry = (
348
447
  headings,
349
448
  id: `${ctx.source.name}:${entry.ref}`,
350
449
  lastModified: meta.lastModified ?? entry.lastModified,
351
- links: extractLinks(entry.body.text),
450
+ links: extractLinks(
451
+ entry.body.text,
452
+ strippedLineOffset(entry.raw, entry.body.text)
453
+ ),
352
454
  meta,
353
455
  navPath,
354
456
  segments,
@@ -100,10 +100,9 @@ export interface NotionSourceOptions {
100
100
  token?: string;
101
101
  properties?: NotionPropertyMap;
102
102
  /**
103
- * When set, the `Status` property is treated as a publish signal: any value
104
- * other than this maps to `draft: true`. Omit to import every page regardless
105
- * of status (the safe default for databases that use Status for an editorial
106
- * workflow rather than publishing).
103
+ * The `Status` property is treated as a publish signal: any value other than
104
+ * this maps to `draft: true`. Defaults to `Published`; pages without a
105
+ * Status/select property are always imported as published.
107
106
  */
108
107
  publishedValue?: string;
109
108
  /** Opt-in dev polling interval (seconds); omit to freeze for the session. */
@@ -395,12 +394,13 @@ export const notionSource = (
395
394
  };
396
395
 
397
396
  const isDraft = (page: NotionPage): boolean => {
398
- if (!options.publishedValue) {
399
- return false;
400
- }
401
397
  const prop = page.properties[props.status ?? "Status"];
402
398
  const status = prop?.status?.name ?? prop?.select?.name;
403
- return Boolean(status && status !== options.publishedValue);
399
+ // A page without a Status/select property stays published; one with a
400
+ // status is a draft unless it matches the published value.
401
+ return Boolean(
402
+ status && status !== (options.publishedValue ?? "Published")
403
+ );
404
404
  };
405
405
 
406
406
  const orderOf = (page: NotionPage): number | undefined => {
@@ -23,6 +23,11 @@ export const ar: UIStringsOverride = {
23
23
  send: "إرسال",
24
24
  title: "اسأل الذكاء الاصطناعي",
25
25
  },
26
+ banner: { dismiss: "إغلاق الإعلان" },
27
+ changelog: {
28
+ description: "تحديثات المنتج وملاحظات الإصدارات.",
29
+ title: "سجل التغييرات",
30
+ },
26
31
  feedback: {
27
32
  no: "لا",
28
33
  question: "هل كانت هذه الصفحة مفيدة؟",
@@ -30,13 +35,16 @@ export const ar: UIStringsOverride = {
30
35
  yes: "نعم",
31
36
  },
32
37
  languageSwitcher: { label: "اللغة", untranslated: "غير مترجم" },
38
+ nav: { breadcrumb: "مسار التنقل" },
33
39
  page: {
34
40
  lastUpdated: "آخر تحديث في",
35
41
  next: "التالي",
42
+ pagination: "ترقيم الصفحات",
36
43
  previous: "السابق",
37
44
  skipToContent: "الانتقال إلى المحتوى",
38
45
  },
39
46
  search: {
47
+ all: "الكل",
40
48
  button: "بحث",
41
49
  devOnly: "البحث متاح في إصدار الإنتاج.",
42
50
  label: "البحث في الوثائق",
@@ -23,6 +23,11 @@ export const bg: UIStringsOverride = {
23
23
  send: "Изпрати",
24
24
  title: "Попитай ИИ",
25
25
  },
26
+ banner: { dismiss: "Затваряне на съобщението" },
27
+ changelog: {
28
+ description: "Актуализации на продукта и бележки към изданията.",
29
+ title: "Дневник на промените",
30
+ },
26
31
  feedback: {
27
32
  no: "Не",
28
33
  question: "Беше ли полезна тази страница?",
@@ -30,13 +35,16 @@ export const bg: UIStringsOverride = {
30
35
  yes: "Да",
31
36
  },
32
37
  languageSwitcher: { label: "Език", untranslated: "Непреведено" },
38
+ nav: { breadcrumb: "Навигационна пътека" },
33
39
  page: {
34
40
  lastUpdated: "Последна актуализация",
35
41
  next: "Напред",
42
+ pagination: "Пагинация",
36
43
  previous: "Назад",
37
44
  skipToContent: "Към съдържанието",
38
45
  },
39
46
  search: {
47
+ all: "Всички",
40
48
  button: "Търсене",
41
49
  devOnly: "Търсенето е достъпно в производствената компилация.",
42
50
  label: "Търсене в документацията",
@@ -23,6 +23,11 @@ export const bn: UIStringsOverride = {
23
23
  send: "পাঠান",
24
24
  title: "AI-কে জিজ্ঞাসা করুন",
25
25
  },
26
+ banner: { dismiss: "ঘোষণা বন্ধ করুন" },
27
+ changelog: {
28
+ description: "পণ্য আপডেট এবং রিলিজ নোট।",
29
+ title: "পরিবর্তন লগ",
30
+ },
26
31
  feedback: {
27
32
  no: "না",
28
33
  question: "এই পৃষ্ঠাটি কি সহায়ক ছিল?",
@@ -30,13 +35,16 @@ export const bn: UIStringsOverride = {
30
35
  yes: "হ্যাঁ",
31
36
  },
32
37
  languageSwitcher: { label: "ভাষা", untranslated: "অনূদিত নয়" },
38
+ nav: { breadcrumb: "ব্রেডক্রাম্ব" },
33
39
  page: {
34
40
  lastUpdated: "সর্বশেষ আপডেট",
35
41
  next: "পরবর্তী",
42
+ pagination: "পেজিনেশন",
36
43
  previous: "পূর্ববর্তী",
37
44
  skipToContent: "বিষয়বস্তুতে যান",
38
45
  },
39
46
  search: {
47
+ all: "সব",
40
48
  button: "অনুসন্ধান",
41
49
  devOnly: "অনুসন্ধান প্রোডাকশন বিল্ডে উপলব্ধ।",
42
50
  label: "ডকুমেন্টেশন অনুসন্ধান করুন",
@@ -23,6 +23,11 @@ export const ca: UIStringsOverride = {
23
23
  send: "Envia",
24
24
  title: "Pregunta a la IA",
25
25
  },
26
+ banner: { dismiss: "Tanca l'anunci" },
27
+ changelog: {
28
+ description: "Actualitzacions del producte i notes de la versió.",
29
+ title: "Registre de canvis",
30
+ },
26
31
  feedback: {
27
32
  no: "No",
28
33
  question: "Aquesta pàgina t'ha estat útil?",
@@ -30,13 +35,16 @@ export const ca: UIStringsOverride = {
30
35
  yes: "Sí",
31
36
  },
32
37
  languageSwitcher: { label: "Idioma", untranslated: "Sense traduir" },
38
+ nav: { breadcrumb: "Ruta de navegació" },
33
39
  page: {
34
40
  lastUpdated: "Última actualització el",
35
41
  next: "Següent",
42
+ pagination: "Paginació",
36
43
  previous: "Anterior",
37
44
  skipToContent: "Vés al contingut",
38
45
  },
39
46
  search: {
47
+ all: "Tots",
40
48
  button: "Cerca",
41
49
  devOnly: "La cerca està disponible a la compilació de producció.",
42
50
  label: "Cerca a la documentació",
@@ -23,6 +23,11 @@ export const cs: UIStringsOverride = {
23
23
  send: "Odeslat",
24
24
  title: "Zeptat se AI",
25
25
  },
26
+ banner: { dismiss: "Zavřít oznámení" },
27
+ changelog: {
28
+ description: "Novinky produktu a poznámky k vydání.",
29
+ title: "Seznam změn",
30
+ },
26
31
  feedback: {
27
32
  no: "Ne",
28
33
  question: "Byla tato stránka užitečná?",
@@ -30,13 +35,16 @@ export const cs: UIStringsOverride = {
30
35
  yes: "Ano",
31
36
  },
32
37
  languageSwitcher: { label: "Jazyk", untranslated: "Nepřeloženo" },
38
+ nav: { breadcrumb: "Drobečková navigace" },
33
39
  page: {
34
40
  lastUpdated: "Naposledy aktualizováno",
35
41
  next: "Další",
42
+ pagination: "Stránkování",
36
43
  previous: "Předchozí",
37
44
  skipToContent: "Přejít k obsahu",
38
45
  },
39
46
  search: {
47
+ all: "Vše",
40
48
  button: "Hledat",
41
49
  devOnly: "Vyhledávání je dostupné v produkčním buildu.",
42
50
  label: "Prohledat dokumentaci",
@@ -23,6 +23,11 @@ export const da: UIStringsOverride = {
23
23
  send: "Send",
24
24
  title: "Spørg AI",
25
25
  },
26
+ banner: { dismiss: "Luk meddelelsen" },
27
+ changelog: {
28
+ description: "Produktopdateringer og udgivelsesnoter.",
29
+ title: "Ændringslog",
30
+ },
26
31
  feedback: {
27
32
  no: "Nej",
28
33
  question: "Var denne side nyttig?",
@@ -30,13 +35,16 @@ export const da: UIStringsOverride = {
30
35
  yes: "Ja",
31
36
  },
32
37
  languageSwitcher: { label: "Sprog", untranslated: "Ikke oversat" },
38
+ nav: { breadcrumb: "Brødkrummesti" },
33
39
  page: {
34
40
  lastUpdated: "Senest opdateret den",
35
41
  next: "Næste",
42
+ pagination: "Paginering",
36
43
  previous: "Forrige",
37
44
  skipToContent: "Spring til indhold",
38
45
  },
39
46
  search: {
47
+ all: "Alle",
40
48
  button: "Søg",
41
49
  devOnly: "Søgning er tilgængelig i produktionsbygningen.",
42
50
  label: "Søg i dokumentationen",
@@ -23,6 +23,11 @@ export const de: UIStringsOverride = {
23
23
  send: "Senden",
24
24
  title: "KI fragen",
25
25
  },
26
+ banner: { dismiss: "Ankündigung schließen" },
27
+ changelog: {
28
+ description: "Produkt-Updates und Versionshinweise.",
29
+ title: "Änderungsprotokoll",
30
+ },
26
31
  feedback: {
27
32
  no: "Nein",
28
33
  question: "War diese Seite hilfreich?",
@@ -30,13 +35,16 @@ export const de: UIStringsOverride = {
30
35
  yes: "Ja",
31
36
  },
32
37
  languageSwitcher: { label: "Sprache", untranslated: "Nicht übersetzt" },
38
+ nav: { breadcrumb: "Brotkrümelnavigation" },
33
39
  page: {
34
40
  lastUpdated: "Zuletzt aktualisiert am",
35
41
  next: "Weiter",
42
+ pagination: "Seitennummerierung",
36
43
  previous: "Zurück",
37
44
  skipToContent: "Zum Inhalt springen",
38
45
  },
39
46
  search: {
47
+ all: "Alle",
40
48
  button: "Suchen",
41
49
  devOnly: "Die Suche ist im Produktions-Build verfügbar.",
42
50
  label: "Dokumentation durchsuchen",
@@ -23,6 +23,11 @@ export const el: UIStringsOverride = {
23
23
  send: "Αποστολή",
24
24
  title: "Ρωτήστε την AI",
25
25
  },
26
+ banner: { dismiss: "Κλείσιμο ανακοίνωσης" },
27
+ changelog: {
28
+ description: "Ενημερώσεις προϊόντος και σημειώσεις έκδοσης.",
29
+ title: "Ιστορικό αλλαγών",
30
+ },
26
31
  feedback: {
27
32
  no: "Όχι",
28
33
  question: "Σας φάνηκε χρήσιμη αυτή η σελίδα;",
@@ -30,13 +35,16 @@ export const el: UIStringsOverride = {
30
35
  yes: "Ναι",
31
36
  },
32
37
  languageSwitcher: { label: "Γλώσσα", untranslated: "Δεν έχει μεταφραστεί" },
38
+ nav: { breadcrumb: "Διαδρομή πλοήγησης" },
33
39
  page: {
34
40
  lastUpdated: "Τελευταία ενημέρωση",
35
41
  next: "Επόμενο",
42
+ pagination: "Σελιδοποίηση",
36
43
  previous: "Προηγούμενο",
37
44
  skipToContent: "Μετάβαση στο περιεχόμενο",
38
45
  },
39
46
  search: {
47
+ all: "Όλα",
40
48
  button: "Αναζήτηση",
41
49
  devOnly: "Η αναζήτηση είναι διαθέσιμη στην έκδοση παραγωγής.",
42
50
  label: "Αναζήτηση στην τεκμηρίωση",
@@ -23,6 +23,11 @@ export const es: UIStringsOverride = {
23
23
  send: "Enviar",
24
24
  title: "Preguntar a la IA",
25
25
  },
26
+ banner: { dismiss: "Cerrar el anuncio" },
27
+ changelog: {
28
+ description: "Novedades del producto y notas de la versión.",
29
+ title: "Registro de cambios",
30
+ },
26
31
  feedback: {
27
32
  no: "No",
28
33
  question: "¿Te ha resultado útil esta página?",
@@ -30,13 +35,16 @@ export const es: UIStringsOverride = {
30
35
  yes: "Sí",
31
36
  },
32
37
  languageSwitcher: { label: "Idioma", untranslated: "Sin traducir" },
38
+ nav: { breadcrumb: "Ruta de navegación" },
33
39
  page: {
34
40
  lastUpdated: "Última actualización el",
35
41
  next: "Siguiente",
42
+ pagination: "Paginación",
36
43
  previous: "Anterior",
37
44
  skipToContent: "Saltar al contenido",
38
45
  },
39
46
  search: {
47
+ all: "Todos",
40
48
  button: "Buscar",
41
49
  devOnly: "La búsqueda está disponible en la compilación de producción.",
42
50
  label: "Buscar en la documentación",
@@ -23,6 +23,11 @@ export const fa: UIStringsOverride = {
23
23
  send: "ارسال",
24
24
  title: "از هوش مصنوعی بپرسید",
25
25
  },
26
+ banner: { dismiss: "بستن اطلاعیه" },
27
+ changelog: {
28
+ description: "به‌روزرسانی‌های محصول و یادداشت‌های انتشار.",
29
+ title: "گزارش تغییرات",
30
+ },
26
31
  feedback: {
27
32
  no: "خیر",
28
33
  question: "آیا این صفحه مفید بود؟",
@@ -30,13 +35,16 @@ export const fa: UIStringsOverride = {
30
35
  yes: "بله",
31
36
  },
32
37
  languageSwitcher: { label: "زبان", untranslated: "ترجمه‌نشده" },
38
+ nav: { breadcrumb: "مسیر ناوبری" },
33
39
  page: {
34
40
  lastUpdated: "آخرین به‌روزرسانی",
35
41
  next: "بعدی",
42
+ pagination: "صفحه‌بندی",
36
43
  previous: "قبلی",
37
44
  skipToContent: "پرش به محتوا",
38
45
  },
39
46
  search: {
47
+ all: "همه",
40
48
  button: "جستجو",
41
49
  devOnly: "جستجو در نسخه تولید در دسترس است.",
42
50
  label: "جستجو در مستندات",
@@ -23,6 +23,11 @@ export const fi: UIStringsOverride = {
23
23
  send: "Lähetä",
24
24
  title: "Kysy tekoälyltä",
25
25
  },
26
+ banner: { dismiss: "Sulje ilmoitus" },
27
+ changelog: {
28
+ description: "Tuotepäivitykset ja julkaisutiedot.",
29
+ title: "Muutosloki",
30
+ },
26
31
  feedback: {
27
32
  no: "Ei",
28
33
  question: "Oliko tästä sivusta apua?",
@@ -30,13 +35,16 @@ export const fi: UIStringsOverride = {
30
35
  yes: "Kyllä",
31
36
  },
32
37
  languageSwitcher: { label: "Kieli", untranslated: "Ei käännetty" },
38
+ nav: { breadcrumb: "Murupolku" },
33
39
  page: {
34
40
  lastUpdated: "Viimeksi päivitetty",
35
41
  next: "Seuraava",
42
+ pagination: "Sivutus",
36
43
  previous: "Edellinen",
37
44
  skipToContent: "Siirry sisältöön",
38
45
  },
39
46
  search: {
47
+ all: "Kaikki",
40
48
  button: "Hae",
41
49
  devOnly: "Haku on käytettävissä tuotantokäännöksessä.",
42
50
  label: "Hae dokumentaatiosta",