@docsector/docsector-reader 4.3.2 → 4.4.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/.env.example +18 -0
- package/README.md +136 -5
- package/bin/docsector.js +36 -1
- package/docsector.config.js +44 -0
- package/package.json +3 -2
- package/public/robots.txt +4 -0
- package/src/ai-assistant/config.js +91 -0
- package/src/ai-assistant/indexing.js +50 -0
- package/src/ai-assistant/layout.js +56 -0
- package/src/ai-assistant/messages.js +41 -0
- package/src/ai-assistant/panel.js +22 -0
- package/src/ai-assistant/server.js +348 -0
- package/src/ai-assistant/session.js +91 -0
- package/src/ai-assistant/stream.js +125 -0
- package/src/components/DAssistantPanel.vue +701 -0
- package/src/components/DPage.vue +114 -4
- package/src/components/DPageAnchor.vue +11 -7
- package/src/components/DPageRichContent.vue +105 -0
- package/src/components/DPageTokens.vue +27 -16
- package/src/components/api-block-model.js +77 -1
- package/src/components/inline-code-copy.js +58 -0
- package/src/components/page-section-tokens.js +6 -4
- package/src/components/quasar-api-extends.json +235 -0
- package/src/composables/useAssistant.js +201 -0
- package/src/i18n/helpers.js +2 -0
- package/src/i18n/languages/en-US.hjson +22 -0
- package/src/i18n/languages/pt-BR.hjson +22 -0
- package/src/layouts/DefaultLayout.vue +22 -0
- package/src/markdown-agent.js +32 -0
- package/src/pages/manual/basic/ai-assistant.overview.en-US.md +69 -0
- package/src/pages/manual/basic/ai-assistant.overview.pt-BR.md +69 -0
- package/src/pages/manual/basic/d-page-anchor.overview.en-US.md +1 -1
- package/src/pages/manual/basic/d-page-anchor.overview.pt-BR.md +1 -1
- package/src/pages/manual.index.js +29 -0
- package/src/quasar.factory.js +166 -33
- package/src/sitemap.js +103 -0
- package/src/store/Layout.js +9 -1
package/src/components/DPage.vue
CHANGED
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
|
|
3
3
|
import { useStore } from 'vuex'
|
|
4
4
|
import { useRoute, useRouter } from 'vue-router'
|
|
5
|
+
import { useI18n } from 'vue-i18n'
|
|
5
6
|
import { useQuasar } from 'quasar'
|
|
7
|
+
import docsectorConfig from 'docsector.config.js'
|
|
6
8
|
|
|
7
9
|
import useNavigator from '../composables/useNavigator'
|
|
8
10
|
import { getReadingProgressState } from '../composables/useReadingProgress'
|
|
11
|
+
import { normalizeAiAssistantConfig } from '../ai-assistant/config'
|
|
12
|
+
import { getAssistantRightRailState } from '../ai-assistant/layout'
|
|
9
13
|
|
|
10
14
|
import DPageAnchor from './DPageAnchor.vue'
|
|
15
|
+
import DAssistantPanel from './DAssistantPanel.vue'
|
|
11
16
|
import DPageMeta from './DPageMeta.vue'
|
|
12
17
|
|
|
13
18
|
const store = useStore()
|
|
14
19
|
const router = useRouter()
|
|
15
20
|
const route = useRoute()
|
|
16
21
|
const $q = useQuasar()
|
|
22
|
+
const { locale } = useI18n()
|
|
17
23
|
|
|
18
24
|
const { scrolling, navigate } = useNavigator()
|
|
19
25
|
|
|
@@ -35,6 +41,8 @@ const pageMinHeight = ref('calc(100vh - 86px)')
|
|
|
35
41
|
const submenuHeight = ref('36px')
|
|
36
42
|
const pageBottomInset = ref('0px')
|
|
37
43
|
const readingProgress = ref(getReadingProgressState())
|
|
44
|
+
const assistantConfig = normalizeAiAssistantConfig(docsectorConfig)
|
|
45
|
+
const assistantEnabled = assistantConfig.enabled === true
|
|
38
46
|
|
|
39
47
|
const getPageScrollContainer = () => {
|
|
40
48
|
return pageScrollArea.value?.$el?.querySelector('.q-scrollarea__container') || null
|
|
@@ -96,6 +104,11 @@ const layoutMeta = computed({
|
|
|
96
104
|
get: () => store.state.layout.meta,
|
|
97
105
|
set: (value) => store.commit('layout/setMeta', value)
|
|
98
106
|
})
|
|
107
|
+
const layoutAssistant = computed({
|
|
108
|
+
get: () => store.state.layout.assistant,
|
|
109
|
+
set: (value) => store.commit('layout/setAssistant', value)
|
|
110
|
+
})
|
|
111
|
+
const assistantWidth = computed(() => store.state.layout.assistantWidth || assistantConfig.ui.drawerWidth)
|
|
99
112
|
const main = computed(() => {
|
|
100
113
|
switch (store.state.page.relative) {
|
|
101
114
|
case '/showcase':
|
|
@@ -109,14 +122,57 @@ const main = computed(() => {
|
|
|
109
122
|
const shouldShowBackToTopControl = computed(() => {
|
|
110
123
|
return props.showBackToTopControl && readingProgress.value.hasOverflow && readingProgress.value.isVisible
|
|
111
124
|
})
|
|
125
|
+
const rightRailState = computed(() => getAssistantRightRailState({
|
|
126
|
+
tocOpen: layoutMeta.value,
|
|
127
|
+
assistantOpen: assistantEnabled && layoutAssistant.value,
|
|
128
|
+
screenWidth: $q.screen.width,
|
|
129
|
+
assistantWidth: assistantWidth.value,
|
|
130
|
+
mobileBreakpoint: 768
|
|
131
|
+
}))
|
|
132
|
+
const rightRailOpen = computed(() => {
|
|
133
|
+
return !rightRailState.value.isMobile && (rightRailState.value.showToc || rightRailState.value.showAssistant)
|
|
134
|
+
})
|
|
135
|
+
const mobileAssistantOpen = computed({
|
|
136
|
+
get: () => assistantEnabled && rightRailState.value.isMobile && layoutAssistant.value,
|
|
137
|
+
set: (value) => { layoutAssistant.value = value }
|
|
138
|
+
})
|
|
112
139
|
const backToTopRightOffset = computed(() => {
|
|
113
|
-
return
|
|
140
|
+
return rightRailState.value.backToTopRightOffset
|
|
141
|
+
})
|
|
142
|
+
const currentMarkdownUrl = computed(() => {
|
|
143
|
+
if (store.state.page.base === 'home') {
|
|
144
|
+
return `${window.location.origin}/Homepage.${locale.value}.md`
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const path = route.path.replace(/\/+$/, '')
|
|
148
|
+
return `${window.location.origin}${path || '/homepage'}.md`
|
|
149
|
+
})
|
|
150
|
+
const currentPageTitle = computed(() => {
|
|
151
|
+
const data = route.matched?.[0]?.meta?.data || route.meta?.data || {}
|
|
152
|
+
return data?.[locale.value]?.title || data?.['*']?.title || data?.['en-US']?.title || ''
|
|
114
153
|
})
|
|
115
154
|
|
|
116
155
|
const toggleSectionsTree = () => {
|
|
117
156
|
layoutMeta.value = !layoutMeta.value
|
|
118
157
|
}
|
|
119
158
|
|
|
159
|
+
const closeAssistant = () => {
|
|
160
|
+
layoutAssistant.value = false
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const onAssistantResize = (value) => {
|
|
164
|
+
const maxWidth = Math.max(320, Math.min(620, Math.round($q.screen.width - 480)))
|
|
165
|
+
const clamped = Math.min(maxWidth, Math.max(320, Math.round(Number(value) || 0)))
|
|
166
|
+
store.commit('layout/setAssistantWidth', clamped)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const setRightRailOpen = (value) => {
|
|
170
|
+
if (!value) {
|
|
171
|
+
layoutMeta.value = false
|
|
172
|
+
layoutAssistant.value = false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
120
176
|
const pActive = (relative) => {
|
|
121
177
|
if (relative === '/' && (store.state.page.relative === relative || store.state.page.relative === '')) {
|
|
122
178
|
return 'active'
|
|
@@ -341,7 +397,9 @@ watch(() => route.fullPath, () => {
|
|
|
341
397
|
/>
|
|
342
398
|
</q-btn-group>
|
|
343
399
|
</q-toolbar-title>
|
|
344
|
-
<q-btn class="d-submenu__toggle" @click="toggleSectionsTree" icon="account_tree"
|
|
400
|
+
<q-btn class="d-submenu__toggle" :class="layoutMeta ? 'active' : null" @click="toggleSectionsTree" icon="account_tree">
|
|
401
|
+
<q-tooltip>{{ $t('page.edit.anchor') }}</q-tooltip>
|
|
402
|
+
</q-btn>
|
|
345
403
|
</div>
|
|
346
404
|
</q-toolbar>
|
|
347
405
|
|
|
@@ -383,9 +441,42 @@ watch(() => route.fullPath, () => {
|
|
|
383
441
|
</q-btn>
|
|
384
442
|
</div>
|
|
385
443
|
|
|
386
|
-
<q-drawer
|
|
387
|
-
|
|
444
|
+
<q-drawer
|
|
445
|
+
v-if="!rightRailState.isMobile"
|
|
446
|
+
elevated
|
|
447
|
+
show-if-above
|
|
448
|
+
side="right"
|
|
449
|
+
:model-value="rightRailOpen"
|
|
450
|
+
:width="rightRailState.totalWidth || 308"
|
|
451
|
+
class="d-right-rail-drawer"
|
|
452
|
+
@update:model-value="setRightRailOpen"
|
|
453
|
+
>
|
|
454
|
+
<div class="d-right-rail">
|
|
455
|
+
<div v-if="rightRailState.showToc" class="d-right-rail__toc" :style="{ width: `${rightRailState.tocWidth}px` }">
|
|
456
|
+
<d-page-anchor id="anchor" />
|
|
457
|
+
</div>
|
|
458
|
+
<q-separator v-if="rightRailState.showToc && rightRailState.showAssistant" vertical />
|
|
459
|
+
<d-assistant-panel
|
|
460
|
+
v-if="rightRailState.showAssistant"
|
|
461
|
+
class="d-right-rail__assistant"
|
|
462
|
+
:style="{ width: `${rightRailState.assistantWidth}px` }"
|
|
463
|
+
:context-title="currentPageTitle"
|
|
464
|
+
:markdown-url="currentMarkdownUrl"
|
|
465
|
+
:width="rightRailState.assistantWidth"
|
|
466
|
+
resizable
|
|
467
|
+
@resize="onAssistantResize"
|
|
468
|
+
@close="closeAssistant"
|
|
469
|
+
/>
|
|
470
|
+
</div>
|
|
388
471
|
</q-drawer>
|
|
472
|
+
|
|
473
|
+
<q-dialog v-if="assistantEnabled" v-model="mobileAssistantOpen" maximized>
|
|
474
|
+
<d-assistant-panel
|
|
475
|
+
:context-title="currentPageTitle"
|
|
476
|
+
:markdown-url="currentMarkdownUrl"
|
|
477
|
+
@close="closeAssistant"
|
|
478
|
+
/>
|
|
479
|
+
</q-dialog>
|
|
389
480
|
</q-page-container>
|
|
390
481
|
</template>
|
|
391
482
|
|
|
@@ -454,10 +545,29 @@ watch(() => route.fullPath, () => {
|
|
|
454
545
|
border-radius: 0
|
|
455
546
|
margin-right: 0
|
|
456
547
|
|
|
548
|
+
&.active
|
|
549
|
+
background: rgba(255, 255, 255, 0.16)
|
|
550
|
+
|
|
457
551
|
.on-left
|
|
458
552
|
margin-right: 5px
|
|
459
553
|
.toolbar-container
|
|
460
554
|
overflow: visible
|
|
555
|
+
|
|
556
|
+
.d-right-rail
|
|
557
|
+
height: 100%
|
|
558
|
+
display: flex
|
|
559
|
+
min-width: 0
|
|
560
|
+
overflow: hidden
|
|
561
|
+
|
|
562
|
+
&__toc
|
|
563
|
+
height: 100%
|
|
564
|
+
min-width: 0
|
|
565
|
+
overflow: auto
|
|
566
|
+
|
|
567
|
+
&__assistant
|
|
568
|
+
height: 100%
|
|
569
|
+
min-width: 320px
|
|
570
|
+
flex: 0 0 auto
|
|
461
571
|
padding: 0
|
|
462
572
|
.q-btn-group
|
|
463
573
|
box-shadow: none
|
|
@@ -15,6 +15,8 @@ const { t } = useI18n()
|
|
|
15
15
|
const { navigate, anchor, selected: navigatorSelected } = useNavigator()
|
|
16
16
|
|
|
17
17
|
const scrolling = ref(null)
|
|
18
|
+
const enableScrollingTimeout = ref(null)
|
|
19
|
+
const initialAnchorTimeout = ref(null)
|
|
18
20
|
|
|
19
21
|
const nodes = computed(() => store.getters['page/nodes'])
|
|
20
22
|
const expanded = computed({
|
|
@@ -77,13 +79,13 @@ watch(selected, () => {
|
|
|
77
79
|
onMounted(() => {
|
|
78
80
|
store.commit('layout/setMetaToggle', true)
|
|
79
81
|
|
|
80
|
-
setTimeout(() => {
|
|
82
|
+
enableScrollingTimeout.value = setTimeout(() => {
|
|
81
83
|
store.commit('page/setScrolling', true)
|
|
82
84
|
}, 1000)
|
|
83
85
|
|
|
84
86
|
const id = route.hash.replace(/^#+/g, '')
|
|
85
87
|
if (id) {
|
|
86
|
-
setTimeout(() => {
|
|
88
|
+
initialAnchorTimeout.value = setTimeout(() => {
|
|
87
89
|
anchor(route.hash)
|
|
88
90
|
}, 500)
|
|
89
91
|
}
|
|
@@ -94,13 +96,15 @@ onBeforeUnmount(() => {
|
|
|
94
96
|
clearTimeout(scrolling.value)
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
if (enableScrollingTimeout.value) {
|
|
100
|
+
clearTimeout(enableScrollingTimeout.value)
|
|
101
|
+
}
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
if (initialAnchorTimeout.value) {
|
|
104
|
+
clearTimeout(initialAnchorTimeout.value)
|
|
105
|
+
}
|
|
102
106
|
|
|
103
|
-
store.commit('
|
|
107
|
+
store.commit('layout/setMetaToggle', false)
|
|
104
108
|
})
|
|
105
109
|
</script>
|
|
106
110
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { defineComponent, h, onMounted, onUpdated, ref } from 'vue'
|
|
3
|
+
import { copyToClipboard, useQuasar } from 'quasar'
|
|
4
|
+
import { useI18n } from 'vue-i18n'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
decorateInlineCodeCopyTargets,
|
|
8
|
+
getInlineCodeCopyTarget
|
|
9
|
+
} from './inline-code-copy'
|
|
10
|
+
|
|
11
|
+
export default defineComponent({
|
|
12
|
+
name: 'DPageRichContent',
|
|
13
|
+
|
|
14
|
+
props: {
|
|
15
|
+
tag: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: 'div'
|
|
18
|
+
},
|
|
19
|
+
html: {
|
|
20
|
+
type: String,
|
|
21
|
+
default: ''
|
|
22
|
+
},
|
|
23
|
+
attrs: {
|
|
24
|
+
type: Object,
|
|
25
|
+
default: null
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
setup(props) {
|
|
30
|
+
const rootRef = ref(null)
|
|
31
|
+
const $q = useQuasar()
|
|
32
|
+
const { t } = useI18n()
|
|
33
|
+
|
|
34
|
+
const syncInlineCodeTargets = () => {
|
|
35
|
+
decorateInlineCodeCopyTargets(rootRef.value, t('page.copyInlineCode'))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const notifyCopied = () => {
|
|
39
|
+
$q.notify({
|
|
40
|
+
message: t('page.copied'),
|
|
41
|
+
color: 'positive',
|
|
42
|
+
textColor: 'white',
|
|
43
|
+
icon: 'check',
|
|
44
|
+
position: 'top',
|
|
45
|
+
timeout: 1200
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const copyInlineCode = (element) => {
|
|
50
|
+
const text = String(element?.textContent || '').trim()
|
|
51
|
+
if (!text) return
|
|
52
|
+
|
|
53
|
+
copyToClipboard(text)
|
|
54
|
+
.then(notifyCopied)
|
|
55
|
+
.catch(() => {})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const handleInlineCodeClick = (event) => {
|
|
59
|
+
const code = getInlineCodeCopyTarget(event.target, rootRef.value)
|
|
60
|
+
if (!code) return
|
|
61
|
+
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
copyInlineCode(code)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const handleInlineCodeKeydown = (event) => {
|
|
67
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const code = getInlineCodeCopyTarget(event.target, rootRef.value)
|
|
72
|
+
if (!code) return
|
|
73
|
+
|
|
74
|
+
event.preventDefault()
|
|
75
|
+
copyInlineCode(code)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onMounted(syncInlineCodeTargets)
|
|
79
|
+
onUpdated(syncInlineCodeTargets)
|
|
80
|
+
|
|
81
|
+
return () => h(props.tag, {
|
|
82
|
+
...(props.attrs || {}),
|
|
83
|
+
ref: rootRef,
|
|
84
|
+
class: ['d-page-rich-content', props.attrs?.class],
|
|
85
|
+
innerHTML: props.html,
|
|
86
|
+
onClick: handleInlineCodeClick,
|
|
87
|
+
onKeydown: handleInlineCodeKeydown
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<style scoped lang="sass">
|
|
94
|
+
.d-page-rich-content
|
|
95
|
+
:deep(.d-copyable-inline-code)
|
|
96
|
+
cursor: copy
|
|
97
|
+
transition: background-color 0.15s ease, outline-color 0.15s ease
|
|
98
|
+
|
|
99
|
+
&:hover
|
|
100
|
+
background-color: rgba(25, 118, 210, 0.14)
|
|
101
|
+
|
|
102
|
+
&:focus-visible
|
|
103
|
+
outline: 2px solid currentColor
|
|
104
|
+
outline-offset: 2px
|
|
105
|
+
</style>
|
|
@@ -23,6 +23,7 @@ import DH3 from './DH3.vue'
|
|
|
23
23
|
import DH4 from './DH4.vue'
|
|
24
24
|
import DH5 from './DH5.vue'
|
|
25
25
|
import DH6 from './DH6.vue'
|
|
26
|
+
import DPageRichContent from './DPageRichContent.vue'
|
|
26
27
|
import DBlockSourceCode from './DBlockSourceCode.vue'
|
|
27
28
|
import DBlockMermaidDiagram from './DBlockMermaidDiagram.vue'
|
|
28
29
|
import DBlockBlockquote from './DBlockBlockquote.vue'
|
|
@@ -71,28 +72,34 @@ import DBlockApi from './DBlockApi.vue'
|
|
|
71
72
|
:value="token.content"
|
|
72
73
|
/>
|
|
73
74
|
|
|
74
|
-
<
|
|
75
|
+
<d-page-rich-content
|
|
75
76
|
v-else-if="token.tag === 'ul'"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
tag="ul"
|
|
78
|
+
:attrs="token.attrs"
|
|
79
|
+
:html="token.content"
|
|
80
|
+
/>
|
|
81
|
+
<d-page-rich-content
|
|
80
82
|
v-else-if="token.tag === 'ol'"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
tag="ol"
|
|
84
|
+
:attrs="token.attrs"
|
|
85
|
+
:html="token.content"
|
|
86
|
+
/>
|
|
84
87
|
|
|
85
88
|
<div
|
|
86
89
|
v-else-if="token.tag === 'table'"
|
|
87
90
|
class="d-table-wrapper"
|
|
88
91
|
>
|
|
89
|
-
<
|
|
92
|
+
<d-page-rich-content
|
|
93
|
+
tag="table"
|
|
94
|
+
:html="token.content"
|
|
95
|
+
/>
|
|
90
96
|
</div>
|
|
91
97
|
|
|
92
|
-
<
|
|
98
|
+
<d-page-rich-content
|
|
93
99
|
v-else-if="token.tag === 'html'"
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
tag="div"
|
|
101
|
+
:html="token.content"
|
|
102
|
+
/>
|
|
96
103
|
|
|
97
104
|
<d-block-image
|
|
98
105
|
v-else-if="token.tag === 'image'"
|
|
@@ -100,16 +107,20 @@ import DBlockApi from './DBlockApi.vue'
|
|
|
100
107
|
:caption-html="token.captionHtml"
|
|
101
108
|
/>
|
|
102
109
|
|
|
103
|
-
<
|
|
110
|
+
<d-page-rich-content
|
|
104
111
|
v-else-if="token.tag === 'p'"
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
tag="p"
|
|
113
|
+
:html="token.content"
|
|
114
|
+
/>
|
|
107
115
|
|
|
108
116
|
<d-block-blockquote
|
|
109
117
|
v-else-if="token.tag === 'blockquote'"
|
|
110
118
|
:message="token.alertType"
|
|
111
119
|
>
|
|
112
|
-
<
|
|
120
|
+
<d-page-rich-content
|
|
121
|
+
tag="div"
|
|
122
|
+
:html="token.content"
|
|
123
|
+
/>
|
|
113
124
|
</d-block-blockquote>
|
|
114
125
|
|
|
115
126
|
<d-block-file
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import quasarApiExtends from './quasar-api-extends.json'
|
|
2
|
+
|
|
1
3
|
const defaultInnerTabName = '__default'
|
|
2
4
|
const fallbackCategoryName = 'general'
|
|
3
5
|
|
|
@@ -9,6 +11,78 @@ const isSupportedTopLevelSection = (value) => {
|
|
|
9
11
|
return typeof value === 'string' || isPlainObject(value)
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
const mergeEntries = (baseValue, overrideValue) => {
|
|
15
|
+
if (!isPlainObject(baseValue) || !isPlainObject(overrideValue)) {
|
|
16
|
+
return overrideValue === undefined ? baseValue : overrideValue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const acc = {
|
|
20
|
+
...baseValue
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Object.entries(overrideValue).forEach(([key, value]) => {
|
|
24
|
+
acc[key] = isPlainObject(value) && isPlainObject(baseValue[key])
|
|
25
|
+
? mergeEntries(baseValue[key], value)
|
|
26
|
+
: value
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return acc
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const resolveExtendsSource = (extendName = '', preferredSection = '') => {
|
|
33
|
+
const normalizedName = String(extendName || '').trim()
|
|
34
|
+
|
|
35
|
+
if (normalizedName === '') {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isPlainObject(quasarApiExtends?.[preferredSection]?.[normalizedName])) {
|
|
40
|
+
return quasarApiExtends[preferredSection][normalizedName]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Object.values(quasarApiExtends || {}).find((sectionEntries) => {
|
|
44
|
+
return isPlainObject(sectionEntries?.[normalizedName])
|
|
45
|
+
})?.[normalizedName]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const resolveExtendedEntries = (value, preferredSection = '', seen = new Set()) => {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map((entry) => resolveExtendedEntries(entry, preferredSection, seen))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!isPlainObject(value)) {
|
|
54
|
+
return value
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const extendName = typeof value.extends === 'string' ? value.extends.trim() : ''
|
|
58
|
+
const seenKey = `${preferredSection}:${extendName}`
|
|
59
|
+
const baseValue = extendName !== '' && !seen.has(seenKey)
|
|
60
|
+
? resolveExtendsSource(extendName, preferredSection)
|
|
61
|
+
: undefined
|
|
62
|
+
const nextSeen = new Set(seen)
|
|
63
|
+
|
|
64
|
+
if (extendName !== '') {
|
|
65
|
+
nextSeen.add(seenKey)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const currentValue = {}
|
|
69
|
+
|
|
70
|
+
Object.entries(value).forEach(([key, entryValue]) => {
|
|
71
|
+
if (key === 'extends') {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
currentValue[key] = resolveExtendedEntries(entryValue, preferredSection, nextSeen)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
return mergeEntries(
|
|
79
|
+
baseValue === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: resolveExtendedEntries(baseValue, preferredSection, nextSeen),
|
|
82
|
+
currentValue
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
12
86
|
const getEntryCategories = (entry = {}) => {
|
|
13
87
|
const raw = String(entry?.category || '').trim()
|
|
14
88
|
|
|
@@ -296,7 +370,9 @@ export const createApiBlockModel = (sourceName = '', apiDocument = {}) => {
|
|
|
296
370
|
const apiSections = {}
|
|
297
371
|
|
|
298
372
|
Object.entries(apiSectionsRaw).forEach(([sectionName, sectionValue]) => {
|
|
299
|
-
const sanitizedValue = pruneInternalEntries(
|
|
373
|
+
const sanitizedValue = pruneInternalEntries(
|
|
374
|
+
resolveExtendedEntries(sectionValue, sectionName)
|
|
375
|
+
)
|
|
300
376
|
|
|
301
377
|
if (sanitizedValue !== undefined && isSupportedTopLevelSection(sanitizedValue)) {
|
|
302
378
|
apiSections[sectionName] = sanitizedValue
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export const INLINE_CODE_COPY_ATTRIBUTE = 'data-docsector-inline-code-copy'
|
|
2
|
+
export const INLINE_CODE_COPY_CLASS = 'd-copyable-inline-code'
|
|
3
|
+
export const INLINE_CODE_COPY_SELECTOR = `[${INLINE_CODE_COPY_ATTRIBUTE}]`
|
|
4
|
+
|
|
5
|
+
export const installInlineCodeCopyRenderer = (markdown) => {
|
|
6
|
+
const originalRender = markdown.renderer.rules.code_inline
|
|
7
|
+
|
|
8
|
+
markdown.renderer.rules.code_inline = (tokens, idx, options, env, self) => {
|
|
9
|
+
const token = tokens[idx]
|
|
10
|
+
|
|
11
|
+
token.attrJoin('class', INLINE_CODE_COPY_CLASS)
|
|
12
|
+
token.attrSet(INLINE_CODE_COPY_ATTRIBUTE, '')
|
|
13
|
+
token.attrSet('role', 'button')
|
|
14
|
+
token.attrSet('tabindex', '0')
|
|
15
|
+
|
|
16
|
+
if (typeof originalRender === 'function') {
|
|
17
|
+
return originalRender(tokens, idx, options, env, self)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `<code${self.renderAttrs(token)}>${markdown.utils.escapeHtml(token.content)}</code>`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return markdown
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const decorateInlineCodeCopyTargets = (container, label = '') => {
|
|
27
|
+
if (!container || typeof container.querySelectorAll !== 'function') {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const normalizedLabel = String(label || '').trim()
|
|
32
|
+
|
|
33
|
+
container.querySelectorAll(INLINE_CODE_COPY_SELECTOR).forEach((element) => {
|
|
34
|
+
if (normalizedLabel) {
|
|
35
|
+
element.setAttribute('title', normalizedLabel)
|
|
36
|
+
element.setAttribute('aria-label', normalizedLabel)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const getInlineCodeCopyTarget = (target, container) => {
|
|
42
|
+
const element = target?.nodeType === 3 ? target.parentElement : target
|
|
43
|
+
|
|
44
|
+
if (!element || typeof element.closest !== 'function') {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const code = element.closest(INLINE_CODE_COPY_SELECTOR)
|
|
49
|
+
if (!code) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (container && typeof container.contains === 'function' && !container.contains(code)) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return code
|
|
58
|
+
}
|
|
@@ -5,6 +5,8 @@ import katex from 'katex'
|
|
|
5
5
|
import taskLists from 'markdown-it-task-lists'
|
|
6
6
|
import texmath from 'markdown-it-texmath'
|
|
7
7
|
|
|
8
|
+
import { installInlineCodeCopyRenderer } from './inline-code-copy'
|
|
9
|
+
|
|
8
10
|
const ALERT_MESSAGE_TYPES = new Set([
|
|
9
11
|
'note',
|
|
10
12
|
'tip',
|
|
@@ -850,9 +852,9 @@ const renderInlineToken = (markdown, markdownInline, element, env) => {
|
|
|
850
852
|
}
|
|
851
853
|
|
|
852
854
|
const createMarkdownBlockParser = () => {
|
|
853
|
-
const markdown = installMathSupport(new MarkdownIt({
|
|
855
|
+
const markdown = installInlineCodeCopyRenderer(installMathSupport(new MarkdownIt({
|
|
854
856
|
html: true
|
|
855
|
-
}))
|
|
857
|
+
})))
|
|
856
858
|
|
|
857
859
|
markdown.use(attrs, {
|
|
858
860
|
leftDelimiter: ':',
|
|
@@ -869,9 +871,9 @@ const createMarkdownBlockParser = () => {
|
|
|
869
871
|
}
|
|
870
872
|
|
|
871
873
|
const createMarkdownInlineParser = () => {
|
|
872
|
-
return installMathSupport(new MarkdownIt({
|
|
874
|
+
return installInlineCodeCopyRenderer(installMathSupport(new MarkdownIt({
|
|
873
875
|
html: true
|
|
874
|
-
}))
|
|
876
|
+
})) )
|
|
875
877
|
}
|
|
876
878
|
|
|
877
879
|
const normalizePageSectionSource = (source = '') => {
|