slidev-theme-practicum 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +72 -10
- package/components/Slide.vue +81 -26
- package/components/Slot.vue +33 -48
- package/components/Text.vue +68 -2
- package/components/Timeline.vue +2 -23
- package/composables/deck-slot-markup.cjs +249 -0
- package/composables/decor-catalog.mjs +169 -47
- package/composables/layout-authoring.ts +6 -2
- package/composables/layout-markdown.mjs +33 -0
- package/composables/layout-recipes.ts +51 -5
- package/composables/layout-shorthands.ts +251 -81
- package/composables/markdown-to-vnodes.cjs +1 -0
- package/composables/markdown-to-vnodes.mjs +237 -0
- package/composables/slot-placement.ts +1 -1
- package/composables/slot-spacing.ts +66 -0
- package/composables/text-fit-runtime.ts +458 -76
- package/composables/text-fit-scope.ts +13 -0
- package/composables/text-slot-markdown.mjs +193 -0
- package/composables/text-typography.mjs +2 -0
- package/composables/theme-assets.mjs +60 -0
- package/composables/timeline-grid.ts +27 -0
- package/composables/typescript-require.cjs +29 -0
- package/composables/validate-deck-layouts.cjs +277 -0
- package/decor-library.md +2 -0
- package/example.md +132 -10
- package/package.json +25 -11
- package/public/photos/photo-1.webp +0 -0
- package/public/photos/photo-10.webp +0 -0
- package/public/photos/photo-11.webp +0 -0
- package/public/photos/photo-12.webp +0 -0
- package/public/photos/photo-13.webp +0 -0
- package/public/photos/photo-14.webp +0 -0
- package/public/photos/photo-15.webp +0 -0
- package/public/photos/photo-16.webp +0 -0
- package/public/photos/photo-17.webp +0 -0
- package/public/photos/photo-18.webp +0 -0
- package/public/photos/photo-19.webp +0 -0
- package/public/photos/photo-2.webp +0 -0
- package/public/photos/photo-20.webp +0 -0
- package/public/photos/photo-21.webp +0 -0
- package/public/photos/photo-22.webp +0 -0
- package/public/photos/photo-23.webp +0 -0
- package/public/photos/photo-24.webp +0 -0
- package/public/photos/photo-25.webp +0 -0
- package/public/photos/photo-26.webp +0 -0
- package/public/photos/photo-27.webp +0 -0
- package/public/photos/photo-28.webp +0 -0
- package/public/photos/photo-29.webp +0 -0
- package/public/photos/photo-3.webp +0 -0
- package/public/photos/photo-30.webp +0 -0
- package/public/photos/photo-4.webp +0 -0
- package/public/photos/photo-5.webp +0 -0
- package/public/photos/photo-6.webp +0 -0
- package/public/photos/photo-7.webp +0 -0
- package/public/photos/photo-8.webp +0 -0
- package/public/photos/photo-9.webp +0 -0
- package/scripts/browser-smoke-runtime.mjs +428 -0
- package/scripts/browser-smoke.mjs +424 -0
- package/scripts/build-artifact-assets.mjs +55 -0
- package/scripts/check-build-artifact.mjs +243 -0
- package/scripts/check-package.mjs +132 -0
- package/scripts/check-test-architecture.mjs +176 -0
- package/scripts/npm-spawn.mjs +31 -0
- package/scripts/test-architecture-policy.mjs +245 -0
- package/scripts/theme-asset-inventory.mjs +83 -0
- package/scripts/validate-deck.cjs +18 -0
- package/setup/decor-save-middleware.ts +244 -0
- package/setup/vite-plugins.ts +14 -84
- package/skills/slidev-practicum/SKILL.md +25 -0
- package/skills/slidev-practicum/agents/openai.yaml +4 -0
- package/styles/index.css +10 -1
- package/composables/layout-registry.ts +0 -47
- package/env.d.ts +0 -6
- package/public/photos/photo-1.png +0 -0
- package/public/photos/photo-10.png +0 -0
- package/public/photos/photo-2.png +0 -0
- package/public/photos/photo-3.png +0 -0
- package/public/photos/photo-4.png +0 -0
- package/public/photos/photo-5.png +0 -0
- package/public/photos/photo-6.png +0 -0
- package/public/photos/photo-7.png +0 -0
- package/public/photos/photo-8.png +0 -0
- package/public/photos/photo-9.png +0 -0
- package/slidev-theme-practicum.png +0 -0
- package/slidev-theme-practicum.svg +0 -102
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const MarkdownIt = require('markdown-it')
|
|
2
|
+
const { parse } = require('vue/compiler-sfc')
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('@vue/compiler-core').TemplateChildNode} TemplateChildNode */
|
|
5
|
+
/** @typedef {{ role: string, hasCoordinates: boolean, label: string }} ExplicitSlotIntent */
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {(
|
|
8
|
+
* | { kind: 'comment' }
|
|
9
|
+
* | { kind: 'text', content: string }
|
|
10
|
+
* | { kind: 'element', tag: string, props: Record<string, unknown>, children: DeckLiveNode[] }
|
|
11
|
+
* )} DeckLiveNode
|
|
12
|
+
*/
|
|
13
|
+
/** @typedef {{ children: DeckLiveNode[], intents: ExplicitSlotIntent[] }} DeckLiveStructure */
|
|
14
|
+
|
|
15
|
+
const SLOT_CONTRACT_PROPS = new Set(['role', 'area', 'col', 'row'])
|
|
16
|
+
const SLOT_CONTROL_FLOW_DIRECTIVES = new Set(['if', 'else-if', 'else', 'for'])
|
|
17
|
+
const markdown = new MarkdownIt({ html: true })
|
|
18
|
+
|
|
19
|
+
/** @param {unknown} error */
|
|
20
|
+
function formatCompilerError(error) {
|
|
21
|
+
return error instanceof Error ? error.message : String(error)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {import('@vue/compiler-core').DirectiveNode} prop
|
|
26
|
+
* @returns {unknown}
|
|
27
|
+
*/
|
|
28
|
+
function readLiteralBinding(prop) {
|
|
29
|
+
const ast = prop.exp?.ast
|
|
30
|
+
if (!ast)
|
|
31
|
+
return undefined
|
|
32
|
+
|
|
33
|
+
if (ast.type === 'StringLiteral'
|
|
34
|
+
|| ast.type === 'NumericLiteral'
|
|
35
|
+
|| ast.type === 'BooleanLiteral') {
|
|
36
|
+
return ast.value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (ast.type === 'NullLiteral')
|
|
40
|
+
return null
|
|
41
|
+
|
|
42
|
+
if (ast.type === 'TemplateLiteral' && ast.expressions.length === 0)
|
|
43
|
+
return ast.quasis[0]?.value?.cooked ?? ''
|
|
44
|
+
|
|
45
|
+
return undefined
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @param {string} directive */
|
|
49
|
+
function failSlotControlFlow(directive) {
|
|
50
|
+
const error = /** @type {Error & { hint: string }} */ (
|
|
51
|
+
new Error(
|
|
52
|
+
`[DeckValidator] Slot не поддерживает v-${directive} `
|
|
53
|
+
+ 'на самом компоненте или его предке.',
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
error.hint = 'Разверните конечное количество Slot явно, чтобы validator мог проверить role и capacity.'
|
|
57
|
+
throw error
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @param {import('@vue/compiler-core').ElementNode} node */
|
|
61
|
+
function controlFlowDirective(node) {
|
|
62
|
+
return node.props.find(prop =>
|
|
63
|
+
prop.type === 7
|
|
64
|
+
&& SLOT_CONTROL_FLOW_DIRECTIVES.has(prop.name),
|
|
65
|
+
)?.name ?? ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @param {TemplateChildNode} node */
|
|
69
|
+
function containsSlot(node) {
|
|
70
|
+
if (node.type !== 1)
|
|
71
|
+
return false
|
|
72
|
+
|
|
73
|
+
return node.tag === 'Slot' || node.children.some(containsSlot)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {import('@vue/compiler-core').ElementNode} node
|
|
78
|
+
* @returns {Record<string, unknown>}
|
|
79
|
+
*/
|
|
80
|
+
function elementProps(node) {
|
|
81
|
+
/** @type {Record<string, unknown>} */
|
|
82
|
+
const props = {}
|
|
83
|
+
|
|
84
|
+
for (const prop of node.props) {
|
|
85
|
+
if (prop.type === 6) {
|
|
86
|
+
props[prop.name] = prop.value?.content ?? ''
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (prop.name !== 'bind')
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
const key = prop.arg?.type === 4 && prop.arg.isStatic
|
|
94
|
+
? prop.arg.content
|
|
95
|
+
: ''
|
|
96
|
+
|
|
97
|
+
if (node.tag !== 'Slot'
|
|
98
|
+
|| (!SLOT_CONTRACT_PROPS.has(key) && key !== 'label' && key)) {
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!key) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'[DeckValidator] Slot не поддерживает динамический v-bind без статического имени prop.',
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const value = readLiteralBinding(prop)
|
|
109
|
+
if (key === 'label' && value === undefined)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
if (value === undefined) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`[DeckValidator] Slot не поддерживает динамический v-bind:${key}; укажите литеральное значение.`,
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
props[key] = value
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return props
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {TemplateChildNode} node
|
|
126
|
+
* @returns {DeckLiveNode | null}
|
|
127
|
+
*/
|
|
128
|
+
function liveNode(node) {
|
|
129
|
+
if (node.type === 1) {
|
|
130
|
+
const directive = controlFlowDirective(node)
|
|
131
|
+
if (directive && containsSlot(node))
|
|
132
|
+
failSlotControlFlow(directive)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
kind: 'element',
|
|
136
|
+
tag: node.tag,
|
|
137
|
+
props: elementProps(node),
|
|
138
|
+
children: node.children
|
|
139
|
+
.map(liveNode)
|
|
140
|
+
.filter(isLiveNode),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (node.type === 2)
|
|
145
|
+
return { kind: 'text', content: node.content }
|
|
146
|
+
|
|
147
|
+
if (node.type === 3)
|
|
148
|
+
return { kind: 'comment' }
|
|
149
|
+
|
|
150
|
+
if (node.type === 5)
|
|
151
|
+
return { kind: 'text', content: `{{ ${node.content.loc.source} }}` }
|
|
152
|
+
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {DeckLiveNode | null} node
|
|
158
|
+
* @returns {node is DeckLiveNode}
|
|
159
|
+
*/
|
|
160
|
+
function isLiveNode(node) {
|
|
161
|
+
return node !== null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @param {DeckLiveNode} node
|
|
166
|
+
* @returns {ExplicitSlotIntent[]}
|
|
167
|
+
*/
|
|
168
|
+
function collectSlotIntents(node) {
|
|
169
|
+
const nested = node.kind === 'element'
|
|
170
|
+
? node.children.flatMap(collectSlotIntents)
|
|
171
|
+
: []
|
|
172
|
+
|
|
173
|
+
if (node.kind !== 'element' || node.tag !== 'Slot')
|
|
174
|
+
return nested
|
|
175
|
+
|
|
176
|
+
const role = String(node.props.role ?? '').trim()
|
|
177
|
+
const label = String(node.props.label ?? '').trim()
|
|
178
|
+
const hasCoordinates = ['area', 'col', 'row']
|
|
179
|
+
.some(key => Boolean(String(node.props[key] ?? '').trim()))
|
|
180
|
+
|
|
181
|
+
return [{ role, hasCoordinates, label }, ...nested]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* MarkdownIt оборачивает одиночные inline HTML components в `<p>`. Slidev
|
|
186
|
+
* поднимает component-only параграф обратно в default slot, поэтому validator
|
|
187
|
+
* нормализует ту же границу.
|
|
188
|
+
*
|
|
189
|
+
* @param {DeckLiveNode} node
|
|
190
|
+
* @returns {DeckLiveNode[]}
|
|
191
|
+
*/
|
|
192
|
+
function normalizeTopLevelNode(node) {
|
|
193
|
+
if (node.kind !== 'element' || node.tag !== 'p')
|
|
194
|
+
return [node]
|
|
195
|
+
|
|
196
|
+
const meaningful = node.children.filter(child =>
|
|
197
|
+
child.kind !== 'comment'
|
|
198
|
+
&& !(child.kind === 'text' && !child.content.trim()),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if (meaningful.length > 0
|
|
202
|
+
&& meaningful.every(child => child.kind === 'element' && child.tag === 'Slot')) {
|
|
203
|
+
return meaningful
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [node]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Компилирует ровно ту top-level структуру, которую Slidev передаст layout-компоненту:
|
|
211
|
+
* Markdown уже превращён в HTML, а live Vue-компоненты сохранены как элементы.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} content
|
|
214
|
+
* @returns {DeckLiveStructure}
|
|
215
|
+
*/
|
|
216
|
+
function extractDeckLiveStructure(content) {
|
|
217
|
+
const rendered = markdown.render(String(content))
|
|
218
|
+
const source = `<template>${rendered}</template>`
|
|
219
|
+
const { descriptor, errors } = parse(source, { filename: 'deck-slide.vue' })
|
|
220
|
+
|
|
221
|
+
if (errors.length) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`[DeckValidator] Не удалось разобрать live markup слайда: ${errors.map(formatCompilerError).join('; ')}`,
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const children = (descriptor.template?.ast?.children ?? [])
|
|
228
|
+
.map(liveNode)
|
|
229
|
+
.filter(isLiveNode)
|
|
230
|
+
.flatMap(normalizeTopLevelNode)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
children,
|
|
234
|
+
intents: children.flatMap(collectSlotIntents),
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {string} content
|
|
240
|
+
* @returns {ExplicitSlotIntent[]}
|
|
241
|
+
*/
|
|
242
|
+
function extractExplicitSlotIntents(content) {
|
|
243
|
+
return extractDeckLiveStructure(content).intents
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
extractDeckLiveStructure,
|
|
248
|
+
extractExplicitSlotIntents,
|
|
249
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { themeDecorPath, themePhotoPath } from './theme-assets.mjs'
|
|
2
|
+
|
|
1
3
|
const PHOTO_DECOR_SIZES = Object.freeze([
|
|
2
4
|
{ cols: [4, 6], rows: [4, 6], ratio: [0.85, 1.2] },
|
|
3
5
|
{ cols: [5, 12], rows: [3, 8], ratio: [1.15, 4] },
|
|
@@ -12,7 +14,7 @@ const PHOTO_DECOR_SIZES = Object.freeze([
|
|
|
12
14
|
function photoDecor(number, meanings, tones, image = {}) {
|
|
13
15
|
return {
|
|
14
16
|
id: `decor-photo-${String(number).padStart(2, '0')}`,
|
|
15
|
-
src:
|
|
17
|
+
src: themePhotoPath(number),
|
|
16
18
|
sizes: PHOTO_DECOR_SIZES,
|
|
17
19
|
meanings: ['photo', ...meanings],
|
|
18
20
|
tones,
|
|
@@ -28,7 +30,7 @@ function photoDecor(number, meanings, tones, image = {}) {
|
|
|
28
30
|
export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
29
31
|
{
|
|
30
32
|
id: 'decor-cover-band-01',
|
|
31
|
-
src: '
|
|
33
|
+
src: themeDecorPath('decor-1.svg'),
|
|
32
34
|
sizes: [{ cols: [3, 8], rows: [2, 5], ratio: [1, 1.5] }],
|
|
33
35
|
meanings: ['cover', 'hero', 'course'],
|
|
34
36
|
tones: ['green', 'orange', 'blue'],
|
|
@@ -40,7 +42,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
40
42
|
},
|
|
41
43
|
{
|
|
42
44
|
id: 'decor-support-raster-01',
|
|
43
|
-
src: '
|
|
45
|
+
src: themeDecorPath('decor-2.png'),
|
|
44
46
|
sizes: [{ cols: [2, 6], rows: [2, 6], ratio: [0.75, 1.2] }],
|
|
45
47
|
meanings: ['support', 'media'],
|
|
46
48
|
tones: ['orange', 'light', 'warm'],
|
|
@@ -53,7 +55,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
53
55
|
},
|
|
54
56
|
{
|
|
55
57
|
id: 'decor-square-orbit-01',
|
|
56
|
-
src: '
|
|
58
|
+
src: themeDecorPath('decor-3.svg'),
|
|
57
59
|
sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
|
|
58
60
|
meanings: ['qa', 'testing', 'practice'],
|
|
59
61
|
tones: ['blue', 'orange', 'green'],
|
|
@@ -65,7 +67,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
65
67
|
},
|
|
66
68
|
{
|
|
67
69
|
id: 'decor-support-wide-01',
|
|
68
|
-
src: '
|
|
70
|
+
src: themeDecorPath('decor-4.svg'),
|
|
69
71
|
sizes: [{ cols: [4, 8], rows: [2, 8], ratio: [1, 4] }],
|
|
70
72
|
meanings: ['support', 'learning'],
|
|
71
73
|
tones: ['green', 'blue', 'orange'],
|
|
@@ -77,7 +79,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
77
79
|
},
|
|
78
80
|
{
|
|
79
81
|
id: 'decor-support-raster-02',
|
|
80
|
-
src: '
|
|
82
|
+
src: themeDecorPath('decor-5.png'),
|
|
81
83
|
sizes: [
|
|
82
84
|
{ cols: 6, rows: 5 },
|
|
83
85
|
{ cols: 6, rows: 6 },
|
|
@@ -97,7 +99,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
97
99
|
},
|
|
98
100
|
{
|
|
99
101
|
id: 'decor-support-raster-03',
|
|
100
|
-
src: '
|
|
102
|
+
src: themeDecorPath('decor-6.png'),
|
|
101
103
|
sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
|
|
102
104
|
meanings: ['support', 'media'],
|
|
103
105
|
tones: ['orange', 'warm'],
|
|
@@ -109,7 +111,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
109
111
|
},
|
|
110
112
|
{
|
|
111
113
|
id: 'decor-media-square-01',
|
|
112
|
-
src: '
|
|
114
|
+
src: themeDecorPath('decor-7.png'),
|
|
113
115
|
sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
|
|
114
116
|
meanings: ['media', 'practice', 'support'],
|
|
115
117
|
tones: ['blue', 'orange', 'green'],
|
|
@@ -122,7 +124,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
122
124
|
},
|
|
123
125
|
{
|
|
124
126
|
id: 'decor-cover-vertical-01',
|
|
125
|
-
src: '
|
|
127
|
+
src: themeDecorPath('decor-8.svg'),
|
|
126
128
|
sizes: [{ cols: [2, 4], rows: [3, 8], ratio: [0.25, 0.95] }],
|
|
127
129
|
meanings: ['cover', 'hero', 'course'],
|
|
128
130
|
tones: ['orange', 'green', 'blue'],
|
|
@@ -134,7 +136,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
134
136
|
},
|
|
135
137
|
{
|
|
136
138
|
id: 'decor-cover-ribbon-01',
|
|
137
|
-
src: '
|
|
139
|
+
src: themeDecorPath('decor-9.svg'),
|
|
138
140
|
sizes: [{ cols: [4, 8], rows: [2, 4], ratio: [1.4, 4] }],
|
|
139
141
|
meanings: ['cover', 'hero', 'support'],
|
|
140
142
|
tones: ['orange', 'blue', 'green'],
|
|
@@ -146,7 +148,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
146
148
|
},
|
|
147
149
|
{
|
|
148
150
|
id: 'decor-rules-wide-01',
|
|
149
|
-
src: '
|
|
151
|
+
src: themeDecorPath('decor-10.svg'),
|
|
150
152
|
sizes: [{ cols: [4, 12], rows: [3, 6], ratio: [1.4, 4] }],
|
|
151
153
|
meanings: ['rules', 'webinar', 'qa'],
|
|
152
154
|
tones: ['green', 'dark', 'blue', 'orange'],
|
|
@@ -163,7 +165,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
163
165
|
},
|
|
164
166
|
{
|
|
165
167
|
id: 'decor-agenda-tall-01',
|
|
166
|
-
src: '
|
|
168
|
+
src: themeDecorPath('decor-11.svg'),
|
|
167
169
|
sizes: [{ cols: [3, 4], rows: [8, 12], ratio: [0.25, 0.55] }],
|
|
168
170
|
meanings: ['agenda', 'toc'],
|
|
169
171
|
tones: ['blue', 'orange', 'green', 'dark'],
|
|
@@ -176,7 +178,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
176
178
|
},
|
|
177
179
|
{
|
|
178
180
|
id: 'decor-signal-tall-01',
|
|
179
|
-
src: '
|
|
181
|
+
src: themeDecorPath('decor-12.svg'),
|
|
180
182
|
sizes: [{ cols: [2, 4], rows: [4, 12], ratio: [0.2, 0.8] }],
|
|
181
183
|
meanings: ['signal', 'cover', 'hero'],
|
|
182
184
|
tones: ['blue', 'orange', 'green'],
|
|
@@ -188,7 +190,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
188
190
|
},
|
|
189
191
|
{
|
|
190
192
|
id: 'decor-support-compact-01',
|
|
191
|
-
src: '
|
|
193
|
+
src: themeDecorPath('decor-13.svg'),
|
|
192
194
|
sizes: [{ cols: [2, 6], rows: [2, 6], ratio: [0.75, 1.35] }],
|
|
193
195
|
meanings: ['support', 'practice'],
|
|
194
196
|
tones: ['green', 'blue', 'orange'],
|
|
@@ -200,7 +202,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
200
202
|
},
|
|
201
203
|
{
|
|
202
204
|
id: 'decor-panel-vertical-01',
|
|
203
|
-
src: '
|
|
205
|
+
src: themeDecorPath('decor-14.svg'),
|
|
204
206
|
sizes: [{ cols: [3, 5], rows: [4, 7], ratio: [0.45, 0.95] }],
|
|
205
207
|
meanings: ['support', 'rules', 'qa'],
|
|
206
208
|
tones: ['orange', 'blue', 'green'],
|
|
@@ -214,7 +216,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
214
216
|
},
|
|
215
217
|
{
|
|
216
218
|
id: 'decor-support-wide-02',
|
|
217
|
-
src: '
|
|
219
|
+
src: themeDecorPath('decor-15.svg'),
|
|
218
220
|
sizes: [{ cols: [4, 8], rows: [2, 5], ratio: [1.2, 4] }],
|
|
219
221
|
meanings: ['support', 'learning'],
|
|
220
222
|
tones: ['orange', 'green', 'blue'],
|
|
@@ -226,7 +228,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
226
228
|
},
|
|
227
229
|
{
|
|
228
230
|
id: 'decor-support-wide-03',
|
|
229
|
-
src: '
|
|
231
|
+
src: themeDecorPath('decor-16.svg'),
|
|
230
232
|
sizes: [{ cols: [5, 9], rows: [2, 4], ratio: [1.4, 4.5] }],
|
|
231
233
|
meanings: ['support', 'learning'],
|
|
232
234
|
tones: ['blue', 'green', 'orange'],
|
|
@@ -238,7 +240,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
238
240
|
},
|
|
239
241
|
{
|
|
240
242
|
id: 'decor-media-square-02',
|
|
241
|
-
src: '
|
|
243
|
+
src: themeDecorPath('decor-17.png'),
|
|
242
244
|
sizes: [{ cols: [3, 5], rows: [3, 5], ratio: [0.8, 1.25] }],
|
|
243
245
|
meanings: ['media', 'support'],
|
|
244
246
|
tones: ['green', 'orange', 'blue'],
|
|
@@ -251,7 +253,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
251
253
|
},
|
|
252
254
|
{
|
|
253
255
|
id: 'decor-media-square-03',
|
|
254
|
-
src: '
|
|
256
|
+
src: themeDecorPath('decor-18.png'),
|
|
255
257
|
sizes: [{ cols: [3, 6], rows: [3, 6], ratio: [0.75, 1.35] }],
|
|
256
258
|
meanings: ['media', 'support'],
|
|
257
259
|
tones: ['orange', 'blue', 'green'],
|
|
@@ -265,7 +267,7 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
265
267
|
},
|
|
266
268
|
{
|
|
267
269
|
id: 'decor-support-tall-01',
|
|
268
|
-
src: '
|
|
270
|
+
src: themeDecorPath('decor-19.svg'),
|
|
269
271
|
sizes: [{ cols: [3, 5], rows: [3, 9], ratio: [0.35, 1.1] }],
|
|
270
272
|
meanings: ['support', 'practice'],
|
|
271
273
|
tones: ['blue', 'orange', 'green'],
|
|
@@ -277,62 +279,182 @@ export const DEFAULT_DECOR_DATA = Object.freeze([
|
|
|
277
279
|
photoDecor(
|
|
278
280
|
1,
|
|
279
281
|
['portrait', 'workspace', 'home', 'learning'],
|
|
280
|
-
['
|
|
282
|
+
['orange', 'yellow', 'light', 'neutral', 'warm'],
|
|
281
283
|
{ position: '50% 44%', zoom: 1.06 },
|
|
282
284
|
),
|
|
283
285
|
photoDecor(
|
|
284
286
|
2,
|
|
285
|
-
['portrait', 'workspace', 'learning'],
|
|
286
|
-
['blue', 'green', '
|
|
287
|
+
['portrait', 'workspace', 'outdoor', 'learning'],
|
|
288
|
+
['orange', 'blue', 'green', 'yellow', 'warm'],
|
|
287
289
|
{ position: '50% 42%', zoom: 1.06 },
|
|
288
290
|
),
|
|
289
291
|
photoDecor(
|
|
290
292
|
3,
|
|
291
|
-
['portrait', '
|
|
292
|
-
['orange', 'yellow', '
|
|
293
|
-
{ position: '
|
|
293
|
+
['portrait', 'workspace', 'office', 'learning'],
|
|
294
|
+
['orange', 'yellow', 'dark', 'warm', 'neutral'],
|
|
295
|
+
{ position: '50% 44%', zoom: 1.04 },
|
|
294
296
|
),
|
|
295
297
|
photoDecor(
|
|
296
298
|
4,
|
|
297
|
-
['portrait', 'workspace', 'learning'],
|
|
298
|
-
['blue', '
|
|
299
|
-
{ position: '
|
|
299
|
+
['portrait', 'transit', 'workspace', 'learning'],
|
|
300
|
+
['blue', 'green', 'light', 'yellow', 'cool'],
|
|
301
|
+
{ position: '42% 42%', zoom: 1.06 },
|
|
300
302
|
),
|
|
301
303
|
photoDecor(
|
|
302
304
|
5,
|
|
303
|
-
['portrait', 'outdoor', 'learning'],
|
|
304
|
-
['blue', '
|
|
305
|
-
{ position: '
|
|
305
|
+
['portrait', 'workshop', 'outdoor', 'learning'],
|
|
306
|
+
['blue', 'dark', 'cool', 'neutral'],
|
|
307
|
+
{ position: '50% 40%', zoom: 1.08 },
|
|
306
308
|
),
|
|
307
309
|
photoDecor(
|
|
308
310
|
6,
|
|
309
|
-
['portrait', 'home', 'learning'],
|
|
310
|
-
['
|
|
311
|
-
{ position: '50%
|
|
311
|
+
['portrait', 'workspace', 'home', 'learning'],
|
|
312
|
+
['blue', 'dark', 'cool', 'neutral'],
|
|
313
|
+
{ position: '50% 44%', zoom: 1.04 },
|
|
312
314
|
),
|
|
313
315
|
photoDecor(
|
|
314
316
|
7,
|
|
315
|
-
['portrait', '
|
|
316
|
-
['
|
|
317
|
-
{ position: '50%
|
|
317
|
+
['portrait', 'workspace', 'outdoor', 'learning'],
|
|
318
|
+
['green', 'yellow', 'neutral', 'warm'],
|
|
319
|
+
{ position: '50% 45%', zoom: 1.04 },
|
|
318
320
|
),
|
|
319
321
|
photoDecor(
|
|
320
322
|
8,
|
|
321
|
-
['
|
|
322
|
-
['orange', 'yellow', '
|
|
323
|
-
{ position: '50%
|
|
323
|
+
['workspace', 'programming', 'home', 'learning'],
|
|
324
|
+
['orange', 'yellow', 'light', 'neutral', 'warm'],
|
|
325
|
+
{ position: '50% 44%', zoom: 1.02 },
|
|
324
326
|
),
|
|
325
327
|
photoDecor(
|
|
326
328
|
9,
|
|
327
|
-
['portrait', '
|
|
328
|
-
['
|
|
329
|
-
{ position: '50%
|
|
329
|
+
['portrait', 'workspace', 'learning'],
|
|
330
|
+
['orange', 'yellow', 'light', 'neutral', 'warm'],
|
|
331
|
+
{ position: '50% 44%', zoom: 1.05 },
|
|
330
332
|
),
|
|
331
333
|
photoDecor(
|
|
332
334
|
10,
|
|
333
|
-
['portrait', '
|
|
334
|
-
['orange', '
|
|
335
|
-
{ position: '50%
|
|
335
|
+
['portrait', 'outdoor', 'mobile', 'learning'],
|
|
336
|
+
['orange', 'green', 'yellow', 'warm', 'neutral'],
|
|
337
|
+
{ position: '50% 44%', zoom: 1.05 },
|
|
338
|
+
),
|
|
339
|
+
photoDecor(
|
|
340
|
+
11,
|
|
341
|
+
['portrait', 'outdoor', 'mobile', 'learning'],
|
|
342
|
+
['blue', 'dark', 'cool', 'neutral'],
|
|
343
|
+
{ position: '50% 42%', zoom: 1.06 },
|
|
344
|
+
),
|
|
345
|
+
photoDecor(
|
|
346
|
+
12,
|
|
347
|
+
['portrait', 'outdoor', 'mobile', 'learning'],
|
|
348
|
+
['green', 'blue', 'yellow', 'cool', 'neutral'],
|
|
349
|
+
{ position: '50% 44%', zoom: 1.06 },
|
|
350
|
+
),
|
|
351
|
+
photoDecor(
|
|
352
|
+
13,
|
|
353
|
+
['portrait', 'outdoor', 'mobile', 'learning'],
|
|
354
|
+
['blue', 'light', 'cool', 'neutral'],
|
|
355
|
+
{ position: '50% 43%', zoom: 1.06 },
|
|
356
|
+
),
|
|
357
|
+
photoDecor(
|
|
358
|
+
14,
|
|
359
|
+
['portrait', 'outdoor', 'hero'],
|
|
360
|
+
['blue', 'green', 'light', 'cool', 'neutral'],
|
|
361
|
+
{ position: '50% 42%', zoom: 1.05 },
|
|
362
|
+
),
|
|
363
|
+
photoDecor(
|
|
364
|
+
15,
|
|
365
|
+
['portrait', 'outdoor', 'hero'],
|
|
366
|
+
['orange', 'blue', 'warm', 'light'],
|
|
367
|
+
{ position: '50% 47%', zoom: 1.04 },
|
|
368
|
+
),
|
|
369
|
+
photoDecor(
|
|
370
|
+
16,
|
|
371
|
+
['portrait', 'outdoor', 'hero'],
|
|
372
|
+
['blue', 'dark', 'cool'],
|
|
373
|
+
{ position: '50% 45%', zoom: 1.04 },
|
|
374
|
+
),
|
|
375
|
+
photoDecor(
|
|
376
|
+
17,
|
|
377
|
+
['portrait', 'outdoor', 'mobile', 'learning'],
|
|
378
|
+
['blue', 'green', 'cool', 'neutral'],
|
|
379
|
+
{ position: '50% 44%', zoom: 1.04 },
|
|
380
|
+
),
|
|
381
|
+
photoDecor(
|
|
382
|
+
18,
|
|
383
|
+
['portrait', 'outdoor', 'mobile', 'community'],
|
|
384
|
+
['blue', 'green', 'neutral', 'cool'],
|
|
385
|
+
{ position: '50% 45%', zoom: 1.04 },
|
|
386
|
+
),
|
|
387
|
+
photoDecor(
|
|
388
|
+
19,
|
|
389
|
+
['portrait', 'outdoor', 'learning'],
|
|
390
|
+
['blue', 'light', 'cool', 'neutral'],
|
|
391
|
+
{ position: '50% 45%', zoom: 1.06 },
|
|
392
|
+
),
|
|
393
|
+
photoDecor(
|
|
394
|
+
20,
|
|
395
|
+
['group', 'community', 'collaboration'],
|
|
396
|
+
['orange', 'warm', 'light', 'neutral'],
|
|
397
|
+
{ position: '50% 45%', zoom: 1.05 },
|
|
398
|
+
),
|
|
399
|
+
photoDecor(
|
|
400
|
+
21,
|
|
401
|
+
['group', 'workspace', 'collaboration', 'learning'],
|
|
402
|
+
['orange', 'warm', 'light', 'neutral'],
|
|
403
|
+
{ position: '50% 45%', zoom: 1.03 },
|
|
404
|
+
),
|
|
405
|
+
photoDecor(
|
|
406
|
+
22,
|
|
407
|
+
['group', 'community', 'outdoor'],
|
|
408
|
+
['blue', 'green', 'light', 'cool', 'neutral'],
|
|
409
|
+
{ position: '50% 43%', zoom: 1.04 },
|
|
410
|
+
),
|
|
411
|
+
photoDecor(
|
|
412
|
+
23,
|
|
413
|
+
['group', 'community', 'outdoor'],
|
|
414
|
+
['blue', 'light', 'cool', 'neutral'],
|
|
415
|
+
{ position: '50% 46%', zoom: 1.04 },
|
|
416
|
+
),
|
|
417
|
+
photoDecor(
|
|
418
|
+
24,
|
|
419
|
+
['group', 'community', 'collaboration'],
|
|
420
|
+
['green', 'orange', 'warm', 'light', 'neutral'],
|
|
421
|
+
{ position: '50% 45%', zoom: 1.04 },
|
|
422
|
+
),
|
|
423
|
+
photoDecor(
|
|
424
|
+
25,
|
|
425
|
+
['group', 'workshop', 'collaboration', 'learning'],
|
|
426
|
+
['green', 'orange', 'warm', 'light'],
|
|
427
|
+
{ position: '50% 44%', zoom: 1.03 },
|
|
428
|
+
),
|
|
429
|
+
photoDecor(
|
|
430
|
+
26,
|
|
431
|
+
['group', 'community', 'office', 'collaboration'],
|
|
432
|
+
['blue', 'green', 'light', 'neutral', 'cool'],
|
|
433
|
+
{ position: '50% 45%', zoom: 1.04 },
|
|
434
|
+
),
|
|
435
|
+
photoDecor(
|
|
436
|
+
27,
|
|
437
|
+
['group', 'community', 'mobile', 'outdoor'],
|
|
438
|
+
['blue', 'light', 'cool', 'neutral'],
|
|
439
|
+
{ position: '50% 46%', zoom: 1.04 },
|
|
440
|
+
),
|
|
441
|
+
photoDecor(
|
|
442
|
+
28,
|
|
443
|
+
['portrait', 'workspace', 'office', 'learning'],
|
|
444
|
+
['light', 'neutral', 'orange', 'warm'],
|
|
445
|
+
{ position: '50% 45%', zoom: 1.05 },
|
|
446
|
+
),
|
|
447
|
+
photoDecor(
|
|
448
|
+
29,
|
|
449
|
+
['portrait', 'outdoor', 'hero'],
|
|
450
|
+
['orange', 'blue', 'green', 'warm', 'light'],
|
|
451
|
+
{ position: '50% 45%', zoom: 1.06 },
|
|
452
|
+
),
|
|
453
|
+
photoDecor(
|
|
454
|
+
30,
|
|
455
|
+
['portrait', 'outdoor', 'hero'],
|
|
456
|
+
['blue', 'orange', 'cool', 'neutral'],
|
|
457
|
+
{ position: '62% 42%', zoom: 1.08 },
|
|
336
458
|
),
|
|
337
459
|
])
|
|
338
460
|
|
|
@@ -10,12 +10,16 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
resolveLayoutSpec,
|
|
12
12
|
resolveLayoutVariant,
|
|
13
|
+
resolveLayoutRecipe,
|
|
14
|
+
resolveLayoutSlotProps,
|
|
15
|
+
THEME_LAYOUT_ROLES,
|
|
13
16
|
type ThemeLayout,
|
|
14
17
|
type ThemeLayoutHeader,
|
|
15
18
|
type ThemeLayoutRole,
|
|
19
|
+
type ThemeLayoutRecipe,
|
|
20
|
+
type ThemeLayoutSpec,
|
|
16
21
|
type ThemeLayoutSlotSpec,
|
|
17
|
-
} from './layout-
|
|
18
|
-
import { resolveLayoutRecipe, resolveLayoutSlotProps, THEME_LAYOUT_ROLES, type ThemeLayoutRecipe, type ThemeLayoutSpec } from './layout-recipes'
|
|
22
|
+
} from './layout-recipes'
|
|
19
23
|
import { isBlankTextVNode, isCommentVNode } from './layout-vnode'
|
|
20
24
|
import { createSlotRules, type SlotRules } from './slot-rules'
|
|
21
25
|
import {
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
const INLINE_WHITESPACE = /[ \t\r\n\f\v]+/g
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} value
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeInlineText(value) {
|
|
7
|
+
return value.replace(INLINE_WHITESPACE, ' ').trim()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} source
|
|
12
|
+
*/
|
|
13
|
+
export function dedentMarkdownSource(source) {
|
|
14
|
+
const lines = String(source ?? '').replace(/\r\n/g, '\n').split('\n')
|
|
15
|
+
const nonEmpty = lines.filter(line => line.trim())
|
|
16
|
+
|
|
17
|
+
if (!nonEmpty.length)
|
|
18
|
+
return ''
|
|
19
|
+
|
|
20
|
+
const minIndent = Math.min(...nonEmpty.map((line) => {
|
|
21
|
+
const match = line.match(/^(\s*)/)
|
|
22
|
+
return match ? match[1].length : 0
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
if (!minIndent)
|
|
26
|
+
return source.trim()
|
|
27
|
+
|
|
28
|
+
return lines
|
|
29
|
+
.map(line => (line.trim() ? line.slice(minIndent) : line))
|
|
30
|
+
.join('\n')
|
|
31
|
+
.trim()
|
|
32
|
+
}
|
|
33
|
+
|
|
1
34
|
/**
|
|
2
35
|
* @param {unknown} input
|
|
3
36
|
* @returns {{ level: number, text: string } | null}
|