@docsector/docsector-reader 0.1.3 → 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.
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { watch } from 'vue'
3
3
 
4
- import useNavigator from 'src/composables/useNavigator'
4
+ import useNavigator from '../composables/useNavigator'
5
5
 
6
6
  const props = defineProps({
7
7
  id: {
@@ -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 'src/i18n/tags.hjson'
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, items] of items.value.entries()) {
39
- searchTermIterate(items, term, locale)
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
@@ -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 'src/composables/useNavigator'
7
+ import useNavigator from '../composables/useNavigator'
8
8
 
9
- import DPageAnchor from 'components/DPageAnchor.vue'
10
- import DPageMeta from 'components/DPageMeta.vue'
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 'src/composables/useNavigator'
7
+ import useNavigator from '../composables/useNavigator'
8
8
 
9
9
  const store = useStore()
10
10
  const $q = useQuasar()
@@ -15,7 +15,7 @@ import { useColorize } from 'q-colorize-mixin'
15
15
  import { dom } from 'quasar'
16
16
  const { offset } = dom
17
17
 
18
- import './QZoom.styl'
18
+ import './QZoom.sass'
19
19
 
20
20
  // @
21
21
  export default {
@@ -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, '&#123;')
32
+ .replace(regex2, '&#125;')
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
- const langs = [
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, '&#123;')
23
- .replace(regex2, '&#125;')
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