@plutocms/docs 0.0.0-stage → 0.15.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.
Files changed (61) hide show
  1. package/.gitattributes +1 -0
  2. package/.prettierignore +5 -0
  3. package/CHANGELOG.md +178 -0
  4. package/FEATURES.md +30 -0
  5. package/README.md +39 -2
  6. package/app/assets/css/pluto-layer.css +40 -0
  7. package/app/components/Pluto/Docs/Blocks/PlutoBlockEditor.vue +163 -0
  8. package/app/components/Pluto/Docs/Blocks/PlutoBlockInspector.vue +519 -0
  9. package/app/components/Pluto/Docs/Blocks/PlutoBlockMarkdownSplit.vue +159 -0
  10. package/app/components/Pluto/Docs/Blocks/PlutoBlockModeSwitch.vue +46 -0
  11. package/app/components/Pluto/Docs/Blocks/PlutoBlockOutline.vue +146 -0
  12. package/app/components/Pluto/Docs/Blocks/PlutoBlockOutlineList.vue +347 -0
  13. package/app/components/Pluto/Docs/Blocks/PlutoBlockPalette.vue +85 -0
  14. package/app/components/Pluto/Docs/Blocks/PlutoBlockPreviewFrame.vue +233 -0
  15. package/app/components/Pluto/Docs/Blocks/PlutoBlockPropControl.vue +246 -0
  16. package/app/components/Pluto/Docs/Blocks/PlutoBlockRawEditor.vue +62 -0
  17. package/app/components/Pluto/Docs/Blocks/PlutoBlockTextEditor.vue +92 -0
  18. package/app/components/Pluto/Docs/Blocks/PlutoPreviewBlock.vue +80 -0
  19. package/app/components/Pluto/Docs/PlutoFieldMdc.vue +117 -0
  20. package/app/components/Pluto/Docs/PlutoMdcInsertMenu.vue +36 -0
  21. package/app/components/PlutoContentPreview.vue +35 -0
  22. package/app/composables/pluto-block-editor.ts +443 -0
  23. package/app/composables/pluto-content-edit-link.ts +19 -0
  24. package/app/composables/pluto-content-preview.ts +29 -0
  25. package/app/composables/pluto-content-runtime.ts +237 -0
  26. package/app/pages/_pluto/preview.vue +555 -0
  27. package/app/plugins/pluto-docs.ts +50 -0
  28. package/i18n/locales/en.json +253 -0
  29. package/i18n/locales/pt-BR.json +289 -0
  30. package/modules/pluto-docs/index.ts +123 -0
  31. package/modules/pluto-docs/manifest.ts +159 -0
  32. package/modules/pluto-docs/options.ts +224 -0
  33. package/nuxt.config.ts +36 -0
  34. package/package.json +55 -4
  35. package/server/handlers/navigation.get.ts +7 -0
  36. package/server/handlers/page.get.ts +7 -0
  37. package/server/handlers/surround.get.ts +7 -0
  38. package/server/plugins/pluto-docs.ts +25 -0
  39. package/server/utils/pluto-docs-runtime.ts +504 -0
  40. package/shared/types/docs.d.ts +40 -0
  41. package/shared/types/pluto-manifest.d.ts +6 -0
  42. package/shared/utils/blocks/editor.ts +357 -0
  43. package/shared/utils/blocks/history.ts +94 -0
  44. package/shared/utils/blocks/mdc-props.ts +321 -0
  45. package/shared/utils/blocks/operations.ts +692 -0
  46. package/shared/utils/blocks/parse.ts +444 -0
  47. package/shared/utils/blocks/presets.ts +383 -0
  48. package/shared/utils/blocks/preview-marks.ts +98 -0
  49. package/shared/utils/blocks/preview-protocol.ts +195 -0
  50. package/shared/utils/blocks/registry.ts +163 -0
  51. package/shared/utils/blocks/serialize.ts +107 -0
  52. package/shared/utils/blocks/tree.ts +85 -0
  53. package/shared/utils/blocks/types.ts +165 -0
  54. package/shared/utils/content-route.ts +40 -0
  55. package/shared/utils/docs-type.ts +290 -0
  56. package/shared/utils/mdc-snippets.ts +114 -0
  57. package/shared/utils/navigation-config.ts +19 -0
  58. package/shared/utils/runtime-content.ts +403 -0
  59. package/tailwind.lint.css +10 -0
  60. package/tsconfig.json +18 -0
  61. package/vitest.config.ts +7 -0
