@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.
Files changed (29) hide show
  1. package/README.md +19 -0
  2. package/bin/docsector.js +1 -1
  3. package/package.json +1 -1
  4. package/public/api/manual/http-client.json +91 -0
  5. package/public/quasar-api/QSeparator.json +39 -0
  6. package/src/components/DBlockApi.vue +634 -0
  7. package/src/components/DBlockApiEntry.js +623 -0
  8. package/src/components/DBlockCodeExample.vue +445 -0
  9. package/src/components/DBlockSourceCode.vue +3 -11
  10. package/src/components/DMenu.vue +70 -25
  11. package/src/components/DPageTokens.vue +22 -0
  12. package/src/components/api-block-model.js +326 -0
  13. package/src/components/code-block-highlighting.js +16 -0
  14. package/src/components/code-example-source.js +363 -0
  15. package/src/components/page-section-tokens.js +141 -1
  16. package/src/components/source-code-lines.js +17 -0
  17. package/src/examples/manual/code-examples/BasicCounter.vue +63 -0
  18. package/src/examples/manual/code-examples/InlineNotice.vue +60 -0
  19. package/src/pages/manual/content/blocks/api-reference.overview.en-US.md +40 -0
  20. package/src/pages/manual/content/blocks/api-reference.overview.pt-BR.md +40 -0
  21. package/src/pages/manual/content/blocks/api-reference.showcase.en-US.md +33 -0
  22. package/src/pages/manual/content/blocks/api-reference.showcase.pt-BR.md +33 -0
  23. package/src/pages/manual/content/blocks/code-examples.overview.en-US.md +56 -0
  24. package/src/pages/manual/content/blocks/code-examples.overview.pt-BR.md +56 -0
  25. package/src/pages/manual/content/blocks/code-examples.showcase.en-US.md +38 -0
  26. package/src/pages/manual/content/blocks/code-examples.showcase.pt-BR.md +38 -0
  27. package/src/pages/manual.index.js +56 -0
  28. package/src/quasar.factory.js +77 -0
  29. 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
+ }