@nubisco/docs-sdk 0.0.0-stage → 0.0.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.
@@ -0,0 +1,95 @@
1
+ import { ref, computed, reactive, inject, watch, onMounted } from 'vue'
2
+ import type { App, Ref, InjectionKey } from 'vue'
3
+
4
+ type TabsSharedState = {
5
+ content?: TabsSharedStateContent
6
+ }
7
+ type TabsSharedStateContent = Record<string, string>
8
+
9
+ const injectionKey: InjectionKey<TabsSharedState> = Symbol(
10
+ 'vitepress:tabSharedState',
11
+ )
12
+ const ls = typeof localStorage !== 'undefined' ? localStorage : null
13
+ const localStorageKey = 'vitepress:tabsSharedState'
14
+
15
+ const getLocalStorageValue = (): TabsSharedStateContent => {
16
+ const rawValue = ls?.getItem(localStorageKey)
17
+ if (rawValue) {
18
+ try {
19
+ return JSON.parse(rawValue)
20
+ } catch {}
21
+ }
22
+ return {}
23
+ }
24
+ const setLocalStorageValue = (v: TabsSharedStateContent) => {
25
+ if (!ls) return
26
+ ls.setItem(localStorageKey, JSON.stringify(v))
27
+ }
28
+
29
+ export const provideTabsSharedState = (app: App) => {
30
+ const state = reactive<TabsSharedState>({})
31
+ watch(
32
+ () => state.content,
33
+ (newStateContent, oldStateContent) => {
34
+ // skip initialize
35
+ if (newStateContent && oldStateContent) {
36
+ setLocalStorageValue(newStateContent)
37
+ }
38
+ },
39
+ { deep: true },
40
+ )
41
+
42
+ app.provide(injectionKey, state)
43
+ }
44
+
45
+ export const useTabsSelectedState = <T extends string>(
46
+ acceptValues: Ref<T[]>,
47
+ sharedStateKey: Ref<string | undefined>,
48
+ ) => {
49
+ const sharedState = inject(injectionKey)
50
+ if (!sharedState) {
51
+ throw new Error('[vitepress-doc-tabs] TabsSharedState should be injected')
52
+ }
53
+
54
+ onMounted(() => {
55
+ if (!sharedState.content) {
56
+ sharedState.content = getLocalStorageValue()
57
+ }
58
+ })
59
+
60
+ const nonSharedState = ref<T | undefined>()
61
+
62
+ const selected = computed({
63
+ get() {
64
+ const key = sharedStateKey.value
65
+ const acceptVals = acceptValues.value
66
+ if (key) {
67
+ const value = sharedState.content?.[key]
68
+ if (value && (acceptVals as string[]).includes(value)) {
69
+ return value as T
70
+ }
71
+ } else {
72
+ const nonSharedStateVal = nonSharedState.value
73
+ if (nonSharedStateVal) {
74
+ return nonSharedStateVal
75
+ }
76
+ }
77
+ return acceptVals[0]
78
+ },
79
+ set(v) {
80
+ const key = sharedStateKey.value
81
+ if (key) {
82
+ if (sharedState.content) {
83
+ sharedState.content[key] = v
84
+ }
85
+ } else {
86
+ nonSharedState.value = v
87
+ }
88
+ },
89
+ })
90
+ const select = (newValue: T) => {
91
+ selected.value = newValue
92
+ }
93
+
94
+ return { selected, select }
95
+ }
@@ -0,0 +1,22 @@
1
+ import { provide, type InjectionKey, type Ref, inject } from 'vue'
2
+
3
+ type TabsSingleState = {
4
+ uid: string
5
+ selected: Ref<string>
6
+ }
7
+
8
+ const injectionKey: InjectionKey<TabsSingleState> = Symbol(
9
+ 'vitepress:tabSingleState',
10
+ )
11
+
12
+ export const provideTabsSingleState = (state: TabsSingleState) => {
13
+ provide(injectionKey, state)
14
+ }
15
+
16
+ export const useTabsSingleState = () => {
17
+ const singleState = inject(injectionKey)
18
+ if (!singleState) {
19
+ throw new Error('[vitepress-doc-tabs] TabsSingleState should be injected')
20
+ }
21
+ return singleState
22
+ }
@@ -0,0 +1 @@
1
+ export * from './components'
@@ -0,0 +1 @@
1
+ export { tabsPlugin as tabsMarkdownPlugin } from './markdownPlugin'
@@ -0,0 +1,47 @@
1
+ // @ts-nocheck: adapted third-party code (see THIRD_PARTY_NOTICES.md), typed loosely upstream.
2
+ import type MarkdownIt from 'markdown-it'
3
+ import type Token from 'markdown-it/lib/token'
4
+ import container from 'markdown-it-container'
5
+ import { ruleBlockTab } from './ruleBlockTab'
6
+
7
+ type Params = {
8
+ shareStateKey: string | undefined
9
+ }
10
+
11
+ const parseTabsParams = (input: string): Params => {
12
+ const match = input.match(/key:(\S+)/)
13
+ return {
14
+ shareStateKey: match?.[1],
15
+ }
16
+ }
17
+
18
+ export const tabsPlugin = (md: MarkdownIt) => {
19
+ md.use(container, 'tabs', {
20
+ render(tokens: Token[], index: number) {
21
+ const token = tokens[index]
22
+ if (token.nesting === 1) {
23
+ const params = parseTabsParams(token.info)
24
+ const shareStateKeyProp = params.shareStateKey
25
+ ? `sharedStateKey="${md.utils.escapeHtml(params.shareStateKey)}"`
26
+ : ''
27
+ return `<ContentTabs ${shareStateKeyProp}>\n`
28
+ } else {
29
+ return `</ContentTabs>\n`
30
+ }
31
+ },
32
+ })
33
+
34
+ md.block.ruler.after('container_tabs', 'tab', ruleBlockTab)
35
+ const renderTab = (tokens: Token[], index: number): string => {
36
+ const token = tokens[index]
37
+ if (token.nesting === 1) {
38
+ const label = token.info
39
+ const labelProp = `label="${md.utils.escapeHtml(label)}"`
40
+ return `<ContentTab ${labelProp}>\n`
41
+ } else {
42
+ return `</ContentTab>\n`
43
+ }
44
+ }
45
+ md.renderer.rules['tab_open'] = renderTab
46
+ md.renderer.rules['tab_close'] = renderTab
47
+ }
@@ -0,0 +1,97 @@
1
+ // @ts-nocheck: adapted third-party code (see THIRD_PARTY_NOTICES.md), typed loosely upstream.
2
+ import type { RuleBlock } from 'markdown-it/lib/parser_block'
3
+
4
+ const tabMarker = '='
5
+ const tabMarkerCode = tabMarker.charCodeAt(0)
6
+ const minTabMarkerLen = 2
7
+
8
+ export const ruleBlockTab: RuleBlock = (state, startLine, endLine, silent) => {
9
+ let pos = state.bMarks[startLine] + state.tShift[startLine]
10
+ const max = state.eMarks[startLine]
11
+
12
+ // @ts-expect-error markdown-it-container uses 'container'
13
+ if (state.parentType !== 'container') {
14
+ return false
15
+ }
16
+
17
+ if (pos + minTabMarkerLen > max) {
18
+ return false
19
+ }
20
+
21
+ const marker = state.src.charCodeAt(pos)
22
+ if (marker !== tabMarkerCode) {
23
+ return false
24
+ }
25
+
26
+ // scan marker length
27
+ const mem = pos
28
+ pos = state.skipChars(pos + 1, marker)
29
+ const tabMarkerLen = pos - mem
30
+
31
+ if (tabMarkerLen < minTabMarkerLen - 1) {
32
+ return false
33
+ }
34
+
35
+ // for validation mode
36
+ if (silent) {
37
+ return true
38
+ }
39
+
40
+ // search for the end of the block
41
+ let nextLine = startLine
42
+ let endStart = mem
43
+ let endPos = pos
44
+
45
+ for (;;) {
46
+ nextLine++
47
+ if (nextLine >= endLine) {
48
+ break // unclosed block is autoclosed
49
+ }
50
+
51
+ endStart = state.bMarks[nextLine] + state.tShift[nextLine]
52
+ const max = state.eMarks[nextLine]
53
+
54
+ if (endStart < max && state.sCount[nextLine] < state.blkIndent) {
55
+ // non-empty line with negative indent should stop the list:
56
+ // - ```
57
+ // test
58
+ break
59
+ }
60
+
61
+ const startCharCode = state.src.charCodeAt(endStart)
62
+ if (startCharCode !== tabMarkerCode) {
63
+ continue
64
+ }
65
+
66
+ const p = state.skipChars(endStart + 1, marker)
67
+ if (p - endStart !== tabMarkerLen) {
68
+ continue
69
+ }
70
+ endPos = p
71
+ break
72
+ }
73
+
74
+ const oldParent = state.parentType
75
+ const oldLineMax = state.lineMax
76
+ // @ts-expect-error use 'tab' for this rule
77
+ state.parentType = 'tab'
78
+ // this will prevent lazy continuations from ever going past our end marker
79
+ state.lineMax = nextLine
80
+
81
+ const startToken = state.push('tab_open', 'div', 1)
82
+ startToken.markup = state.src.slice(mem, pos)
83
+ startToken.block = true
84
+ startToken.info = state.src.slice(pos, max).trimStart()
85
+ startToken.map = [startLine, nextLine - 1]
86
+
87
+ state.md.block.tokenize(state, startLine + 1, nextLine)
88
+
89
+ const endToken = state.push('tab_close', 'div', -1)
90
+ endToken.markup = state.src.slice(endStart, endPos)
91
+ endToken.block = true
92
+
93
+ state.parentType = oldParent
94
+ state.lineMax = oldLineMax
95
+ state.line = nextLine
96
+ return true
97
+ }
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <DefaultLayout>
3
+ <!--
4
+ The site-wide colour-mode and appearance switches.
5
+
6
+ `nav-bar-content-after` is used rather than a bespoke bar so the controls
7
+ sit in the header VitePress already renders, on every page, including
8
+ pages that declare their own `layout:` in frontmatter. A custom layout
9
+ replaces the page content, not the chrome around it.
10
+ -->
11
+ <template #nav-bar-content-after>
12
+ <DocsControls />
13
+ </template>
14
+
15
+ <!--
16
+ The left navigation. VitePress's own group list is hidden in
17
+ custom.css and this one renders from the same sidebar config, with a
18
+ filter box and collapsible groups.
19
+ -->
20
+ <template #sidebar-nav-before>
21
+ <DocsSidebarNav />
22
+ </template>
23
+ </DefaultLayout>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import DefaultTheme from 'vitepress/theme'
28
+ import DocsControls from './components/DocsControls.vue'
29
+ import DocsSidebarNav from './components/DocsSidebarNav.vue'
30
+
31
+ const DefaultLayout = DefaultTheme.Layout
32
+ </script>