Skip to content

Commit a19bfd9

Browse files
committed
feat(extension-indent): allow indent only in configured contexts
Replace coarse node-type options with an explicit allowlist of textblock and parent pairs so product and schema stay aligned. Publish 0.2.0 with updated README and unit tests; strict package exports for semver stability. BREAKING CHANGE: Configure literal indent with allowedIndentContexts: { textblock, parent }[]. Replace former parent-only or allowedNodeTypes usage; map old parent rules to { textblock: 'paragraph', parent }. Made-with: Cursor
1 parent d8aaf1d commit a19bfd9

6 files changed

Lines changed: 739 additions & 140 deletions

File tree

packages/extension-indent/README.md

Lines changed: 74 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,117 @@
11
# @docs.plus/extension-indent
22

3-
A professional Tiptap extension for text indentation management with customizable options.
3+
Tiptap extension that adds **line-prefix** indent and outdent: each step inserts or removes a configured string (`indentChars`, default two spaces) at line starts when the caret (or each line of a multi-line selection) sits in an allowed **textblock + parent** context (see `allowedIndentContexts` below).
44

5-
## Features
5+
By default, only **`paragraph`** under **`doc`** or **`blockquote`** is allowed. Any other textblock (e.g. `heading`, `codeBlock`) is excluded until you add an explicit rule. **Tab / Shift-Tab** still run first through list and table behavior when those extensions are present (see [Keyboard](#keyboard) below).
66

7-
- Indent/outdent at cursor position or for text selections
8-
- Configurable indentation characters and behavior
9-
- Node-type filtering for targeted indentation
10-
- Tab and Shift+Tab keyboard shortcuts (configurable)
11-
- Typescript support with full type definitions
7+
## Requirements
8+
9+
Peer dependencies (your app should already include them):
10+
11+
- `@tiptap/core` ^3.20.4
12+
- `@tiptap/pm` ^3.20.4
13+
14+
Optional: `@tiptap/extension-table` for cell navigation on Tab; list extensions (`listItem` / `taskItem`) for sink/lift before literal indent.
1215

1316
## Installation
1417

18+
In this monorepo, from the root:
19+
1520
```bash
16-
npm install @docs.plus/extension-indent
21+
bun install
1722
```
1823

19-
## Usage
24+
Published installs use the same package name; align TipTap versions with the peer range above.
2025

21-
### Basic
26+
## Usage
2227

23-
```js
28+
```ts
2429
import { Editor } from '@tiptap/core'
30+
import StarterKit from '@tiptap/starter-kit'
2531
import { Indent } from '@docs.plus/extension-indent'
2632

2733
new Editor({
28-
extensions: [
29-
// ...other extensions
30-
Indent
31-
]
34+
extensions: [StarterKit, Indent.configure({ indentChars: '\t' })]
3235
})
3336
```
3437

35-
### Configuration
38+
`Indent.configure({})` merges with extension defaults (see [Options](#options)).
3639

37-
```js
38-
Indent.configure({
39-
// Character(s) for each indentation (default: ' ' - 2 spaces)
40-
indentChars: ' ', // 4 spaces
40+
### `allowedIndentContexts`
4141

42-
// Enable/disable the extension (default: true)
43-
enabled: true,
42+
Literal `indent()` / `outdent()` only run when the innermost textblock at the caret/line and its **immediate parent** match one of the rules. Each rule is `{ textblock: string, parent: string }` (TipTap / ProseMirror `NodeType.name`).
4443

45-
// Control which node types can be indented (default: paragraph, listItem, orderedList)
46-
// Empty array allows all nodes to be indented
47-
allowedNodeTypes: ['paragraph', 'listItem', 'orderedList']
48-
})
49-
```
44+
The list is a **full** allowlist: TipTap merges `configure({ … })` into defaults — if you pass `allowedIndentContexts`, it **replaces** the default array; list every `(textblock, parent)` pair you need.
5045

51-
### Examples
46+
| You want | Add / use rules like |
47+
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------- |
48+
| Body + blockquote paragraphs (package default) | `{ textblock: 'paragraph', parent: 'doc' }` and `{ textblock: 'paragraph', parent: 'blockquote' }` |
49+
| Body paragraphs only | Only `paragraph` + `doc` |
50+
| Blockquote paragraphs only | Only `paragraph` + `blockquote` |
51+
| List item paragraphs | `{ textblock: 'paragraph', parent: 'listItem' }` and/or `taskItem` |
52+
| Table cell paragraphs | `{ textblock: 'paragraph', parent: 'tableCell' }` (if your schema uses it) |
53+
| Headings | e.g. `{ textblock: 'heading', parent: 'doc' }` — type name is lowercase `heading`, not HTML `H1` |
5254

53-
```js
54-
// Use tabs instead of spaces
55-
Indent.configure({
56-
indentChars: '\t'
57-
})
55+
Pass **`[]`** to turn off **literal** indent/outdent everywhere (Tab can still sink/lift lists or move table cells).
5856

59-
// Allow indentation only for paragraphs
60-
Indent.configure({
61-
allowedNodeTypes: ['paragraph']
62-
})
63-
```
57+
**Migration:** older releases used `allowedParentTypes` (parent names for **`paragraph`** only). Replace each parent `p` with `{ textblock: 'paragraph', parent: p }`. If you used `allowedNodeTypes`, same migration.
58+
59+
### Options
60+
61+
| Option | Type | Default | Description |
62+
| ----------------------- | ------------------------------ | ----------------------------- | ---------------------------------------------------------------------- |
63+
| `indentChars` | `string` | `' '` | Inserted or removed per step (often `'\t'` or two spaces). |
64+
| `enabled` | `boolean` | `true` | Disable behavior without removing the extension. |
65+
| `allowedIndentContexts` | `Array<{ textblock, parent }>` | body + blockquote `paragraph` | Full allowlist of textblock + parent pairs for literal indent/outdent. |
66+
67+
### Keyboard
68+
69+
| Key | Order of handling |
70+
| ------------- | ----------------------------------------------------------------------------------------------------- |
71+
| **Tab** | `sinkListItem` (`listItem` / `taskItem`) → `goToNextCell` (if table extension is loaded) → `indent()` |
72+
| **Shift-Tab** | `liftListItem``goToPreviousCell``outdent()` |
73+
74+
The extension registers with **priority `25`** so delegated commands run first when applicable.
6475

6576
### Commands
6677

67-
```js
68-
// Add indentation
78+
```ts
6979
editor.commands.indent()
70-
71-
// Remove indentation
7280
editor.commands.outdent()
7381
```
7482

75-
### Keyboard Shortcuts
83+
These respect `enabled` and the same `allowedIndentContexts` rules as the keyboard path.
7684

77-
Default keyboard bindings:
85+
### Multiline selections
7886

79-
- `Tab` - Indent
80-
- `Shift+Tab` - Outdent
87+
Selected ranges are split into **visual lines** using `doc.textBetween(from, to, '\n')` (a single newline between blocks). **Every** line must match `allowedIndentContexts`; otherwise the command returns `false` and the document is unchanged.
8188

82-
## Development
89+
### Empty selection: outdent
90+
91+
**Outdent** can remove:
92+
93+
- leading `indentChars` at the **start of the current line**, or
94+
- a trailing prefix of `indentChars` **immediately before the caret** (e.g. undo a tab just inserted without moving to column 0).
95+
96+
## Testing
97+
98+
From `packages/extension-indent`:
8399

84100
```bash
85-
# Install dependencies
86-
npm install
101+
bun run test
102+
```
87103

88-
# Build
89-
npm run build
104+
Jest + jsdom; config in `jest.config.cjs` (Jest stack from the monorepo root per workspace rules). Fixtures typically use `StarterKit` like a real app.
90105

91-
# Development with auto-rebuild
92-
npm run dev
106+
End-to-end coverage lives in `packages/webapp/cypress/e2e/editor/indent/` against the production editor stack.
93107

94-
# Lint code
95-
npm run lint
108+
## Development
109+
110+
```bash
111+
bun install
112+
bun run build
113+
bun run dev
114+
bun run lint
96115
```
97116

98117
## License
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Self-contained Jest config for this package. Jest deps stay on the repo root only.
3+
* If a second library package adds Jest, consider extracting a shared preset again.
4+
*/
5+
/** @type {import('jest').Config} */
6+
module.exports = {
7+
testEnvironment: 'jsdom',
8+
roots: ['<rootDir>/src'],
9+
testMatch: ['**/__tests__/**/*.test.ts'],
10+
transform: {
11+
'^.+\\.tsx?$': [
12+
'babel-jest',
13+
{
14+
presets: [
15+
['@babel/preset-env', { targets: { node: 'current' } }],
16+
'@babel/preset-typescript'
17+
]
18+
}
19+
]
20+
},
21+
moduleFileExtensions: ['ts', 'tsx', 'js', 'json']
22+
}

packages/extension-indent/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@docs.plus/extension-indent",
3-
"version": "0.1.1",
3+
"version": "0.2.0",
44
"description": "A Tiptap extension for managing text indentation in documents",
55
"main": "./dist/index.cjs",
66
"module": "./dist/index.js",
@@ -29,6 +29,7 @@
2929
"build": "NODE_ENV=production bunx tsup",
3030
"build:dev": "bunx tsup",
3131
"dev": "bunx tsup --watch",
32+
"test": "jest --config jest.config.cjs",
3233
"lint": "eslint .",
3334
"lint:fix": "eslint . --fix",
3435
"update:packages": "bunx npm-check-updates -u"
@@ -53,8 +54,9 @@
5354
"devDependencies": {
5455
"@tiptap/core": "^3.20.4",
5556
"@tiptap/pm": "^3.20.4",
56-
"typescript": "^5.9.3",
57+
"@tiptap/starter-kit": "^3.20.4",
58+
"eslint": "^9.39.3",
5759
"tsup": "^8.5.1",
58-
"eslint": "^9.39.3"
60+
"typescript": "^5.9.3"
5961
}
6062
}

0 commit comments

Comments
 (0)