package/.gitattributes ADDED
@@ -0,0 +1 @@
1
+ test/fixtures/** -text
@@ -0,0 +1,5 @@
1
+
2
+ # Generated by release-please.
3
+ CHANGELOG.md
4
+ # MDC test fixtures: the tests compare their exact text.
5
+ test/fixtures/
package/CHANGELOG.md ADDED
@@ -0,0 +1,178 @@
1
+ # Changelog
2
+
3
+ ## [0.15.0](https://github.com/plutocms/nuxt-content/compare/v0.14.0...v0.15.0) (2026-10-31)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * the package is now @plutocms/docs. The config key plutoNuxtContent is now plutoDocs. The i18n namespace nuxt-content is now docs. The env var PLUTO_NUXT_CONTENT_PATH is not used in this repo.
9
+
10
+ ### Features
11
+
12
+ * rename the package to @plutocms/docs ([#71](https://github.com/plutocms/nuxt-content/issues/71)) ([b480cfa](https://github.com/plutocms/nuxt-content/commit/b480cfacca3df143ce5844ec26f46679e5ea293b))
13
+
14
+ ## [0.14.0](https://github.com/plutocms/nuxt-content/compare/v0.13.0...v0.14.0) (2026-10-31)
15
+
16
+
17
+ ### Features
18
+
19
+ * the docs types go to the pages nav group, with a navGroup option ([#65](https://github.com/plutocms/nuxt-content/issues/65)) ([8d366e7](https://github.com/plutocms/nuxt-content/commit/8d366e76558d7a9b163aa6c9fca77a9eaa2a9358))
20
+
21
+ ## [0.13.0](https://github.com/plutocms/nuxt-content/compare/v0.12.3...v0.13.0) (2026-10-30)
22
+
23
+
24
+ ### ⚠ BREAKING CHANGES
25
+
26
+ * the layer extends @plutocms/core >=0.38.0, not @plutocms/pluto. PLUTO_PLUTO_PATH is now PLUTO_CORE_PATH.
27
+
28
+ ### Features
29
+
30
+ * extend @plutocms/core ([#59](https://github.com/plutocms/nuxt-content/issues/59)) ([a5ca2b9](https://github.com/plutocms/nuxt-content/commit/a5ca2b9a1b340d6ceb3d00487414d098c9fae683))
31
+
32
+ ## [0.12.3](https://github.com/plutocms/nuxt-content/compare/v0.12.2...v0.12.3) (2026-10-29)
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * **deps:** accept every 0.x version of the [@plutocms](https://github.com/plutocms) packages; shared CI and the update workflow ([#42](https://github.com/plutocms/nuxt-content/issues/42)) ([91b2f58](https://github.com/plutocms/nuxt-content/commit/91b2f58e1f9fa6a47866df6d84e89b59552d9af4))
38
+
39
+ ## [0.12.2](https://github.com/plutocms/nuxt-content/compare/v0.12.1...v0.12.2) (2026-10-29)
40
+
41
+
42
+ ### Bug Fixes
43
+
44
+ * **deps:** use @plutocms/pluto 0.34 ([#40](https://github.com/plutocms/nuxt-content/issues/40)) ([000c153](https://github.com/plutocms/nuxt-content/commit/000c1538d414bc95483a4293f826a747d47820d5))
45
+
46
+ ## [0.12.1](https://github.com/plutocms/nuxt-content/compare/v0.12.0...v0.12.1) (2026-10-29)
47
+
48
+
49
+ ### Bug Fixes
50
+
51
+ * **deps:** use @plutocms/pluto 0.33 ([#38](https://github.com/plutocms/nuxt-content/issues/38)) ([ca54a9f](https://github.com/plutocms/nuxt-content/commit/ca54a9fb8b2bfa8ce5349fbcbd488941413064d6))
52
+
53
+ ## [0.12.0](https://github.com/plutocms/nuxt-content/compare/v0.11.0...v0.12.0) (2026-10-29)
54
+
55
+
56
+ ### Features
57
+
58
+ * **mdc:** use the Markdown code editor of core ([#36](https://github.com/plutocms/nuxt-content/issues/36)) ([b11c355](https://github.com/plutocms/nuxt-content/commit/b11c3550c165373a7080bf4583ceddd4b104d2ae))
59
+
60
+ ## [0.11.0](https://github.com/plutocms/nuxt-content/compare/v0.10.0...v0.11.0) (2026-10-28)
61
+
62
+
63
+ ### Features
64
+
65
+ * **editor:** block list layout, scroll area, preview loading, Markdown footer, canonical classes and Prettier ([#33](https://github.com/plutocms/nuxt-content/issues/33)) ([6743ca9](https://github.com/plutocms/nuxt-content/commit/6743ca98f044641fc4ac8074270efb1b6ce33e75))
66
+
67
+ ## [0.10.0](https://github.com/plutocms/nuxt-content/compare/v0.9.0...v0.10.0) (2026-10-28)
68
+
69
+
70
+ ### Features
71
+
72
+ * **editor:** show the block row controls on hover ([#30](https://github.com/plutocms/nuxt-content/issues/30)) ([5a2faf5](https://github.com/plutocms/nuxt-content/commit/5a2faf56461cc944670931eafea1cbac705623a4))
73
+
74
+
75
+ ### Bug Fixes
76
+
77
+ * **deps:** use @plutocms/pluto 0.29 ([#32](https://github.com/plutocms/nuxt-content/issues/32)) ([423c6db](https://github.com/plutocms/nuxt-content/commit/423c6dba402f67ad8306651e377c5697041d9ddb))
78
+
79
+ ## [0.9.0](https://github.com/plutocms/nuxt-content/compare/v0.8.0...v0.9.0) (2026-10-27)
80
+
81
+
82
+ ### ⚠ BREAKING CHANGES
83
+
84
+ * the layer reads `plutoGithub`, not `plutoCms`. An app that sets `plutoCms` options sets `plutoGithub` now.
85
+
86
+ ### Features
87
+
88
+ * use the GitHub names of pluto 0.28 ([#28](https://github.com/plutocms/nuxt-content/issues/28)) ([f3f2ee7](https://github.com/plutocms/nuxt-content/commit/f3f2ee7dc08a0f5872eab82fb02e7271cded822f))
89
+
90
+ ## [0.8.0](https://github.com/plutocms/nuxt-content/compare/v0.7.0...v0.8.0) (2026-10-27)
91
+
92
+
93
+ ### ⚠ BREAKING CHANGES
94
+
95
+ * `/api/_pluto/nuxt-content/:type/{page,navigation,surround}` is now `/api/pluto/v1/nuxt-content/:type/{page,navigation,surround}`, and the answer is `{ data }`. A site that uses the composables needs no change.
96
+
97
+ ### Features
98
+
99
+ * serve the layer routes on the public API of Pluto, and make the docs pages public ([#26](https://github.com/plutocms/nuxt-content/issues/26)) ([9c1ddd9](https://github.com/plutocms/nuxt-content/commit/9c1ddd982d7c24ff22e70337a345a76ddeb63131))
100
+
101
+ ## [0.7.0](https://github.com/plutocms/nuxt-content/compare/v0.6.0...v0.7.0) (2026-10-27)
102
+
103
+
104
+ ### ⚠ BREAKING CHANGES
105
+
106
+ * the default render mode is now 'hybrid'. Content pages must not be prerendered in hybrid or runtime mode. Set `render: 'build'` to keep a fully static site. Needs @plutocms/pluto with `markPublicRead` and `readFile`.
107
+
108
+ ### Features
109
+
110
+ * render the pages in hybrid mode by default, with build and runtime modes ([#24](https://github.com/plutocms/nuxt-content/issues/24)) ([44efefe](https://github.com/plutocms/nuxt-content/commit/44efefec130c9a48744ec79eba0f454399f5560b))
111
+
112
+ ## [0.6.0](https://github.com/plutocms/nuxt-content/compare/v0.5.0...v0.6.0) (2026-10-27)
113
+
114
+
115
+ ### Features
116
+
117
+ * use the admin bar buttons of core, and shorten the README ([#22](https://github.com/plutocms/nuxt-content/issues/22)) ([568f54b](https://github.com/plutocms/nuxt-content/commit/568f54b3f0a73e0346c44f2aec44e6c34c0320fe))
118
+
119
+ ## [0.5.0](https://github.com/plutocms/nuxt-content/compare/v0.4.1...v0.5.0) (2026-10-26)
120
+
121
+
122
+ ### Features
123
+
124
+ * **deps:** use @plutocms/pluto 0.24 ([#20](https://github.com/plutocms/nuxt-content/issues/20)) ([acb7195](https://github.com/plutocms/nuxt-content/commit/acb71955bdf7f2fb18d545e6281aaef50cc9fc23))
125
+
126
+ ## [0.4.1](https://github.com/plutocms/nuxt-content/compare/v0.4.0...v0.4.1) (2026-10-26)
127
+
128
+
129
+ ### Bug Fixes
130
+
131
+ * **editor:** type the editor for the sites that type-check the layer ([#18](https://github.com/plutocms/nuxt-content/issues/18)) ([c7b08bb](https://github.com/plutocms/nuxt-content/commit/c7b08bbb92e7417487303d599ba42820c0d07059))
132
+
133
+ ## [0.4.0](https://github.com/plutocms/nuxt-content/compare/v0.3.2...v0.4.0) (2026-10-26)
134
+
135
+
136
+ ### Features
137
+
138
+ * add the visual block editor and a hybrid Markdown mode ([#16](https://github.com/plutocms/nuxt-content/issues/16)) ([22c8c5e](https://github.com/plutocms/nuxt-content/commit/22c8c5e7f7f745ced4ef01d85160b08aa5749d7a))
139
+
140
+ ## [0.3.2](https://github.com/plutocms/nuxt-content/compare/v0.3.1...v0.3.2) (2026-10-25)
141
+
142
+
143
+ ### Bug Fixes
144
+
145
+ * **i18n:** use the nuxt-content namespace, so core translates the labels ([#14](https://github.com/plutocms/nuxt-content/issues/14)) ([4c44e3c](https://github.com/plutocms/nuxt-content/commit/4c44e3ce43d8a7d47e9ce0cd20477c41a676ba39))
146
+
147
+ ## [0.3.1](https://github.com/plutocms/nuxt-content/compare/v0.3.0...v0.3.1) (2026-10-25)
148
+
149
+
150
+ ### Bug Fixes
151
+
152
+ * **i18n:** translate the doc type in the admin ([#12](https://github.com/plutocms/nuxt-content/issues/12)) ([2ff7034](https://github.com/plutocms/nuxt-content/commit/2ff70343257ecf912be2e39a94a73a80a9c8aeee))
153
+
154
+ ## [0.3.0](https://github.com/plutocms/nuxt-content/compare/v0.2.0...v0.3.0) (2026-10-25)
155
+
156
+
157
+ ### Features
158
+
159
+ * add New Page and Edit Page to the admin bar ([#9](https://github.com/plutocms/nuxt-content/issues/9)) ([c5fe6ad](https://github.com/plutocms/nuxt-content/commit/c5fe6ad5ebc4a868aacfe8013647b325fc3898a7))
160
+
161
+ ## [0.2.0](https://github.com/plutocms/nuxt-content/compare/v0.1.0...v0.2.0) (2026-10-25)
162
+
163
+
164
+ ### Features
165
+
166
+ * **deps:** use @plutocms/pluto 0.20 and default to no post type ([#6](https://github.com/plutocms/nuxt-content/issues/6)) ([b4fb0f0](https://github.com/plutocms/nuxt-content/commit/b4fb0f044da3411162047503e9ac8ab23df48001))
167
+
168
+ ## 0.1.0 (2026-10-25)
169
+
170
+
171
+ ### Features
172
+
173
+ * add the Nuxt Content layer with a docs preset ([90db635](https://github.com/plutocms/nuxt-content/commit/90db63534eead5ed108d01304eae3092072a44dc))
174
+
175
+
176
+ ### Miscellaneous Chores
177
+
178
+ * start the release line at 0.1.0 ([36cfc78](https://github.com/plutocms/nuxt-content/commit/36cfc7892ae904e2bc7014da643c1fd3e5b18d1a))
package/FEATURES.md ADDED
@@ -0,0 +1,30 @@
1
+ # Features
2
+
3
+ This file lists every implemented feature of this project. Each entry links to a skill file
4
+ with full detail, so this file stays short.
5
+
6
+ See the workspace-level `CLAUDE.md` (one directory up) for the delegation, writing, and git
7
+ policies shared by every project here.
8
+
9
+ - Docs setup: the `plutoDocs` module, its options, and the plugins that register
10
+ the types. See @.claude/skills/docs-setup/SKILL.md.
11
+ - Docs preset: the `docs` page type, the frontmatter hooks, and tree order. See
12
+ @.claude/skills/docs-preset/SKILL.md.
13
+ - MDC field: the `mdc` field widget. Visual mode (the block editor) and Markdown mode (with the
14
+ Insert menu of snippets). See @.claude/skills/mdc-field/SKILL.md.
15
+ - Render modes: `plutoDocs.render` is `'hybrid'` (default: the build data, and the repository for the
16
+ pages that changed after the build), `'build'` or `'runtime'`. A build manifest, server routes, `.navigation.yml`
17
+ and frontmatter parity, and the composables `usePlutoContentPage`, `usePlutoContentNavigation` and
18
+ `usePlutoContentSurround` that hide the mode. The layer routes are on the public API
19
+ (`/api/pluto/v1/docs/:type/{page,navigation,surround}`, `{ data }` answers), and the docs type is
20
+ public on core's `/api/pluto/v1/content/<type>`. See @.claude/skills/docs-setup/SKILL.md.
21
+ - Page links: the View link of the form (`previewUrl`), the "New Page" and "Edit Page" buttons of the admin bar (core draws them from `adminBar`),
22
+ `contentRoutePath`, `contentEditPath`, and `usePlutoContentEditLink`. See
23
+ @.claude/skills/page-links/SKILL.md.
24
+ - Admin bar offset: CSS that keeps a Nuxt UI header under the Pluto admin bar. See
25
+ @.claude/skills/admin-bar-offset/SKILL.md.
26
+ - Block model: the pure model of the visual block editor (parse, serialize, edit, history,
27
+ presets, registry, preview protocol). The text of an unedited block stays byte for byte. See
28
+ @.claude/skills/block-model/SKILL.md.
29
+ - Block editor: the visual editor for the page body. A live preview, the block list, the palette, and
30
+ the controls of the selected block, with no raw MDC. See @.claude/skills/block-editor/SKILL.md.
package/README.md CHANGED
@@ -1,3 +1,40 @@
1
- # Temporary Holding Version
1
+ # Docs layer for Pluto
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Edit the Markdown pages of a [Nuxt Content](https://content.nuxt.com) site in the Pluto admin,
4
+ with a visual block editor and a live preview. The pages stay Markdown files in your GitHub
5
+ repository, and each save is a commit.
6
+
7
+ **Docs:** [Docs layer](https://pluto.vulmix.dev/docs/layers/docs) ·
8
+ [Edit pages visually](https://pluto.vulmix.dev/docs/guides/edit-pages-visually)
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @plutocms/docs
14
+ ```
15
+
16
+ ```ts
17
+ // nuxt.config.ts
18
+ export default defineNuxtConfig({
19
+ extends: [['@plutocms/docs', { install: true }]],
20
+
21
+ plutoDocs: {
22
+ types: [{ preset: 'docs', name: 'doc', source: '' }],
23
+ },
24
+ })
25
+ ```
26
+
27
+ The layer brings `@plutocms/core`. It needs Node 22.5 or newer. The docs show the rest of the
28
+ setup: the collections, the CSS entry, `PlutoRoot`, and the GitHub App.
29
+
30
+ ## What you get
31
+
32
+ - A `docs` page type for nested, numbered Markdown pages.
33
+ - A visual block editor with a live preview, and a Markdown mode.
34
+ - Hybrid render mode (default): a save shows on the site at once, with no wait for a build. Build and runtime modes are options.
35
+ - "New Page" and "Edit Page" in the admin bar, and an "Edit in admin" link for the public page.
36
+ - The pages on the public API of Pluto (`/api/pluto/v1/content/<type>`), for a site in any stack.
37
+
38
+ ## License
39
+
40
+ MIT
@@ -0,0 +1,40 @@
1
+ /*
2
+ * The docs layer's CSS. It is not a Tailwind entry.
3
+ *
4
+ * The app's CSS entry owns the one Tailwind build of the app. It imports this
5
+ * file through `#build/pluto.css`. This file imports no Tailwind, adds no
6
+ * @theme and no @source. See the single-tailwind-build skill in pluto.
7
+ *
8
+ * It offsets a Nuxt UI site (a docs site) under the Pluto admin bar. The bar
9
+ * is `position: fixed`, 40px high, and shows on public pages for signed-in
10
+ * staff. Pluto marks <html> with `data-pluto-admin-bar` while the bar shows.
11
+ *
12
+ * The site sets its header height in `--pluto-site-header-height` (default
13
+ * 4rem). Without the bar nothing changes. With the bar:
14
+ * - the page moves down by the bar height (`body` padding), so no content is
15
+ * under the bar;
16
+ * - `--ui-header-height` grows by the bar height. The Nuxt UI parts that stick
17
+ * below the header read it: the page aside, the table of contents, the
18
+ * mobile menu. They then stick below the bar and the header;
19
+ * - `UHeader` (class `pluto-header-offset`) keeps its own height and sticks
20
+ * below the bar.
21
+ * The admin pages (`data-pluto-admin` on <html>) never use these rules.
22
+ */
23
+ :root:not([data-pluto-admin]) {
24
+ --pluto-site-header-height: 4rem;
25
+ }
26
+
27
+ :root[data-pluto-admin-bar]:not([data-pluto-admin]) {
28
+ --ui-header-height: calc(
29
+ var(--pluto-site-header-height) + var(--pluto-admin-bar-height)
30
+ );
31
+ }
32
+
33
+ :root[data-pluto-admin-bar]:not([data-pluto-admin]) body {
34
+ padding-top: var(--pluto-admin-bar-height);
35
+ }
36
+
37
+ :root[data-pluto-admin-bar]:not([data-pluto-admin]) .pluto-header-offset {
38
+ top: var(--pluto-admin-bar-height);
39
+ height: var(--pluto-site-header-height);
40
+ }
@@ -0,0 +1,163 @@
1
+ <script setup lang="ts">
2
+ // The visual block editor: the canvas of the 'canvas' form layout. The canvas
3
+ // shows the live preview of the page. The block list goes in the left panel,
4
+ // and the controls of the selected block go in the right panel (a tab of the
5
+ // form's aside). The widget keeps all the state, so the panels are slots of
6
+ // this one component tree. The value is the Markdown text of the page body. A
7
+ // block that no edit touched keeps its bytes, so opening and saving a page
8
+ // with no edit changes nothing. See the block-editor skill.
9
+ import PlutoBlockInspector from './PlutoBlockInspector.vue'
10
+ import PlutoBlockOutline from './PlutoBlockOutline.vue'
11
+ import PlutoBlockPalette from './PlutoBlockPalette.vue'
12
+ import PlutoBlockPreviewFrame from './PlutoBlockPreviewFrame.vue'
13
+
14
+ const props = defineProps<{
15
+ modelValue: string
16
+ disabled?: boolean
17
+ mode: 'visual' | 'markdown'
18
+ }>()
19
+
20
+ const emit = defineEmits<{
21
+ 'update:modelValue': [value: string]
22
+ 'update:mode': [mode: 'visual' | 'markdown']
23
+ }>()
24
+
25
+ const { t } = usePlutoI18n()
26
+ const form = usePlutoContentForm()
27
+ const blocksConfig = usePlutoBlocksConfig()
28
+
29
+ const editor = createPlutoBlockEditor({
30
+ source: () => props.modelValue,
31
+ emit: (value) => emit('update:modelValue', value),
32
+ overrides: blocksConfig,
33
+ })
34
+ provide(plutoBlockEditorKey, editor)
35
+
36
+ // The front matter for the preview header: the item without the body and the internal `_` keys.
37
+ const frontmatter = computed<Record<string, unknown>>(() => {
38
+ const item = form?.item.value ?? {}
39
+ return Object.fromEntries(
40
+ Object.entries(item).filter(
41
+ ([key]) => key !== 'body' && !key.startsWith('_')
42
+ )
43
+ )
44
+ })
45
+
46
+ // A selection brings the Block tab forward (and opens the panel on a narrow screen). On a narrow
47
+ // screen the block list is a slideover. A choice in it closes the slideover, so the controls show.
48
+ const wide = useMediaQuery('(min-width: 1024px)')
49
+ watch(
50
+ () => editor.selection.value,
51
+ (selection) => {
52
+ if (!selection) {
53
+ return
54
+ }
55
+ if (!wide.value && selection.source === 'ui') {
56
+ document
57
+ .querySelector('[data-block-outline]')
58
+ ?.closest('[role="dialog"]')
59
+ ?.querySelector<HTMLElement>('button[data-slot="close"]')
60
+ ?.click()
61
+ }
62
+ form?.openPanel('block')
63
+ }
64
+ )
65
+
66
+ // One undo stack for the page: Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z (or Ctrl+Y). They act while the
67
+ // focus is in the editor (a panel or the canvas). Elsewhere (the Page tab) the browser keeps its own undo.
68
+ function onKeydown(event: KeyboardEvent) {
69
+ if (props.disabled || !(event.ctrlKey || event.metaKey) || event.altKey) {
70
+ return
71
+ }
72
+ const target = event.target
73
+ const inside =
74
+ target instanceof Element &&
75
+ (target.closest('[data-pluto-block-ui]') ||
76
+ target.closest('[data-block-canvas]'))
77
+ const key = event.key.toLowerCase()
78
+ if (!inside) {
79
+ return
80
+ }
81
+ const row =
82
+ target instanceof Element
83
+ ? target.closest<HTMLElement>('[data-block-row]')?.dataset.blockRow
84
+ : undefined
85
+ if (key === 'z' && !event.shiftKey) {
86
+ event.preventDefault()
87
+ editor.undo()
88
+ } else if ((key === 'z' && event.shiftKey) || key === 'y') {
89
+ event.preventDefault()
90
+ editor.redo()
91
+ } else {
92
+ return
93
+ }
94
+ // A list row that moves in the DOM loses the focus. Give it back, so the next shortcut works.
95
+ nextTick(() => {
96
+ if (
97
+ row &&
98
+ (!document.activeElement || document.activeElement === document.body)
99
+ ) {
100
+ document
101
+ .querySelector<HTMLElement>(`[data-block-row="${CSS.escape(row)}"]`)
102
+ ?.focus()
103
+ }
104
+ })
105
+ }
106
+
107
+ onMounted(() => document.addEventListener('keydown', onKeydown, true))
108
+ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown, true))
109
+ </script>
110
+
111
+ <template>
112
+ <div class="flex h-full min-h-0 flex-col" data-block-canvas>
113
+ <div v-if="editor.error.value" class="p-4">
114
+ <UAlert
115
+ :title="t('docs.editor.parseErrorTitle')"
116
+ :description="t('docs.editor.parseError')"
117
+ :actions="[
118
+ {
119
+ label: t('docs.editor.switchToMarkdown'),
120
+ color: 'neutral',
121
+ variant: 'outline',
122
+ onClick: () => emit('update:mode', 'markdown'),
123
+ },
124
+ ]"
125
+ color="warning"
126
+ variant="subtle"
127
+ icon="i-lucide-triangle-alert"
128
+ />
129
+ </div>
130
+
131
+ <PlutoBlockPreviewFrame
132
+ v-else
133
+ :frontmatter="frontmatter"
134
+ class="min-h-0 flex-1"
135
+ />
136
+
137
+ <template v-if="!editor.error.value">
138
+ <PlutoContentCanvasPanel
139
+ id="blocks"
140
+ :label="t('docs.editor.blocks')"
141
+ side="left"
142
+ icon="i-lucide-blocks"
143
+ >
144
+ <PlutoBlockOutline
145
+ :mode="mode"
146
+ :disabled="disabled"
147
+ @update:mode="emit('update:mode', $event)"
148
+ />
149
+ </PlutoContentCanvasPanel>
150
+
151
+ <PlutoContentCanvasPanel
152
+ id="block"
153
+ :label="t('docs.editor.blockTab')"
154
+ side="right"
155
+ icon="i-lucide-square-pen"
156
+ >
157
+ <PlutoBlockInspector :disabled="disabled" />
158
+ </PlutoContentCanvasPanel>
159
+
160
+ <PlutoBlockPalette />
161
+ </template>
162
+ </div>
163
+ </template>