@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.
Files changed (65) hide show
  1. package/.gitattributes +1 -0
  2. package/.prettierignore +5 -0
  3. package/CHANGELOG.md +186 -0
  4. package/FEATURES.md +34 -0
  5. package/README.md +40 -2
  6. package/app/assets/css/pluto-layer.css +40 -0
  7. package/app/components/Pluto/Docs/Blocks/PlutoBlockEditor.vue +165 -0
  8. package/app/components/Pluto/Docs/Blocks/PlutoBlockInspector.vue +519 -0
  9. package/app/components/Pluto/Docs/Blocks/PlutoBlockMarkdownSplit.vue +161 -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 +86 -0
  14. package/app/components/Pluto/Docs/Blocks/PlutoBlockPreviewFrame.vue +232 -0
  15. package/app/components/Pluto/Docs/Blocks/PlutoBlockPropControl.vue +269 -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 +459 -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/blocks.ts +217 -0
  31. package/modules/pluto-docs/index.ts +230 -0
  32. package/modules/pluto-docs/manifest.ts +159 -0
  33. package/modules/pluto-docs/options.ts +258 -0
  34. package/nuxt.config.ts +36 -0
  35. package/package.json +56 -4
  36. package/server/handlers/navigation.get.ts +7 -0
  37. package/server/handlers/page.get.ts +7 -0
  38. package/server/handlers/surround.get.ts +7 -0
  39. package/server/plugins/pluto-docs.ts +25 -0
  40. package/server/utils/pluto-docs-runtime.ts +504 -0
  41. package/shared/types/docs.d.ts +40 -0
  42. package/shared/types/pluto-blocks.d.ts +10 -0
  43. package/shared/types/pluto-manifest.d.ts +6 -0
  44. package/shared/utils/blocks/component-meta.ts +285 -0
  45. package/shared/utils/blocks/define.ts +15 -0
  46. package/shared/utils/blocks/editor.ts +357 -0
  47. package/shared/utils/blocks/history.ts +94 -0
  48. package/shared/utils/blocks/mdc-props.ts +321 -0
  49. package/shared/utils/blocks/operations.ts +692 -0
  50. package/shared/utils/blocks/parse.ts +444 -0
  51. package/shared/utils/blocks/presets.ts +384 -0
  52. package/shared/utils/blocks/preview-marks.ts +98 -0
  53. package/shared/utils/blocks/preview-protocol.ts +195 -0
  54. package/shared/utils/blocks/registry.ts +295 -0
  55. package/shared/utils/blocks/serialize.ts +107 -0
  56. package/shared/utils/blocks/tree.ts +85 -0
  57. package/shared/utils/blocks/types.ts +195 -0
  58. package/shared/utils/content-route.ts +40 -0
  59. package/shared/utils/docs-type.ts +290 -0
  60. package/shared/utils/mdc-snippets.ts +114 -0
  61. package/shared/utils/navigation-config.ts +19 -0
  62. package/shared/utils/runtime-content.ts +403 -0
  63. package/tailwind.lint.css +10 -0
  64. package/tsconfig.json +18 -0
  65. 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,186 @@
