@driftime/sanity-plugin-handbook 0.3.1 → 0.4.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/README.md +317 -163
- package/dist/index.d.ts +60 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1373 -1321
- package/dist/index.js.map +1 -1
- package/package.json +17 -28
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,52 +1,69 @@
|
|
|
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
|
-
<h1>Handbook
|
|
7
|
+
<h1>Handbook</h1>
|
|
8
|
+
<p><strong>A Sanity Studio plugin by Driftime®</strong></p>
|
|
8
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>
|
|
9
14
|
</div>
|
|
10
15
|
|
|
11
16
|
<br />
|
|
12
17
|
|
|
13
18
|
## Overview
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
Every content model comes with knowledge the schema alone cannot hold: what a field is for, which values work best, and how the team writes. Handbook gives that knowledge a place in the Studio, as a tool in the navigation, so an author reaches the guidance beside the content it describes.
|
|
21
|
+
|
|
22
|
+
The first part of the Handbook is generated from the schema. Every document type is listed with its fields, and each field shows its type, its description, an example value, and any tips or cautions written alongside its definition, so the documentation stays in step with the editor it describes. The second part is written by the team. Guides are Portable Text documents for house style, editorial process, and anything too long for a field description, arranged into groups in the same sidebar.
|
|
16
23
|
|
|
17
24
|
<br />
|
|
18
25
|
|
|
19
26
|
<figure>
|
|
20
27
|
<picture>
|
|
21
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
22
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
23
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
28
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-how-to-use-dark.png" />
|
|
29
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-how-to-use-light.png" />
|
|
30
|
+
<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" />
|
|
24
31
|
</picture>
|
|
25
|
-
<p align="center"><sub><em>
|
|
32
|
+
<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>
|
|
26
33
|
</figure>
|
|
27
34
|
|
|
28
35
|
<br />
|
|
29
36
|
|
|
30
37
|
## Installation
|
|
31
38
|
|
|
32
|
-
Handbook is built for Sanity Studio 6 and React 19
|
|
39
|
+
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 22.12 or later is required.
|
|
33
40
|
|
|
34
41
|
```bash
|
|
35
42
|
bun add -E @driftime/sanity-plugin-handbook
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
<br />
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
<br />
|
|
52
|
+
|
|
53
|
+
### Registering the Plugin
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
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.
|
|
41
56
|
|
|
42
57
|
```typescript
|
|
43
58
|
import { defineConfig } from "sanity";
|
|
44
59
|
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
45
|
-
|
|
60
|
+
|
|
61
|
+
import { home, navigation, page, settings, templates } from "@/schemas";
|
|
46
62
|
|
|
47
63
|
export default defineConfig({
|
|
48
64
|
// ...
|
|
49
65
|
plugins: [
|
|
66
|
+
// ...
|
|
50
67
|
handbookPlugin({
|
|
51
68
|
roles: [
|
|
52
69
|
{
|
|
@@ -70,35 +87,45 @@ export default defineConfig({
|
|
|
70
87
|
});
|
|
71
88
|
```
|
|
72
89
|
|
|
73
|
-
|
|
90
|
+
| Option | Type | Default | Purpose |
|
|
91
|
+
| -------------------------- | --------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `title` | `string` | `"Handbook"` | The tool's name in the Studio navigation. See [Naming the Tool](#naming-the-tool). |
|
|
93
|
+
| `sidebarTitle` | `string` | `"Handbook"` | The heading at the top of the sidebar. See [Naming the Tool](#naming-the-tool). |
|
|
94
|
+
| `roles` | `SanityHandbookDocumentRole[]` | required | The document types to document, grouped by role, in the order listed. |
|
|
95
|
+
| `blocks` | `SanityHandbookBlockDefinition[]` | none | Blocks added to guide bodies. See [Adding Custom Blocks](#adding-custom-blocks). |
|
|
96
|
+
| `editors` | `string[]` | everyone | Email addresses of the people who write guides. See [Choosing the Editors](#choosing-the-editors). |
|
|
97
|
+
| `undocumentedFieldMessage` | `string` | a prompt to ask the team | Shown for a field with no description. See [Documenting Fields](#documenting-fields). |
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
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.
|
|
74
102
|
|
|
75
103
|
<br />
|
|
76
104
|
|
|
77
105
|
<figure>
|
|
78
106
|
<picture>
|
|
79
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
80
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
81
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
107
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-document-types-dark.png" />
|
|
108
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-document-types-light.png" />
|
|
109
|
+
<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" />
|
|
82
110
|
</picture>
|
|
83
|
-
<p align="center"><sub><em>Document
|
|
111
|
+
<p align="center"><sub><em>The Document Types overview, listing each role with its description and the types it holds.</em></sub></p>
|
|
84
112
|
</figure>
|
|
85
113
|
|
|
86
114
|
<br />
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
### Documenting Fields
|
|
89
117
|
|
|
90
|
-
|
|
118
|
+
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.
|
|
91
119
|
|
|
92
120
|
```typescript
|
|
93
121
|
import { defineField, defineType } from "sanity";
|
|
94
122
|
|
|
95
|
-
export const
|
|
123
|
+
export const page = defineType({
|
|
96
124
|
name: "page",
|
|
97
125
|
type: "document",
|
|
98
126
|
title: "Pages",
|
|
99
127
|
description: "General-purpose pages.",
|
|
100
128
|
handbook: {
|
|
101
|
-
title: "Page",
|
|
102
129
|
description:
|
|
103
130
|
"Standalone pages for anything that does not warrant a document type of its own. Each is reached at its own address and assembled from blocks rather than following a fixed layout.",
|
|
104
131
|
},
|
|
@@ -109,134 +136,112 @@ export const pageType = defineType({
|
|
|
109
136
|
description: "Appears in navigation, browser tabs, and search results.",
|
|
110
137
|
handbook: {
|
|
111
138
|
description:
|
|
112
|
-
"The name this
|
|
139
|
+
"The name this document goes by wherever it is referred to rather than read — navigation menus, browser tabs, search results, and the Studio's own lists. Every document needs one, and it stands behind both the slug and the SEO title whenever those are left blank.",
|
|
113
140
|
example: "About Acme Inc.",
|
|
114
|
-
tip: "Keep it short enough to sit comfortably in a navigation menu
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
tip: "Keep it short enough to sit comfortably in a navigation menu.",
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
defineField({
|
|
145
|
+
name: "slug",
|
|
146
|
+
type: "slug",
|
|
147
|
+
description: "Last part of the page's web address.",
|
|
148
|
+
options: { source: "title" },
|
|
149
|
+
handbook: {
|
|
150
|
+
description:
|
|
151
|
+
"The last part of the page's web address. Generate it from the title and then edit it if the result reads awkwardly — shorter is almost always better.",
|
|
152
|
+
example: "about-acme-inc",
|
|
153
|
+
caution: "Changing the slug of a published page breaks every link that already points at it.",
|
|
117
154
|
},
|
|
118
155
|
}),
|
|
119
156
|
],
|
|
120
157
|
});
|
|
121
158
|
```
|
|
122
159
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `info` | Additional context shown as a popover hint. |
|
|
132
|
-
| `caution` | Warning about constraints or potential issues, shown as a popover hint. |
|
|
133
|
-
|
|
134
|
-
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)).
|
|
135
|
-
|
|
136
|
-
### Subfield Browsing
|
|
137
|
-
|
|
138
|
-
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.
|
|
160
|
+
| Key | Type | Purpose |
|
|
161
|
+
| ------------- | -------- | --------------------------------------------------------------------------- |
|
|
162
|
+
| `title` | `string` | Replaces the field or document title in the Handbook. |
|
|
163
|
+
| `description` | `string` | Replaces the description, shown under the heading. |
|
|
164
|
+
| `example` | `string` | An example value, shown in italics beneath the description. |
|
|
165
|
+
| `tip` | `string` | Guidance for the author, shown as a hint icon that opens on hover or click. |
|
|
166
|
+
| `info` | `string` | Extra context about how the field is used, shown the same way. |
|
|
167
|
+
| `caution` | `string` | A warning about constraints or pitfalls, shown the same way. |
|
|
139
168
|
|
|
140
|
-
|
|
169
|
+
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.
|
|
141
170
|
|
|
142
|
-
|
|
171
|
+
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.
|
|
143
172
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
- Image blocks with caption and alternative text
|
|
151
|
-
- Video blocks with caption
|
|
152
|
-
- Syntax-highlighted code blocks with language selection
|
|
153
|
-
- Callout blocks with tip, information, and warning variants
|
|
154
|
-
- Horizontal rule dividers
|
|
155
|
-
|
|
156
|
-
The Handbook tool displays guides in the sidebar alongside your document types. Selecting a guide opens its content in the main panel.
|
|
173
|
+
```typescript
|
|
174
|
+
handbookPlugin({
|
|
175
|
+
// ...
|
|
176
|
+
undocumentedFieldMessage: "Not documented yet. Ask in the content channel.",
|
|
177
|
+
});
|
|
178
|
+
```
|
|
157
179
|
|
|
158
180
|
<br />
|
|
159
181
|
|
|
160
182
|
<figure>
|
|
161
183
|
<picture>
|
|
162
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
163
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
164
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
184
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-fields-dark.png" />
|
|
185
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-fields-light.png" />
|
|
186
|
+
<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" />
|
|
165
187
|
</picture>
|
|
166
|
-
<p align="center"><sub><em>
|
|
188
|
+
<p align="center"><sub><em>A document type's fields, each with its type, description, example, and hints.</em></sub></p>
|
|
167
189
|
</figure>
|
|
168
190
|
|
|
169
191
|
<br />
|
|
170
192
|
|
|
171
|
-
|
|
193
|
+
### Nested Fields
|
|
172
194
|
|
|
173
|
-
|
|
195
|
+
An object or array field has a control beneath 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.
|
|
174
196
|
|
|
175
|
-
|
|
176
|
-
import { defineConfig, defineField, defineType } from "sanity";
|
|
177
|
-
import type { PortableTextObject } from "sanity";
|
|
178
|
-
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
179
|
-
// ...
|
|
197
|
+
<br />
|
|
180
198
|
|
|
181
|
-
|
|
182
|
-
name: "handbook.example",
|
|
183
|
-
type: "object",
|
|
184
|
-
title: "Example",
|
|
185
|
-
fields: [
|
|
186
|
-
defineField({
|
|
187
|
-
name: "label",
|
|
188
|
-
type: "string",
|
|
189
|
-
description: "Text displayed inside the block.",
|
|
190
|
-
}),
|
|
191
|
-
],
|
|
192
|
-
});
|
|
199
|
+
### Naming the Tool
|
|
193
200
|
|
|
194
|
-
|
|
195
|
-
return <p>{value.label}</p>;
|
|
196
|
-
}
|
|
201
|
+
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.
|
|
197
202
|
|
|
198
|
-
|
|
203
|
+
```typescript
|
|
204
|
+
handbookPlugin({
|
|
199
205
|
// ...
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// ...
|
|
203
|
-
blocks: [
|
|
204
|
-
{
|
|
205
|
-
schema: exampleSchema,
|
|
206
|
-
component: ExampleBlock,
|
|
207
|
-
},
|
|
208
|
-
],
|
|
209
|
-
}),
|
|
210
|
-
],
|
|
206
|
+
title: "Guide",
|
|
207
|
+
sidebarTitle: "Acme Inc. Guide",
|
|
211
208
|
});
|
|
212
209
|
```
|
|
213
210
|
|
|
214
|
-
|
|
211
|
+
<br />
|
|
215
212
|
|
|
216
|
-
|
|
213
|
+
## Guides
|
|
217
214
|
|
|
218
|
-
|
|
219
|
-
handbookPlugin({
|
|
220
|
-
// ...
|
|
221
|
-
editors: ["admin@acme.com", "editor@acme.com"],
|
|
222
|
-
});
|
|
223
|
-
```
|
|
215
|
+
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.
|
|
224
216
|
|
|
225
|
-
|
|
217
|
+
A guide has a title, an optional description, and a Portable Text body offering:
|
|
226
218
|
|
|
227
|
-
|
|
219
|
+
- Paragraphs, headings, and blockquotes
|
|
220
|
+
- Bullet and numbered lists
|
|
221
|
+
- Bold, italic, strikethrough, and inline code
|
|
222
|
+
- Links, to `http`, `https`, `mailto`, and `tel` addresses
|
|
223
|
+
- Images with a caption and alternative text
|
|
224
|
+
- Videos with a caption
|
|
225
|
+
- Code blocks with syntax highlighting for CSS, GROQ, HTML, JavaScript, JSON, React, Shell, TypeScript, and plain text
|
|
226
|
+
- Callouts in tip, information, and warning styles
|
|
227
|
+
- Horizontal rules
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
<br />
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
<figure>
|
|
232
|
+
<picture>
|
|
233
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-guide-dark.png" />
|
|
234
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-guide-light.png" />
|
|
235
|
+
<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" />
|
|
236
|
+
</picture>
|
|
237
|
+
<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>
|
|
238
|
+
</figure>
|
|
233
239
|
|
|
234
|
-
|
|
235
|
-
```
|
|
240
|
+
<br />
|
|
236
241
|
|
|
237
|
-
|
|
242
|
+
### Adding Guides to the Structure
|
|
238
243
|
|
|
239
|
-
|
|
244
|
+
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. 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.
|
|
240
245
|
|
|
241
246
|
```typescript
|
|
242
247
|
import { handbookStructure } from "@driftime/sanity-plugin-handbook";
|
|
@@ -258,91 +263,240 @@ export const structure: StructureResolver = (structureBuilder, context) => {
|
|
|
258
263
|
};
|
|
259
264
|
```
|
|
260
265
|
|
|
261
|
-
The
|
|
262
|
-
|
|
263
|
-
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.
|
|
266
|
+
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 put beneath 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.
|
|
264
267
|
|
|
265
|
-
|
|
268
|
+
<br />
|
|
266
269
|
|
|
267
|
-
|
|
270
|
+
<figure>
|
|
271
|
+
<picture>
|
|
272
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-groups-dark.png" />
|
|
273
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-groups-light.png" />
|
|
274
|
+
<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 two guide groups with reorder handles and the guides referenced in each" />
|
|
275
|
+
</picture>
|
|
276
|
+
<p align="center"><sub><em>The Handbook singleton in the Structure tool, where groups and their order are arranged.</em></sub></p>
|
|
277
|
+
</figure>
|
|
268
278
|
|
|
269
279
|
<br />
|
|
270
280
|
|
|
271
281
|
<figure>
|
|
272
282
|
<picture>
|
|
273
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
274
|
-
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-
|
|
275
|
-
<img src="https://raw.githubusercontent.com/driftime/sanity-
|
|
283
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-editor-dark.png" />
|
|
284
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/driftime/sanity-plugins/HEAD/packages/handbook/assets/handbook-editor-light.png" />
|
|
285
|
+
<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 title, description, and rich text editor" />
|
|
276
286
|
</picture>
|
|
277
|
-
<p align="center"><sub><em>The
|
|
287
|
+
<p align="center"><sub><em>The guides list in the Structure tool beside an open guide.</em></sub></p>
|
|
278
288
|
</figure>
|
|
279
289
|
|
|
280
290
|
<br />
|
|
281
291
|
|
|
282
|
-
|
|
292
|
+
### Choosing the Editors
|
|
293
|
+
|
|
294
|
+
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.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
handbookPlugin({
|
|
298
|
+
// ...
|
|
299
|
+
editors: ["editor@acme.com", "lead@acme.com"],
|
|
300
|
+
});
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
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.
|
|
304
|
+
|
|
305
|
+
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.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { useIsHandbookEditor } from "@driftime/sanity-plugin-handbook";
|
|
309
|
+
|
|
310
|
+
const isEditor = useIsHandbookEditor();
|
|
311
|
+
const isAcmeEditor = useIsHandbookEditor(["editor@acme.com", "lead@acme.com"]);
|
|
312
|
+
```
|
|
283
313
|
|
|
284
314
|
<br />
|
|
285
315
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
316
|
+
### Adding Custom Blocks
|
|
317
|
+
|
|
318
|
+
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`.
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { defineConfig, defineField, defineType } from "sanity";
|
|
322
|
+
import type { PortableTextObject } from "sanity";
|
|
323
|
+
import { handbookPlugin } from "@driftime/sanity-plugin-handbook";
|
|
324
|
+
|
|
325
|
+
const shortcutType = defineType({
|
|
326
|
+
name: "handbook.shortcut",
|
|
327
|
+
type: "object",
|
|
328
|
+
title: "Keyboard Shortcut",
|
|
329
|
+
description: "Key combination shown as it appears on the keyboard.",
|
|
330
|
+
fields: [
|
|
331
|
+
defineField({
|
|
332
|
+
name: "keys",
|
|
333
|
+
type: "string",
|
|
334
|
+
description: "Keys to press, separated by plus signs.",
|
|
335
|
+
}),
|
|
336
|
+
],
|
|
337
|
+
preview: {
|
|
338
|
+
select: { title: "keys" },
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
function ShortcutBlock({ value }: { value: PortableTextObject }) {
|
|
343
|
+
const { keys } = value;
|
|
344
|
+
|
|
345
|
+
return <kbd>{typeof keys === "string" && keys}</kbd>;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default defineConfig({
|
|
349
|
+
// ...
|
|
350
|
+
plugins: [
|
|
351
|
+
// ...
|
|
352
|
+
handbookPlugin({
|
|
353
|
+
// ...
|
|
354
|
+
blocks: [{ schema: shortcutType, component: ShortcutBlock }],
|
|
355
|
+
}),
|
|
356
|
+
],
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The plugin registers the block's schema type itself, so it is not added to the Studio's own `schema.types` as well.
|
|
294
361
|
|
|
295
362
|
<br />
|
|
296
363
|
|
|
297
|
-
|
|
364
|
+
### Querying
|
|
365
|
+
|
|
366
|
+
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.
|
|
367
|
+
|
|
368
|
+
```groq
|
|
369
|
+
*[_type == "handbook.handbook"][0] {
|
|
370
|
+
...,
|
|
371
|
+
groups[] {
|
|
372
|
+
...,
|
|
373
|
+
guides[] {
|
|
374
|
+
...,
|
|
375
|
+
...@-> {
|
|
376
|
+
...,
|
|
377
|
+
content[] {
|
|
378
|
+
...,
|
|
379
|
+
_type == "handbook.image" => { ..., "asset": { "url": asset.asset->url } },
|
|
380
|
+
_type == "handbook.video" => { ..., "asset": { "url": asset.asset->url } }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
298
387
|
|
|
299
|
-
|
|
388
|
+
<br />
|
|
300
389
|
|
|
301
|
-
|
|
302
|
-
| -------------------------- | --------------------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
303
|
-
| `title` | `string` | `"Handbook"` | Title shown in the Studio tool navigation. |
|
|
304
|
-
| `sidebarTitle` | `string` | `"Handbook"` | Heading displayed at the top of the sidebar. |
|
|
305
|
-
| `roles` | `SanityHandbookDocumentRole[]` | — (required) | Document roles, each shown as a labelled section in the sidebar. |
|
|
306
|
-
| `blocks` | `SanityHandbookBlockDefinition[]` | `[]` | Custom Portable Text block definitions for guide content. |
|
|
307
|
-
| `editors` | `string[]` | `undefined` | Email addresses permitted to edit Handbook documents. Unrestricted when omitted. |
|
|
308
|
-
| `undocumentedFieldMessage` | `string` | `"This field has not been documented yet. Contact your development team for guidance."` | Fallback message shown when a field has no description. |
|
|
390
|
+
## Reference
|
|
309
391
|
|
|
310
|
-
|
|
392
|
+
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`.
|
|
311
393
|
|
|
312
|
-
|
|
394
|
+
<br />
|
|
313
395
|
|
|
314
|
-
|
|
396
|
+
### Stored Guides
|
|
315
397
|
|
|
316
|
-
|
|
317
|
-
| ------------------------------- | --------------------------------------------------------------------------- |
|
|
318
|
-
| `SanityHandbookMetadata` | The `handbook` property added to field and document definitions. |
|
|
319
|
-
| `SanityHandbookDocumentRole` | A named role, holding the document types that fill it. |
|
|
320
|
-
| `SanityHandbookBlockDefinition` | A custom Portable Text block, pairing a schema definition with a component. |
|
|
321
|
-
| `SanityHandbookConfig` | The full plugin configuration accepted by `handbookPlugin`. |
|
|
398
|
+
`SanityHandbook` is the type of the singleton, and `SanityHandbookGuide` the type of a guide once its reference has been followed.
|
|
322
399
|
|
|
323
|
-
|
|
400
|
+
```typescript
|
|
401
|
+
import type { SanityHandbookGuide } from "@driftime/sanity-plugin-handbook";
|
|
402
|
+
|
|
403
|
+
interface GuidePageProps {
|
|
404
|
+
guide: SanityHandbookGuide;
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```json
|
|
409
|
+
{
|
|
410
|
+
"_type": "handbook.handbook",
|
|
411
|
+
"groups": [
|
|
412
|
+
{
|
|
413
|
+
"_key": "a1b2",
|
|
414
|
+
"title": "Content Guidelines",
|
|
415
|
+
"guides": [{ "_key": "c3d4", "_type": "handbook.guide", "title": "Writing for the Web", "content": [] }]
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
```
|
|
324
420
|
|
|
325
|
-
| Type
|
|
326
|
-
|
|
|
327
|
-
| `
|
|
328
|
-
| `SanityHandbookGuideGroup`
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
|
421
|
+
| Field | Type | Document | Purpose |
|
|
422
|
+
| ------------- | ---------------------------- | ------------------- | -------------------------------------------------------------------- |
|
|
423
|
+
| `_type` | `string` | both | `handbook.handbook` for the singleton, `handbook.guide` for a guide. |
|
|
424
|
+
| `groups` | `SanityHandbookGuideGroup[]` | `handbook.handbook` | The groups in sidebar order, each with a `title` and its `guides`. |
|
|
425
|
+
| `title` | `string` | `handbook.guide` | The guide's title, shown in the sidebar and as its heading. |
|
|
426
|
+
| `description` | `string` | `handbook.guide` | An optional introduction shown beneath the heading. |
|
|
427
|
+
| `content` | `PortableTextBlock[]` | `handbook.guide` | The body, holding text blocks and the blocks below. |
|
|
428
|
+
|
|
429
|
+
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.
|
|
430
|
+
|
|
431
|
+
| Field | Type | Block | Purpose |
|
|
432
|
+
| ---------- | ------------------------------ | ------------------ | ---------------------------------------------------------------------------- |
|
|
433
|
+
| `asset` | `{ url?: string }` | `handbook.image` | The image, once the query resolves its reference to a URL. |
|
|
434
|
+
| `caption` | `string` | `handbook.image` | An optional caption shown beneath the image. |
|
|
435
|
+
| `alt` | `string` | `handbook.image` | Optional alternative text for the image. |
|
|
436
|
+
| `asset` | `{ url?: string }` | `handbook.video` | The video, once the query resolves its reference to a URL. |
|
|
437
|
+
| `caption` | `string` | `handbook.video` | An optional caption shown beneath the video. |
|
|
438
|
+
| `code` | `string` | `handbook.code` | The source code. |
|
|
439
|
+
| `language` | `string` | `handbook.code` | The language it is highlighted as. |
|
|
440
|
+
| `variant` | `SanityHandbookCalloutVariant` | `handbook.callout` | `tip`, `info`, or `warning`. |
|
|
441
|
+
| `body` | `PortableTextBlock[]` | `handbook.callout` | The callout's text, with inline formatting and links but no lists or blocks. |
|
|
442
|
+
| `href` | `string` | `link` | The address a run of text links to. |
|
|
443
|
+
|
|
444
|
+
<br />
|
|
445
|
+
|
|
446
|
+
### API
|
|
447
|
+
|
|
448
|
+
| Export | Purpose |
|
|
449
|
+
| -------------------------------------------------------- | -------------------------------------------------------------- |
|
|
450
|
+
| `handbookPlugin(config)` | Registers the tool, its document types, and its blocks. |
|
|
451
|
+
| `handbookStructure(structureBuilder, context, editors?)` | Returns Structure items for the singleton and the guides list. |
|
|
452
|
+
| `useIsHandbookEditor(editors?)` | Whether the current user can edit guides. |
|
|
453
|
+
|
|
454
|
+
<br />
|
|
455
|
+
|
|
456
|
+
### Types
|
|
457
|
+
|
|
458
|
+
| Type | Purpose |
|
|
459
|
+
| ------------------------------- | --------------------------------------------------------------- |
|
|
460
|
+
| `SanityHandbookConfig` | Everything `handbookPlugin` accepts. |
|
|
461
|
+
| `SanityHandbookMetadata` | The `handbook` property on field and document definitions. |
|
|
462
|
+
| `SanityHandbookDocumentRole` | A role, with its title, description, and document definitions. |
|
|
463
|
+
| `SanityHandbookBlockDefinition` | A custom block, as a schema definition paired with a component. |
|
|
464
|
+
| `SanityHandbook` | The singleton document. |
|
|
465
|
+
| `SanityHandbookGuideGroup` | A group in the singleton, with its ordered guides. |
|
|
466
|
+
| `SanityHandbookGuide` | A guide document, with its title, description, and body. |
|
|
467
|
+
| `SanityHandbookImage` | An image block. |
|
|
468
|
+
| `SanityHandbookVideo` | A video block. |
|
|
469
|
+
| `SanityHandbookCode` | A code block. |
|
|
470
|
+
| `SanityHandbookCallout` | A callout block. |
|
|
471
|
+
| `SanityHandbookCalloutVariant` | `"tip"`, `"info"`, or `"warning"`. |
|
|
472
|
+
| `SanityHandbookHorizontalRule` | A horizontal rule block. |
|
|
473
|
+
| `SanityHandbookLink` | The link annotation. |
|
|
474
|
+
|
|
475
|
+
<br />
|
|
476
|
+
|
|
477
|
+
## License
|
|
478
|
+
|
|
479
|
+
MIT © [Driftime®](https://driftime.com). See [LICENSE](https://github.com/driftime/sanity-plugins/blob/main/LICENSE).
|
|
480
|
+
|
|
481
|
+
<br />
|
|
336
482
|
|
|
337
483
|
## Acknowledgements
|
|
338
484
|
|
|
339
|
-
|
|
485
|
+
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).
|
|
340
486
|
|
|
341
487
|
<br />
|
|
342
488
|
<br />
|
|
343
489
|
|
|
344
490
|
<div align="center">
|
|
345
|
-
<a href="https://driftime.com"
|
|
491
|
+
<p><strong>Built alongside <a href="https://cairn.driftime.com">Cairn</a>, a starting point for responsible web experiences.</strong></p>
|
|
492
|
+
<p>Part of a suite of Sanity Studio plugins by Driftime®</p>
|
|
493
|
+
<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>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<br />
|
|
497
|
+
|
|
498
|
+
<div align="center">
|
|
499
|
+
<a href="https://driftime.com">
|
|
346
500
|
<picture>
|
|
347
501
|
<source media="(prefers-color-scheme: dark)" srcset="https://driftime.com/driftime-github-logo-dark.svg" />
|
|
348
502
|
<source media="(prefers-color-scheme: light)" srcset="https://driftime.com/driftime-github-logo.svg" />
|