@docsector/docsector-reader 0.4.1 → 0.5.1
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/bin/docsector.js +14 -2
- package/index.html +12 -0
- package/package.json +1 -1
- package/src/layouts/DefaultLayout.vue +30 -1
- package/src/quasar.factory.js +93 -2
- package/src/router/routes.js +1 -0
package/bin/docsector.js
CHANGED
|
@@ -23,7 +23,7 @@ const packageRoot = resolve(__dirname, '..')
|
|
|
23
23
|
const args = process.argv.slice(2)
|
|
24
24
|
const command = args[0]
|
|
25
25
|
|
|
26
|
-
const VERSION = '0.
|
|
26
|
+
const VERSION = '0.5.1'
|
|
27
27
|
|
|
28
28
|
const HELP = `
|
|
29
29
|
Docsector Reader v${VERSION}
|
|
@@ -71,7 +71,7 @@ function getTemplatePackageJson (name) {
|
|
|
71
71
|
serve: 'docsector serve'
|
|
72
72
|
},
|
|
73
73
|
dependencies: {
|
|
74
|
-
'@docsector/docsector-reader': '^0.
|
|
74
|
+
'@docsector/docsector-reader': '^0.5.1',
|
|
75
75
|
'@quasar/extras': '^1.16.12',
|
|
76
76
|
'quasar': '^2.16.6',
|
|
77
77
|
'vue': '^3.5.13',
|
|
@@ -640,6 +640,18 @@ const TEMPLATE_INDEX_HTML = `\
|
|
|
640
640
|
<meta name="msapplication-tap-highlight" content="no">
|
|
641
641
|
<meta name="viewport" content="user-scalable=yes, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width">
|
|
642
642
|
|
|
643
|
+
<!-- Open Graph -->
|
|
644
|
+
<meta property="og:title" content="<%= productName %>">
|
|
645
|
+
<meta property="og:description" content="<%= productDescription %>">
|
|
646
|
+
<meta property="og:type" content="website">
|
|
647
|
+
<meta property="og:image" content="/images/logo.png">
|
|
648
|
+
|
|
649
|
+
<!-- Twitter Card -->
|
|
650
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
651
|
+
<meta name="twitter:title" content="<%= productName %>">
|
|
652
|
+
<meta name="twitter:description" content="<%= productDescription %>">
|
|
653
|
+
<meta name="twitter:image" content="/images/logo.png">
|
|
654
|
+
|
|
643
655
|
<link rel="icon" type="image/png" sizes="128x128" href="images/icons/favicon-128.png">
|
|
644
656
|
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/favicon-32.png">
|
|
645
657
|
<link rel="icon" type="image/png" sizes="16x16" href="images/icons/favicon-16.png">
|
package/index.html
CHANGED
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
<meta name="msapplication-tap-highlight" content="no">
|
|
10
10
|
<meta name="viewport" content="user-scalable=yes, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width<% if (ctx.mode.cordova || ctx.mode.capacitor) { %>, viewport-fit=cover<% } %>">
|
|
11
11
|
|
|
12
|
+
<!-- Open Graph -->
|
|
13
|
+
<meta property="og:title" content="<%= productName %>">
|
|
14
|
+
<meta property="og:description" content="<%= productDescription %>">
|
|
15
|
+
<meta property="og:type" content="website">
|
|
16
|
+
<meta property="og:image" content="">
|
|
17
|
+
|
|
18
|
+
<!-- Twitter Card -->
|
|
19
|
+
<meta name="twitter:card" content="summary">
|
|
20
|
+
<meta name="twitter:title" content="<%= productName %>">
|
|
21
|
+
<meta name="twitter:description" content="<%= productDescription %>">
|
|
22
|
+
<meta name="twitter:image" content="">
|
|
23
|
+
|
|
12
24
|
<link rel="icon" type="image/png" sizes="128x128" href="images/icons/favicon-128.png">
|
|
13
25
|
<link rel="icon" type="image/png" sizes="32x32" href="images/icons/favicon-32.png">
|
|
14
26
|
<link rel="icon" type="image/png" sizes="16x16" href="images/icons/favicon-16.png">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docsector/docsector-reader",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A documentation rendering engine built with Vue 3, Quasar v2 and Vite. Transform Markdown into beautiful, navigable documentation sites.",
|
|
5
5
|
"productName": "Docsector Reader",
|
|
6
6
|
"author": "Rodrigo de Araujo Vieira",
|
|
@@ -32,6 +32,7 @@ import { ref, computed } from 'vue'
|
|
|
32
32
|
import { useRoute, useRouter } from 'vue-router'
|
|
33
33
|
import { useStore } from 'vuex'
|
|
34
34
|
import { useI18n } from 'vue-i18n'
|
|
35
|
+
import { useMeta } from 'quasar'
|
|
35
36
|
|
|
36
37
|
import DMenu from '../components/DMenu.vue'
|
|
37
38
|
import docsectorConfig from 'docsector.config.js'
|
|
@@ -43,7 +44,7 @@ const branding = docsectorConfig.branding || {}
|
|
|
43
44
|
const route = useRoute()
|
|
44
45
|
const router = useRouter()
|
|
45
46
|
const store = useStore()
|
|
46
|
-
const { t } = useI18n()
|
|
47
|
+
const { t, locale } = useI18n()
|
|
47
48
|
|
|
48
49
|
const layout = ref({
|
|
49
50
|
menu: false
|
|
@@ -61,6 +62,34 @@ const headerTitleText = computed(() => {
|
|
|
61
62
|
}
|
|
62
63
|
})
|
|
63
64
|
|
|
65
|
+
// @ Dynamic page title & meta tags
|
|
66
|
+
const pageTitle = computed(() => {
|
|
67
|
+
const data = route.matched[0]?.meta?.data
|
|
68
|
+
if (data) {
|
|
69
|
+
const langData = data[locale.value] || data['en-US'] || Object.values(data)[0]
|
|
70
|
+
return langData?.title || ''
|
|
71
|
+
}
|
|
72
|
+
return ''
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
useMeta(() => {
|
|
76
|
+
const title = pageTitle.value
|
|
77
|
+
? `${pageTitle.value} — ${branding.name || ''}`
|
|
78
|
+
: branding.name || ''
|
|
79
|
+
|
|
80
|
+
const image = branding.logo || ''
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
title,
|
|
84
|
+
meta: {
|
|
85
|
+
ogTitle: { property: 'og:title', content: title },
|
|
86
|
+
ogType: { property: 'og:type', content: 'article' },
|
|
87
|
+
ogImage: { property: 'og:image', content: image },
|
|
88
|
+
twitterImage: { name: 'twitter:image', content: image }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
64
93
|
function toogleMenu () {
|
|
65
94
|
layout.value.menu = !layout.value.menu
|
|
66
95
|
}
|
package/src/quasar.factory.js
CHANGED
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
* @param {Function} [options.extendViteConf] - Additional Vite config extension
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
import { readFileSync, existsSync, rmSync } from 'fs'
|
|
26
|
+
import { readFileSync, existsSync, rmSync, mkdirSync, writeFileSync } from 'fs'
|
|
27
27
|
import { createHash } from 'crypto'
|
|
28
28
|
import { resolve } from 'path'
|
|
29
|
+
import { pathToFileURL } from 'url'
|
|
29
30
|
import HJSON from 'hjson'
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -116,6 +117,95 @@ function createHjsonPlugin () {
|
|
|
116
117
|
}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Create a Vite plugin that pre-renders route-specific index.html files at build
|
|
122
|
+
* time with correct <title> and Open Graph / Twitter Card meta tags.
|
|
123
|
+
*
|
|
124
|
+
* Why: SPA builds produce a single index.html with a generic title. Search engine
|
|
125
|
+
* crawlers and social media link previews read the static HTML without executing
|
|
126
|
+
* JavaScript, so they always see the same generic meta tags regardless of the URL.
|
|
127
|
+
*
|
|
128
|
+
* How: After Vite writes the bundle (closeBundle hook), the plugin dynamically
|
|
129
|
+
* imports the consumer's pages registry and docsector config, then for each route
|
|
130
|
+
* creates a directory with an index.html copy whose <title> and meta tags reflect
|
|
131
|
+
* the page's actual title. Cloudflare Pages (and any static host) serves these
|
|
132
|
+
* route-specific files automatically.
|
|
133
|
+
*
|
|
134
|
+
* Zero external dependencies — no Puppeteer or headless browser required.
|
|
135
|
+
*/
|
|
136
|
+
function createPrerenderMetaPlugin (projectRoot) {
|
|
137
|
+
return {
|
|
138
|
+
name: 'docsector-prerender-meta',
|
|
139
|
+
apply: 'build',
|
|
140
|
+
async closeBundle () {
|
|
141
|
+
const distDir = resolve(projectRoot, 'dist', 'spa')
|
|
142
|
+
const baseHtmlPath = resolve(distDir, 'index.html')
|
|
143
|
+
|
|
144
|
+
if (!existsSync(baseHtmlPath)) return
|
|
145
|
+
|
|
146
|
+
const baseHtml = readFileSync(baseHtmlPath, 'utf-8')
|
|
147
|
+
|
|
148
|
+
// Dynamic import pages registry and docsector config
|
|
149
|
+
const pagesUrl = pathToFileURL(resolve(projectRoot, 'src', 'pages', 'index.js')).href
|
|
150
|
+
const configUrl = pathToFileURL(resolve(projectRoot, 'docsector.config.js')).href
|
|
151
|
+
|
|
152
|
+
const { default: pages } = await import(pagesUrl)
|
|
153
|
+
const { default: config } = await import(configUrl)
|
|
154
|
+
|
|
155
|
+
const brandingName = config.branding?.name || ''
|
|
156
|
+
const brandingLogo = config.branding?.logo || ''
|
|
157
|
+
const defaultLang = config.defaultLanguage || config.languages?.[0]?.value || 'en-US'
|
|
158
|
+
|
|
159
|
+
let count = 0
|
|
160
|
+
|
|
161
|
+
for (const [pagePath, page] of Object.entries(pages)) {
|
|
162
|
+
if (page.config === null) continue
|
|
163
|
+
|
|
164
|
+
const type = page.config.type ?? 'manual'
|
|
165
|
+
const title = page.data?.[defaultLang]?.title || ''
|
|
166
|
+
const fullTitle = title
|
|
167
|
+
? `${title} — ${brandingName}`
|
|
168
|
+
: brandingName
|
|
169
|
+
|
|
170
|
+
// Each page can have sub-routes: overview, showcase, vs
|
|
171
|
+
const subpages = ['overview']
|
|
172
|
+
if (page.config.subpages?.showcase) subpages.push('showcase')
|
|
173
|
+
if (page.config.subpages?.vs) subpages.push('vs')
|
|
174
|
+
|
|
175
|
+
for (const subpage of subpages) {
|
|
176
|
+
const routePath = `${type}${pagePath}/${subpage}`
|
|
177
|
+
|
|
178
|
+
const html = baseHtml
|
|
179
|
+
.replace(/<title>[^<]*<\/title>/, () => `<title>${fullTitle}</title>`)
|
|
180
|
+
.replace(
|
|
181
|
+
/(<meta\s+property="?og:title"?\s+content=")[^"]*"/,
|
|
182
|
+
(_, p1) => `${p1}${fullTitle}"`
|
|
183
|
+
)
|
|
184
|
+
.replace(
|
|
185
|
+
/(<meta\s+property="?og:image"?\s+content=")[^"]*"/,
|
|
186
|
+
(_, p1) => `${p1}${brandingLogo}"`
|
|
187
|
+
)
|
|
188
|
+
.replace(
|
|
189
|
+
/(<meta\s+name="?twitter:title"?\s+content=")[^"]*"/,
|
|
190
|
+
(_, p1) => `${p1}${fullTitle}"`
|
|
191
|
+
)
|
|
192
|
+
.replace(
|
|
193
|
+
/(<meta\s+name="?twitter:image"?\s+content=")[^"]*"/,
|
|
194
|
+
(_, p1) => `${p1}${brandingLogo}"`
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
const dir = resolve(distDir, routePath)
|
|
198
|
+
mkdirSync(dir, { recursive: true })
|
|
199
|
+
writeFileSync(resolve(dir, 'index.html'), html)
|
|
200
|
+
count++
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(`\x1b[36m[docsector]\x1b[0m Pre-rendered meta tags for ${count} routes`)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
119
209
|
/**
|
|
120
210
|
* Create a complete Quasar configuration for a docsector-reader consumer project.
|
|
121
211
|
*
|
|
@@ -187,6 +277,7 @@ export function createQuasarConfig (options = {}) {
|
|
|
187
277
|
vitePlugins: [
|
|
188
278
|
createHjsonPlugin(),
|
|
189
279
|
createPagesWatchPlugin(projectRoot),
|
|
280
|
+
createPrerenderMetaPlugin(projectRoot),
|
|
190
281
|
...vitePlugins
|
|
191
282
|
],
|
|
192
283
|
|
|
@@ -327,7 +418,7 @@ export function createQuasarConfig (options = {}) {
|
|
|
327
418
|
config: {},
|
|
328
419
|
lang: 'en-US',
|
|
329
420
|
plugins: [
|
|
330
|
-
'LocalStorage', 'SessionStorage'
|
|
421
|
+
'Meta', 'LocalStorage', 'SessionStorage'
|
|
331
422
|
]
|
|
332
423
|
},
|
|
333
424
|
|