simple-content-site 1.0.2

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 (69) hide show
  1. package/app/app.config.ts +29 -0
  2. package/app/app.vue +77 -0
  3. package/app/components/IconMenuToggle.vue +83 -0
  4. package/app/components/LanguageSelect.vue +82 -0
  5. package/app/components/OgImage/OgImageDocs.vue +76 -0
  6. package/app/components/OgImage/OgImageLanding.vue +73 -0
  7. package/app/components/app/AppFooter.vue +11 -0
  8. package/app/components/app/AppFooterLeft.vue +5 -0
  9. package/app/components/app/AppFooterRight.vue +30 -0
  10. package/app/components/app/AppHeader.vue +82 -0
  11. package/app/components/app/AppHeaderBody.vue +13 -0
  12. package/app/components/app/AppHeaderCTA.vue +3 -0
  13. package/app/components/app/AppHeaderCenter.vue +10 -0
  14. package/app/components/app/AppHeaderLogo.vue +16 -0
  15. package/app/components/docs/DocsAsideLeftBody.vue +12 -0
  16. package/app/components/docs/DocsAsideLeftTop.vue +3 -0
  17. package/app/components/docs/DocsAsideRightBottom.vue +18 -0
  18. package/app/components/docs/DocsPageHeaderLinks.vue +74 -0
  19. package/app/composables/useDocusI18n.ts +33 -0
  20. package/app/error.vue +76 -0
  21. package/app/layouts/default.vue +5 -0
  22. package/app/layouts/page.vue +7 -0
  23. package/app/pages/[[lang]]/[...slug].vue +56 -0
  24. package/app/plugins/i18n.ts +16 -0
  25. package/app/templates/landing.vue +44 -0
  26. package/app/types/index.d.ts +39 -0
  27. package/app/utils/navigation.ts +7 -0
  28. package/app/utils/prerender.ts +12 -0
  29. package/content.config.ts +74 -0
  30. package/i18n/locales/ar.json +22 -0
  31. package/i18n/locales/be.json +23 -0
  32. package/i18n/locales/bn.json +22 -0
  33. package/i18n/locales/ca.json +22 -0
  34. package/i18n/locales/ckb.json +18 -0
  35. package/i18n/locales/cs.json +22 -0
  36. package/i18n/locales/da.json +22 -0
  37. package/i18n/locales/de.json +22 -0
  38. package/i18n/locales/el.json +22 -0
  39. package/i18n/locales/en.json +22 -0
  40. package/i18n/locales/et.json +22 -0
  41. package/i18n/locales/fr.json +22 -0
  42. package/i18n/locales/he.json +22 -0
  43. package/i18n/locales/hi.json +22 -0
  44. package/i18n/locales/hy.json +22 -0
  45. package/i18n/locales/it.json +22 -0
  46. package/i18n/locales/ja.json +22 -0
  47. package/i18n/locales/kk.json +22 -0
  48. package/i18n/locales/km.json +22 -0
  49. package/i18n/locales/ko.json +22 -0
  50. package/i18n/locales/ky.json +22 -0
  51. package/i18n/locales/lb.json +22 -0
  52. package/i18n/locales/ms.json +22 -0
  53. package/i18n/locales/nb.json +22 -0
  54. package/i18n/locales/pl.json +23 -0
  55. package/i18n/locales/ru.json +23 -0
  56. package/i18n/locales/sl.json +22 -0
  57. package/i18n/locales/sv.json +22 -0
  58. package/i18n/locales/uk.json +22 -0
  59. package/i18n/locales/ur.json +22 -0
  60. package/i18n/locales/vi.json +22 -0
  61. package/modules/config.ts +116 -0
  62. package/modules/css.ts +32 -0
  63. package/modules/routing.ts +41 -0
  64. package/nuxt.config.ts +75 -0
  65. package/nuxt.schema.ts +218 -0
  66. package/package.json +52 -0
  67. package/server/routes/raw/[...slug].md.get.ts +45 -0
  68. package/utils/git.ts +110 -0
  69. package/utils/meta.ts +29 -0
