@docsector/docsector-reader 4.0.1 → 4.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 +19 -0
- package/bin/docsector.js +1 -1
- package/package.json +1 -1
- package/public/api/manual/http-client.json +91 -0
- package/public/quasar-api/QSeparator.json +39 -0
- package/src/components/DBlockApi.vue +634 -0
- package/src/components/DBlockApiEntry.js +623 -0
- package/src/components/DBlockCodeExample.vue +445 -0
- package/src/components/DBlockSourceCode.vue +3 -11
- package/src/components/DMenu.vue +70 -25
- package/src/components/DPageTokens.vue +22 -0
- package/src/components/api-block-model.js +326 -0
- package/src/components/code-block-highlighting.js +16 -0
- package/src/components/code-example-source.js +363 -0
- package/src/components/page-section-tokens.js +141 -1
- package/src/components/source-code-lines.js +17 -0
- package/src/examples/manual/code-examples/BasicCounter.vue +63 -0
- package/src/examples/manual/code-examples/InlineNotice.vue +60 -0
- package/src/pages/manual/content/blocks/api-reference.overview.en-US.md +40 -0
- package/src/pages/manual/content/blocks/api-reference.overview.pt-BR.md +40 -0
- package/src/pages/manual/content/blocks/api-reference.showcase.en-US.md +33 -0
- package/src/pages/manual/content/blocks/api-reference.showcase.pt-BR.md +33 -0
- package/src/pages/manual/content/blocks/code-examples.overview.en-US.md +56 -0
- package/src/pages/manual/content/blocks/code-examples.overview.pt-BR.md +56 -0
- package/src/pages/manual/content/blocks/code-examples.showcase.en-US.md +38 -0
- package/src/pages/manual/content/blocks/code-examples.showcase.pt-BR.md +38 -0
- package/src/pages/manual.index.js +56 -0
- package/src/quasar.factory.js +77 -0
- package/src/store/Page.js +26 -2
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
const defaultInnerTabName = '__default'
|
|
2
|
+
const fallbackCategoryName = 'general'
|
|
3
|
+
|
|
4
|
+
const isPlainObject = (value) => {
|
|
5
|
+
return Object.prototype.toString.call(value) === '[object Object]'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const isSupportedTopLevelSection = (value) => {
|
|
9
|
+
return typeof value === 'string' || isPlainObject(value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const getEntryCategories = (entry = {}) => {
|
|
13
|
+
const raw = String(entry?.category || '').trim()
|
|
14
|
+
|
|
15
|
+
if (raw === '') {
|
|
16
|
+
return [fallbackCategoryName]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const groups = raw
|
|
20
|
+
.split('|')
|
|
21
|
+
.map((value) => value.trim())
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
|
|
24
|
+
return groups.length === 0 ? [fallbackCategoryName] : groups
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pruneInternalEntries = (value) => {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value
|
|
30
|
+
.map((entry) => pruneInternalEntries(entry))
|
|
31
|
+
.filter((entry) => entry !== undefined)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isPlainObject(value)) {
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (value.internal === true) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const acc = {}
|
|
43
|
+
|
|
44
|
+
Object.entries(value).forEach(([key, entryValue]) => {
|
|
45
|
+
if (key === 'internal') {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const nextValue = pruneInternalEntries(entryValue)
|
|
50
|
+
|
|
51
|
+
if (nextValue !== undefined) {
|
|
52
|
+
acc[key] = nextValue
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return acc
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getApiSourceName = (sourceName = '') => {
|
|
60
|
+
const normalized = String(sourceName || '')
|
|
61
|
+
.split('?')[0]
|
|
62
|
+
.split('#')[0]
|
|
63
|
+
.replace(/\\/g, '/')
|
|
64
|
+
const fileName = normalized.split('/').filter(Boolean).pop() || ''
|
|
65
|
+
|
|
66
|
+
return fileName.replace(/\.json$/i, '') || 'API'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isEmptySingleEntry = (value) => {
|
|
70
|
+
if (value === undefined || value === null) {
|
|
71
|
+
return true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof value === 'string') {
|
|
75
|
+
return value.trim() === ''
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return value.length === 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (isPlainObject(value)) {
|
|
83
|
+
return Object.keys(value).length === 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const normalizeApiDocsLink = (value = '') => {
|
|
90
|
+
const normalized = String(value || '').trim()
|
|
91
|
+
|
|
92
|
+
if (normalized === '') {
|
|
93
|
+
return ''
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return normalized
|
|
97
|
+
.replace(/^https:\/\/v[\d]+\.quasar\.dev/i, '')
|
|
98
|
+
.replace(/^https:\/\/quasar\.dev/i, '')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const getPropsCategories = (props = {}) => {
|
|
102
|
+
const acc = new Set()
|
|
103
|
+
|
|
104
|
+
Object.values(props || {}).forEach((value) => {
|
|
105
|
+
if (value !== undefined && value !== null) {
|
|
106
|
+
getEntryCategories(value).forEach((groupKey) => {
|
|
107
|
+
acc.add(groupKey)
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
return acc.size === 1 ? [defaultInnerTabName] : [...acc].sort()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const getInnerTabs = (api = {}, tabs = []) => {
|
|
116
|
+
const acc = {}
|
|
117
|
+
|
|
118
|
+
tabs.forEach((tab) => {
|
|
119
|
+
acc[tab] = tab === 'props' && isPlainObject(api[tab])
|
|
120
|
+
? getPropsCategories(api[tab])
|
|
121
|
+
: [defaultInnerTabName]
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return acc
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const parseApi = (api = {}, tabs = [], innerTabs = {}) => {
|
|
128
|
+
const acc = {}
|
|
129
|
+
|
|
130
|
+
tabs.forEach((tab) => {
|
|
131
|
+
const apiValue = api[tab]
|
|
132
|
+
|
|
133
|
+
if (innerTabs[tab]?.length > 1 && isPlainObject(apiValue)) {
|
|
134
|
+
const inner = {}
|
|
135
|
+
|
|
136
|
+
innerTabs[tab].forEach((subTab) => {
|
|
137
|
+
inner[subTab] = {}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
Object.entries(apiValue).forEach(([key, value]) => {
|
|
141
|
+
if (value === undefined || value === null) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getEntryCategories(value).forEach((groupKey) => {
|
|
146
|
+
if (inner[groupKey] !== undefined) {
|
|
147
|
+
inner[groupKey][key] = value
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
acc[tab] = inner
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
acc[tab] = {
|
|
157
|
+
[defaultInnerTabName]: apiValue ?? {}
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
return acc
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const passesFilter = (filter, name, desc) => {
|
|
165
|
+
const normalizedFilter = String(filter || '').trim().toLowerCase()
|
|
166
|
+
|
|
167
|
+
if (normalizedFilter === '') {
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
String(name || '').toLowerCase().includes(normalizedFilter) ||
|
|
173
|
+
String(desc || '').toLowerCase().includes(normalizedFilter)
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const getFilteredApi = (parsedApi = {}, filter = '', tabs = [], innerTabs = {}) => {
|
|
178
|
+
const normalizedFilter = String(filter || '').trim().toLowerCase()
|
|
179
|
+
|
|
180
|
+
if (normalizedFilter === '') {
|
|
181
|
+
return parsedApi
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const acc = {}
|
|
185
|
+
|
|
186
|
+
tabs.forEach((tab) => {
|
|
187
|
+
if (tab === 'injection') {
|
|
188
|
+
const name = parsedApi?.[tab]?.[defaultInnerTabName]
|
|
189
|
+
|
|
190
|
+
acc[tab] = {
|
|
191
|
+
[defaultInnerTabName]: passesFilter(normalizedFilter, name, '') ? name : {}
|
|
192
|
+
}
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (tab === 'quasarConfOptions') {
|
|
197
|
+
const api = parsedApi?.[tab]?.[defaultInnerTabName] || {}
|
|
198
|
+
const result = {
|
|
199
|
+
...api,
|
|
200
|
+
definition: {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Object.entries(api.definition || {}).forEach(([name, entry]) => {
|
|
204
|
+
if (passesFilter(normalizedFilter, name, entry?.desc)) {
|
|
205
|
+
result.definition[name] = entry
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
acc[tab] = {
|
|
210
|
+
[defaultInnerTabName]: Object.keys(result.definition).length === 0 && !passesFilter(normalizedFilter, api.propName, '')
|
|
211
|
+
? {}
|
|
212
|
+
: result
|
|
213
|
+
}
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const tabApi = parsedApi?.[tab] || {}
|
|
218
|
+
const tabCategories = innerTabs[tab] || [defaultInnerTabName]
|
|
219
|
+
|
|
220
|
+
acc[tab] = {}
|
|
221
|
+
|
|
222
|
+
tabCategories.forEach((category) => {
|
|
223
|
+
const subTabs = {}
|
|
224
|
+
const categoryEntries = tabApi[category] || {}
|
|
225
|
+
|
|
226
|
+
Object.entries(categoryEntries).forEach(([name, entry]) => {
|
|
227
|
+
if (passesFilter(normalizedFilter, name, entry?.desc)) {
|
|
228
|
+
subTabs[name] = entry
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
acc[tab][category] = subTabs
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
return acc
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export const getApiCount = (parsedApi = {}, tabs = [], innerTabs = {}) => {
|
|
240
|
+
const acc = {}
|
|
241
|
+
|
|
242
|
+
tabs.forEach((tab) => {
|
|
243
|
+
const tabApi = parsedApi?.[tab] || {}
|
|
244
|
+
const tabCategories = innerTabs[tab] || [defaultInnerTabName]
|
|
245
|
+
|
|
246
|
+
if (['value', 'arg', 'injection'].includes(tab)) {
|
|
247
|
+
const value = tabApi[tabCategories[0]]
|
|
248
|
+
|
|
249
|
+
acc[tab] = {
|
|
250
|
+
overall: isEmptySingleEntry(value) ? 0 : 1
|
|
251
|
+
}
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (tab === 'quasarConfOptions') {
|
|
256
|
+
const api = tabApi[tabCategories[0]] || {}
|
|
257
|
+
|
|
258
|
+
acc[tab] = {
|
|
259
|
+
overall: Object.keys(api).length === 0
|
|
260
|
+
? 0
|
|
261
|
+
: api.definition === undefined
|
|
262
|
+
? 1
|
|
263
|
+
: Object.keys(api.definition || {}).length
|
|
264
|
+
}
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const nextValue = {
|
|
269
|
+
overall: 0,
|
|
270
|
+
category: {}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
tabCategories.forEach((category) => {
|
|
274
|
+
const count = Object.keys(tabApi[category] || {}).length
|
|
275
|
+
|
|
276
|
+
nextValue.category[category] = count
|
|
277
|
+
nextValue.overall += count
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
acc[tab] = nextValue
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
return acc
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const createApiBlockModel = (sourceName = '', apiDocument = {}) => {
|
|
287
|
+
const rawDocument = isPlainObject(apiDocument) ? apiDocument : {}
|
|
288
|
+
const {
|
|
289
|
+
type: _type,
|
|
290
|
+
behavior: _behavior,
|
|
291
|
+
meta,
|
|
292
|
+
addedIn: _addedIn,
|
|
293
|
+
internal: _internalSection,
|
|
294
|
+
...apiSectionsRaw
|
|
295
|
+
} = rawDocument
|
|
296
|
+
const apiSections = {}
|
|
297
|
+
|
|
298
|
+
Object.entries(apiSectionsRaw).forEach(([sectionName, sectionValue]) => {
|
|
299
|
+
const sanitizedValue = pruneInternalEntries(sectionValue)
|
|
300
|
+
|
|
301
|
+
if (sanitizedValue !== undefined && isSupportedTopLevelSection(sanitizedValue)) {
|
|
302
|
+
apiSections[sectionName] = sanitizedValue
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const tabs = Object.keys(apiSections)
|
|
307
|
+
const innerTabs = getInnerTabs(apiSections, tabs)
|
|
308
|
+
const api = parseApi(apiSections, tabs, innerTabs)
|
|
309
|
+
const sourceLabel = getApiSourceName(sourceName)
|
|
310
|
+
const docsUrl = String(meta?.docsUrl || '').trim()
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
sourceLabel,
|
|
314
|
+
title: `${sourceLabel} API`,
|
|
315
|
+
docsUrl,
|
|
316
|
+
docsLink: normalizeApiDocsLink(docsUrl),
|
|
317
|
+
tabs,
|
|
318
|
+
innerTabs,
|
|
319
|
+
api,
|
|
320
|
+
nothingToShow: tabs.length === 0
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export {
|
|
325
|
+
defaultInnerTabName
|
|
326
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Prism from 'prismjs'
|
|
2
|
+
|
|
3
|
+
import 'prismjs/components/prism-markup'
|
|
4
|
+
import 'prismjs/components/prism-markup-templating'
|
|
5
|
+
import 'prismjs/components/prism-javascript'
|
|
6
|
+
import 'prismjs/components/prism-css'
|
|
7
|
+
import 'prismjs/components/prism-php'
|
|
8
|
+
import 'prismjs/components/prism-bash'
|
|
9
|
+
|
|
10
|
+
if (!Prism.languages.vue && Prism.languages.markup?.tag?.addInlined) {
|
|
11
|
+
Prism.languages.markup.tag.addInlined('script', 'javascript')
|
|
12
|
+
Prism.languages.markup.tag.addInlined('style', 'css')
|
|
13
|
+
Prism.languages.vue = Prism.languages.markup
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default Prism
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
const PART_LABELS = Object.freeze({
|
|
2
|
+
template: 'Template',
|
|
3
|
+
script: 'Script',
|
|
4
|
+
style: 'Style'
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
const PART_ORDER = ['Template', 'Script', 'Style']
|
|
8
|
+
const ALLOWED_CODEPEN_IMPORTS = new Set(['vue', 'quasar'])
|
|
9
|
+
|
|
10
|
+
const createSfcOpeningTagPattern = () => /<(template|script|style)\b((?:"[^"]*"|'[^']*'|[^'">])*)>/gi
|
|
11
|
+
|
|
12
|
+
const createSameTagPattern = (tag) => new RegExp(`</?${tag}\\b((?:"[^"]*"|'[^']*'|[^'">])*)>`, 'gi')
|
|
13
|
+
|
|
14
|
+
const findSfcBlockRange = (source = '', tag = '', searchStart = 0) => {
|
|
15
|
+
const pattern = createSameTagPattern(tag)
|
|
16
|
+
pattern.lastIndex = searchStart
|
|
17
|
+
|
|
18
|
+
let depth = 1
|
|
19
|
+
let match = pattern.exec(source)
|
|
20
|
+
|
|
21
|
+
while (match !== null) {
|
|
22
|
+
const token = match[0]
|
|
23
|
+
const isClosing = token.startsWith('</')
|
|
24
|
+
const isSelfClosing = /\/\s*>$/.test(token)
|
|
25
|
+
|
|
26
|
+
if (isClosing) {
|
|
27
|
+
depth--
|
|
28
|
+
} else if (!isSelfClosing) {
|
|
29
|
+
depth++
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (depth === 0) {
|
|
33
|
+
return {
|
|
34
|
+
closingStart: match.index,
|
|
35
|
+
blockEnd: pattern.lastIndex
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
match = pattern.exec(source)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parseVueSfcBlocks = (source = '') => {
|
|
46
|
+
const blocks = {
|
|
47
|
+
template: [],
|
|
48
|
+
script: [],
|
|
49
|
+
style: []
|
|
50
|
+
}
|
|
51
|
+
const content = String(source)
|
|
52
|
+
const openingPattern = createSfcOpeningTagPattern()
|
|
53
|
+
|
|
54
|
+
let cursor = 0
|
|
55
|
+
while (cursor < content.length) {
|
|
56
|
+
openingPattern.lastIndex = cursor
|
|
57
|
+
|
|
58
|
+
const match = openingPattern.exec(content)
|
|
59
|
+
if (!match) {
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const tag = match[1].toLowerCase()
|
|
64
|
+
const openingStart = match.index
|
|
65
|
+
const openingEnd = openingPattern.lastIndex
|
|
66
|
+
const range = findSfcBlockRange(content, tag, openingEnd)
|
|
67
|
+
|
|
68
|
+
if (!range) {
|
|
69
|
+
cursor = openingEnd
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
blocks[tag].push({
|
|
74
|
+
tag,
|
|
75
|
+
attrs: match[2] || '',
|
|
76
|
+
content: content.slice(openingEnd, range.closingStart).trim(),
|
|
77
|
+
raw: content.slice(openingStart, range.blockEnd).trim()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
cursor = range.blockEnd
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return blocks
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const hasAttribute = (rawAttrs = '', name = '') => {
|
|
87
|
+
const pattern = new RegExp(`(?:^|\\s)${name}(?:\\s|=|$)`, 'i')
|
|
88
|
+
return pattern.test(String(rawAttrs || ''))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getLangAttribute = (rawAttrs = '') => {
|
|
92
|
+
const match = String(rawAttrs || '').match(/(?:^|\s)lang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i)
|
|
93
|
+
return (match?.[1] || match?.[2] || match?.[3] || '').trim().toLowerCase()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const normalizeNamedImport = (rawName = '') => {
|
|
97
|
+
const trimmed = rawName.trim()
|
|
98
|
+
const aliasMatch = trimmed.match(/^(.+?)\s+as\s+(.+)$/i)
|
|
99
|
+
|
|
100
|
+
if (aliasMatch) {
|
|
101
|
+
return `${aliasMatch[1].trim()}: ${aliasMatch[2].trim()}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return trimmed
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const createGlobalDestructure = (globalName, rawNames = '') => {
|
|
108
|
+
const names = String(rawNames)
|
|
109
|
+
.split(',')
|
|
110
|
+
.map((name) => normalizeNamedImport(name))
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
|
|
113
|
+
if (names.length === 0) {
|
|
114
|
+
return ''
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return `const { ${names.join(', ')} } = ${globalName}`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const findUnsupportedImport = (script = '') => {
|
|
121
|
+
const importsWithSourcePattern = /^\s*import\s+(.+?)\s+from\s+["']([^"']+)["'];?\s*$/gm
|
|
122
|
+
const sideEffectImportPattern = /^\s*import\s+["']([^"']+)["'];?\s*$/gm
|
|
123
|
+
|
|
124
|
+
let match = importsWithSourcePattern.exec(script)
|
|
125
|
+
while (match !== null) {
|
|
126
|
+
const importSpecifiers = match[1].trim()
|
|
127
|
+
const importSource = match[2].trim()
|
|
128
|
+
|
|
129
|
+
if (!ALLOWED_CODEPEN_IMPORTS.has(importSource) || !importSpecifiers.startsWith('{')) {
|
|
130
|
+
return importSource
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
match = importsWithSourcePattern.exec(script)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
match = sideEffectImportPattern.exec(script)
|
|
137
|
+
if (match !== null) {
|
|
138
|
+
return match[1].trim()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return ''
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const transformAllowedImports = (script = '') => {
|
|
145
|
+
return String(script)
|
|
146
|
+
.replace(/^\s*import\s+\{([^}]+)\}\s+from\s+["']vue["'];?\s*$/gm, (_, imports) => createGlobalDestructure('Vue', imports))
|
|
147
|
+
.replace(/^\s*import\s+\{([^}]+)\}\s+from\s+["']quasar["'];?\s*$/gm, (_, imports) => createGlobalDestructure('Quasar', imports))
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const getScriptForValidation = (script = '') => {
|
|
151
|
+
return transformAllowedImports(script).trim()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const stripSfcTags = (blocks = []) => {
|
|
155
|
+
return blocks
|
|
156
|
+
.map((block) => block.content)
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join('\n\n')
|
|
159
|
+
.trim()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const getStylePreprocessor = (styleBlock) => {
|
|
163
|
+
const lang = getLangAttribute(styleBlock?.attrs || '')
|
|
164
|
+
return lang || 'none'
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const getPartLanguage = (label, text = '') => {
|
|
168
|
+
if (label === 'Template') return 'html'
|
|
169
|
+
if (label === 'Script') return getLangAttribute(text.match(/^<script\b([^>]*)>/i)?.[1] || '') || 'javascript'
|
|
170
|
+
if (label === 'Style') return getLangAttribute(text.match(/^<style\b([^>]*)>/i)?.[1] || '') || 'css'
|
|
171
|
+
if (label === 'All') return 'vue'
|
|
172
|
+
return 'text'
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const createEditorsFlag = ({ html, css, js }) => {
|
|
176
|
+
const flag = (html ? 0b100 : 0) | (css ? 0b010 : 0) | (js ? 0b001 : 0)
|
|
177
|
+
return flag.toString(2)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const createCodepenResources = (quasarVersion = 'latest') => {
|
|
181
|
+
const version = String(quasarVersion || 'latest').trim() || 'latest'
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
cssExternal: [
|
|
185
|
+
'https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons',
|
|
186
|
+
`https://cdn.jsdelivr.net/npm/quasar@${version}/dist/quasar.min.css`
|
|
187
|
+
].join(';'),
|
|
188
|
+
jsExternal: [
|
|
189
|
+
'https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js',
|
|
190
|
+
`https://cdn.jsdelivr.net/npm/quasar@${version}/dist/quasar.umd.prod.js`
|
|
191
|
+
].join(';')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const normalizeRepositoryFilePath = (filePath = '') => {
|
|
196
|
+
return String(filePath || '')
|
|
197
|
+
.replace(/\\/g, '/')
|
|
198
|
+
.replace(/^\/+/, '')
|
|
199
|
+
.trim()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const createCodepenJs = (script = '') => {
|
|
203
|
+
const transformedScript = transformAllowedImports(script).trim()
|
|
204
|
+
const componentScript = transformedScript
|
|
205
|
+
? transformedScript.replace(/\bexport\s+default\b/, 'const __CodeExample =')
|
|
206
|
+
: 'const __CodeExample = {}'
|
|
207
|
+
|
|
208
|
+
return `${componentScript}
|
|
209
|
+
|
|
210
|
+
const app = Vue.createApp(__CodeExample)
|
|
211
|
+
|
|
212
|
+
app.use(Quasar, { config: {} })
|
|
213
|
+
app.mount('#q-app')
|
|
214
|
+
`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const parseVueSfcParts = (source = '') => {
|
|
218
|
+
const blocks = parseVueSfcBlocks(source)
|
|
219
|
+
const parts = {}
|
|
220
|
+
|
|
221
|
+
Object.entries(blocks).forEach(([tag, tagBlocks]) => {
|
|
222
|
+
const label = PART_LABELS[tag]
|
|
223
|
+
const raw = tagBlocks
|
|
224
|
+
.map((block) => block.raw)
|
|
225
|
+
.filter(Boolean)
|
|
226
|
+
.join('\n\n')
|
|
227
|
+
.trim()
|
|
228
|
+
|
|
229
|
+
if (label && raw) {
|
|
230
|
+
parts[label] = raw
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
return parts
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const createCodeExampleTabs = (source = '') => {
|
|
238
|
+
const trimmedSource = String(source || '').trim()
|
|
239
|
+
const parts = parseVueSfcParts(trimmedSource)
|
|
240
|
+
const tabs = PART_ORDER
|
|
241
|
+
.filter((label) => parts[label])
|
|
242
|
+
.map((label) => ({
|
|
243
|
+
label,
|
|
244
|
+
language: getPartLanguage(label, parts[label]),
|
|
245
|
+
text: parts[label]
|
|
246
|
+
}))
|
|
247
|
+
|
|
248
|
+
if (tabs.length > 1) {
|
|
249
|
+
tabs.push({
|
|
250
|
+
label: 'All',
|
|
251
|
+
language: getPartLanguage('All'),
|
|
252
|
+
text: trimmedSource
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (tabs.length === 0 && trimmedSource) {
|
|
257
|
+
tabs.push({
|
|
258
|
+
label: 'Source',
|
|
259
|
+
language: 'text',
|
|
260
|
+
text: trimmedSource
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return tabs
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const getCodepenUnsupportedReason = (source = '') => {
|
|
268
|
+
const blocks = parseVueSfcBlocks(source)
|
|
269
|
+
|
|
270
|
+
if (blocks.template.length === 0 || !stripSfcTags(blocks.template)) {
|
|
271
|
+
return 'CodePen export requires a Vue SFC template section.'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (blocks.script.length > 1) {
|
|
275
|
+
return 'CodePen export supports a single script section in this version.'
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const scriptBlock = blocks.script[0]
|
|
279
|
+
if (!scriptBlock) {
|
|
280
|
+
return ''
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (hasAttribute(scriptBlock.attrs, 'setup')) {
|
|
284
|
+
return 'CodePen export does not support script setup examples yet.'
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (getLangAttribute(scriptBlock.attrs) === 'ts') {
|
|
288
|
+
return 'CodePen export does not support TypeScript script sections yet.'
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const unsupportedImport = findUnsupportedImport(scriptBlock.content)
|
|
292
|
+
if (unsupportedImport) {
|
|
293
|
+
return `CodePen export does not support local or external imports (${unsupportedImport}).`
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const validationScript = getScriptForValidation(scriptBlock.content)
|
|
297
|
+
if (validationScript && !/\bexport\s+default\b/.test(validationScript)) {
|
|
298
|
+
return 'CodePen export requires an Options API default export in this version.'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return ''
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const canCreateCodepenPayload = (source = '') => {
|
|
305
|
+
return getCodepenUnsupportedReason(source) === ''
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const createCodepenPayload = (source = '', options = {}) => {
|
|
309
|
+
const unsupportedReason = getCodepenUnsupportedReason(source)
|
|
310
|
+
if (unsupportedReason) {
|
|
311
|
+
throw new Error(unsupportedReason)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const blocks = parseVueSfcBlocks(source)
|
|
315
|
+
const html = stripSfcTags(blocks.template)
|
|
316
|
+
const css = stripSfcTags(blocks.style)
|
|
317
|
+
const script = blocks.script[0]?.content || ''
|
|
318
|
+
const js = createCodepenJs(script)
|
|
319
|
+
const resources = createCodepenResources(options.quasarVersion)
|
|
320
|
+
const sourceUrl = String(options.sourceUrl || '').trim()
|
|
321
|
+
const sourceComment = sourceUrl
|
|
322
|
+
? `<!--\nGenerated from:\n${sourceUrl}\n-->\n`
|
|
323
|
+
: ''
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
title: String(options.title || 'Docsector code example').trim() || 'Docsector code example',
|
|
327
|
+
html: `${sourceComment}<div id="q-app" style="min-height: 100vh;">
|
|
328
|
+
${html}
|
|
329
|
+
</div>`,
|
|
330
|
+
head: '',
|
|
331
|
+
html_pre_processor: 'none',
|
|
332
|
+
css,
|
|
333
|
+
css_pre_processor: getStylePreprocessor(blocks.style[0]),
|
|
334
|
+
css_external: resources.cssExternal,
|
|
335
|
+
js,
|
|
336
|
+
js_pre_processor: 'babel',
|
|
337
|
+
js_external: resources.jsExternal,
|
|
338
|
+
editors: createEditorsFlag({ html, css, js })
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export const createCodeExampleGitHubUrl = (filePath = '', config = {}) => {
|
|
343
|
+
const normalizedFilePath = normalizeRepositoryFilePath(filePath)
|
|
344
|
+
|
|
345
|
+
if (!normalizedFilePath) {
|
|
346
|
+
return ''
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const editBaseUrl = String(config.github?.editBaseUrl || '').trim()
|
|
350
|
+
const editMatch = editBaseUrl.match(/^(https:\/\/github\.com\/[^/]+\/[^/]+)\/(?:edit|blob|tree)\/([^/]+)(?:\/.*)?$/)
|
|
351
|
+
|
|
352
|
+
if (editMatch) {
|
|
353
|
+
return `${editMatch[1]}/blob/${editMatch[2]}/${normalizedFilePath}`
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const githubUrl = String(config.links?.github || '').trim().replace(/\/+$/, '')
|
|
357
|
+
|
|
358
|
+
if (/^https:\/\/github\.com\/[^/]+\/[^/]+$/.test(githubUrl)) {
|
|
359
|
+
return `${githubUrl}/blob/main/${normalizedFilePath}`
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return ''
|
|
363
|
+
}
|