docus 5.8.1 → 5.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.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/app/app.config.ts +13 -0
  3. package/app/app.vue +5 -1
  4. package/app/components/OgImage/Docs.takumi.vue +43 -0
  5. package/app/components/OgImage/Landing.takumi.vue +67 -0
  6. package/app/components/app/AppFooterRight.vue +4 -1
  7. package/app/components/app/AppHeader.vue +6 -7
  8. package/app/components/app/AppHeaderBody.vue +6 -2
  9. package/app/components/app/AppHeaderBottom.vue +6 -2
  10. package/app/components/app/AppHeaderLeft.vue +16 -0
  11. package/app/components/docs/DocsAsideLeftBody.vue +6 -1
  12. package/app/components/docs/DocsAsideMobileBar.vue +11 -1
  13. package/app/components/docs/DocsAsideRight.vue +6 -1
  14. package/app/composables/useDocusColorMode.ts +7 -0
  15. package/app/composables/useUIConfig.ts +30 -0
  16. package/app/error.vue +3 -0
  17. package/app/middleware/colorMode.global.ts +8 -0
  18. package/app/pages/[[lang]]/[...slug].vue +16 -12
  19. package/app/templates/landing.vue +3 -3
  20. package/app/utils/ogImage.ts +23 -0
  21. package/i18n/locales/ar.json +27 -0
  22. package/i18n/locales/be.json +27 -0
  23. package/i18n/locales/bg.json +27 -0
  24. package/i18n/locales/bn.json +27 -0
  25. package/i18n/locales/ca.json +27 -0
  26. package/i18n/locales/ckb.json +32 -1
  27. package/i18n/locales/cs.json +27 -0
  28. package/i18n/locales/da.json +27 -0
  29. package/i18n/locales/de.json +27 -0
  30. package/i18n/locales/el.json +27 -0
  31. package/i18n/locales/es.json +27 -0
  32. package/i18n/locales/et.json +27 -0
  33. package/i18n/locales/fi.json +27 -0
  34. package/i18n/locales/he.json +27 -0
  35. package/i18n/locales/hi.json +27 -0
  36. package/i18n/locales/hy.json +27 -0
  37. package/i18n/locales/id.json +27 -0
  38. package/i18n/locales/it.json +27 -0
  39. package/i18n/locales/ja.json +27 -0
  40. package/i18n/locales/kk.json +27 -0
  41. package/i18n/locales/km.json +27 -0
  42. package/i18n/locales/ko.json +27 -0
  43. package/i18n/locales/ky.json +27 -0
  44. package/i18n/locales/lb.json +27 -0
  45. package/i18n/locales/ms.json +27 -0
  46. package/i18n/locales/nb.json +27 -0
  47. package/i18n/locales/nl.json +27 -0
  48. package/i18n/locales/pl.json +27 -0
  49. package/i18n/locales/pt-BR.json +27 -0
  50. package/i18n/locales/ro.json +27 -0
  51. package/i18n/locales/ru.json +27 -0
  52. package/i18n/locales/si.json +27 -0
  53. package/i18n/locales/sl.json +27 -0
  54. package/i18n/locales/sv.json +27 -0
  55. package/i18n/locales/tr.json +27 -0
  56. package/i18n/locales/uk.json +27 -0
  57. package/i18n/locales/ur.json +27 -0
  58. package/i18n/locales/vi.json +27 -0
  59. package/i18n/locales/zh-CN.json +27 -0
  60. package/index.d.ts +33 -0
  61. package/modules/assistant/README.md +17 -8
  62. package/modules/assistant/index.ts +26 -16
  63. package/modules/assistant/runtime/server/api/search.ts +5 -0
  64. package/modules/config.ts +7 -2
  65. package/modules/css.ts +12 -0
  66. package/modules/skills/index.ts +158 -0
  67. package/modules/skills/runtime/server/routes/skills-files.ts +49 -0
  68. package/modules/skills/runtime/server/routes/skills-index.ts +8 -0
  69. package/nuxt.config.ts +8 -2
  70. package/nuxt.schema.ts +22 -0
  71. package/package.json +24 -21
  72. package/server/mcp/tools/get-page.ts +16 -5
  73. package/server/mcp/tools/list-pages.ts +13 -3
  74. package/app/components/OgImage/OgImageDocs.vue +0 -76
  75. package/app/components/OgImage/OgImageLanding.vue +0 -98
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![docus](https://docus.dev/__og-image__/static/og.png)](https://docus.dev)
1
+ [![docus](https://docus.dev/_og/s/c_Landing,title_Write+beautiful+docs+with+Markdown,description_Ship+fast+flexible+and+SEO-optimized+documentation+with+beautiful+design+out+of+the+box.+Docus+brings+together+the+best+of+the+Nuxt+ecosystem.+Powered+by+Nuxt+UI.,p_Ii9lbiI.png)](https://docus.dev)
2
2
 
3
3
  # Docus
4
4
 
package/app/app.config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export default defineAppConfig({
2
2
  docus: {
3
3
  locale: 'en',
4
+ colorMode: '',
4
5
  },
5
6
  ui: {
6
7
  colors: {
@@ -14,6 +15,11 @@ export default defineAppConfig({
14
15
  itemLeadingIcon: 'size-4 mx-0.5',
15
16
  },
16
17
  },
18
+ contentToc: {
19
+ defaultVariants: {
20
+ highlight: true,
21
+ },
22
+ },
17
23
  contentNavigation: {
18
24
  slots: {
19
25
  linkLeadingIcon: 'size-4 mr-1',
@@ -21,6 +27,13 @@ export default defineAppConfig({
21
27
  },
22
28
  defaultVariants: {
23
29
  variant: 'link',
30
+ highlight: true,
31
+ },
32
+ },
33
+ navigationMenu: {
34
+ defaultVariants: {
35
+ variant: 'pill',
36
+ highlight: true,
24
37
  },
25
38
  },
26
39
  pageLinks: {
package/app/app.vue CHANGED
@@ -2,9 +2,12 @@
2
2
  import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
3
3
  import * as nuxtUiLocales from '@nuxt/ui/locale'
4
4
  import { transformNavigation } from './utils/navigation'
5
+ import { useDocusColorMode } from './composables/useDocusColorMode'
5
6
  import { useSubNavigation } from './composables/useSubNavigation'
6
7
 
7
- const { seo } = useAppConfig()
8
+ const appConfig = useAppConfig()
9
+ const { seo } = appConfig
10
+ const { forced: forcedColorMode } = useDocusColorMode()
8
11
  const site = useSiteConfig()
9
12
  const { locale, locales, isEnabled, switchLocalePath } = useDocusI18n()
10
13
  const { isEnabled: isAssistantEnabled, panelWidth: assistantPanelWidth, shouldPushContent } = useAssistant()
@@ -79,6 +82,7 @@ const { subNavigationMode } = useSubNavigation(navigation)
79
82
  <LazyUContentSearch
80
83
  :files="files"
81
84
  :navigation="navigation"
85
+ :color-mode="!forcedColorMode"
82
86
  />
83
87
  <template v-if="isAssistantEnabled">
84
88
  <LazyAssistantPanel />
@@ -0,0 +1,43 @@
1
+ <script lang="ts" setup>
2
+ const { title, description, headline } = defineProps<{ title?: string, description?: string, headline?: string }>()
3
+
4
+ const appConfig = useAppConfig()
5
+ const { name: siteName } = useSiteConfig()
6
+ const primaryColor = appConfig.ui?.colors?.primary ?? 'emerald'
7
+ </script>
8
+
9
+ <template>
10
+ <div class="w-full h-full flex flex-col justify-between bg-neutral-950 px-[80px] py-[60px]">
11
+ <!-- Radial glow top-right: wide soft layer -->
12
+ <div class="absolute top-0 right-0 w-[700px] h-[700px] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.10)_0%,rgba(255,255,255,0.04)_40%,transparent_70%)]" />
13
+ <!-- Radial glow top-right: tight bright core -->
14
+ <div class="absolute top-0 right-0 w-[350px] h-[350px] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.22)_0%,rgba(255,255,255,0.08)_35%,transparent_65%)]" />
15
+
16
+ <div class="flex-1 flex flex-col justify-center">
17
+ <p
18
+ v-if="headline"
19
+ :class="`uppercase text-[22px] font-bold m-0 mb-5 tracking-[0.05em] text-${primaryColor}-500`"
20
+ >
21
+ {{ headline }}
22
+ </p>
23
+ <h1
24
+ v-if="title"
25
+ class="m-0 mb-6 text-[50px] font-bold text-white leading-[1.1] w-full max-w-[900px] wrap-break-word"
26
+ >
27
+ {{ title?.slice(0, 60) }}
28
+ </h1>
29
+ <p
30
+ v-if="description"
31
+ class="m-0 text-[28px] text-neutral-400 leading-[1.4] w-full max-w-[900px] wrap-break-word"
32
+ >
33
+ {{ description?.slice(0, 200) }}
34
+ </p>
35
+ </div>
36
+
37
+ <div class="flex">
38
+ <div class="text-white text-[18px] font-normal rounded-lg px-5 py-2">
39
+ {{ siteName }}
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </template>
@@ -0,0 +1,67 @@
1
+ <script lang="ts" setup>
2
+ const { title, description } = defineProps<{ title?: string, description?: string }>()
3
+
4
+ const appConfig = useAppConfig()
5
+ const { name: siteName } = useSiteConfig()
6
+ const primaryColor = appConfig.ui?.colors?.primary ?? 'emerald'
7
+ const logoPath = appConfig.header?.logo?.dark || appConfig.header?.logo?.light
8
+
9
+ const logoSvg = await fetchLogoSvg(logoPath)
10
+
11
+ async function fetchLogoSvg(path?: string): Promise<string> {
12
+ if (!path) return ''
13
+ try {
14
+ const { url: siteUrl } = useSiteConfig()
15
+ const url = path.startsWith('http') ? path : `${siteUrl}${path}`
16
+ const svg = await $fetch<string>(url, { responseType: 'text' })
17
+ return svg.replace('<svg', '<svg width="48" height="48"')
18
+ }
19
+ catch {
20
+ return ''
21
+ }
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <div class="w-full h-full flex flex-col justify-between bg-neutral-950 px-[80px] py-[60px]">
27
+ <!-- Radial glow top-right: wide soft layer -->
28
+ <div class="absolute top-0 right-0 w-[700px] h-[700px] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.10)_0%,rgba(255,255,255,0.04)_40%,transparent_70%)]" />
29
+ <!-- Radial glow top-right: tight bright core -->
30
+ <div class="absolute top-0 right-0 w-[350px] h-[350px] bg-[radial-gradient(circle_at_top_right,rgba(255,255,255,0.22)_0%,rgba(255,255,255,0.08)_35%,transparent_65%)]" />
31
+
32
+ <div class="flex-1 flex flex-col justify-center w-full">
33
+ <div
34
+ v-if="logoSvg"
35
+ class="flex justify-center mb-8"
36
+ >
37
+ <!-- eslint-disable-next-line vue/no-v-html -->
38
+ <div
39
+ class="w-[48px] h-[48px]"
40
+ v-html="logoSvg"
41
+ />
42
+ </div>
43
+ <div
44
+ v-if="title"
45
+ class="flex justify-center mb-6"
46
+ >
47
+ <h1 class="m-0 text-[50px] font-bold text-white leading-[1.1] text-center wrap-break-word">
48
+ {{ title?.slice(0, 60) }}
49
+ </h1>
50
+ </div>
51
+ <div
52
+ v-if="description"
53
+ class="flex justify-center"
54
+ >
55
+ <p class="m-0 text-[28px] text-neutral-400 leading-[1.4] text-center wrap-break-word">
56
+ {{ description?.slice(0, 200) }}
57
+ </p>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="flex">
62
+ <div :class="`text-[18px] font-normal rounded-lg px-5 py-2 text-${primaryColor}-500`">
63
+ {{ siteName }}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </template>
@@ -1,5 +1,8 @@
1
1
  <script setup lang="ts">
2
+ import { useDocusColorMode } from '../../composables/useDocusColorMode'
3
+
2
4
  const appConfig = useAppConfig()
5
+ const { forced: forcedColorMode } = useDocusColorMode()
3
6
 
4
7
  interface FooterLink {
5
8
  'icon': string
@@ -44,5 +47,5 @@ const links = computed<FooterLink[]>(() => {
44
47
  v-bind="{ color: 'neutral', variant: 'ghost', ...link }"
45
48
  />
46
49
  </template>
47
- <UColorModeButton />
50
+ <UColorModeButton v-if="!forcedColorMode" />
48
51
  </template>
@@ -1,12 +1,13 @@
1
1
  <script setup lang="ts">
2
+ import { useDocusColorMode } from '../../composables/useDocusColorMode'
2
3
  import { useDocusI18n } from '../../composables/useDocusI18n'
3
4
  import { useSubNavigation } from '../../composables/useSubNavigation'
4
5
 
5
6
  const appConfig = useAppConfig()
6
- const site = useSiteConfig()
7
+ const { forced: forcedColorMode } = useDocusColorMode()
7
8
 
8
9
  const { isEnabled: isAssistantEnabled } = useAssistant()
9
- const { localePath, isEnabled, locales } = useDocusI18n()
10
+ const { isEnabled, locales } = useDocusI18n()
10
11
  const { subNavigationMode } = useSubNavigation()
11
12
 
12
13
  const links = computed(() => appConfig.github && appConfig.github.url
@@ -25,13 +26,11 @@ const links = computed(() => appConfig.github && appConfig.github.url
25
26
  <UHeader
26
27
  :ui="{ center: 'flex-1' }"
27
28
  :class="{ 'flex flex-col': subNavigationMode === 'header' }"
28
- :to="localePath('/')"
29
- :title="appConfig.header?.title || site.name"
30
29
  >
31
30
  <AppHeaderCenter />
32
31
 
33
- <template #title>
34
- <AppHeaderLogo class="h-6 w-auto shrink-0" />
32
+ <template #left>
33
+ <AppHeaderLeft />
35
34
  </template>
36
35
 
37
36
  <template #right>
@@ -58,7 +57,7 @@ const links = computed(() => appConfig.github && appConfig.github.url
58
57
 
59
58
  <UContentSearchButton class="lg:hidden" />
60
59
 
61
- <ClientOnly>
60
+ <ClientOnly v-if="!forcedColorMode">
62
61
  <UColorModeButton />
63
62
 
64
63
  <template #fallback>
@@ -2,12 +2,16 @@
2
2
  import type { ContentNavigationItem } from '@nuxt/content'
3
3
 
4
4
  const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
5
+
6
+ const contentNavVariants = useUIConfig('contentNavigation')
5
7
  </script>
6
8
 
7
9
  <template>
8
10
  <UContentNavigation
9
- highlight
10
- variant="link"
11
+ :highlight="contentNavVariants.highlight ?? true"
12
+ :highlight-color="contentNavVariants.highlightColor"
13
+ :variant="contentNavVariants.variant ?? 'link'"
14
+ :color="contentNavVariants.color"
11
15
  :navigation="navigation"
12
16
  />
13
17
  </template>
@@ -2,6 +2,8 @@
2
2
  import { useSubNavigation } from '../../composables/useSubNavigation'
3
3
 
4
4
  const { sections } = useSubNavigation()
5
+
6
+ const navMenuVariants = useUIConfig('navigationMenu')
5
7
  </script>
6
8
 
7
9
  <template>
@@ -10,8 +12,10 @@ const { sections } = useSubNavigation()
10
12
  <UContainer class="hidden lg:flex items-center justify-between">
11
13
  <UNavigationMenu
12
14
  :items="sections"
13
- variant="pill"
14
- highlight
15
+ :highlight="navMenuVariants.highlight ?? true"
16
+ :highlight-color="navMenuVariants.highlightColor"
17
+ :variant="navMenuVariants.variant ?? 'pill'"
18
+ :color="navMenuVariants.color"
15
19
  class="-mx-2.5 -mb-px"
16
20
  />
17
21
 
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ const appConfig = useAppConfig()
3
+ const site = useSiteConfig()
4
+ const { localePath } = useDocusI18n()
5
+
6
+ const ariaLabel = appConfig.header?.title || site.name
7
+ </script>
8
+
9
+ <template>
10
+ <NuxtLink
11
+ :to="localePath('/')"
12
+ :aria-label="ariaLabel"
13
+ >
14
+ <AppHeaderLogo class="h-6 w-auto shrink-0" />
15
+ </NuxtLink>
16
+ </template>
@@ -1,10 +1,15 @@
1
1
  <script setup lang="ts">
2
2
  const { sidebarNavigation } = useSubNavigation()
3
+
4
+ const contentNavVariants = useUIConfig('contentNavigation')
3
5
  </script>
4
6
 
5
7
  <template>
6
8
  <UContentNavigation
7
- highlight
9
+ :highlight="contentNavVariants.highlight ?? true"
10
+ :highlight-color="contentNavVariants.highlightColor"
11
+ :variant="contentNavVariants.variant ?? 'link'"
12
+ :color="contentNavVariants.color"
8
13
  :navigation="sidebarNavigation"
9
14
  />
10
15
  </template>
@@ -9,6 +9,9 @@ defineProps<{
9
9
  const { subNavigationMode, sidebarNavigation, currentSection } = useSubNavigation()
10
10
  const { t } = useDocusI18n()
11
11
 
12
+ const contentNavVariants = useUIConfig('contentNavigation')
13
+ const contentTocVariants = useUIConfig('contentToc')
14
+
12
15
  const menuDrawerOpen = ref(false)
13
16
  const tocDrawerOpen = ref(false)
14
17
  </script>
@@ -39,10 +42,13 @@ const tocDrawerOpen = ref(false)
39
42
  <template #body>
40
43
  <UContentNavigation
41
44
  :navigation="sidebarNavigation"
45
+ :highlight="contentNavVariants.highlight ?? true"
46
+ :highlight-color="contentNavVariants.highlightColor"
47
+ :variant="contentNavVariants.variant ?? 'link'"
48
+ :color="contentNavVariants.color"
42
49
  default-open
43
50
  trailing-icon="i-lucide-chevron-right"
44
51
  :ui="{ linkTrailingIcon: 'group-data-[state=open]:rotate-90' }"
45
- highlight
46
52
  />
47
53
  </template>
48
54
  </UDrawer>
@@ -68,6 +74,10 @@ const tocDrawerOpen = ref(false)
68
74
  <template #body>
69
75
  <UContentToc
70
76
  v-if="links?.length"
77
+ :highlight="contentTocVariants.highlight ?? true"
78
+ :highlight-color="contentTocVariants.highlightColor"
79
+ :highlight-variant="contentTocVariants.highlightVariant"
80
+ :color="contentTocVariants.color"
71
81
  :links="links"
72
82
  :open="true"
73
83
  default-open
@@ -12,13 +12,18 @@ const { shouldPushContent: shouldHideToc } = useAssistant()
12
12
  const { subNavigationMode } = useSubNavigation()
13
13
  const appConfig = useAppConfig()
14
14
  const { t } = useDocusI18n()
15
+
16
+ const contentTocVariants = useUIConfig('contentToc')
15
17
  </script>
16
18
 
17
19
  <template>
18
20
  <div>
19
21
  <UContentToc
20
22
  v-if="links.length && !shouldHideToc"
21
- highlight
23
+ :highlight="contentTocVariants.highlight ?? true"
24
+ :highlight-color="contentTocVariants.highlightColor"
25
+ :highlight-variant="contentTocVariants.highlightVariant"
26
+ :color="contentTocVariants.color"
22
27
  :title="appConfig.toc?.title || t('docs.toc')"
23
28
  :links="links"
24
29
  :class="{ 'hidden lg:block': subNavigationMode }"
@@ -0,0 +1,7 @@
1
+ export function useDocusColorMode() {
2
+ const appConfig = useAppConfig()
3
+ const forced = (appConfig.docus as { colorMode?: string })?.colorMode
4
+ return {
5
+ forced: forced === 'light' || forced === 'dark' ? forced : undefined,
6
+ }
7
+ }
@@ -0,0 +1,30 @@
1
+ type UIColor = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral'
2
+
3
+ interface UIConfigMap {
4
+ contentToc: {
5
+ highlight?: boolean
6
+ highlightColor?: UIColor
7
+ highlightVariant?: 'straight' | 'circuit'
8
+ color?: UIColor
9
+ }
10
+ contentNavigation: {
11
+ highlight?: boolean
12
+ highlightColor?: UIColor
13
+ variant?: 'pill' | 'link'
14
+ color?: UIColor
15
+ }
16
+ navigationMenu: {
17
+ highlight?: boolean
18
+ highlightColor?: UIColor
19
+ variant?: 'pill' | 'link'
20
+ color?: UIColor
21
+ }
22
+ }
23
+
24
+ export function useUIConfig<K extends keyof UIConfigMap>(componentName: K): ComputedRef<UIConfigMap[K]> {
25
+ const appConfig = useAppConfig()
26
+ return computed(() => {
27
+ const ui = appConfig.ui as Record<string, Record<string, Record<string, unknown>>>
28
+ return (ui?.[componentName]?.defaultVariants || {}) as UIConfigMap[K]
29
+ })
30
+ }
package/app/error.vue CHANGED
@@ -3,11 +3,13 @@ import type { NuxtError } from '#app'
3
3
  import type { ContentNavigationItem, PageCollections } from '@nuxt/content'
4
4
  import * as nuxtUiLocales from '@nuxt/ui/locale'
5
5
  import { transformNavigation } from './utils/navigation'
6
+ import { useDocusColorMode } from './composables/useDocusColorMode'
6
7
 
7
8
  const props = defineProps<{
8
9
  error: NuxtError
9
10
  }>()
10
11
 
12
+ const { forced: forcedColorMode } = useDocusColorMode()
11
13
  const { locale, locales, isEnabled, t, switchLocalePath } = useDocusI18n()
12
14
 
13
15
  const nuxtUiLocale = computed(() => nuxtUiLocales[locale.value as keyof typeof nuxtUiLocales] || nuxtUiLocales.en)
@@ -70,6 +72,7 @@ provide('navigation', navigation)
70
72
  <LazyUContentSearch
71
73
  :files="files"
72
74
  :navigation="navigation"
75
+ :color-mode="!forcedColorMode"
73
76
  />
74
77
  </ClientOnly>
75
78
  </UApp>
@@ -0,0 +1,8 @@
1
+ import { useDocusColorMode } from '../composables/useDocusColorMode'
2
+
3
+ export default defineNuxtRouteMiddleware((to) => {
4
+ const { forced } = useDocusColorMode()
5
+ if (forced) {
6
+ to.meta.colorMode = forced
7
+ }
8
+ })
@@ -45,8 +45,10 @@ watch(() => navigation?.value, () => {
45
45
  headline.value = findPageHeadline(navigation?.value, page.value?.path) || headline.value
46
46
  })
47
47
 
48
- defineOgImageComponent('Docs', {
48
+ defineOgImage('Docs', {
49
49
  headline: headline.value,
50
+ title: title?.slice(0, 60),
51
+ description: formatOgDescription(title, description),
50
52
  })
51
53
 
52
54
  const github = computed(() => appConfig.github ? appConfig.github : null)
@@ -115,17 +117,19 @@ addPrerenderPath(`/raw${route.path}.md`)
115
117
  >
116
118
  {{ t('docs.edit') }}
117
119
  </UButton>
118
- <span>{{ t('common.or') }}</span>
119
- <UButton
120
- variant="link"
121
- color="neutral"
122
- :to="`${github.url}/issues/new/choose`"
123
- target="_blank"
124
- icon="i-lucide-alert-circle"
125
- :ui="{ leadingIcon: 'size-4' }"
126
- >
127
- {{ t('docs.report') }}
128
- </UButton>
120
+ <template v-if="github?.url">
121
+ <span>{{ t('common.or') }}</span>
122
+ <UButton
123
+ variant="link"
124
+ color="neutral"
125
+ :to="`${github.url}/issues/new/choose`"
126
+ target="_blank"
127
+ icon="i-lucide-alert-circle"
128
+ :ui="{ leadingIcon: 'size-4' }"
129
+ >
130
+ {{ t('docs.report') }}
131
+ </UButton>
132
+ </template>
129
133
  </div>
130
134
  </USeparator>
131
135
  <UContentSurround :surround="surround" />
@@ -23,9 +23,9 @@ useSeo({
23
23
  })
24
24
 
25
25
  if (!page.value?.seo?.ogImage) {
26
- defineOgImageComponent('Landing', {
27
- title,
28
- description,
26
+ defineOgImage('Landing', {
27
+ title: title?.slice(0, 60),
28
+ description: formatOgDescription(title, description),
29
29
  })
30
30
  }
31
31
  </script>
@@ -0,0 +1,23 @@
1
+ // nuxt-og-image caps the encoded URL segment at 200 chars.
2
+ // Fixed overhead (component name, param keys, path encoding) is ~50 chars,
3
+ // leaving a ~150-char budget for title + description combined.
4
+ const OG_BUDGET = 150
5
+
6
+ /**
7
+ * Trims description to fit within the nuxt-og-image 200-char URL segment limit,
8
+ * accounting for the title length and trying to cut at the last sentence boundary.
9
+ */
10
+ export function formatOgDescription(title: string | undefined, description: string | undefined): string | undefined {
11
+ if (!description) return undefined
12
+
13
+ const titleLen = Math.min(title?.length ?? 0, 60)
14
+ const maxLen = OG_BUDGET - titleLen
15
+ if (maxLen <= 0) return undefined
16
+
17
+ const cleaned = description.replace(/,/g, '')
18
+ if (cleaned.length <= maxLen) return cleaned
19
+
20
+ const truncated = cleaned.slice(0, maxLen)
21
+ const lastDot = truncated.lastIndexOf('.')
22
+ return lastDot > 0 ? truncated.slice(0, lastDot + 1) : truncated
23
+ }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "تم تحميل العلامة النصية",
33
33
  "copyLogoFailed": "فشل نسخ الشعار",
34
34
  "copyWordmarkFailed": "فشل نسخ العلامة النصية"
35
+ },
36
+ "assistant": {
37
+ "title": "اسأل الذكاء الاصطناعي",
38
+ "placeholder": "اطرح سؤالاً...",
39
+ "tooltip": "اطرح سؤالاً على الذكاء الاصطناعي",
40
+ "tryAsking": "حاول طرح سؤال",
41
+ "askAnything": "اسأل أي شيء...",
42
+ "clearChat": "محو المحادثة",
43
+ "close": "إغلاق",
44
+ "expand": "توسيع",
45
+ "collapse": "طي",
46
+ "thinking": "التفكير...",
47
+ "askMeAnything": "اسأل عن أي شيء",
48
+ "askMeAnythingDescription": "احصل على المساعدة في التنقل بين الوثائق وفهم المفاهيم والعثور على الإجابات.",
49
+ "faq": "الأسئلة الشائعة",
50
+ "chatCleared": "تم مسح الدردشة عند التحديث",
51
+ "lineBreak": "فاصل الأسطر",
52
+ "explainWithAi": "اشرح باستخدام الذكاء الاصطناعي",
53
+ "toolListPages": "صفحات الوثائق المدرجة",
54
+ "toolReadPage": "قراءة",
55
+ "loading": {
56
+ "searching": "البحث في الوثائق",
57
+ "reading": "قراءة المستندات",
58
+ "analyzing": "تحليل المحتوى",
59
+ "finding": "العثور على أفضل إجابة",
60
+ "finished": "المصادر المستخدمة"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Словесны знак спампаваны",
33
33
  "copyLogoFailed": "Не ўдалося скапіяваць лагатып",
34
34
  "copyWordmarkFailed": "Не ўдалося скапіяваць словесны знак"
35
+ },
36
+ "assistant": {
37
+ "title": "Спытаць ІІ",
38
+ "placeholder": "Задаць пытанне...",
39
+ "tooltip": "Задаць пытанне ІІ",
40
+ "tryAsking": "Паспрабуйце задаць пытанне",
41
+ "askAnything": "Спытай што заўгодна...",
42
+ "clearChat": "Ачысціць чат",
43
+ "close": "Закрыць",
44
+ "expand": "Пашырыць",
45
+ "collapse": "Згарнуць",
46
+ "thinking": "Мысленне...",
47
+ "askMeAnything": "Спытаць што-небудзь",
48
+ "askMeAnythingDescription": "Атрымаць дапамогу ў навігацыі па дакументацыі, разуменні канцэпцый і пошуку адказаў.",
49
+ "faq": "Пытанні і адказы",
50
+ "chatCleared": "Чат ачышчаецца пры абнаўленні",
51
+ "lineBreak": "Перапынак у радку",
52
+ "explainWithAi": "Растлумачыць ІІ",
53
+ "toolListPages": "Старонкі дакументацыі",
54
+ "toolReadPage": "Чытаць",
55
+ "loading": {
56
+ "searching": "Пошук дакументацыі",
57
+ "reading": "Чытанне праз дакументы",
58
+ "analyzing": "Аналіз зместу",
59
+ "finding": "Пошук найлепшага адказу",
60
+ "finished": "Выкарыстаныя крыніцы"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "Словната марка е изтеглена",
33
33
  "copyLogoFailed": "Неуспешно копиране на логото",
34
34
  "copyWordmarkFailed": "Неуспешно копиране на словната марка"
35
+ },
36
+ "assistant": {
37
+ "title": "Попитайте AI",
38
+ "placeholder": "Задайте въпрос...",
39
+ "tooltip": "Задайте въпрос на AI",
40
+ "tryAsking": "Опитайте да зададете въпрос",
41
+ "askAnything": "Попитайте каквото и да е...",
42
+ "clearChat": "Изчистване на чата",
43
+ "close": "Затваряне",
44
+ "expand": "Разширяване",
45
+ "collapse": "Свиване",
46
+ "thinking": "Мисля си...",
47
+ "askMeAnything": "Попитайте за всичко",
48
+ "askMeAnythingDescription": "Потърсете помощ за навигация в документацията, разбиране на концепциите и намиране на отговори.",
49
+ "faq": "Често задавани въпроси",
50
+ "chatCleared": "Чатът е изчистен при обновяване",
51
+ "lineBreak": "Прекъсване на линията",
52
+ "explainWithAi": "Обяснете с ИИ",
53
+ "toolListPages": "Изброени страници от документацията",
54
+ "toolReadPage": "Четене",
55
+ "loading": {
56
+ "searching": "Търсене в документацията",
57
+ "reading": "Четене на документите",
58
+ "analyzing": "Анализиране на съдържанието",
59
+ "finding": "Намиране на най-добрия отговор",
60
+ "finished": "Използвани източници"
61
+ }
35
62
  }
36
63
  }
@@ -32,5 +32,32 @@
32
32
  "wordmarkDownloaded": "ওয়ার্ডমার্ক ডাউনলোড হয়েছে",
33
33
  "copyLogoFailed": "লোগো কপি করা যায়নি",
34
34
  "copyWordmarkFailed": "ওয়ার্ডমার্ক কপি করা যায়নি"
35
+ },
36
+ "assistant": {
37
+ "title": "AI-কে জিজ্ঞাসা করুন",
38
+ "placeholder": "একটি প্রশ্ন জিজ্ঞাসা করুন...",
39
+ "tooltip": "AI-কে একটি প্রশ্ন জিজ্ঞাসা করুন",
40
+ "tryAsking": "একটি প্রশ্ন জিজ্ঞাসা করার চেষ্টা করুন",
41
+ "askAnything": "যেকোনো কিছু জিজ্ঞাসা করুন...",
42
+ "clearChat": "চ্যাট সাফ করুন",
43
+ "close": "বন্ধ করুন",
44
+ "expand": "প্রসারিত করুন",
45
+ "collapse": "সঙ্কুচিত",
46
+ "thinking": "চিন্তা করা হচ্ছে...",
47
+ "askMeAnything": "যেকোনো কিছু জিজ্ঞাসা করুন",
48
+ "askMeAnythingDescription": "ডকুমেন্টেশন নেভিগেট করতে, ধারণাগুলি বুঝতে এবং উত্তর খুঁজে পেতে সহায়তা পান ।",
49
+ "faq": "প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী",
50
+ "chatCleared": "রিফ্রেশে চ্যাট সাফ করা হয়",
51
+ "lineBreak": "লাইন ব্রেক",
52
+ "explainWithAi": "AI এর সাথে ব্যাখ্যা করুন",
53
+ "toolListPages": "তালিকাভুক্ত ডকুমেন্টেশন পৃষ্ঠাগুলি",
54
+ "toolReadPage": "পড়ুন",
55
+ "loading": {
56
+ "searching": "ডকুমেন্টেশন অনুসন্ধান করা হচ্ছে",
57
+ "reading": "ডকুমেন্টের মাধ্যমে পড়া",
58
+ "analyzing": "বিষয়বস্তু বিশ্লেষণ করা হচ্ছে",
59
+ "finding": "সেরা উত্তর খোঁজা",
60
+ "finished": "ব্যবহৃত উৎসসমূহ"
61
+ }
35
62
  }
36
63
  }