@docsector/docsector-reader 0.1.2 → 0.2.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/README.md +175 -47
- package/bin/docsector.js +408 -17
- package/package.json +18 -10
- package/quasar.config.js +4 -111
- package/src/components/DH1.vue +1 -1
- package/src/components/DH2.vue +1 -1
- package/src/components/DH3.vue +1 -1
- package/src/components/DH4.vue +1 -1
- package/src/components/DH5.vue +1 -1
- package/src/components/DH6.vue +1 -1
- package/src/components/DMenu.vue +4 -4
- package/src/components/DPage.vue +3 -3
- package/src/components/DPageAnchor.vue +1 -1
- package/src/components/QZoom.js +1 -1
- package/src/components/QZoom.sass +43 -0
- package/src/i18n/helpers.js +160 -0
- package/src/i18n/index.js +4 -117
- package/src/layouts/DefaultLayout.vue +1 -1
- package/src/quasar.factory.js +249 -0
- package/src/components/QZoom.styl +0 -43
package/src/components/DH5.vue
CHANGED
package/src/components/DH6.vue
CHANGED
package/src/components/DMenu.vue
CHANGED
|
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|
|
4
4
|
import { useQuasar, scroll, openURL } from 'quasar'
|
|
5
5
|
import { useI18n } from 'vue-i18n'
|
|
6
6
|
|
|
7
|
-
import tags from '
|
|
7
|
+
import tags from '@docsector/tags'
|
|
8
8
|
import DMenuItem from './DMenuItem.vue'
|
|
9
9
|
import docsectorConfig from 'docsector.config.js'
|
|
10
10
|
|
|
@@ -35,8 +35,8 @@ const searchTerm = (term) => {
|
|
|
35
35
|
const locale = $q.localStorage.getItem('setting.language')
|
|
36
36
|
founds.value = []
|
|
37
37
|
|
|
38
|
-
for (const [index,
|
|
39
|
-
searchTermIterate(
|
|
38
|
+
for (const [index, group] of items.value.entries()) {
|
|
39
|
+
searchTermIterate(group, term, locale)
|
|
40
40
|
}
|
|
41
41
|
} else {
|
|
42
42
|
founds.value = false
|
|
@@ -54,7 +54,7 @@ const searchTermIterate = (items, term, locale) => {
|
|
|
54
54
|
founds.value[path] = false
|
|
55
55
|
|
|
56
56
|
// @ search in i18n/tags.hjson
|
|
57
|
-
if (tags[locale] && tags[locale].length > 0) {
|
|
57
|
+
if (tags[locale] && Object.keys(tags[locale]).length > 0) {
|
|
58
58
|
founds.value[path] = tags[locale][path]?.indexOf(term) !== -1
|
|
59
59
|
if (founds.value[path] === false && locale !== 'en-US') {
|
|
60
60
|
founds.value[path] = tags['en-US'][path]?.indexOf(term) !== -1
|
package/src/components/DPage.vue
CHANGED
|
@@ -4,10 +4,10 @@ import { useStore } from 'vuex'
|
|
|
4
4
|
import { useRoute, useRouter } from 'vue-router'
|
|
5
5
|
import { useQuasar } from 'quasar'
|
|
6
6
|
|
|
7
|
-
import useNavigator from '
|
|
7
|
+
import useNavigator from '../composables/useNavigator'
|
|
8
8
|
|
|
9
|
-
import DPageAnchor from '
|
|
10
|
-
import DPageMeta from '
|
|
9
|
+
import DPageAnchor from './DPageAnchor.vue'
|
|
10
|
+
import DPageMeta from './DPageMeta.vue'
|
|
11
11
|
|
|
12
12
|
const store = useStore()
|
|
13
13
|
const router = useRouter()
|
|
@@ -4,7 +4,7 @@ import { useStore } from 'vuex'
|
|
|
4
4
|
import { useQuasar } from 'quasar'
|
|
5
5
|
import { useRoute } from "vue-router";
|
|
6
6
|
|
|
7
|
-
import useNavigator from '
|
|
7
|
+
import useNavigator from '../composables/useNavigator'
|
|
8
8
|
|
|
9
9
|
const store = useStore()
|
|
10
10
|
const $q = useQuasar()
|
package/src/components/QZoom.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
$box-shadow: 1px 1px 7px 1px rgba(0,0,0,.2) !default
|
|
2
|
+
|
|
3
|
+
.q-zoom
|
|
4
|
+
position: relative
|
|
5
|
+
padding: 0
|
|
6
|
+
margin: 0
|
|
7
|
+
|
|
8
|
+
&__zoom-in
|
|
9
|
+
cursor: zoom-in
|
|
10
|
+
|
|
11
|
+
&__zoom-out
|
|
12
|
+
cursor: zoom-out
|
|
13
|
+
|
|
14
|
+
&__overlay
|
|
15
|
+
position: fixed
|
|
16
|
+
transition: all .5s linear
|
|
17
|
+
left: 0
|
|
18
|
+
top: 0
|
|
19
|
+
width: 100%
|
|
20
|
+
height: 100%
|
|
21
|
+
background-color: transparent
|
|
22
|
+
padding: 0
|
|
23
|
+
margin: 0
|
|
24
|
+
z-index: 6000
|
|
25
|
+
|
|
26
|
+
&__content
|
|
27
|
+
position: relative
|
|
28
|
+
display: block
|
|
29
|
+
transition: all .5s cubic-bezier(.2,0,.2,1)
|
|
30
|
+
text-align: center
|
|
31
|
+
vertical-align: middle
|
|
32
|
+
width: 100%
|
|
33
|
+
height: 0
|
|
34
|
+
max-width: 100%
|
|
35
|
+
max-height: 100%
|
|
36
|
+
overflow: hidden
|
|
37
|
+
|
|
38
|
+
&__no-center
|
|
39
|
+
text-align: unset
|
|
40
|
+
vertical-align: unset
|
|
41
|
+
|
|
42
|
+
&__no-scroll
|
|
43
|
+
overflow: hidden
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docsector Reader — i18n Message Builder
|
|
3
|
+
*
|
|
4
|
+
* Extracts the markdown-to-i18n processing logic so consumer projects
|
|
5
|
+
* can call it with their own import.meta.glob results.
|
|
6
|
+
*
|
|
7
|
+
* Usage in consumer's src/i18n/index.js:
|
|
8
|
+
*
|
|
9
|
+
* import { buildMessages } from '@docsector/docsector-reader/i18n'
|
|
10
|
+
* import boot from 'pages/boot'
|
|
11
|
+
* import pages from 'pages'
|
|
12
|
+
*
|
|
13
|
+
* const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
|
|
14
|
+
* const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
15
|
+
*
|
|
16
|
+
* export default buildMessages({ langModules, mdModules, pages, boot })
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Escape characters that conflict with vue-i18n message syntax.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} source - Raw markdown string
|
|
23
|
+
* @returns {string} Escaped string safe for vue-i18n
|
|
24
|
+
*/
|
|
25
|
+
export function filter (source) {
|
|
26
|
+
const regex1 = /{/gm
|
|
27
|
+
const regex2 = /}/gm
|
|
28
|
+
const regex3 = /([@|])+/gm
|
|
29
|
+
|
|
30
|
+
source = source
|
|
31
|
+
.replace(regex1, '{')
|
|
32
|
+
.replace(regex2, '}')
|
|
33
|
+
.replace(regex3, "{'$&'}")
|
|
34
|
+
|
|
35
|
+
return source
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build complete i18n messages from HJSON locale files and Markdown page content.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options
|
|
42
|
+
* @param {Object} options.langModules - Result of import.meta.glob('./languages/*.hjson', { eager: true })
|
|
43
|
+
* @param {Object} options.mdModules - Result of import.meta.glob('../pages/**/*.md', { eager: true, query: '?raw', import: 'default' })
|
|
44
|
+
* @param {Object} options.pages - Page registry from pages/index.js
|
|
45
|
+
* @param {Object} options.boot - Boot meta from pages/boot.js
|
|
46
|
+
* @param {string[]} [options.langs] - Language codes to process (auto-detected from langModules if omitted)
|
|
47
|
+
* @returns {Object} Complete i18n messages object keyed by locale
|
|
48
|
+
*/
|
|
49
|
+
export function buildMessages ({ langModules, mdModules, pages, boot, langs }) {
|
|
50
|
+
// Auto-detect languages from HJSON files if not provided
|
|
51
|
+
if (!langs) {
|
|
52
|
+
langs = Object.keys(langModules).map(key => {
|
|
53
|
+
// key is like './languages/en-US.hjson' — extract 'en-US'
|
|
54
|
+
const match = key.match(/\/([^/]+)\.hjson$/)
|
|
55
|
+
return match ? match[1] : null
|
|
56
|
+
}).filter(Boolean)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const i18n = {}
|
|
60
|
+
|
|
61
|
+
function load (topPage, path, subpage, lang) {
|
|
62
|
+
const key = `../pages/${topPage}/${path}.${subpage}.${lang}.md`
|
|
63
|
+
const content = mdModules[key]
|
|
64
|
+
|
|
65
|
+
if (!content) {
|
|
66
|
+
console.warn(`[i18n] Missing markdown: ${key}`)
|
|
67
|
+
return ''
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const source = filter(typeof content === 'string' ? content : String(content))
|
|
71
|
+
return source
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// @ Iterate langs
|
|
75
|
+
for (const lang of langs) {
|
|
76
|
+
// Load HJSON language file
|
|
77
|
+
const langKey = `./languages/${lang}.hjson`
|
|
78
|
+
i18n[lang] = langModules[langKey]?.default || langModules[langKey] || {}
|
|
79
|
+
|
|
80
|
+
// @ Iterate pages
|
|
81
|
+
for (const [key, page] of Object.entries(pages)) {
|
|
82
|
+
const path = key.slice(1)
|
|
83
|
+
|
|
84
|
+
const config = page.config
|
|
85
|
+
const data = page.data
|
|
86
|
+
const meta = page.meta || boot.meta
|
|
87
|
+
|
|
88
|
+
const topPage = config?.type ?? 'manual'
|
|
89
|
+
|
|
90
|
+
// ---
|
|
91
|
+
|
|
92
|
+
const _ = path.split('/').reduce((accumulator, current) => {
|
|
93
|
+
let node = accumulator[current]
|
|
94
|
+
|
|
95
|
+
// Set object if not exists
|
|
96
|
+
if (node === undefined) {
|
|
97
|
+
accumulator[current] = {}
|
|
98
|
+
node = accumulator[current]
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// @ Set metadata
|
|
102
|
+
// title
|
|
103
|
+
if (node._ === undefined) {
|
|
104
|
+
node._ = data[lang]?.title || data['*']?.title
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (config === null) {
|
|
108
|
+
return node
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Set subpages sources if not exists
|
|
112
|
+
if (node.overview === undefined) {
|
|
113
|
+
node.overview = {
|
|
114
|
+
_translations: meta[lang]?.overview?._translations,
|
|
115
|
+
_sections: meta[lang]?.overview?._sections,
|
|
116
|
+
source: ''
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (config.subpages?.showcase && node.showcase === undefined) {
|
|
120
|
+
node.showcase = {
|
|
121
|
+
_translations: meta[lang]?.showcase?._translations,
|
|
122
|
+
_sections: meta[lang]?.showcase?._sections,
|
|
123
|
+
source: ''
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (config.subpages?.vs && node.vs === undefined) {
|
|
127
|
+
node.vs = {
|
|
128
|
+
_translations: meta[lang]?.vs?._translations,
|
|
129
|
+
_sections: meta[lang]?.vs?._sections,
|
|
130
|
+
source: ''
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return node
|
|
135
|
+
}, i18n[lang]._[topPage])
|
|
136
|
+
|
|
137
|
+
// ---
|
|
138
|
+
|
|
139
|
+
if (config === null || config.status === 'empty') {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// @ Subpages
|
|
144
|
+
// Overview
|
|
145
|
+
_.overview.source = load(topPage, path, 'overview', lang)
|
|
146
|
+
// showcase
|
|
147
|
+
if (config.subpages?.showcase === true) {
|
|
148
|
+
_.showcase.source = load(topPage, path, 'showcase', lang)
|
|
149
|
+
}
|
|
150
|
+
// Vs
|
|
151
|
+
if (config.subpages?.vs === true) {
|
|
152
|
+
_.vs.source = load(topPage, path, 'vs', lang)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return i18n
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default buildMessages
|
package/src/i18n/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// @ Import i18n message builder
|
|
2
|
+
import { buildMessages } from './helpers'
|
|
3
|
+
|
|
1
4
|
// @ Import language HJSON files (Vite-compatible eager import)
|
|
2
5
|
const langModules = import.meta.glob('./languages/*.hjson', { eager: true })
|
|
3
6
|
// @ Import markdown files (Vite-compatible eager import as raw strings)
|
|
@@ -7,120 +10,4 @@ const mdModules = import.meta.glob('../pages/**/*.md', { eager: true, query: '?r
|
|
|
7
10
|
import boot from 'pages/boot'
|
|
8
11
|
import pages from 'pages'
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
'en-US',
|
|
12
|
-
'pt-BR'
|
|
13
|
-
]
|
|
14
|
-
const i18n = {}
|
|
15
|
-
|
|
16
|
-
function filter (source) {
|
|
17
|
-
const regex1 = /{/gm
|
|
18
|
-
const regex2 = /}/gm
|
|
19
|
-
const regex3 = /([@|])+/gm
|
|
20
|
-
|
|
21
|
-
source = source
|
|
22
|
-
.replace(regex1, '{')
|
|
23
|
-
.replace(regex2, '}')
|
|
24
|
-
.replace(regex3, "{'$&'}")
|
|
25
|
-
|
|
26
|
-
return source
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function load (topPage, path, subpage, lang) {
|
|
30
|
-
const key = `../pages/${topPage}/${path}.${subpage}.${lang}.md`
|
|
31
|
-
const content = mdModules[key]
|
|
32
|
-
|
|
33
|
-
if (!content) {
|
|
34
|
-
console.warn(`[i18n] Missing markdown: ${key}`)
|
|
35
|
-
return ''
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const source = filter(typeof content === 'string' ? content : String(content))
|
|
39
|
-
|
|
40
|
-
return source
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// @ Iterate langs
|
|
44
|
-
for (const lang of langs) {
|
|
45
|
-
// Load HJSON language file
|
|
46
|
-
const langKey = `./languages/${lang}.hjson`
|
|
47
|
-
i18n[lang] = langModules[langKey]?.default || langModules[langKey] || {}
|
|
48
|
-
|
|
49
|
-
// @ Iterate pages
|
|
50
|
-
for (const [key, page] of Object.entries(pages)) {
|
|
51
|
-
const path = key.slice(1)
|
|
52
|
-
|
|
53
|
-
const config = page.config
|
|
54
|
-
const data = page.data
|
|
55
|
-
const meta = page.meta || boot.meta
|
|
56
|
-
|
|
57
|
-
const topPage = config?.type ?? 'manual'
|
|
58
|
-
|
|
59
|
-
// ---
|
|
60
|
-
|
|
61
|
-
const _ = path.split('/').reduce((accumulator, current) => {
|
|
62
|
-
let node = accumulator[current]
|
|
63
|
-
|
|
64
|
-
// Set object if not exists
|
|
65
|
-
if (node === undefined) {
|
|
66
|
-
accumulator[current] = {}
|
|
67
|
-
node = accumulator[current]
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// @ Set metadata
|
|
71
|
-
// title
|
|
72
|
-
if (node._ === undefined) {
|
|
73
|
-
node._ = data[lang]?.title || data['*']?.title
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (config === null) {
|
|
77
|
-
return node
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Set subpages sources if not exists
|
|
81
|
-
if (node.overview === undefined) {
|
|
82
|
-
node.overview = {
|
|
83
|
-
_translations: meta[lang]?.overview?._translations,
|
|
84
|
-
_sections: meta[lang]?.overview?._sections,
|
|
85
|
-
source: ''
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (config.subpages?.showcase && node.showcase === undefined) {
|
|
89
|
-
node.showcase = {
|
|
90
|
-
_translations: meta[lang]?.showcase?._translations,
|
|
91
|
-
_sections: meta[lang]?.showcase?._sections,
|
|
92
|
-
source: ''
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (config.subpages?.vs && node.vs === undefined) {
|
|
96
|
-
node.vs = {
|
|
97
|
-
_translations: meta[lang]?.vs?._translations,
|
|
98
|
-
_sections: meta[lang]?.vs?._sections,
|
|
99
|
-
source: ''
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return node
|
|
104
|
-
}, i18n[lang]._[topPage])
|
|
105
|
-
|
|
106
|
-
// ---
|
|
107
|
-
|
|
108
|
-
if (config === null || config.status === 'empty') {
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// @ Subpages
|
|
113
|
-
// Overview
|
|
114
|
-
_.overview.source = load(topPage, path, 'overview', lang)
|
|
115
|
-
// showcase
|
|
116
|
-
if (config.subpages?.showcase === true) {
|
|
117
|
-
_.showcase.source = load(topPage, path, 'showcase', lang)
|
|
118
|
-
}
|
|
119
|
-
// Vs
|
|
120
|
-
if (config.subpages?.vs === true) {
|
|
121
|
-
_.vs.source = load(topPage, path, 'vs', lang)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export default i18n
|
|
13
|
+
export default buildMessages({ langModules, mdModules, pages, boot })
|
|
@@ -33,7 +33,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|
|
33
33
|
import { useStore } from 'vuex'
|
|
34
34
|
import { useI18n } from 'vue-i18n'
|
|
35
35
|
|
|
36
|
-
import DMenu from 'components/DMenu.vue'
|
|
36
|
+
import DMenu from '../components/DMenu.vue'
|
|
37
37
|
import docsectorConfig from 'docsector.config.js'
|
|
38
38
|
|
|
39
39
|
defineOptions({ name: 'LayoutDefault' })
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Docsector Reader — Quasar Config Factory
|
|
3
|
+
*
|
|
4
|
+
* This module exports a function that generates a complete Quasar configuration
|
|
5
|
+
* for consumer projects that use @docsector/docsector-reader as a dependency.
|
|
6
|
+
*
|
|
7
|
+
* Usage in consumer's quasar.config.js:
|
|
8
|
+
*
|
|
9
|
+
* import { createQuasarConfig } from '@docsector/docsector-reader/quasar-factory'
|
|
10
|
+
* import { configure } from 'quasar/wrappers'
|
|
11
|
+
*
|
|
12
|
+
* export default configure((ctx) => {
|
|
13
|
+
* return createQuasarConfig({
|
|
14
|
+
* projectRoot: import.meta.dirname
|
|
15
|
+
* })
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {string} options.projectRoot - Absolute path to the consumer project root
|
|
20
|
+
* @param {number} [options.port=8181] - Dev server port
|
|
21
|
+
* @param {Object} [options.pwa] - PWA manifest overrides
|
|
22
|
+
* @param {Array} [options.vitePlugins] - Additional Vite plugins
|
|
23
|
+
* @param {Function} [options.extendViteConf] - Additional Vite config extension
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { readFileSync, existsSync } from 'fs'
|
|
27
|
+
import { resolve } from 'path'
|
|
28
|
+
import HJSON from 'hjson'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* No-op configure wrapper.
|
|
32
|
+
* Quasar's `configure` from `quasar/wrappers` is a TypeScript identity function.
|
|
33
|
+
* We re-export it here so consumer projects don't need `quasar` in their own
|
|
34
|
+
* node_modules just for the config file.
|
|
35
|
+
*/
|
|
36
|
+
export function configure (callback) {
|
|
37
|
+
return callback
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the docsector-reader package root.
|
|
42
|
+
*
|
|
43
|
+
* In consumer mode, the package lives in node_modules/@docsector/docsector-reader.
|
|
44
|
+
* In standalone mode (docsector-reader running itself), the package IS the project.
|
|
45
|
+
*
|
|
46
|
+
* Note: We can't use import.meta.dirname here because Quasar's ESM config loader
|
|
47
|
+
* inlines imports, causing import.meta to refer to the config file's context.
|
|
48
|
+
*/
|
|
49
|
+
function getPackageRoot (projectRoot) {
|
|
50
|
+
// Consumer mode: package installed in node_modules
|
|
51
|
+
const nodeModulesPath = resolve(projectRoot, 'node_modules', '@docsector', 'docsector-reader')
|
|
52
|
+
if (existsSync(resolve(nodeModulesPath, 'package.json'))) {
|
|
53
|
+
return nodeModulesPath
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Standalone mode: we ARE the project
|
|
57
|
+
return projectRoot
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create the HJSON Vite plugin for loading .hjson files as ES modules.
|
|
62
|
+
*/
|
|
63
|
+
function createHjsonPlugin () {
|
|
64
|
+
return {
|
|
65
|
+
name: 'vite-plugin-hjson',
|
|
66
|
+
transform (code, id) {
|
|
67
|
+
if (id.endsWith('.hjson')) {
|
|
68
|
+
const parsed = HJSON.parse(readFileSync(id, 'utf-8'))
|
|
69
|
+
return {
|
|
70
|
+
code: `export default ${JSON.stringify(parsed, null, 2)};`,
|
|
71
|
+
map: null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a complete Quasar configuration for a docsector-reader consumer project.
|
|
80
|
+
*
|
|
81
|
+
* In **standalone** mode (docsector-reader running itself), all paths resolve
|
|
82
|
+
* naturally via Quasar defaults — no alias overrides needed.
|
|
83
|
+
*
|
|
84
|
+
* In **consumer** mode (installed as a dependency), Vite aliases redirect
|
|
85
|
+
* engine internals (components, layouts, composables, boot) to the package
|
|
86
|
+
* while content paths (pages, src/i18n) stay in the consumer project.
|
|
87
|
+
*
|
|
88
|
+
* Boot file resolution trick:
|
|
89
|
+
* - boot/store, boot/QZoom, boot/axios → package (relative imports: ../store/, ../components/)
|
|
90
|
+
* - boot/i18n → package file, BUT it imports 'src/i18n' which Quasar aliases to consumer's src/
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} options - Configuration options
|
|
93
|
+
* @param {string} options.projectRoot - Absolute path to the consumer project root
|
|
94
|
+
* @param {number} [options.port=8181] - Dev server port
|
|
95
|
+
* @param {Object} [options.pwa] - PWA manifest overrides (merged with defaults)
|
|
96
|
+
* @param {Array} [options.vitePlugins=[]] - Additional Vite plugins to include
|
|
97
|
+
* @param {Array} [options.boot=[]] - Additional boot files to include
|
|
98
|
+
* @param {Function} [options.extendViteConf] - Additional Vite configuration callback
|
|
99
|
+
* @returns {Object} Complete Quasar configuration object
|
|
100
|
+
*/
|
|
101
|
+
export function createQuasarConfig (options = {}) {
|
|
102
|
+
const {
|
|
103
|
+
projectRoot = process.cwd(),
|
|
104
|
+
port = 8181,
|
|
105
|
+
pwa = {},
|
|
106
|
+
vitePlugins = [],
|
|
107
|
+
boot: extraBoot = [],
|
|
108
|
+
extendViteConf: userExtendViteConf
|
|
109
|
+
} = options
|
|
110
|
+
|
|
111
|
+
const packageRoot = getPackageRoot(projectRoot)
|
|
112
|
+
const isStandalone = projectRoot === packageRoot
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
// Boot files — Quasar resolves these via 'boot/<name>' imports.
|
|
116
|
+
// Since the 'boot' alias points to packageRoot/src/boot/ in consumer mode,
|
|
117
|
+
// consumer-specific boot files are resolved via per-file Vite aliases
|
|
118
|
+
// added in extendViteConf below.
|
|
119
|
+
boot: [
|
|
120
|
+
'store',
|
|
121
|
+
'QZoom',
|
|
122
|
+
'i18n',
|
|
123
|
+
'axios',
|
|
124
|
+
...extraBoot
|
|
125
|
+
],
|
|
126
|
+
|
|
127
|
+
// CSS — Quasar resolves from src/css/
|
|
128
|
+
css: [
|
|
129
|
+
'app.sass'
|
|
130
|
+
],
|
|
131
|
+
|
|
132
|
+
extras: [
|
|
133
|
+
'fontawesome-v5',
|
|
134
|
+
'roboto-font',
|
|
135
|
+
'material-icons'
|
|
136
|
+
],
|
|
137
|
+
|
|
138
|
+
build: {
|
|
139
|
+
target: {
|
|
140
|
+
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
|
|
141
|
+
node: 'node20'
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
vueRouterMode: 'history',
|
|
145
|
+
|
|
146
|
+
vitePlugins: [
|
|
147
|
+
createHjsonPlugin(),
|
|
148
|
+
...vitePlugins
|
|
149
|
+
],
|
|
150
|
+
|
|
151
|
+
extendViteConf (viteConf) {
|
|
152
|
+
viteConf.resolve = viteConf.resolve || {}
|
|
153
|
+
viteConf.resolve.alias = viteConf.resolve.alias || {}
|
|
154
|
+
|
|
155
|
+
if (!isStandalone) {
|
|
156
|
+
// --- Consumer project mode ---
|
|
157
|
+
// Allow Vite to serve files from the package root (needed for symlinked/npm-linked packages)
|
|
158
|
+
viteConf.server = viteConf.server || {}
|
|
159
|
+
viteConf.server.fs = viteConf.server.fs || {}
|
|
160
|
+
viteConf.server.fs.allow = viteConf.server.fs.allow || []
|
|
161
|
+
viteConf.server.fs.allow.push(packageRoot, projectRoot)
|
|
162
|
+
viteConf.server.fs.strict = false
|
|
163
|
+
|
|
164
|
+
// Consumer boot files — per-file aliases must come BEFORE the general
|
|
165
|
+
// 'boot' alias in the object, otherwise Vite matches 'boot' first.
|
|
166
|
+
// We delete the pre-existing 'boot' alias (set by Quasar), add specific
|
|
167
|
+
// entries, then re-add the general 'boot' alias after them.
|
|
168
|
+
delete viteConf.resolve.alias.boot
|
|
169
|
+
for (const bootName of extraBoot) {
|
|
170
|
+
if (typeof bootName === 'string') {
|
|
171
|
+
viteConf.resolve.alias[`boot/${bootName}`] = resolve(projectRoot, 'src/boot', bootName)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Engine internals from the package (components, layouts, composables, boot, css):
|
|
176
|
+
viteConf.resolve.alias.components = resolve(packageRoot, 'src/components')
|
|
177
|
+
viteConf.resolve.alias.layouts = resolve(packageRoot, 'src/layouts')
|
|
178
|
+
viteConf.resolve.alias.composables = resolve(packageRoot, 'src/composables')
|
|
179
|
+
viteConf.resolve.alias.boot = resolve(packageRoot, 'src/boot')
|
|
180
|
+
viteConf.resolve.alias.css = resolve(packageRoot, 'src/css')
|
|
181
|
+
viteConf.resolve.alias.stores = resolve(packageRoot, 'src/store')
|
|
182
|
+
|
|
183
|
+
// Content from the consumer project:
|
|
184
|
+
viteConf.resolve.alias.pages = resolve(projectRoot, 'src/pages')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Tags for menu search — consumer's tags if available, else package's
|
|
188
|
+
const consumerTags = resolve(projectRoot, 'src/i18n/tags.hjson')
|
|
189
|
+
const packageTags = resolve(packageRoot, 'src/i18n/tags.hjson')
|
|
190
|
+
viteConf.resolve.alias['@docsector/tags'] = existsSync(consumerTags) ? consumerTags : packageTags
|
|
191
|
+
|
|
192
|
+
// docsector.config.js — always from consumer/project root
|
|
193
|
+
viteConf.resolve.alias['docsector.config.js'] = resolve(projectRoot, 'docsector.config.js')
|
|
194
|
+
|
|
195
|
+
// Allow consumer to extend further
|
|
196
|
+
if (typeof userExtendViteConf === 'function') {
|
|
197
|
+
userExtendViteConf(viteConf)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
// Source files — point App.vue, router, store to the package in consumer mode
|
|
203
|
+
// Quasar prepends 'app/' to these, so use paths relative to projectRoot
|
|
204
|
+
sourceFiles: isStandalone
|
|
205
|
+
? {}
|
|
206
|
+
: {
|
|
207
|
+
rootComponent: 'node_modules/@docsector/docsector-reader/src/App.vue',
|
|
208
|
+
router: 'node_modules/@docsector/docsector-reader/src/router/index'
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
devServer: {
|
|
212
|
+
port,
|
|
213
|
+
open: false
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
framework: {
|
|
217
|
+
config: {},
|
|
218
|
+
lang: 'en-US',
|
|
219
|
+
plugins: [
|
|
220
|
+
'LocalStorage', 'SessionStorage'
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
animations: ['zoomIn', 'zoomOut'],
|
|
225
|
+
|
|
226
|
+
pwa: {
|
|
227
|
+
workboxMode: 'GenerateSW',
|
|
228
|
+
manifest: {
|
|
229
|
+
name: 'Documentation',
|
|
230
|
+
short_name: 'Docs',
|
|
231
|
+
description: 'Documentation powered by Docsector Reader.',
|
|
232
|
+
display: 'standalone',
|
|
233
|
+
orientation: 'portrait',
|
|
234
|
+
background_color: '#ffffff',
|
|
235
|
+
theme_color: '#655529',
|
|
236
|
+
icons: [
|
|
237
|
+
{ src: 'icons/icon-128x128.png', sizes: '128x128', type: 'image/png' },
|
|
238
|
+
{ src: 'icons/icon-192x192.png', sizes: '192x192', type: 'image/png' },
|
|
239
|
+
{ src: 'icons/icon-256x256.png', sizes: '256x256', type: 'image/png' },
|
|
240
|
+
{ src: 'icons/icon-384x384.png', sizes: '384x384', type: 'image/png' },
|
|
241
|
+
{ src: 'icons/icon-512x512.png', sizes: '512x512', type: 'image/png' }
|
|
242
|
+
],
|
|
243
|
+
...pwa
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default createQuasarConfig
|