@driftime/sanity-plugin-handbook 0.3.2 → 0.4.1
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/README.md +302 -167
- package/dist/index.d.ts +99 -96
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1725 -1679
- package/dist/index.js.map +1 -1
- package/package.json +17 -28
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,36 +1,44 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<picture>
|
|
3
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
4
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
5
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/icon-dark.svg" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/icon-light.svg" />
|
|
5
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/icon-light.svg" alt="Handbook plugin logo" width="48" />
|
|
6
6
|
</picture>
|
|
7
7
|
<h1>Handbook</h1>
|
|
8
8
|
<p><strong>A Sanity Studio plugin by Driftime®</strong></p>
|
|
9
9
|
<p>Schema-driven documentation and editorial guides, built right into Sanity Studio.</p>
|
|
10
|
+
<p>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@driftime/sanity-plugin-handbook"><img src="https://img.shields.io/npm/v/@driftime/sanity-plugin-handbook?style=flat-square&labelColor=1a1a1a&color=666666" alt="npm version" /></a>
|
|
12
|
+
<a href="https://github.com/driftime/sanity-plugins/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@driftime/sanity-plugin-handbook?style=flat-square&labelColor=1a1a1a&color=666666" alt="License: MIT" /></a>
|
|
13
|
+
</p>
|
|
10
14
|
</div>
|
|
11
15
|
|
|
12
16
|
<br />
|
|
13
17
|
|
|
14
18
|
## Overview
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
Every content model comes with knowledge the schema alone can't hold: what a field is for, which values work best, and how the site's content should be written. Handbook gives that knowledge a place in the Studio, so authors find the guidance right beside the content it describes.
|
|
21
|
+
|
|
22
|
+
Most of it needs no writing at all. Handbook reads the documentation straight from the schema, listing every document type and every field with its description, an example value, and any tips or cautions defined alongside it. When the schema changes, the Handbook changes with it.
|
|
23
|
+
|
|
24
|
+
The rest is written by the site's editors. Guides cover house style, editorial process, and anything too long for a field description, arranged into groups in the same sidebar.
|
|
17
25
|
|
|
18
26
|
<br />
|
|
19
27
|
|
|
20
28
|
<figure>
|
|
21
29
|
<picture>
|
|
22
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
23
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
24
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
30
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-how-to-use-dark.png" />
|
|
31
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-how-to-use-light.png" />
|
|
32
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-how-to-use-light.png" alt="The Handbook tool in Sanity Studio: a sidebar listing introductory pages, document types grouped by role, and the team's own guides, with the How to use the Handbook page open" />
|
|
25
33
|
</picture>
|
|
26
|
-
<p align="center"><sub><em>
|
|
34
|
+
<p align="center"><sub><em>The Handbook tool, with introductory pages, document types by role, and the team's guides in one sidebar.</em></sub></p>
|
|
27
35
|
</figure>
|
|
28
36
|
|
|
29
37
|
<br />
|
|
30
38
|
|
|
31
39
|
## Installation
|
|
32
40
|
|
|
33
|
-
Handbook is built for Sanity Studio 6 and React 19
|
|
41
|
+
Handbook is built for Sanity Studio 6.10 and React 19 and declares both as peer dependencies, so the Studio needs to be on those versions already. Node 20.19 or later is required, or 22.12 or later on Node 22.
|
|
34
42
|
|
|
35
43
|
```bash
|
|
36
44
|
bun add -E @driftime/sanity-plugin-handbook
|
|
@@ -38,18 +46,26 @@ bun add -E @driftime/sanity-plugin-handbook
|
|
|
38
46
|
|
|
39
47
|
<br />
|
|
40
48
|
|
|
41
|
-
##
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
In the Studio, the plugin registers a Handbook tool, takes the document types it documents grouped by the role each plays in the content model, and reads a `handbook` property it adds to field and document definitions.
|
|
52
|
+
|
|
53
|
+
<br />
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
### Registering the Plugin
|
|
56
|
+
|
|
57
|
+
The plugin needs to know which document types to document and how to group them. Register it with a list of roles, each holding the document definitions that play that part in the content model, and each role becomes a section of the sidebar in the order listed.
|
|
44
58
|
|
|
45
59
|
```typescript
|
|
46
60
|
import { defineConfig } from "sanity";
|
|
47
61
|
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
48
|
-
|
|
62
|
+
|
|
63
|
+
import { home, navigation, page, settings, templates } from "@/schemas";
|
|
49
64
|
|
|
50
65
|
export default defineConfig({
|
|
51
66
|
// ...
|
|
52
67
|
plugins: [
|
|
68
|
+
// ...
|
|
53
69
|
handbookPlugin({
|
|
54
70
|
roles: [
|
|
55
71
|
{
|
|
@@ -73,37 +89,47 @@ export default defineConfig({
|
|
|
73
89
|
});
|
|
74
90
|
```
|
|
75
91
|
|
|
76
|
-
|
|
92
|
+
| Option | Type | Default | Purpose |
|
|
93
|
+
| -------------------------- | --------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| `title` | `string` | `"Handbook"` | The tool's name in the Studio navigation. See [Naming the Tool](#naming-the-tool). |
|
|
95
|
+
| `sidebarTitle` | `string` | `"Handbook"` | The heading at the top of the sidebar. See [Naming the Tool](#naming-the-tool). |
|
|
96
|
+
| `roles` | `SanityHandbookDocumentRole[]` | required | The document types to document, grouped by role, in the order listed. |
|
|
97
|
+
| `blocks` | `SanityHandbookBlockDefinition[]` | none | Blocks added to guide bodies. See [Adding Custom Blocks](#adding-custom-blocks). |
|
|
98
|
+
| `editors` | `string[]` | everyone | Email addresses of the people who write guides. See [Choosing the Editors](#choosing-the-editors). |
|
|
99
|
+
| `undocumentedFieldMessage` | `string` | a prompt to ask the team | Shown for a field with no description. See [Documenting Fields](#documenting-fields). |
|
|
100
|
+
|
|
101
|
+
A role takes a `title`, an optional `description` shown on the Document Types overview, and the `documents` that fill it, passed as definitions rather than names. The sidebar opens with a Getting Started section, holding a page on how to use the Handbook and the Document Types overview, followed by a section for each role and then the guides.
|
|
102
|
+
|
|
103
|
+
The plugin also registers two document types of its own for the guides, `handbook.handbook` and `handbook.guide`, along with an object type for each block a guide can hold, all under the `handbook.` prefix, and the tool itself is named `handbook`. A structure or a query that lists every registered type sees those types too, and a Studio that already has a tool or a type by one of these names needs to rename it before installing.
|
|
77
104
|
|
|
78
105
|
<br />
|
|
79
106
|
|
|
80
107
|
<figure>
|
|
81
108
|
<picture>
|
|
82
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
83
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
84
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
109
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-document-types-dark.png" />
|
|
110
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-document-types-light.png" />
|
|
111
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-document-types-light.png" alt="The Document Types overview in the Handbook tool, listing each role with its description and the document types it contains" />
|
|
85
112
|
</picture>
|
|
86
|
-
<p align="center"><sub><em>Document
|
|
113
|
+
<p align="center"><sub><em>The Document Types overview, listing each role with its description and the types it holds.</em></sub></p>
|
|
87
114
|
</figure>
|
|
88
115
|
|
|
89
116
|
<br />
|
|
90
117
|
|
|
91
|
-
|
|
118
|
+
### Documenting Fields
|
|
92
119
|
|
|
93
|
-
|
|
120
|
+
Every field is documented from what the schema already says. Its title comes from `title`, or from its name in Title Case, and its description from `description`, so an existing schema is a usable handbook from the start. The `handbook` property carries the documentation the editor has no room for. The plugin declares it on Sanity's own field and document definitions, so it is typed and completes like any other property.
|
|
94
121
|
|
|
95
122
|
```typescript
|
|
96
123
|
import { defineField, defineType } from "sanity";
|
|
97
124
|
|
|
98
|
-
export const
|
|
125
|
+
export const page = defineType({
|
|
99
126
|
name: "page",
|
|
100
127
|
type: "document",
|
|
101
128
|
title: "Pages",
|
|
102
129
|
description: "General-purpose pages.",
|
|
103
130
|
handbook: {
|
|
104
|
-
title: "Page",
|
|
105
131
|
description:
|
|
106
|
-
"Standalone pages for
|
|
132
|
+
"Standalone pages for any content that doesn't need a document type of its own. Each page has its own address and is built from blocks rather than following a fixed layout.",
|
|
107
133
|
},
|
|
108
134
|
fields: [
|
|
109
135
|
defineField({
|
|
@@ -112,140 +138,112 @@ export const pageType = defineType({
|
|
|
112
138
|
description: "Appears in navigation, browser tabs, and search results.",
|
|
113
139
|
handbook: {
|
|
114
140
|
description:
|
|
115
|
-
"
|
|
141
|
+
"What the document is called wherever it's listed: navigation menus, browser tabs, search results, and the Studio's own lists. Every document needs one, and it's also used to generate the slug and as the SEO title when those fields are left empty.",
|
|
116
142
|
example: "About Acme Inc.",
|
|
117
|
-
tip: "Keep it short enough to
|
|
118
|
-
|
|
119
|
-
|
|
143
|
+
tip: "Keep it short enough to fit in a navigation menu without wrapping.",
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
defineField({
|
|
147
|
+
name: "slug",
|
|
148
|
+
type: "slug",
|
|
149
|
+
description: "Last part of the page's web address.",
|
|
150
|
+
options: { source: "title" },
|
|
151
|
+
handbook: {
|
|
152
|
+
description:
|
|
153
|
+
"The last part of the page's web address. Generate it from the title, then edit it if the result is long or unclear. Shorter slugs are usually better.",
|
|
154
|
+
example: "about-acme-inc",
|
|
155
|
+
caution: "Changing the slug of a published page breaks every link that already points at it.",
|
|
120
156
|
},
|
|
121
157
|
}),
|
|
122
158
|
],
|
|
123
159
|
});
|
|
124
160
|
```
|
|
125
161
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `info` | Additional context shown as a popover hint. |
|
|
135
|
-
| `caution` | Warning about constraints or potential issues, shown as a popover hint. |
|
|
136
|
-
|
|
137
|
-
If no `handbook` property is provided, the tool falls back to the field's `title` and `description` properties. If neither exists, the field name is converted to title case. Fields without any description display a configurable fallback message (see `undocumentedFieldMessage` in [Configuration](#configuration)).
|
|
138
|
-
|
|
139
|
-
### Subfield Browsing
|
|
140
|
-
|
|
141
|
-
Fields with nested structure display a collapsible section that editors can expand to explore subfields. Custom types extending built-in types like `image` or `file` show only the fields you add. Inherited fields are excluded automatically. Circular type references are detected and labelled rather than rendering infinitely.
|
|
162
|
+
| Key | Type | Purpose |
|
|
163
|
+
| ------------- | -------- | --------------------------------------------------------------------------- |
|
|
164
|
+
| `title` | `string` | Replaces the field or document title in the Handbook. |
|
|
165
|
+
| `description` | `string` | Replaces the description, shown under the heading. |
|
|
166
|
+
| `example` | `string` | An example value, shown in italics below the description. |
|
|
167
|
+
| `tip` | `string` | Guidance for the author, shown as a hint icon that opens on hover or click. |
|
|
168
|
+
| `info` | `string` | Extra context about how the field is used, shown the same way. |
|
|
169
|
+
| `caution` | `string` | A warning about constraints or pitfalls, shown the same way. |
|
|
142
170
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
## Guide Content
|
|
146
|
-
|
|
147
|
-
Handbook includes a guide authoring system powered by Portable Text. The plugin automatically registers a `handbook.handbook` singleton and a `handbook.guide` document type. No additional schema setup is required.
|
|
171
|
+
Every key is optional, and so is `handbook` itself. On a document, only `title` and `description` are read. Hints appear beside the field name in the order tip, info, caution, and each popover is labeled with its kind.
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
- Paragraph, heading, and blockquote styles
|
|
152
|
-
- Bullet and numbered lists
|
|
153
|
-
- Bold, italic, strikethrough, and inline code formatting
|
|
154
|
-
- Inline links
|
|
155
|
-
- Image blocks with caption and alternative text
|
|
156
|
-
- Video blocks with caption
|
|
157
|
-
- Syntax-highlighted code blocks with language selection
|
|
158
|
-
- Callout blocks with tip, information, and warning variants
|
|
159
|
-
- Horizontal rule dividers
|
|
173
|
+
A field with neither a `description` nor a `handbook.description` shows a message saying it has not been documented yet. Pass `undocumentedFieldMessage` to the plugin to replace that message for every field.
|
|
160
174
|
|
|
161
|
-
|
|
175
|
+
```typescript
|
|
176
|
+
handbookPlugin({
|
|
177
|
+
// ...
|
|
178
|
+
undocumentedFieldMessage: "Not documented yet. Ask in the content channel.",
|
|
179
|
+
});
|
|
180
|
+
```
|
|
162
181
|
|
|
163
182
|
<br />
|
|
164
183
|
|
|
165
184
|
<figure>
|
|
166
185
|
<picture>
|
|
167
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
168
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
169
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
186
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-fields-dark.png" />
|
|
187
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-fields-light.png" />
|
|
188
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-fields-light.png" alt="A document type's fields in the Handbook tool, each with its type, description, an example value in italics, and hint icons" />
|
|
170
189
|
</picture>
|
|
171
|
-
<p align="center"><sub><em>
|
|
190
|
+
<p align="center"><sub><em>A document type's fields, each with its type, description, example, and hints.</em></sub></p>
|
|
172
191
|
</figure>
|
|
173
192
|
|
|
174
193
|
<br />
|
|
175
194
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Register custom Portable Text blocks for use in guide content by providing a schema definition and a React component. Custom blocks appear as insertion options in the guide content editor and are rendered using the provided component in the Handbook viewer.
|
|
179
|
-
|
|
180
|
-
```tsx
|
|
181
|
-
import { defineConfig, defineField, defineType } from "sanity";
|
|
182
|
-
import type { PortableTextObject } from "sanity";
|
|
183
|
-
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
184
|
-
// ...
|
|
185
|
-
|
|
186
|
-
const exampleSchema = defineType({
|
|
187
|
-
name: "handbook.example",
|
|
188
|
-
type: "object",
|
|
189
|
-
title: "Example",
|
|
190
|
-
fields: [
|
|
191
|
-
defineField({
|
|
192
|
-
name: "label",
|
|
193
|
-
type: "string",
|
|
194
|
-
description: "Text displayed inside the block.",
|
|
195
|
-
}),
|
|
196
|
-
],
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
function ExampleBlock({ value }: { value: PortableTextObject & { label?: string } }) {
|
|
200
|
-
return <p>{value.label}</p>;
|
|
201
|
-
}
|
|
195
|
+
### Nested Fields
|
|
202
196
|
|
|
203
|
-
|
|
204
|
-
// ...
|
|
205
|
-
plugins: [
|
|
206
|
-
handbookPlugin({
|
|
207
|
-
// ...
|
|
208
|
-
blocks: [
|
|
209
|
-
{
|
|
210
|
-
schema: exampleSchema,
|
|
211
|
-
component: ExampleBlock,
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
}),
|
|
215
|
-
],
|
|
216
|
-
});
|
|
217
|
-
```
|
|
197
|
+
An object or array field has a control below its description that counts its subfields and expands to list them, each documented the way a top-level field is. An array with one member type lists that type's fields, and an array with several lists the members themselves. Sanity's own types, such as `image`, `file`, and `slug`, are not expanded, so a custom type built on one of them shows only the fields added to it. A type that contains itself is listed once, with a note referring back to the first occurrence in place of a second expansion.
|
|
218
198
|
|
|
219
199
|
<br />
|
|
220
200
|
|
|
221
|
-
|
|
201
|
+
### Naming the Tool
|
|
222
202
|
|
|
223
|
-
|
|
203
|
+
The tool is called Handbook in the Studio navigation and at the top of its sidebar. The two names are set separately, so a Studio can rename the tool without changing the sidebar heading, or give each its own wording.
|
|
224
204
|
|
|
225
205
|
```typescript
|
|
226
206
|
handbookPlugin({
|
|
227
207
|
// ...
|
|
228
|
-
|
|
208
|
+
title: "Guide",
|
|
209
|
+
sidebarTitle: "Acme Inc. Guide",
|
|
229
210
|
});
|
|
230
211
|
```
|
|
231
212
|
|
|
232
|
-
|
|
213
|
+
<br />
|
|
233
214
|
|
|
234
|
-
|
|
215
|
+
## Guides
|
|
235
216
|
|
|
236
|
-
|
|
217
|
+
Guides are documents in the dataset, and the plugin registers everything they need, so no schema setup is involved. A `handbook.guide` document holds one guide, and a `handbook.handbook` singleton arranges the guides into groups and sets their order in the sidebar. The tool fetches both and fetches them again whenever either changes, so a published edit appears without a reload. Because there is only ever one singleton, it is never offered in the create menu and cannot be deleted, duplicated, or unpublished.
|
|
237
218
|
|
|
238
|
-
|
|
239
|
-
import { useIsHandbookEditor } from "@driftime/sanity-plugin-handbook";
|
|
219
|
+
A guide has a title, an optional description, and a Portable Text body offering:
|
|
240
220
|
|
|
241
|
-
|
|
242
|
-
|
|
221
|
+
- Paragraphs, headings, and blockquotes
|
|
222
|
+
- Bullet and numbered lists
|
|
223
|
+
- Bold, italic, strikethrough, and inline code
|
|
224
|
+
- Links, to `http`, `https`, `mailto`, and `tel` addresses
|
|
225
|
+
- Images with a caption and alternative text
|
|
226
|
+
- Videos with a caption
|
|
227
|
+
- Code blocks with syntax highlighting for CSS, GROQ, HTML, JavaScript, JSON, React, Shell, TypeScript, and plain text
|
|
228
|
+
- Callouts in tip, information, and warning styles
|
|
229
|
+
- Horizontal rules
|
|
230
|
+
|
|
231
|
+
<br />
|
|
232
|
+
|
|
233
|
+
<figure>
|
|
234
|
+
<picture>
|
|
235
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-guide-dark.png" />
|
|
236
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-guide-light.png" />
|
|
237
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-guide-light.png" alt="A guide open in the Handbook tool, showing a heading, paragraphs, a blockquote, a list, a callout, and an image with a caption" />
|
|
238
|
+
</picture>
|
|
239
|
+
<p align="center"><sub><em>A guide in the tool, with a heading, a blockquote, a list, a callout, and a captioned image.</em></sub></p>
|
|
240
|
+
</figure>
|
|
243
241
|
|
|
244
242
|
<br />
|
|
245
243
|
|
|
246
|
-
|
|
244
|
+
### Adding Guides to the Structure
|
|
247
245
|
|
|
248
|
-
|
|
246
|
+
Guides are read in the Handbook tool and written in the Structure tool, and they appear in the Structure tool only once its resolver adds them. `handbookStructure` returns the two list items for that, an editor for the singleton and a list of the guides in the order they were created. It reads the current user from the Structure context, and returns an empty array for anyone who is not an editor, so the resolver needs both of its arguments.
|
|
249
247
|
|
|
250
248
|
```typescript
|
|
251
249
|
import { handbookStructure } from "@driftime/sanity-plugin-handbook";
|
|
@@ -267,89 +265,226 @@ export const structure: StructureResolver = (structureBuilder, context) => {
|
|
|
267
265
|
};
|
|
268
266
|
```
|
|
269
267
|
|
|
270
|
-
The
|
|
271
|
-
|
|
272
|
-
The helper reads the `editors` you gave the plugin, so you don't declare them twice. Pass a list as the optional third argument to override that, which you need if your Studio runs several workspaces with different editors.
|
|
268
|
+
The filter keeps the two Handbook document types out of a list built from every registered type, and the divider is added only when there is something to show below it. A Studio running several workspaces with different editors passes the workspace's own list as a third argument, since the plugin otherwise reads the list from whichever workspace was configured last.
|
|
273
269
|
|
|
274
|
-
|
|
270
|
+
<br />
|
|
275
271
|
|
|
276
|
-
|
|
272
|
+
<figure>
|
|
273
|
+
<picture>
|
|
274
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-groups-dark.png" />
|
|
275
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-groups-light.png" />
|
|
276
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-groups-light.png" alt="The Handbook singleton open in the Structure tool, showing three guide groups with reorder handles and the guides referenced in each" />
|
|
277
|
+
</picture>
|
|
278
|
+
<p align="center"><sub><em>The Handbook singleton in the Structure tool, where groups and their order are arranged.</em></sub></p>
|
|
279
|
+
</figure>
|
|
277
280
|
|
|
278
281
|
<br />
|
|
279
282
|
|
|
280
283
|
<figure>
|
|
281
284
|
<picture>
|
|
282
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
283
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
284
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
285
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-editor-dark.png" />
|
|
286
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-editor-light.png" />
|
|
287
|
+
<img src="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-editor-light.png" alt="The Handbook Guides list in the Structure tool beside an open guide, showing its rich text editor and toolbar" />
|
|
285
288
|
</picture>
|
|
286
|
-
<p align="center"><sub><em>The
|
|
289
|
+
<p align="center"><sub><em>The guides list in the Structure tool beside an open guide.</em></sub></p>
|
|
287
290
|
</figure>
|
|
288
291
|
|
|
289
292
|
<br />
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
### Choosing the Editors
|
|
295
|
+
|
|
296
|
+
Everyone in the Studio can read the Handbook, and a smaller group usually writes it. The editors are the people who create guides and arrange them into groups on the singleton, and the plugin takes their email addresses.
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
handbookPlugin({
|
|
300
|
+
// ...
|
|
301
|
+
editors: ["editor@acme.com", "lead@acme.com"],
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
For an editor, `handbookStructure` returns its items and the create menu offers a new guide. For everyone else, the structure items are empty and both Handbook document types are left out of the create menu. When no list is given, everyone is an editor. The list decides what the Studio shows rather than what the dataset allows, so a project's own roles remain the place where write access is granted or withheld.
|
|
306
|
+
|
|
307
|
+
The same answer is available to a Studio's own components through `useIsHandbookEditor`, which checks the current user against the plugin's list, or against a list passed to it for a Studio running several workspaces.
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { useIsHandbookEditor } from "@driftime/sanity-plugin-handbook";
|
|
311
|
+
|
|
312
|
+
const isEditor = useIsHandbookEditor();
|
|
313
|
+
const isAcmeEditor = useIsHandbookEditor(["editor@acme.com", "lead@acme.com"]);
|
|
314
|
+
```
|
|
292
315
|
|
|
293
316
|
<br />
|
|
294
317
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
318
|
+
### Adding Custom Blocks
|
|
319
|
+
|
|
320
|
+
A guide body can hold custom blocks alongside the built-in ones. Each needs a schema definition for the editor and a component for the tool to render it with, and the component receives the stored block as `value`.
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
import { defineConfig, defineField, defineType } from "sanity";
|
|
324
|
+
import type { PortableTextObject } from "sanity";
|
|
325
|
+
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
326
|
+
|
|
327
|
+
const shortcutType = defineType({
|
|
328
|
+
name: "handbook.shortcut",
|
|
329
|
+
type: "object",
|
|
330
|
+
title: "Keyboard Shortcut",
|
|
331
|
+
description: "Key combination shown as it appears on the keyboard.",
|
|
332
|
+
fields: [
|
|
333
|
+
defineField({
|
|
334
|
+
name: "keys",
|
|
335
|
+
type: "string",
|
|
336
|
+
description: "Keys to press, separated by plus signs.",
|
|
337
|
+
}),
|
|
338
|
+
],
|
|
339
|
+
preview: {
|
|
340
|
+
select: { title: "keys" },
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
function ShortcutBlock({ value }: { value: PortableTextObject }) {
|
|
345
|
+
const { keys } = value;
|
|
346
|
+
|
|
347
|
+
return <kbd>{typeof keys === "string" && keys}</kbd>;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export default defineConfig({
|
|
351
|
+
// ...
|
|
352
|
+
plugins: [
|
|
353
|
+
// ...
|
|
354
|
+
handbookPlugin({
|
|
355
|
+
// ...
|
|
356
|
+
blocks: [{ schema: shortcutType, component: ShortcutBlock }],
|
|
357
|
+
}),
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
The plugin registers the block's schema type itself, so it is not added to the Studio's own `schema.types` as well.
|
|
303
363
|
|
|
304
364
|
<br />
|
|
305
365
|
|
|
306
|
-
|
|
366
|
+
### Querying
|
|
367
|
+
|
|
368
|
+
Guides are written and read inside the Studio, and the tool fetches everything it shows, so a site needs no query for the Handbook at all. A site that does want to publish the guides queries the singleton, follows its references, and resolves each image and video block's asset to a URL, which is the shape the exported types describe.
|
|
369
|
+
|
|
370
|
+
```groq
|
|
371
|
+
*[_type == "handbook.handbook"][0] {
|
|
372
|
+
...,
|
|
373
|
+
groups[] {
|
|
374
|
+
...,
|
|
375
|
+
guides[] {
|
|
376
|
+
...,
|
|
377
|
+
...@-> {
|
|
378
|
+
...,
|
|
379
|
+
content[] {
|
|
380
|
+
...,
|
|
381
|
+
_type == "handbook.image" => { ..., "asset": { "url": asset.asset->url } },
|
|
382
|
+
_type == "handbook.video" => { ..., "asset": { "url": asset.asset->url } }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
307
389
|
|
|
308
|
-
|
|
390
|
+
<br />
|
|
309
391
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
| `sidebarTitle` | `string` | `"Handbook"` | Heading displayed at the top of the sidebar. |
|
|
314
|
-
| `roles` | `SanityHandbookDocumentRole[]` | — (required) | Document roles, each shown as a labelled section in the sidebar. |
|
|
315
|
-
| `blocks` | `SanityHandbookBlockDefinition[]` | `[]` | Custom Portable Text block definitions for guide content. |
|
|
316
|
-
| `editors` | `string[]` | `undefined` | Email addresses permitted to edit Handbook documents. Unrestricted when omitted. |
|
|
317
|
-
| `undocumentedFieldMessage` | `string` | `"This field has not been documented yet. Contact your development team for guidance."` | Fallback message shown when a field has no description. |
|
|
392
|
+
## Reference
|
|
393
|
+
|
|
394
|
+
The rest of this document covers the shape of the stored guides and everything the package exports. Everything is imported from `@driftime/sanity-plugin-handbook`.
|
|
318
395
|
|
|
319
396
|
<br />
|
|
320
397
|
|
|
321
|
-
|
|
398
|
+
### Stored Guides
|
|
399
|
+
|
|
400
|
+
`SanityHandbook` is the type of the singleton, and `SanityHandbookGuide` the type of a guide once its reference has been followed.
|
|
322
401
|
|
|
323
|
-
|
|
402
|
+
```typescript
|
|
403
|
+
import type { SanityHandbookGuide } from "@driftime/sanity-plugin-handbook";
|
|
404
|
+
|
|
405
|
+
interface GuidePageProps {
|
|
406
|
+
guide: SanityHandbookGuide;
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"_type": "handbook.handbook",
|
|
413
|
+
"groups": [
|
|
414
|
+
{
|
|
415
|
+
"_key": "a1b2",
|
|
416
|
+
"title": "Content Guidelines",
|
|
417
|
+
"guides": [{ "_key": "c3d4", "_type": "handbook.guide", "title": "Writing for the Web", "content": [] }]
|
|
418
|
+
}
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
```
|
|
324
422
|
|
|
325
|
-
|
|
423
|
+
| Field | Type | Document | Purpose |
|
|
424
|
+
| ------------- | ---------------------------- | ------------------- | -------------------------------------------------------------------- |
|
|
425
|
+
| `_type` | `string` | both | `handbook.handbook` for the singleton, `handbook.guide` for a guide. |
|
|
426
|
+
| `groups` | `SanityHandbookGuideGroup[]` | `handbook.handbook` | The groups in sidebar order, each with a `title` and its `guides`. |
|
|
427
|
+
| `title` | `string` | `handbook.guide` | The guide's title, shown in the sidebar and as its heading. |
|
|
428
|
+
| `description` | `string` | `handbook.guide` | An optional introduction shown below the heading. |
|
|
429
|
+
| `content` | `PortableTextBlock[]` | `handbook.guide` | The body, holding text blocks and the blocks below. |
|
|
430
|
+
|
|
431
|
+
The blocks a body can hold each have a type of their own, and the link annotation is stored in a text block's `markDefs`. A horizontal rule, `handbook.horizontalRule`, carries no fields.
|
|
432
|
+
|
|
433
|
+
| Field | Type | Block | Purpose |
|
|
434
|
+
| ---------- | ------------------------------ | ------------------ | ---------------------------------------------------------------------------- |
|
|
435
|
+
| `asset` | `{ url?: string }` | `handbook.image` | The image, once the query resolves its reference to a URL. |
|
|
436
|
+
| `caption` | `string` | `handbook.image` | An optional caption shown below the image. |
|
|
437
|
+
| `alt` | `string` | `handbook.image` | Optional alternative text for the image. |
|
|
438
|
+
| `asset` | `{ url?: string }` | `handbook.video` | The video, once the query resolves its reference to a URL. |
|
|
439
|
+
| `caption` | `string` | `handbook.video` | An optional caption shown below the video. |
|
|
440
|
+
| `code` | `string` | `handbook.code` | The source code. |
|
|
441
|
+
| `language` | `string` | `handbook.code` | The language it is highlighted as. |
|
|
442
|
+
| `variant` | `SanityHandbookCalloutVariant` | `handbook.callout` | `tip`, `info`, or `warning`. |
|
|
443
|
+
| `body` | `PortableTextBlock[]` | `handbook.callout` | The callout's text, with inline formatting and links but no lists or blocks. |
|
|
444
|
+
| `href` | `string` | `link` | The address a run of text links to. |
|
|
326
445
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
|
332
|
-
|
|
|
446
|
+
<br />
|
|
447
|
+
|
|
448
|
+
### API
|
|
449
|
+
|
|
450
|
+
| Export | Purpose |
|
|
451
|
+
| -------------------------------------------------------- | -------------------------------------------------------------- |
|
|
452
|
+
| `handbookPlugin(config)` | Registers the tool, its document types, and its blocks. |
|
|
453
|
+
| `handbookStructure(structureBuilder, context, editors?)` | Returns Structure items for the singleton and the guides list. |
|
|
454
|
+
| `useIsHandbookEditor(editors?)` | Whether the current user can edit guides. |
|
|
455
|
+
|
|
456
|
+
<br />
|
|
457
|
+
|
|
458
|
+
### Types
|
|
459
|
+
|
|
460
|
+
| Type | Purpose |
|
|
461
|
+
| ------------------------------- | --------------------------------------------------------------- |
|
|
462
|
+
| `SanityHandbookConfig` | Everything `handbookPlugin` accepts. |
|
|
463
|
+
| `SanityHandbookMetadata` | The `handbook` property on field and document definitions. |
|
|
464
|
+
| `SanityHandbookDocumentRole` | A role, with its title, description, and document definitions. |
|
|
465
|
+
| `SanityHandbookBlockDefinition` | A custom block, as a schema definition paired with a component. |
|
|
466
|
+
| `SanityHandbook` | The singleton document. |
|
|
467
|
+
| `SanityHandbookGuideGroup` | A group in the singleton, with its ordered guides. |
|
|
468
|
+
| `SanityHandbookGuide` | A guide document, with its title, description, and body. |
|
|
469
|
+
| `SanityHandbookImage` | An image block. |
|
|
470
|
+
| `SanityHandbookVideo` | A video block. |
|
|
471
|
+
| `SanityHandbookCode` | A code block. |
|
|
472
|
+
| `SanityHandbookCallout` | A callout block. |
|
|
473
|
+
| `SanityHandbookCalloutVariant` | `"tip"`, `"info"`, or `"warning"`. |
|
|
474
|
+
| `SanityHandbookHorizontalRule` | A horizontal rule block. |
|
|
475
|
+
| `SanityHandbookLink` | The link annotation. |
|
|
476
|
+
|
|
477
|
+
<br />
|
|
333
478
|
|
|
334
|
-
|
|
479
|
+
## License
|
|
335
480
|
|
|
336
|
-
|
|
337
|
-
| ------------------------------ | ------------------------------------------------------------------------- |
|
|
338
|
-
| `SanityHandbook` | The Handbook singleton document, defining the groups and their ordering. |
|
|
339
|
-
| `SanityHandbookGuideGroup` | A group within the Handbook singleton, holding an ordered list of guides. |
|
|
340
|
-
| `SanityHandbookGuide` | A guide document, with its title, description, and Portable Text content. |
|
|
341
|
-
| `SanityHandbookCallout` | A callout block, with its variant and Portable Text body. |
|
|
342
|
-
| `SanityHandbookCode` | A code block, with its source and language. |
|
|
343
|
-
| `SanityHandbookHorizontalRule` | A horizontal rule block. |
|
|
344
|
-
| `SanityHandbookImage` | An image block, with its asset, caption, and alternative text. |
|
|
345
|
-
| `SanityHandbookVideo` | A video block, with its asset and caption. |
|
|
346
|
-
| `SanityHandbookLink` | A link annotation, with the web address it points to. |
|
|
481
|
+
MIT © [Driftime®](https://driftime.com). See [LICENSE](https://github.com/driftime/sanity-plugins/blob/main/LICENSE).
|
|
347
482
|
|
|
348
483
|
<br />
|
|
349
484
|
|
|
350
485
|
## Acknowledgements
|
|
351
486
|
|
|
352
|
-
|
|
487
|
+
Icons for the Studio's own controls come from `@sanity/icons`, so they match the rest of the Studio. The plugin's own icons are based on [Lucide](https://lucide.dev), distributed under the [ISC License](https://github.com/lucide-icons/lucide/blob/main/LICENSE).
|
|
353
488
|
|
|
354
489
|
<br />
|
|
355
490
|
<br />
|
|
@@ -357,7 +492,7 @@ The icons in `src/icons` are derived from [Lucide](https://lucide.dev) and redra
|
|
|
357
492
|
<div align="center">
|
|
358
493
|
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
359
494
|
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
360
|
-
<p><a href="https://github.com/driftime/sanity-
|
|
495
|
+
<p><a href="https://github.com/driftime/sanity-plugins#readme">Sanity Plugins</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/handbook#readme">Handbook</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/icon#readme">Icon</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/color#readme">Color</a> · <a href="https://github.com/driftime/sanity-plugins/tree/main/packages/link#readme">Link</a></p>
|
|
361
496
|
</div>
|
|
362
497
|
|
|
363
498
|
<br />
|