@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,80 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// One block in the preview page, inside its own error boundary. A component
|
|
3
|
+
// that throws (it needs data that the preview has no, for example) shows a
|
|
4
|
+
// small card in place of the block, and the other blocks keep working.
|
|
5
|
+
// `preview: false` in the block definition shows the same card, with a note.
|
|
6
|
+
import type { MDCRoot } from '@nuxtjs/mdc'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
id: string
|
|
10
|
+
/** The parsed body, or `undefined` for a block that the preview does not render. */
|
|
11
|
+
body?: MDCRoot
|
|
12
|
+
/** The name of the block, for the card. */
|
|
13
|
+
label: string
|
|
14
|
+
/** A parse error of this block. */
|
|
15
|
+
error?: string
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
error: [id: string, message: string]
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const { t } = usePlutoI18n()
|
|
23
|
+
|
|
24
|
+
const failed = ref<string | null>(null)
|
|
25
|
+
const value = computed(() => (props.body ? { body: props.body } : undefined))
|
|
26
|
+
|
|
27
|
+
// The render options of the site page (`usePlutoPreviewRender` in the site's PlutoContentPreview).
|
|
28
|
+
const renderOptions = inject(plutoPreviewRenderKey, {})
|
|
29
|
+
const prose = computed(() => toValue(renderOptions).prose ?? true)
|
|
30
|
+
const components = computed(() => toValue(renderOptions).components ?? {})
|
|
31
|
+
|
|
32
|
+
// A new body gets a new chance to render.
|
|
33
|
+
watch(
|
|
34
|
+
() => props.body,
|
|
35
|
+
() => {
|
|
36
|
+
failed.value = null
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
onErrorCaptured((error) => {
|
|
41
|
+
failed.value = error instanceof Error ? error.message : String(error)
|
|
42
|
+
emit('error', props.id, failed.value)
|
|
43
|
+
// Stop the error here, so the other blocks stay on screen.
|
|
44
|
+
return false
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const note = computed(() => {
|
|
48
|
+
if (props.error) {
|
|
49
|
+
return t('docs.preview.parseError')
|
|
50
|
+
}
|
|
51
|
+
if (failed.value) {
|
|
52
|
+
return t('docs.preview.blockError')
|
|
53
|
+
}
|
|
54
|
+
return t('docs.preview.notShown')
|
|
55
|
+
})
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<ContentRenderer
|
|
60
|
+
v-if="value && !failed && !error"
|
|
61
|
+
:value="value"
|
|
62
|
+
:tag="false as unknown as string"
|
|
63
|
+
:prose="prose"
|
|
64
|
+
:components="components"
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
v-else
|
|
69
|
+
:data-pluto-block="id"
|
|
70
|
+
class="my-4 flex items-center gap-3 rounded-lg border border-dashed border-default bg-elevated/50 px-4 py-3 text-sm text-muted"
|
|
71
|
+
>
|
|
72
|
+
<UIcon name="i-lucide-eye-off" class="size-5 shrink-0" />
|
|
73
|
+
<div class="min-w-0">
|
|
74
|
+
<p class="font-medium text-default">
|
|
75
|
+
{{ label }}
|
|
76
|
+
</p>
|
|
77
|
+
<p>{{ note }}</p>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The `mdc` widget of the docs layer. It edits the Markdown (MDC) body
|
|
3
|
+
// of a page in one of two modes:
|
|
4
|
+
//
|
|
5
|
+
// - Visual: the block editor. A live preview in the middle, the block list at
|
|
6
|
+
// the left and the controls of the selected block at the right. It needs the
|
|
7
|
+
// 'canvas' form layout (the docs type uses it) and loads on demand, so
|
|
8
|
+
// Tiptap and the remark parser stay out of the other admin pages.
|
|
9
|
+
// - Markdown: `PlutoMarkdownCodeEditor` of core (CodeMirror: the author sees all the MDC,
|
|
10
|
+
// with the format shown as they type, quick formats and an Insert menu). In a canvas form the live preview shows at
|
|
11
|
+
// its side, and a click on a block in the preview goes to that block in the
|
|
12
|
+
// Markdown (PlutoBlockMarkdownSplit, loaded on demand too).
|
|
13
|
+
//
|
|
14
|
+
// The first mode is the `editor` option of the field (`visual` or `source`). A
|
|
15
|
+
// cookie keeps the last choice of the author. The model value is the Markdown
|
|
16
|
+
// text, as it is. The widget contract: props `field`, `modelValue`, `disabled`,
|
|
17
|
+
// and the `update:modelValue` event. The layer registers it as the widget `mdc`.
|
|
18
|
+
import { defineAsyncComponent } from 'vue'
|
|
19
|
+
import PlutoMdcInsertMenu from './PlutoMdcInsertMenu.vue'
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
field: PlutoTextField
|
|
23
|
+
modelValue: unknown
|
|
24
|
+
disabled?: boolean
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
'update:modelValue': [value: string]
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
// The block editor and the Markdown split load on demand. The skeleton shows while they load.
|
|
32
|
+
const loadingComponent = () =>
|
|
33
|
+
h('div', { class: 'flex h-full flex-col gap-3 p-6' }, [
|
|
34
|
+
h(resolveComponent('USkeleton'), { class: 'h-8 w-1/3' }),
|
|
35
|
+
h(resolveComponent('USkeleton'), { class: 'h-64 w-full' }),
|
|
36
|
+
])
|
|
37
|
+
const PlutoBlockEditor = defineAsyncComponent({
|
|
38
|
+
loader: () => import('./Blocks/PlutoBlockEditor.vue'),
|
|
39
|
+
loadingComponent,
|
|
40
|
+
delay: 120,
|
|
41
|
+
})
|
|
42
|
+
const PlutoBlockMarkdownSplit = defineAsyncComponent({
|
|
43
|
+
loader: () => import('./Blocks/PlutoBlockMarkdownSplit.vue'),
|
|
44
|
+
loadingComponent,
|
|
45
|
+
delay: 120,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { t } = usePlutoI18n()
|
|
49
|
+
const form = usePlutoContentForm()
|
|
50
|
+
|
|
51
|
+
const value = computed(() =>
|
|
52
|
+
typeof props.modelValue === 'string' ? props.modelValue : ''
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// Visual needs the canvas layout: the panels live in the form.
|
|
56
|
+
const canVisual = computed(() => Boolean(form?.canvas.value))
|
|
57
|
+
|
|
58
|
+
type Mode = 'visual' | 'markdown'
|
|
59
|
+
const saved = useCookie<Mode | null>('pluto-mdc-editor-mode', {
|
|
60
|
+
default: () => null,
|
|
61
|
+
maxAge: 60 * 60 * 24 * 365,
|
|
62
|
+
sameSite: 'lax',
|
|
63
|
+
})
|
|
64
|
+
const mode = ref<Mode>(
|
|
65
|
+
canVisual.value
|
|
66
|
+
? (saved.value ?? (props.field.editor === 'source' ? 'markdown' : 'visual'))
|
|
67
|
+
: 'markdown'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
function setMode(next: Mode) {
|
|
71
|
+
mode.value = next
|
|
72
|
+
saved.value = next
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<PlutoBlockEditor
|
|
78
|
+
v-if="mode === 'visual' && canVisual"
|
|
79
|
+
:model-value="value"
|
|
80
|
+
:disabled="disabled"
|
|
81
|
+
:mode="mode"
|
|
82
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
83
|
+
@update:mode="setMode"
|
|
84
|
+
/>
|
|
85
|
+
|
|
86
|
+
<PlutoBlockMarkdownSplit
|
|
87
|
+
v-else-if="canVisual"
|
|
88
|
+
:model-value="value"
|
|
89
|
+
:disabled="disabled"
|
|
90
|
+
:mode="mode"
|
|
91
|
+
:placeholder="field.placeholder ?? t('docs.mdc.placeholder')"
|
|
92
|
+
:label="field.label"
|
|
93
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
94
|
+
@update:mode="setMode"
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
<!-- A form without the canvas layout: the Markdown only, with no preview and no Visual mode. -->
|
|
98
|
+
<UFormField
|
|
99
|
+
v-else
|
|
100
|
+
:label="field.label"
|
|
101
|
+
:description="field.description"
|
|
102
|
+
:required="field.required"
|
|
103
|
+
>
|
|
104
|
+
<!-- The core editor (CodeMirror) loads on demand, so the Visual mode does not load it. -->
|
|
105
|
+
<LazyPlutoMarkdownCodeEditor
|
|
106
|
+
:model-value="value"
|
|
107
|
+
:disabled="disabled"
|
|
108
|
+
:placeholder="field.placeholder ?? t('docs.mdc.placeholder')"
|
|
109
|
+
:aria-label="field.label"
|
|
110
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
111
|
+
>
|
|
112
|
+
<template #toolbar="{ insert }">
|
|
113
|
+
<PlutoMdcInsertMenu :insert="insert" :disabled="disabled" />
|
|
114
|
+
</template>
|
|
115
|
+
</LazyPlutoMarkdownCodeEditor>
|
|
116
|
+
</UFormField>
|
|
117
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The Insert menu of the Markdown editor: one item for each MDC snippet. The
|
|
3
|
+
// `insert` function is the one of the `toolbar` slot of PlutoMarkdownSource.
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
insert: (text: string, options?: { block?: boolean }) => void
|
|
6
|
+
disabled?: boolean
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const { t } = usePlutoI18n()
|
|
10
|
+
|
|
11
|
+
const items = computed(() =>
|
|
12
|
+
MDC_SNIPPETS.map((snippet) => ({
|
|
13
|
+
label: t(snippet.label),
|
|
14
|
+
icon: snippet.icon,
|
|
15
|
+
onSelect: () => props.insert(snippet.text, { block: snippet.block }),
|
|
16
|
+
}))
|
|
17
|
+
)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<UDropdownMenu
|
|
22
|
+
:items="items"
|
|
23
|
+
:content="{ align: 'start' }"
|
|
24
|
+
:disabled="disabled"
|
|
25
|
+
>
|
|
26
|
+
<UButton
|
|
27
|
+
:label="t('docs.mdc.insert')"
|
|
28
|
+
:disabled="disabled"
|
|
29
|
+
color="neutral"
|
|
30
|
+
variant="ghost"
|
|
31
|
+
size="xs"
|
|
32
|
+
icon="i-lucide-plus"
|
|
33
|
+
trailing-icon="i-lucide-chevron-down"
|
|
34
|
+
/>
|
|
35
|
+
</UDropdownMenu>
|
|
36
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// The frame around the blocks in the preview page (`/_pluto/preview`). The
|
|
3
|
+
// default is a generic docs-like page: a header with the title and the
|
|
4
|
+
// description, and the blocks in the body. A site that wants the real layout
|
|
5
|
+
// of its pages overrides this component. Put a component with the same name in
|
|
6
|
+
// the app (`app/components/PlutoContentPreview.vue`) and render the default
|
|
7
|
+
// slot where the blocks go. The slot must hold the blocks directly: no
|
|
8
|
+
// wrapper element, because the prose styles read the first and last child.
|
|
9
|
+
//
|
|
10
|
+
// `frontmatter` is the front matter of the page being edited: the title, the
|
|
11
|
+
// description and the other fields of the form. The preview never fetches.
|
|
12
|
+
defineProps<{
|
|
13
|
+
frontmatter: Record<string, unknown>
|
|
14
|
+
}>()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<UContainer>
|
|
19
|
+
<UPage>
|
|
20
|
+
<UPageHeader
|
|
21
|
+
v-if="typeof frontmatter.title === 'string' && frontmatter.title"
|
|
22
|
+
:title="frontmatter.title"
|
|
23
|
+
:description="
|
|
24
|
+
typeof frontmatter.description === 'string'
|
|
25
|
+
? frontmatter.description
|
|
26
|
+
: undefined
|
|
27
|
+
"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<UPageBody>
|
|
31
|
+
<slot />
|
|
32
|
+
</UPageBody>
|
|
33
|
+
</UPage>
|
|
34
|
+
</UContainer>
|
|
35
|
+
</template>
|