1
+ # Changelog
2
+
3
+ ## [0.16.0](https://github.com/plutocms/docs/compare/v0.15.0...v0.16.0) (2026-10-31)
4
+
5
+
6
+ ### Features
7
+
8
+ * block sources and component meta mapping for the editor ([#75](https://github.com/plutocms/docs/issues/75)) ([cc04cf0](https://github.com/plutocms/docs/commit/cc04cf044112b6d4432817711c1e8ed34cce1b7e))
9
+ * discover editor blocks from definePlutoBlock in components/content ([#78](https://github.com/plutocms/docs/issues/78)) ([0ce82b9](https://github.com/plutocms/docs/commit/0ce82b9672b276c6b50edf6f647fd42490595c84))
10
+
11
+ ## [0.15.0](https://github.com/plutocms/nuxt-content/compare/v0.14.0...v0.15.0) (2026-10-31)
12
+
13
+
14
+ ### ⚠ BREAKING CHANGES
15
+
16
+ * 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.
17
+
18
+ ### Features
19
+
20
+ * rename the package to @plutocms/docs ([#71](https://github.com/plutocms/nuxt-content/issues/71)) ([b480cfa](https://github.com/plutocms/nuxt-content/commit/b480cfacca3df143ce5844ec26f46679e5ea293b))
21
+
22
+ ## [0.14.0](https://github.com/plutocms/nuxt-content/compare/v0.13.0...v0.14.0) (2026-10-31)
23
+
24
+
25
+ ### Features
26
+
27
+ * 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))
28
+
29
+ ## [0.13.0](https://github.com/plutocms/nuxt-content/compare/v0.12.3...v0.13.0) (2026-10-30)
30
+
31
+
32
+ ### ⚠ BREAKING CHANGES
33
+
34
+ * the layer extends @plutocms/core >=0.38.0, not @plutocms/pluto. PLUTO_PLUTO_PATH is now PLUTO_CORE_PATH.
35
+
36
+ ### Features
37
+
38
+ * extend @plutocms/core ([#59](https://github.com/plutocms/nuxt-content/issues/59)) ([a5ca2b9](https://github.com/plutocms/nuxt-content/commit/a5ca2b9a1b340d6ceb3d00487414d098c9fae683))
39
+
40
+ ## [0.12.3](https://github.com/plutocms/nuxt-content/compare/v0.12.2...v0.12.3) (2026-10-29)
41
+
42
+
43
+ ### Bug Fixes
44
+
45
+ * **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))
46
+
47
+ ## [0.12.2](https://github.com/plutocms/nuxt-content/compare/v0.12.1...v0.12.2) (2026-10-29)
48
+
49
+
50
+ ### Bug Fixes
51
+
52
+ * **deps:** use @plutocms/pluto 0.34 ([#40](https://github.com/plutocms/nuxt-content/issues/40)) ([000c153](https://github.com/plutocms/nuxt-content/commit/000c1538d414bc95483a4293f826a747d47820d5))
53
+
54
+ ## [0.12.1](https://github.com/plutocms/nuxt-content/compare/v0.12.0...v0.12.1) (2026-10-29)
55
+
56
+
57
+ ### Bug Fixes
58
+
59
+ * **deps:** use @plutocms/pluto 0.33 ([#38](https://github.com/plutocms/nuxt-content/issues/38)) ([ca54a9f](https://github.com/plutocms/nuxt-content/commit/ca54a9fb8b2bfa8ce5349fbcbd488941413064d6))
60
+
61
+ ## [0.12.0](https://github.com/plutocms/nuxt-content/compare/v0.11.0...v0.12.0) (2026-10-29)
62
+
63
+
64
+ ### Features
65
+
66
+ * **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))
67
+
68
+ ## [0.11.0](https://github.com/plutocms/nuxt-content/compare/v0.10.0...v0.11.0) (2026-10-28)
69
+
70
+
71
+ ### Features
72
+
73
+ * **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))
74
+
75
+ ## [0.10.0](https://github.com/plutocms/nuxt-content/compare/v0.9.0...v0.10.0) (2026-10-28)
76
+
77
+
78
+ ### Features
79
+
80
+ * **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))
81
+
82
+
83
+ ### Bug Fixes
84
+
85
+ * **deps:** use @plutocms/pluto 0.29 ([#32](https://github.com/plutocms/nuxt-content/issues/32)) ([423c6db](https://github.com/plutocms/nuxt-content/commit/423c6dba402f67ad8306651e377c5697041d9ddb))
86
+
87
+ ## [0.9.0](https://github.com/plutocms/nuxt-content/compare/v0.8.0...v0.9.0) (2026-10-27)
88
+
89
+
90
+ ### ⚠ BREAKING CHANGES
91
+
92
+ * the layer reads `plutoGithub`, not `plutoCms`. An app that sets `plutoCms` options sets `plutoGithub` now.
93
+
94
+ ### Features
95
+
96
+ * 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))
97
+
98
+ ## [0.8.0](https://github.com/plutocms/nuxt-content/compare/v0.7.0...v0.8.0) (2026-10-27)
99
+
100
+
101
+ ### ⚠ BREAKING CHANGES
102
+
103
+ * `/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.
104
+
105
+ ### Features
106
+
107
+ * 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))
108
+
109
+ ## [0.7.0](https://github.com/plutocms/nuxt-content/compare/v0.6.0...v0.7.0) (2026-10-27)
110
+
111
+
112
+ ### ⚠ BREAKING CHANGES
113
+
114
+ * 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`.
115
+
116
+ ### Features
117
+
118
+ * 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))
119
+
120
+ ## [0.6.0](https://github.com/plutocms/nuxt-content/compare/v0.5.0...v0.6.0) (2026-10-27)
121
+
122
+
123
+ ### Features
124
+
125
+ * 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))
126
+
127
+ ## [0.5.0](https://github.com/plutocms/nuxt-content/compare/v0.4.1...v0.5.0) (2026-10-26)
128
+
129
+
130
+ ### Features
131
+
132
+ * **deps:** use @plutocms/pluto 0.24 ([#20](https://github.com/plutocms/nuxt-content/issues/20)) ([acb7195](https://github.com/plutocms/nuxt-content/commit/acb71955bdf7f2fb18d545e6281aaef50cc9fc23))
133
+
134
+ ## [0.4.1](https://github.com/plutocms/nuxt-content/compare/v0.4.0...v0.4.1) (2026-10-26)
135
+
136
+
137
+ ### Bug Fixes
138
+
139
+ * **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))
140
+
141
+ ## [0.4.0](https://github.com/plutocms/nuxt-content/compare/v0.3.2...v0.4.0) (2026-10-26)
142
+
143
+
144
+ ### Features
145
+
146
+ * 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))
147
+
148
+ ## [0.3.2](https://github.com/plutocms/nuxt-content/compare/v0.3.1...v0.3.2) (2026-10-25)
149
+
150
+
151
+ ### Bug Fixes
152
+
153
+ * **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))
154
+
155
+ ## [0.3.1](https://github.com/plutocms/nuxt-content/compare/v0.3.0...v0.3.1) (2026-10-25)
156
+
157
+
158
+ ### Bug Fixes
159
+
160
+ * **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))
161
+
162
+ ## [0.3.0](https://github.com/plutocms/nuxt-content/compare/v0.2.0...v0.3.0) (2026-10-25)
163
+
164
+
165
+ ### Features
166
+
167
+ * 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))
168
+
169
+ ## [0.2.0](https://github.com/plutocms/nuxt-content/compare/v0.1.0...v0.2.0) (2026-10-25)
170
+
171
+
172
+ ### Features
173
+
174
+ * **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))
175
+
176
+ ## 0.1.0 (2026-10-25)
177
+
178
+
179
+ ### Features
180
+
181
+ * add the Nuxt Content layer with a docs preset ([90db635](https://github.com/plutocms/nuxt-content/commit/90db63534eead5ed108d01304eae3092072a44dc))
182
+
183
+
184
+ ### Miscellaneous Chores
185
+
186
+ * start the release line at 0.1.0 ([36cfc78](https://github.com/plutocms/nuxt-content/commit/36cfc7892ae904e2bc7014da643c1fd3e5b18d1a))
package/FEATURES.md ADDED
@@ -0,0 +1,34 @@
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 discovery: a component in `components/content/` (the app or a layer) that calls
30
+ `definePlutoBlock({...})` becomes a block in the palette, with its props and slots read from its
31
+ TypeScript types. Opt-in. The option `plutoDocs.blocks.discover` turns it off. See
32
+ @.claude/skills/block-model/SKILL.md.
33
+ - Block editor: the visual editor for the page body. A live preview, the block list, the palette, and
34
+ the controls of the selected block, with no raw MDC. See @.claude/skills/block-editor/SKILL.md.
package/README.md CHANGED
@@ -1,3 +1,41 @@
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
+ - Your own components in the palette: call `definePlutoBlock({ label, icon })` in a component of `components/content/`. The props and slots come from its TypeScript types.
35
+ - Hybrid render mode (default): a save shows on the site at once, with no wait for a build. Build and runtime modes are options.
36
+ - "New Page" and "Edit Page" in the admin bar, and an "Edit in admin" link for the public page.
37
+ - The pages on the public API of Pluto (`/api/pluto/v1/content/<type>`), for a site in any stack.
38
+
39
+ ## License
40
+
41
+ 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,165 @@
1
+ <script setup lang="ts">
2
+ // The component layer. Only the two editor roots import it, so it stays out of the public pages.
3
+ import componentBlocks from '#pluto-docs/blocks'
4
+ // The visual block editor: the canvas of the 'canvas' form layout. The canvas
5
+ // shows the live preview of the page. The block list goes in the left panel,
6
+ // and the controls of the selected block go in the right panel (a tab of the
7
+ // form's aside). The widget keeps all the state, so the panels are slots of
8
+ // this one component tree. The value is the Markdown text of the page body. A
9
+ // block that no edit touched keeps its bytes, so opening and saving a page
10
+ // with no edit changes nothing. See the block-editor skill.
11
+ import PlutoBlockInspector from './PlutoBlockInspector.vue'
12
+ import PlutoBlockOutline from './PlutoBlockOutline.vue'
13
+ import PlutoBlockPalette from './PlutoBlockPalette.vue'
14
+ import PlutoBlockPreviewFrame from './PlutoBlockPreviewFrame.vue'
15
+
16
+ const props = defineProps<{
17
+ modelValue: string
18
+ disabled?: boolean
19
+ mode: 'visual' | 'markdown'
20
+ }>()
21
+
22
+ const emit = defineEmits<{
23
+ 'update:modelValue': [value: string]
24
+ 'update:mode': [mode: 'visual' | 'markdown']
25
+ }>()
26
+
27
+ const { t } = usePlutoI18n()
28
+ const form = usePlutoContentForm()
29
+ const blockSources = usePlutoBlockSources(componentBlocks)
30
+
31
+ const editor = createPlutoBlockEditor({
32
+ source: () => props.modelValue,
33
+ emit: (value) => emit('update:modelValue', value),
34
+ sources: blockSources,
35
+ })
36
+ provide(plutoBlockEditorKey, editor)
37
+
38
+ // The front matter for the preview header: the item without the body and the internal `_` keys.
39
+ const frontmatter = computed<Record<string, unknown>>(() => {
40
+ const item = form?.item.value ?? {}
41
+ return Object.fromEntries(
42
+ Object.entries(item).filter(
43
+ ([key]) => key !== 'body' && !key.startsWith('_')
44
+ )
45
+ )
46
+ })
47
+
48
+ // A selection brings the Block tab forward (and opens the panel on a narrow screen). On a narrow
49
+ // screen the block list is a slideover. A choice in it closes the slideover, so the controls show.
50
+ const wide = useMediaQuery('(min-width: 1024px)')
51
+ watch(
52
+ () => editor.selection.value,
53
+ (selection) => {
54
+ if (!selection) {
55
+ return
56
+ }
57
+ if (!wide.value && selection.source === 'ui') {
58
+ document
59
+ .querySelector('[data-block-outline]')
60
+ ?.closest('[role="dialog"]')
61
+ ?.querySelector<HTMLElement>('button[data-slot="close"]')
62
+ ?.click()
63
+ }
64
+ form?.openPanel('block')
65
+ }
66
+ )
67
+
68
+ // One undo stack for the page: Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z (or Ctrl+Y). They act while the
69
+ // focus is in the editor (a panel or the canvas). Elsewhere (the Page tab) the browser keeps its own undo.
70
+ function onKeydown(event: KeyboardEvent) {
71
+ if (props.disabled || !(event.ctrlKey || event.metaKey) || event.altKey) {
72
+ return
73
+ }
74
+ const target = event.target
75
+ const inside =
76
+ target instanceof Element &&
77
+ (target.closest('[data-pluto-block-ui]') ||
78
+ target.closest('[data-block-canvas]'))
79
+ const key = event.key.toLowerCase()
80
+ if (!inside) {
81
+ return
82
+ }
83
+ const row =
84
+ target instanceof Element
85
+ ? target.closest<HTMLElement>('[data-block-row]')?.dataset.blockRow
86
+ : undefined
87
+ if (key === 'z' && !event.shiftKey) {
88
+ event.preventDefault()
89
+ editor.undo()
90
+ } else if ((key === 'z' && event.shiftKey) || key === 'y') {
91
+ event.preventDefault()
92
+ editor.redo()
93
+ } else {
94
+ return
95
+ }
96
+ // A list row that moves in the DOM loses the focus. Give it back, so the next shortcut works.
97
+ nextTick(() => {
98
+ if (
99
+ row &&
100
+ (!document.activeElement || document.activeElement === document.body)
101
+ ) {
102
+ document
103
+ .querySelector<HTMLElement>(`[data-block-row="${CSS.escape(row)}"]`)
104
+ ?.focus()
105
+ }
106
+ })
107
+ }
108
+
109
+ onMounted(() => document.addEventListener('keydown', onKeydown, true))
110
+ onBeforeUnmount(() => document.removeEventListener('keydown', onKeydown, true))
111
+ </script>
112
+
113
+ <template>
114
+ <div class="flex h-full min-h-0 flex-col" data-block-canvas>
115
+ <div v-if="editor.error.value" class="p-4">
116
+ <UAlert
117
+ :title="t('docs.editor.parseErrorTitle')"
118
+ :description="t('docs.editor.parseError')"
119
+ :actions="[
120
+ {
121
+ label: t('docs.editor.switchToMarkdown'),
122
+ color: 'neutral',
123
+ variant: 'outline',
124
+ onClick: () => emit('update:mode', 'markdown'),
125
+ },
126
+ ]"
127
+ color="warning"
128
+ variant="subtle"
129
+ icon="i-lucide-triangle-alert"
130
+ />
131
+ </div>
132
+
133
+ <PlutoBlockPreviewFrame
134
+ v-else
135
+ :frontmatter="frontmatter"
136
+ class="min-h-0 flex-1"
137
+ />
138
+
139
+ <template v-if="!editor.error.value">
140
+ <PlutoContentCanvasPanel
141
+ id="blocks"
142
+ :label="t('docs.editor.blocks')"
143
+ side="left"
144
+ icon="i-lucide-blocks"
145
+ >
146
+ <PlutoBlockOutline
147
+ :mode="mode"
148
+ :disabled="disabled"
149
+ @update:mode="emit('update:mode', $event)"
150
+ />
151
+ </PlutoContentCanvasPanel>
152
+
153
+ <PlutoContentCanvasPanel
154
+ id="block"
155
+ :label="t('docs.editor.blockTab')"
156
+ side="right"
157
+ icon="i-lucide-square-pen"
158
+ >
159
+ <PlutoBlockInspector :disabled="disabled" />
160
+ </PlutoContentCanvasPanel>
161
+
162
+ <PlutoBlockPalette />
163
+ </template>
164
+ </div>
165
+ </template>