@plutocms/docs 0.0.0-stage → 0.16.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/.gitattributes +1 -0
- package/.prettierignore +5 -0
- package/CHANGELOG.md +186 -0
- package/FEATURES.md +34 -0
- package/README.md +40 -2
- package/app/assets/css/pluto-layer.css +40 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockEditor.vue +165 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockInspector.vue +519 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockMarkdownSplit.vue +161 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockModeSwitch.vue +46 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockOutline.vue +146 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockOutlineList.vue +347 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockPalette.vue +86 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockPreviewFrame.vue +232 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockPropControl.vue +269 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockRawEditor.vue +62 -0
- package/app/components/Pluto/Docs/Blocks/PlutoBlockTextEditor.vue +92 -0
- package/app/components/Pluto/Docs/Blocks/PlutoPreviewBlock.vue +80 -0
- package/app/components/Pluto/Docs/PlutoFieldMdc.vue +117 -0
- package/app/components/Pluto/Docs/PlutoMdcInsertMenu.vue +36 -0
- package/app/components/PlutoContentPreview.vue +35 -0
- package/app/composables/pluto-block-editor.ts +459 -0
- package/app/composables/pluto-content-edit-link.ts +19 -0
- package/app/composables/pluto-content-preview.ts +29 -0
- package/app/composables/pluto-content-runtime.ts +237 -0
- package/app/pages/_pluto/preview.vue +555 -0
- package/app/plugins/pluto-docs.ts +50 -0
- package/i18n/locales/en.json +253 -0
- package/i18n/locales/pt-BR.json +289 -0
- package/modules/pluto-docs/blocks.ts +217 -0
- package/modules/pluto-docs/index.ts +230 -0
- package/modules/pluto-docs/manifest.ts +159 -0
- package/modules/pluto-docs/options.ts +258 -0
- package/nuxt.config.ts +36 -0
- package/package.json +56 -4
- package/server/handlers/navigation.get.ts +7 -0
- package/server/handlers/page.get.ts +7 -0
- package/server/handlers/surround.get.ts +7 -0
- package/server/plugins/pluto-docs.ts +25 -0
- package/server/utils/pluto-docs-runtime.ts +504 -0
- package/shared/types/docs.d.ts +40 -0
- package/shared/types/pluto-blocks.d.ts +10 -0
- package/shared/types/pluto-manifest.d.ts +6 -0
- package/shared/utils/blocks/component-meta.ts +285 -0
- package/shared/utils/blocks/define.ts +15 -0
- package/shared/utils/blocks/editor.ts +357 -0
- package/shared/utils/blocks/history.ts +94 -0
- package/shared/utils/blocks/mdc-props.ts +321 -0
- package/shared/utils/blocks/operations.ts +692 -0
- package/shared/utils/blocks/parse.ts +444 -0
- package/shared/utils/blocks/presets.ts +384 -0
- package/shared/utils/blocks/preview-marks.ts +98 -0
- package/shared/utils/blocks/preview-protocol.ts +195 -0
- package/shared/utils/blocks/registry.ts +295 -0
- package/shared/utils/blocks/serialize.ts +107 -0
- package/shared/utils/blocks/tree.ts +85 -0
- package/shared/utils/blocks/types.ts +195 -0
- package/shared/utils/content-route.ts +40 -0
- package/shared/utils/docs-type.ts +290 -0
- package/shared/utils/mdc-snippets.ts +114 -0
- package/shared/utils/navigation-config.ts +19 -0
- package/shared/utils/runtime-content.ts +403 -0
- package/tailwind.lint.css +10 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// The pure part of the run-time render mode. No Nuxt import, so plain Vitest tests it.
|
|
2
|
+
// It turns the flat list of pages of a type into what Nuxt Content gives at build time:
|
|
3
|
+
// the navigation tree and the previous and next page. The tree follows the algorithm of
|
|
4
|
+
// `generateNavigationTree` in Nuxt Content, so a site can use the same components.
|
|
5
|
+
// See the docs-setup skill.
|
|
6
|
+
|
|
7
|
+
import { contentRoutePath } from './content-route'
|
|
8
|
+
|
|
9
|
+
/** The fields of a page that the navigation needs. They come from the docs type fields. */
|
|
10
|
+
export interface RuntimeNavPage {
|
|
11
|
+
/** The content id, for example `2.core/03.content-model`. */
|
|
12
|
+
id: string
|
|
13
|
+
title: string
|
|
14
|
+
description?: string
|
|
15
|
+
navigationTitle?: string
|
|
16
|
+
navigationIcon?: string
|
|
17
|
+
navigationHidden?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A node of the navigation tree. The shape of `ContentNavigationItem` of Nuxt Content. */
|
|
21
|
+
export interface RuntimeNavItem {
|
|
22
|
+
title: string
|
|
23
|
+
path: string
|
|
24
|
+
stem: string
|
|
25
|
+
children?: RuntimeNavItem[]
|
|
26
|
+
/** `false` for a folder that has no index page. */
|
|
27
|
+
page?: false
|
|
28
|
+
icon?: string
|
|
29
|
+
description?: string
|
|
30
|
+
/** Other keys of a `.navigation.yml` file, as Nuxt Content passes them. */
|
|
31
|
+
[key: string]: unknown
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A neighbor page for the surround. */
|
|
35
|
+
export interface RuntimeSurroundItem {
|
|
36
|
+
title: string
|
|
37
|
+
path: string
|
|
38
|
+
stem: string
|
|
39
|
+
description?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const INDEX_STEM = /(?:[1-9]\d*\.)?index/
|
|
43
|
+
|
|
44
|
+
/** `getting-started` gives `Getting Started`. Nuxt Content names a folder without an index page this way. */
|
|
45
|
+
export function titleFromSegment(segment: string): string {
|
|
46
|
+
return segment
|
|
47
|
+
.split(/[\s-]/g)
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
50
|
+
.join(' ')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether the id is the root page of the source (`index`, or `1.index`). */
|
|
54
|
+
export function isRootIndexId(id: string): boolean {
|
|
55
|
+
return !id.includes('/') && INDEX_STEM.test(id)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The parsed `.navigation.yml` of a folder. Nuxt Content reads `title`, `navigation` (`false` hides
|
|
60
|
+
* the folder, an object overrides the keys) and passes every other key (`icon`, ...) to the folder item.
|
|
61
|
+
*/
|
|
62
|
+
export type RuntimeFolderConfig = Record<string, unknown>
|
|
63
|
+
|
|
64
|
+
/** The keys that Nuxt Content keeps as page fields. They do not go to the folder item as they are. */
|
|
65
|
+
const FOLDER_PAGE_KEYS = new Set([
|
|
66
|
+
'title',
|
|
67
|
+
'description',
|
|
68
|
+
'seo',
|
|
69
|
+
'navigation',
|
|
70
|
+
'path',
|
|
71
|
+
'id',
|
|
72
|
+
'stem',
|
|
73
|
+
'extension',
|
|
74
|
+
'body',
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
78
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The keys that a folder config puts on its navigation item, like `pickConfigNavigationFields`
|
|
83
|
+
* of Nuxt Content: the title, then the other keys, then the `navigation` object.
|
|
84
|
+
*/
|
|
85
|
+
export function folderItemFields(
|
|
86
|
+
config: RuntimeFolderConfig | undefined
|
|
87
|
+
): Record<string, unknown> {
|
|
88
|
+
if (!config) {
|
|
89
|
+
return {}
|
|
90
|
+
}
|
|
91
|
+
const out: Record<string, unknown> = {}
|
|
92
|
+
if (
|
|
93
|
+
typeof config.title === 'string' &&
|
|
94
|
+
config.title !== '' &&
|
|
95
|
+
config.title.toLowerCase() !== 'navigation'
|
|
96
|
+
) {
|
|
97
|
+
out.title = config.title
|
|
98
|
+
}
|
|
99
|
+
for (const [key, value] of Object.entries(config)) {
|
|
100
|
+
if (!FOLDER_PAGE_KEYS.has(key)) {
|
|
101
|
+
out[key] = value
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (isRecord(config.navigation)) {
|
|
105
|
+
Object.assign(out, config.navigation)
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface RuntimeNavOptions {
|
|
111
|
+
/** The `.navigation.yml` configs, by folder id path (`2.core`, `2.core/1.sub`). */
|
|
112
|
+
folders?: Record<string, RuntimeFolderConfig>
|
|
113
|
+
/** The route prefix of the type, for example `/docs`. */
|
|
114
|
+
routePrefix?: string
|
|
115
|
+
/** Keep the root `index` page in the tree. Default false: it is the landing page. */
|
|
116
|
+
includeRoot?: boolean
|
|
117
|
+
/** Copy the page description to each item. The surround needs it. Default false. */
|
|
118
|
+
withDescription?: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Whether the folder config of this folder, or of a parent folder, hides it. */
|
|
122
|
+
function isInHiddenFolder(
|
|
123
|
+
id: string,
|
|
124
|
+
folders: Record<string, RuntimeFolderConfig> | undefined
|
|
125
|
+
): boolean {
|
|
126
|
+
if (!folders) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
const parts = id.split('/').slice(0, -1)
|
|
130
|
+
return parts.some(
|
|
131
|
+
(_part, index) =>
|
|
132
|
+
folders[parts.slice(0, index + 1).join('/')]?.navigation === false
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function visiblePages(
|
|
137
|
+
pages: RuntimeNavPage[],
|
|
138
|
+
options: RuntimeNavOptions
|
|
139
|
+
): RuntimeNavPage[] {
|
|
140
|
+
return pages.filter(
|
|
141
|
+
(page) =>
|
|
142
|
+
page.navigationHidden !== true &&
|
|
143
|
+
(options.includeRoot === true || !isRootIndexId(page.id)) &&
|
|
144
|
+
!isInHiddenFolder(page.id, options.folders)
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Builds the navigation tree from the pages, in the order given (the tree order of ids).
|
|
150
|
+
* A page with `navigationHidden` is left out. The root `index` page is left out unless
|
|
151
|
+
* `includeRoot` is set. A folder takes the title of its index page. A folder with no
|
|
152
|
+
* index page gets a title from its name and `page: false`.
|
|
153
|
+
*/
|
|
154
|
+
export function buildNavigationTree(
|
|
155
|
+
pages: RuntimeNavPage[],
|
|
156
|
+
options: RuntimeNavOptions = {}
|
|
157
|
+
): RuntimeNavItem[] {
|
|
158
|
+
const prefix = options.routePrefix ?? ''
|
|
159
|
+
const nav: RuntimeNavItem[] = []
|
|
160
|
+
|
|
161
|
+
for (const page of visiblePages(pages, options)) {
|
|
162
|
+
const path = contentRoutePath(page.id, prefix)
|
|
163
|
+
const prefixParts = prefix.split('/').filter(Boolean)
|
|
164
|
+
const parts = path.substring(1).split('/').slice(prefixParts.length)
|
|
165
|
+
const idParts = page.id.split('/')
|
|
166
|
+
const isIndex = INDEX_STEM.test(idParts.at(-1) ?? '')
|
|
167
|
+
|
|
168
|
+
const makeItem = (): RuntimeNavItem => ({
|
|
169
|
+
title: page.navigationTitle || page.title,
|
|
170
|
+
path,
|
|
171
|
+
stem: page.id,
|
|
172
|
+
children: [],
|
|
173
|
+
...(page.navigationIcon ? { icon: page.navigationIcon } : {}),
|
|
174
|
+
...(options.withDescription && page.description
|
|
175
|
+
? { description: page.description }
|
|
176
|
+
: {}),
|
|
177
|
+
})
|
|
178
|
+
const item = makeItem()
|
|
179
|
+
if (isIndex && path !== (prefix || '/')) {
|
|
180
|
+
item.children!.push(makeItem())
|
|
181
|
+
// The config of the folder wins over the title and the icon of its index page.
|
|
182
|
+
Object.assign(
|
|
183
|
+
item,
|
|
184
|
+
folderItemFields(options.folders?.[idParts.slice(0, -1).join('/')])
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// A page at the top level of the source.
|
|
189
|
+
if (parts.length <= 1) {
|
|
190
|
+
const placeholder = isIndex
|
|
191
|
+
? nav.find((node) => node.path === item.path && node.page === false)
|
|
192
|
+
: undefined
|
|
193
|
+
if (placeholder) {
|
|
194
|
+
Object.assign(placeholder, {
|
|
195
|
+
page: undefined,
|
|
196
|
+
children: [...(item.children ?? []), ...(placeholder.children ?? [])],
|
|
197
|
+
})
|
|
198
|
+
} else {
|
|
199
|
+
nav.push(item)
|
|
200
|
+
}
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Walk down the folders. A missing folder becomes a placeholder.
|
|
205
|
+
let siblings = nav
|
|
206
|
+
for (const [index, part] of parts.slice(0, -1).entries()) {
|
|
207
|
+
const folderPath = contentRoutePath(
|
|
208
|
+
idParts.slice(0, index + 1).join('/'),
|
|
209
|
+
prefix
|
|
210
|
+
)
|
|
211
|
+
let parent = siblings.find((node) => node.path === folderPath)
|
|
212
|
+
if (!parent) {
|
|
213
|
+
const fields = folderItemFields(
|
|
214
|
+
options.folders?.[idParts.slice(0, index + 1).join('/')]
|
|
215
|
+
)
|
|
216
|
+
parent = {
|
|
217
|
+
...fields,
|
|
218
|
+
title:
|
|
219
|
+
typeof fields.title === 'string' && fields.title
|
|
220
|
+
? fields.title
|
|
221
|
+
: titleFromSegment(part),
|
|
222
|
+
path: folderPath,
|
|
223
|
+
stem: idParts.slice(0, index + 1).join('/'),
|
|
224
|
+
children: [],
|
|
225
|
+
page: false,
|
|
226
|
+
}
|
|
227
|
+
siblings.push(parent)
|
|
228
|
+
}
|
|
229
|
+
siblings = parent.children!
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const existing = siblings.find(
|
|
233
|
+
(node) => node.path === item.path && node.page === false
|
|
234
|
+
)
|
|
235
|
+
if (existing) {
|
|
236
|
+
Object.assign(existing, {
|
|
237
|
+
...item,
|
|
238
|
+
page: undefined,
|
|
239
|
+
children: [...(item.children ?? []), ...(existing.children ?? [])],
|
|
240
|
+
})
|
|
241
|
+
} else {
|
|
242
|
+
siblings.push(item)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return clearTree(nav)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Removes empty `children` and the `page: undefined` keys, like Nuxt Content does. */
|
|
250
|
+
function clearTree(nav: RuntimeNavItem[]): RuntimeNavItem[] {
|
|
251
|
+
for (const item of nav) {
|
|
252
|
+
if (item.page === undefined) {
|
|
253
|
+
delete item.page
|
|
254
|
+
}
|
|
255
|
+
if (item.children?.length) {
|
|
256
|
+
clearTree(item.children)
|
|
257
|
+
} else {
|
|
258
|
+
delete item.children
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return nav
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** The pages of a tree in reading order. A folder with no page is skipped. */
|
|
265
|
+
export function flattenNavigation(nav: RuntimeNavItem[]): RuntimeNavItem[] {
|
|
266
|
+
return nav.flatMap((item) => {
|
|
267
|
+
const children = item.children ? flattenNavigation(item.children) : []
|
|
268
|
+
if (
|
|
269
|
+
item.page === false ||
|
|
270
|
+
children.some((child) => child.path === item.path)
|
|
271
|
+
) {
|
|
272
|
+
return children
|
|
273
|
+
}
|
|
274
|
+
return [{ ...item, children: undefined }, ...children]
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** The previous and the next page of `path`. A missing neighbor is `null`. An unknown path gives two `null`. */
|
|
279
|
+
export function surroundOf(
|
|
280
|
+
nav: RuntimeNavItem[],
|
|
281
|
+
path: string
|
|
282
|
+
): [RuntimeSurroundItem | null, RuntimeSurroundItem | null] {
|
|
283
|
+
const flat = flattenNavigation(nav)
|
|
284
|
+
const index = flat.findIndex((item) => item.path === path)
|
|
285
|
+
const pick = (
|
|
286
|
+
item: RuntimeNavItem | undefined
|
|
287
|
+
): RuntimeSurroundItem | null =>
|
|
288
|
+
item
|
|
289
|
+
? {
|
|
290
|
+
title: item.title,
|
|
291
|
+
path: item.path,
|
|
292
|
+
stem: item.stem,
|
|
293
|
+
...(item.description ? { description: item.description } : {}),
|
|
294
|
+
}
|
|
295
|
+
: null
|
|
296
|
+
if (index === -1) {
|
|
297
|
+
return [null, null]
|
|
298
|
+
}
|
|
299
|
+
return [pick(flat[index - 1]), pick(flat[index + 1])]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The content id of the page for a route path, or `undefined`. Ignores a trailing slash. */
|
|
303
|
+
export function pageIdForRoute(
|
|
304
|
+
ids: string[],
|
|
305
|
+
path: string,
|
|
306
|
+
routePrefix = ''
|
|
307
|
+
): string | undefined {
|
|
308
|
+
const wanted = path.length > 1 ? path.replace(/\/+$/, '') : path
|
|
309
|
+
return ids.find((id) => contentRoutePath(id, routePrefix) === wanted)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** One entry of the store listing: the id and the version (`_version`) of the file. */
|
|
313
|
+
export interface ListedVersion {
|
|
314
|
+
id: string
|
|
315
|
+
version?: string
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** The difference between the build manifest of a type and the store listing. */
|
|
319
|
+
export interface ManifestDiff {
|
|
320
|
+
/** True when nothing differs: the build data is current. */
|
|
321
|
+
same: boolean
|
|
322
|
+
/** Ids in both, with another version. */
|
|
323
|
+
changed: string[]
|
|
324
|
+
/** Ids only in the store. */
|
|
325
|
+
added: string[]
|
|
326
|
+
/** Ids only in the manifest. */
|
|
327
|
+
removed: string[]
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Compares the build manifest (id → version) with the store listing. A store entry without a
|
|
332
|
+
* version counts as changed, because the build data cannot be proven current.
|
|
333
|
+
*/
|
|
334
|
+
export function diffManifest(
|
|
335
|
+
manifest: Record<string, string> | undefined,
|
|
336
|
+
current: ListedVersion[]
|
|
337
|
+
): ManifestDiff {
|
|
338
|
+
const built = manifest ?? {}
|
|
339
|
+
const seen = new Set<string>()
|
|
340
|
+
const changed: string[] = []
|
|
341
|
+
const added: string[] = []
|
|
342
|
+
for (const entry of current) {
|
|
343
|
+
seen.add(entry.id)
|
|
344
|
+
if (!Object.hasOwn(built, entry.id)) {
|
|
345
|
+
added.push(entry.id)
|
|
346
|
+
} else if (!entry.version || built[entry.id] !== entry.version) {
|
|
347
|
+
changed.push(entry.id)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const removed = Object.keys(built).filter((id) => !seen.has(id))
|
|
351
|
+
return {
|
|
352
|
+
same: changed.length === 0 && added.length === 0 && removed.length === 0,
|
|
353
|
+
changed,
|
|
354
|
+
added,
|
|
355
|
+
removed,
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The page fields that Nuxt Content builds itself. Frontmatter with these keys does not go to `meta`. */
|
|
360
|
+
const PAGE_FIELD_KEYS = new Set([
|
|
361
|
+
'id',
|
|
362
|
+
'stem',
|
|
363
|
+
'extension',
|
|
364
|
+
'path',
|
|
365
|
+
'title',
|
|
366
|
+
'description',
|
|
367
|
+
'seo',
|
|
368
|
+
'navigation',
|
|
369
|
+
'body',
|
|
370
|
+
'rawbody',
|
|
371
|
+
'meta',
|
|
372
|
+
])
|
|
373
|
+
|
|
374
|
+
/** Flat keys that the docs type adds to the frontmatter for its form fields. They are not file keys. */
|
|
375
|
+
const FORM_ONLY_KEYS = new Set([
|
|
376
|
+
'navigationTitle',
|
|
377
|
+
'navigationIcon',
|
|
378
|
+
'navigationHidden',
|
|
379
|
+
'seoTitle',
|
|
380
|
+
'seoDescription',
|
|
381
|
+
])
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* The extra keys of a page from its whole frontmatter. Nuxt Content keeps the keys that the
|
|
385
|
+
* collection schema declares at the top level of the page and puts the other keys in `meta`. The
|
|
386
|
+
* server does not know the schema, so a key goes in both places: the page reads it the same way in
|
|
387
|
+
* both modes. A key that is a page field (`title`, `seo`, ...) is left out: the page builds it.
|
|
388
|
+
*/
|
|
389
|
+
export function frontmatterExtras(
|
|
390
|
+
frontmatter: Record<string, unknown> | undefined
|
|
391
|
+
): { top: Record<string, unknown>; meta: Record<string, unknown> } {
|
|
392
|
+
const top: Record<string, unknown> = {}
|
|
393
|
+
for (const [key, value] of Object.entries(frontmatter ?? {})) {
|
|
394
|
+
if (
|
|
395
|
+
!PAGE_FIELD_KEYS.has(key) &&
|
|
396
|
+
!FORM_ONLY_KEYS.has(key) &&
|
|
397
|
+
key !== '__proto__'
|
|
398
|
+
) {
|
|
399
|
+
top[key] = value
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return { top, meta: { ...top } }
|
|
403
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The Tailwind design system for ESLint (better-tailwindcss) and the editor
|
|
3
|
+
* (Tailwind IntelliSense). It is not part of the build: a Pluto layer has no
|
|
4
|
+
* Tailwind entry, the app owns it (see the single-tailwind-build skill in pluto). This file gives the
|
|
5
|
+
* lint the theme of an app: Tailwind, Nuxt UI and the Pluto layer CSS.
|
|
6
|
+
*/
|
|
7
|
+
@import 'tailwindcss';
|
|
8
|
+
@import '@nuxt/ui';
|
|
9
|
+
@import '@plutocms/core/app/assets/css/pluto-layer.css';
|
|
10
|
+
@import './app/assets/css/pluto-layer.css';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"references": [
|
|
3
|
+
{
|
|
4
|
+
"path": "./.nuxt/tsconfig.app.json"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"path": "./.nuxt/tsconfig.server.json"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"path": "./.nuxt/tsconfig.shared.json"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"path": "./.nuxt/tsconfig.node.json"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
// https://nuxt.com/docs/guide/concepts/typescript
|
|
17
|
+
"files": []
|
|
18
|
+
}
|