@@ -0,0 +1,41 @@
1
+ import { defineNuxtModule, extendPages, createResolver } from '@nuxt/kit'
2
+
3
+ export default defineNuxtModule({
4
+ meta: {
5
+ name: 'routing',
6
+ },
7
+ async setup(_options, nuxt) {
8
+ const { resolve } = createResolver(import.meta.url)
9
+
10
+ const isI18nEnabled = !!(nuxt.options.i18n && nuxt.options.i18n.locales)
11
+
12
+ // Ensure useDocusI18n is available in the app
13
+ nuxt.hook('imports:extend', (imports) => {
14
+ if (imports.some(i => i.name === 'useDocusI18n')) return
15
+
16
+ imports.push({
17
+ name: 'useDocusI18n',
18
+ from: resolve('../app/composables/useDocusI18n'),
19
+ })
20
+ })
21
+
22
+ extendPages((pages) => {
23
+ const landingTemplate = resolve('../app/templates/landing.vue')
24
+
25
+ if (isI18nEnabled) {
26
+ pages.push({
27
+ name: 'lang-index',
28
+ path: '/:lang([a-zA-Z]{2})?',
29
+ file: landingTemplate,
30
+ })
31
+ }
32
+ else {
33
+ pages.push({
34
+ name: 'index',
35
+ path: '/',
36
+ file: landingTemplate,
37
+ })
38
+ }
39
+ })
40
+ },
41
+ })
package/nuxt.config.ts ADDED
@@ -0,0 +1,75 @@
1
+ import { extendViteConfig, createResolver, useNuxt } from '@nuxt/kit'
2
+
3
+ const { resolve } = createResolver(import.meta.url)
4
+
5
+ export default defineNuxtConfig({
6
+ modules: [
7
+ resolve('./modules/config'),
8
+ resolve('./modules/routing'),
9
+ resolve('./modules/css'),
10
+ '@nuxt/ui',
11
+ '@nuxt/content',
12
+ '@nuxt/image',
13
+ '@nuxtjs/robots',
14
+ 'nuxt-og-image',
15
+ () => {
16
+ // Update @nuxt/content optimizeDeps options
17
+ extendViteConfig((config) => {
18
+ config.optimizeDeps ||= {}
19
+ config.optimizeDeps.include ||= []
20
+ config.optimizeDeps.include.push('@nuxt/content > slugify')
21
+ config.optimizeDeps.include = config.optimizeDeps.include
22
+ .map(id => id.replace(/^@nuxt\/content > /, 'docus > @nuxt/content > '))
23
+ })
24
+ },
25
+ ],
26
+ devtools: {
27
+ enabled: true,
28
+ },
29
+ content: {
30
+ build: {
31
+ markdown: {
32
+ highlight: {
33
+ langs: ['bash', 'diff', 'json', 'js', 'ts', 'html', 'css', 'vue', 'shell', 'mdc', 'md', 'yaml'],
34
+ },
35
+ remarkPlugins: {
36
+ 'remark-mdc': {
37
+ options: {
38
+ autoUnwrap: true,
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ },
45
+ compatibilityDate: '2025-07-22',
46
+ nitro: {
47
+ prerender: {
48
+ crawlLinks: true,
49
+ failOnError: false,
50
+ autoSubfolderIndex: false,
51
+ },
52
+ },
53
+ hooks: {
54
+ 'nitro:config'(nitroConfig) {
55
+ const nuxt = useNuxt()
56
+
57
+ const i18nOptions = nuxt.options.i18n
58
+
59
+ const routes: string[] = []
60
+ if (!i18nOptions) {
61
+ routes.push('/')
62
+ }
63
+ else {
64
+ routes.push(...(i18nOptions.locales?.map(locale => typeof locale === 'string' ? `/${locale}` : `/${locale.code}`) || []))
65
+ }
66
+
67
+ nitroConfig.prerender = nitroConfig.prerender || {}
68
+ nitroConfig.prerender.routes = nitroConfig.prerender.routes || []
69
+ nitroConfig.prerender.routes.push(...(routes || []))
70
+ },
71
+ },
72
+ icon: {
73
+ provider: 'iconify',
74
+ },
75
+ })
package/nuxt.schema.ts ADDED
@@ -0,0 +1,218 @@
1
+ import { field, group } from '@nuxt/content/preview'
2
+
3
+ export default defineNuxtSchema({
4
+ appConfig: {
5
+ ui: group({
6
+ title: 'UI',
7
+ description: 'UI Customization.',
8
+ icon: 'i-lucide-palette',
9
+ fields: {
10
+ colors: group({
11
+ title: 'Colors',
12
+ description: 'Manage main colors of your application',
13
+ icon: 'i-lucide-palette',
14
+ fields: {
15
+ primary: field({
16
+ type: 'string',
17
+ title: 'Primary',
18
+ description: 'Primary color of your UI.',
19
+ icon: 'i-lucide-palette',
20
+ default: 'green',
21
+ required: ['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue', 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'],
22
+ }),
23
+ neutral: field({
24
+ type: 'string',
25
+ title: 'Neutral',
26
+ description: 'Neutral color of your UI.',
27
+ icon: 'i-lucide-palette',
28
+ default: 'slate',
29
+ required: ['slate', 'gray', 'zinc', 'neutral', 'stone'],
30
+ }),
31
+ },
32
+ }),
33
+ icons: group({
34
+ title: 'Icons',
35
+ description: 'Manage icons used in the application.',
36
+ icon: 'i-lucide-settings',
37
+ fields: {
38
+ search: field({
39
+ type: 'icon',
40
+ title: 'Search Bar',
41
+ description: 'Icon to display in the search bar.',
42
+ icon: 'i-lucide-search',
43
+ default: 'i-lucide-search',
44
+ }),
45
+ dark: field({
46
+ type: 'icon',
47
+ title: 'Dark mode',
48
+ description: 'Icon of color mode button for dark mode.',
49
+ icon: 'i-lucide-moon',
50
+ default: 'i-lucide-moon',
51
+ }),
52
+ light: field({
53
+ type: 'icon',
54
+ title: 'Light mode',
55
+ description: 'Icon of color mode button for light mode.',
56
+ icon: 'i-lucide-sun',
57
+ default: 'i-lucide-sun',
58
+ }),
59
+ external: field({
60
+ type: 'icon',
61
+ title: 'External Link',
62
+ description: 'Icon for external link.',
63
+ icon: 'i-lucide-external-link',
64
+ default: 'i-lucide-external-link',
65
+ }),
66
+ chevron: field({
67
+ type: 'icon',
68
+ title: 'Chevron',
69
+ description: 'Icon for chevron.',
70
+ icon: 'i-lucide-chevron-down',
71
+ default: 'i-lucide-chevron-down',
72
+ }),
73
+ hash: field({
74
+ type: 'icon',
75
+ title: 'Hash',
76
+ description: 'Icon for hash anchors.',
77
+ icon: 'i-lucide-hash',
78
+ default: 'i-lucide-hash',
79
+ }),
80
+ },
81
+ }),
82
+ },
83
+ }),
84
+ seo: group({
85
+ title: 'SEO',
86
+ description: 'SEO configuration.',
87
+ icon: 'i-lucide-search',
88
+ fields: {
89
+ title: field({
90
+ type: 'string',
91
+ title: 'Title',
92
+ description: 'Title to display in the header.',
93
+ icon: 'i-lucide-type',
94
+ default: '',
95
+ }),
96
+ description: field({
97
+ type: 'string',
98
+ title: 'Description',
99
+ description: 'Description to display in the header.',
100
+ icon: 'i-lucide-type',
101
+ default: '',
102
+ }),
103
+ },
104
+ }),
105
+ header: group({
106
+ title: 'Header',
107
+ description: 'Header configuration.',
108
+ icon: 'i-lucide-layout',
109
+ fields: {
110
+ title: field({
111
+ type: 'string',
112
+ title: 'Title',
113
+ description: 'Title to display in the header.',
114
+ icon: 'i-lucide-type',
115
+ default: '',
116
+ }),
117
+ logo: group({
118
+ title: 'Logo',
119
+ description: 'Header logo configuration.',
120
+ icon: 'i-lucide-image',
121
+ fields: {
122
+ light: field({
123
+ type: 'media',
124
+ title: 'Light Mode Logo',
125
+ description: 'Pick an image from your gallery.',
126
+ icon: 'i-lucide-sun',
127
+ default: '',
128
+ }),
129
+ dark: field({
130
+ type: 'media',
131
+ title: 'Dark Mode Logo',
132
+ description: 'Pick an image from your gallery.',
133
+ icon: 'i-lucide-moon',
134
+ default: '',
135
+ }),
136
+ alt: field({
137
+ type: 'string',
138
+ title: 'Alt',
139
+ description: 'Alt to display for accessibility.',
140
+ icon: 'i-lucide-text',
141
+ default: '',
142
+ }),
143
+ },
144
+ }),
145
+ },
146
+ }),
147
+ socials: field({
148
+ type: 'object',
149
+ title: 'Social Networks',
150
+ description: 'Social links configuration.',
151
+ icon: 'i-lucide-network',
152
+ default: {},
153
+ }),
154
+ toc: group({
155
+ title: 'Table of contents',
156
+ description: 'TOC configuration.',
157
+ icon: 'i-lucide-list',
158
+ fields: {
159
+ title: field({
160
+ type: 'string',
161
+ title: 'Title',
162
+ description: 'Title of the table of contents.',
163
+ icon: 'i-lucide-heading',
164
+ default: 'On this page',
165
+ }),
166
+ bottom: group({
167
+ title: 'Bottom',
168
+ description: 'Bottom section of the table of contents.',
169
+ icon: 'i-lucide-list',
170
+ fields: {
171
+ title: field({
172
+ type: 'string',
173
+ title: 'Title',
174
+ description: 'Title of the bottom section.',
175
+ icon: 'i-lucide-heading',
176
+ default: 'Community',
177
+ }),
178
+ links: field({
179
+ type: 'array',
180
+ title: 'Links',
181
+ description: 'Links to display in the bottom section.',
182
+ icon: 'i-lucide-link',
183
+ default: [],
184
+ }),
185
+ },
186
+ }),
187
+ },
188
+ }),
189
+ github: group({
190
+ title: 'GitHub',
191
+ description: 'GitHub configuration.',
192
+ icon: 'i-simple-icons-github',
193
+ fields: {
194
+ url: field({
195
+ type: 'string',
196
+ title: 'URL',
197
+ description: 'GitHub URL.',
198
+ icon: 'i-simple-icons-github',
199
+ default: '',
200
+ }),
201
+ branch: field({
202
+ type: 'string',
203
+ title: 'Branch',
204
+ description: 'GitHub branch.',
205
+ icon: 'i-lucide-git-branch',
206
+ default: 'main',
207
+ }),
208
+ rootDir: field({
209
+ type: 'string',
210
+ title: 'Root Directory',
211
+ description: 'Root directory of the GitHub repository.',
212
+ icon: 'i-lucide-folder',
213
+ default: '',
214
+ }),
215
+ },
216
+ }),
217
+ },
218
+ })
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "simple-content-site",
3
+ "description": "Nuxt layer for simple website with basic functions.",
4
+ "version": "1.0.2",
5
+ "type": "module",
6
+ "main": "./nuxt.config.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/hareland/nuxt-content-site-layer.git"
10
+ },
11
+ "private": false,
12
+ "license": "MIT",
13
+ "files": [
14
+ "app",
15
+ "i18n",
16
+ "content.config.ts",
17
+ "modules",
18
+ "nuxt.config.ts",
19
+ "nuxt.schema.ts",
20
+ "server",
21
+ "utils",
22
+ "README.md"
23
+ ],
24
+ "dependencies": {
25
+ "@iconify-json/lucide": "^1.2.69",
26
+ "@iconify-json/simple-icons": "^1.2.54",
27
+ "@iconify-json/vscode-icons": "^1.2.32",
28
+ "@nuxt/content": "https://pkg.pr.new/@nuxt/content@dd854d5",
29
+ "@nuxt/image": "^1.11.0",
30
+ "@nuxt/kit": "^4.1.3",
31
+ "@nuxt/ui": "^4.0.1",
32
+ "@nuxtjs/i18n": "^10.1.1",
33
+ "@nuxtjs/mdc": "^0.18.0",
34
+ "@nuxtjs/robots": "^5.5.5",
35
+ "@vueuse/core": "^13.9.0",
36
+ "nuxt-studio": "^1.0.0-alpha.1",
37
+ "defu": "^6.1.4",
38
+ "exsolve": "^1.0.7",
39
+ "git-url-parse": "^16.1.0",
40
+ "minimark": "^0.2.0",
41
+ "motion-v": "^1.7.2",
42
+ "nuxt-og-image": "^5.1.12",
43
+ "pkg-types": "^2.3.0",
44
+ "scule": "^1.3.0",
45
+ "tailwindcss": "^4.1.14",
46
+ "ufo": "^1.6.1"
47
+ },
48
+ "peerDependencies": {
49
+ "better-sqlite3": "12.x",
50
+ "nuxt": "4.x"
51
+ }
52
+ }
@@ -0,0 +1,45 @@
1
+ import { withLeadingSlash } from 'ufo'
2
+ import { stringify } from 'minimark/stringify'
3
+ import { queryCollection } from '@nuxt/content/server'
4
+ import type { Collections } from '@nuxt/content'
5
+
6
+ export default eventHandler(async (event) => {
7
+ const slug = getRouterParams(event)['slug.md']
8
+ if (!slug?.endsWith('.md')) {
9
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
10
+ }
11
+
12
+ const path = withLeadingSlash(slug.replace('.md', ''))
13
+ const config = useRuntimeConfig(event).public
14
+
15
+ let collectionName = 'pages'
16
+ if (config.i18n?.locales) {
17
+ const pathSegments = path.split('/').filter(Boolean)
18
+ const firstSegment = pathSegments[0]
19
+
20
+ const availableLocales = config.i18n.locales.map((locale: string | { code: string }) =>
21
+ typeof locale === 'string' ? locale : locale.code,
22
+ )
23
+
24
+ if (firstSegment && availableLocales.includes(firstSegment)) {
25
+ collectionName = `pages_${firstSegment}`
26
+ }
27
+ else if (config.i18n.defaultLocale) {
28
+ collectionName = `pages_${config.i18n.defaultLocale}`
29
+ }
30
+ }
31
+
32
+ const page = await queryCollection(event, collectionName as keyof Collections).path(path).first()
33
+ if (!page) {
34
+ throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
35
+ }
36
+
37
+ // Add title and description to the top of the page if missing
38
+ if (page.body.value[0]?.[0] !== 'h1') {
39
+ page.body.value.unshift(['blockquote', {}, page.description])
40
+ page.body.value.unshift(['h1', {}, page.title])
41
+ }
42
+
43
+ setHeader(event, 'Content-Type', 'text/markdown; charset=utf-8')
44
+ return stringify({ ...page.body, type: 'minimark' }, { format: 'markdown/html' })
45
+ })
package/utils/git.ts ADDED
@@ -0,0 +1,110 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { readGitConfig } from 'pkg-types'
3
+ import gitUrlParse from 'git-url-parse'
4
+
5
+ export interface GitInfo {
6
+ // Repository name
7
+ name: string
8
+ // Repository owner/organization
9
+ owner: string
10
+ // Repository URL
11
+ url: string
12
+ }
13
+
14
+ export function getGitBranch() {
15
+ const envName
16
+ = process.env.CF_PAGES_BRANCH
17
+ || process.env.CI_COMMIT_BRANCH
18
+ || process.env.VERCEL_GIT_COMMIT_REF
19
+ || process.env.BRANCH
20
+ || process.env.GITHUB_REF_NAME
21
+
22
+ if (envName && envName !== 'HEAD') {
23
+ return envName
24
+ }
25
+ try {
26
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
27
+ if (branch && branch !== 'HEAD') {
28
+ return branch
29
+ }
30
+ }
31
+ catch {
32
+ // Ignore error
33
+ }
34
+
35
+ return 'main'
36
+ }
37
+
38
+ export async function getLocalGitInfo(rootDir: string): Promise<GitInfo | undefined> {
39
+ const remote = await getLocalGitRemote(rootDir)
40
+ if (!remote) {
41
+ return
42
+ }
43
+
44
+ // https://www.npmjs.com/package/git-url-parse#clipboard-example
45
+ const { name, owner, source } = gitUrlParse(remote)
46
+ const url = `https://${source}/${owner}/${name}`
47
+
48
+ return {
49
+ name,
50
+ owner,
51
+ url,
52
+ }
53
+ }
54
+
55
+ async function getLocalGitRemote(dir: string): Promise<string | undefined> {
56
+ try {
57
+ const parsed = await readGitConfig(dir)
58
+ if (!parsed) {
59
+ return
60
+ }
61
+ return parsed.remote?.['origin']?.url
62
+ }
63
+ catch {
64
+ // Ignore error
65
+ }
66
+ }
67
+
68
+ export function getGitEnv(): GitInfo {
69
+ // https://github.com/unjs/std-env/issues/59
70
+ const envInfo = {
71
+ // Provider
72
+ provider: process.env.VERCEL_GIT_PROVIDER // vercel
73
+ || (process.env.GITHUB_SERVER_URL ? 'github' : undefined) // github
74
+ || '',
75
+ // Owner
76
+ owner: process.env.VERCEL_GIT_REPO_OWNER // vercel
77
+ || process.env.GITHUB_REPOSITORY_OWNER // github
78
+ || process.env.CI_PROJECT_PATH?.split('/').shift() // gitlab
79
+ || '',
80
+ // Name
81
+ name: process.env.VERCEL_GIT_REPO_SLUG
82
+ || process.env.GITHUB_REPOSITORY?.split('/').pop() // github
83
+ || process.env.CI_PROJECT_PATH?.split('/').splice(1).join('/') // gitlab
84
+ || '',
85
+ // Url
86
+ url: process.env.REPOSITORY_URL || '', // netlify
87
+ }
88
+
89
+ if (!envInfo.url && envInfo.provider && envInfo.owner && envInfo.name) {
90
+ envInfo.url = `https://${envInfo.provider}.com/${envInfo.owner}/${envInfo.name}`
91
+ }
92
+
93
+ // If only url available (ex: Netlify)
94
+ if (!envInfo.name && !envInfo.owner && envInfo.url) {
95
+ try {
96
+ const { name, owner } = gitUrlParse(envInfo.url)
97
+ envInfo.name = name
98
+ envInfo.owner = owner
99
+ }
100
+ catch {
101
+ // Ignore error
102
+ }
103
+ }
104
+
105
+ return {
106
+ name: envInfo.name,
107
+ owner: envInfo.owner,
108
+ url: envInfo.url,
109
+ }
110
+ }
package/utils/meta.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { resolve } from 'node:path'
3
+
4
+ export function inferSiteURL() {
5
+ // https://github.com/unjs/std-env/issues/59
6
+ return (
7
+ process.env.NUXT_SITE_URL
8
+ || (process.env.NEXT_PUBLIC_VERCEL_URL && `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`) // Vercel
9
+ || process.env.URL // Netlify
10
+ || process.env.CI_PAGES_URL // Gitlab Pages
11
+ || process.env.CF_PAGES_URL // Cloudflare Pages
12
+ )
13
+ }
14
+
15
+ export async function getPackageJsonMetadata(dir: string) {
16
+ try {
17
+ const packageJson = await readFile(resolve(dir, 'package.json'), 'utf-8')
18
+ const parsed = JSON.parse(packageJson)
19
+ return {
20
+ name: parsed.name,
21
+ description: parsed.description,
22
+ }
23
+ }
24
+ catch {
25
+ return {
26
+ name: 'docs',
27
+ }
28
+ }
29
+ }