@input/pen-autoformat 0.1.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/LICENSE.md +21 -0
- package/README.md +82 -0
- package/dist/index.cjs +396 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.mjs +366 -0
- package/package.json +61 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Input B.V.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# `@input/pen-autoformat`
|
|
2
|
+
|
|
3
|
+
Opt-in markdown autoformat for Pen.
|
|
4
|
+
|
|
5
|
+
This package is intentionally **not** included in `defaultPreset()`. Library users enable it only when they want markdown-style typing shortcuts. It does not ship a renderer or a keymap.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @input/pen @input/pen-autoformat
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createEditor } from "@input/pen";
|
|
17
|
+
import { autoformatExtension } from "@input/pen-autoformat";
|
|
18
|
+
|
|
19
|
+
const editor = createEditor({
|
|
20
|
+
extensions: [autoformatExtension()],
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Without the extension:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createEditor } from "@input/pen";
|
|
28
|
+
|
|
29
|
+
const editor = createEditor();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
markdown autoformat remains disabled.
|
|
33
|
+
|
|
34
|
+
## What it adds
|
|
35
|
+
|
|
36
|
+
Block shortcuts:
|
|
37
|
+
|
|
38
|
+
- `# ` through `###### ` -> headings
|
|
39
|
+
- `- `, `* `, `+ ` -> bullet list
|
|
40
|
+
- `1. ` -> numbered list
|
|
41
|
+
- `[ ] `, `[x] ` -> checklist
|
|
42
|
+
- `> ` -> blockquote
|
|
43
|
+
- ` ``` ` -> code block
|
|
44
|
+
- `---`, `***`, `___` -> divider
|
|
45
|
+
- `> [!note] ` -> callout
|
|
46
|
+
|
|
47
|
+
Inline shortcuts:
|
|
48
|
+
|
|
49
|
+
- `**text**` -> bold
|
|
50
|
+
- `*text*` -> italic
|
|
51
|
+
- `` `text` `` -> code
|
|
52
|
+
- `~~text~~` -> strikethrough
|
|
53
|
+
- `==text==` -> highlight
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
- This package is headless and renderer-agnostic.
|
|
58
|
+
- `@input/pen-react` keeps a small fallback list-input convenience path, but full markdown autoformat lives here.
|
|
59
|
+
- If you want custom rules, pass them through `autoformatExtension({ rules, inlineRules })`.
|
|
60
|
+
|
|
61
|
+
## Options
|
|
62
|
+
|
|
63
|
+
| Option | Default | Effect |
|
|
64
|
+
| --------------------------- | ------- | ------------------------------ |
|
|
65
|
+
| `disableDefaults` | `false` | Skip the built-in block rules |
|
|
66
|
+
| `disableDefaultInlineRules` | `false` | Skip the built-in inline rules |
|
|
67
|
+
| `rules` | unset | Extra block rules |
|
|
68
|
+
| `inlineRules` | unset | Extra inline rules |
|
|
69
|
+
|
|
70
|
+
## Facets and commands
|
|
71
|
+
|
|
72
|
+
Contributes no facet providers and no commands. The extension publishes a rules engine on `inputRulesEngineFacet` and rewrites matching input from `onBeforeApply`. Requires no other extensions. `defaultPreset()` does not install it.
|
|
73
|
+
|
|
74
|
+
## Documentation
|
|
75
|
+
|
|
76
|
+
The docs site (the `@input/pen-docs` package) covers this area on the Extensions and facets page (`#/extensions`).
|
|
77
|
+
|
|
78
|
+
The public signatures of record are in `api-report.md` next to this package's source in the Pen repository. The docs site does not host a generated browsable reference.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT © Input B.V. See [`LICENSE.md`](./LICENSE.md).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AUTOFORMAT_EXTENSION_NAME: () => AUTOFORMAT_EXTENSION_NAME,
|
|
24
|
+
autoformatExtension: () => autoformatExtension,
|
|
25
|
+
defaultBlockRules: () => defaultBlockRules,
|
|
26
|
+
defaultInlineRules: () => defaultInlineRules
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/extension.ts
|
|
31
|
+
var import_pen_types = require("@input/pen-types");
|
|
32
|
+
|
|
33
|
+
// src/engine.ts
|
|
34
|
+
var import_pen_core = require("@input/pen-core");
|
|
35
|
+
var InputRuleEngine = class {
|
|
36
|
+
_rules = [];
|
|
37
|
+
_inlineRules = [];
|
|
38
|
+
register(rule) {
|
|
39
|
+
const idx = this._rules.findIndex((r) => r.id === rule.id);
|
|
40
|
+
if (idx >= 0) {
|
|
41
|
+
this._rules[idx] = rule;
|
|
42
|
+
} else {
|
|
43
|
+
this._rules.push(rule);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
unregister(id) {
|
|
47
|
+
this._rules = this._rules.filter((r) => r.id !== id);
|
|
48
|
+
}
|
|
49
|
+
registerInline(rule) {
|
|
50
|
+
const idx = this._inlineRules.findIndex((r) => r.id === rule.id);
|
|
51
|
+
if (idx >= 0) {
|
|
52
|
+
this._inlineRules[idx] = rule;
|
|
53
|
+
} else {
|
|
54
|
+
this._inlineRules.push(rule);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
tryMatch(editor, blockId, insertedText, options = {}) {
|
|
58
|
+
if (insertedText !== " " && insertedText !== "\n") return null;
|
|
59
|
+
const handle = editor.getBlock(blockId);
|
|
60
|
+
if (!handle) return null;
|
|
61
|
+
const blockType = handle.type;
|
|
62
|
+
const fullText = handle.textContent();
|
|
63
|
+
const offset = this._resolveOffset(editor, blockId, options.offset);
|
|
64
|
+
if (offset == null) return null;
|
|
65
|
+
const textBefore = fullText.slice(0, offset) + insertedText;
|
|
66
|
+
const ctx = {
|
|
67
|
+
editor,
|
|
68
|
+
blockId,
|
|
69
|
+
blockType,
|
|
70
|
+
textBefore,
|
|
71
|
+
fullText
|
|
72
|
+
};
|
|
73
|
+
for (const rule of this._rules) {
|
|
74
|
+
if (rule.blockTypes && !rule.blockTypes.includes(blockType))
|
|
75
|
+
continue;
|
|
76
|
+
const match = textBefore.match(rule.match);
|
|
77
|
+
if (!match) continue;
|
|
78
|
+
const ops = rule.handler(match, ctx);
|
|
79
|
+
if (ops && ops.length > 0) return ops;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Attempts to match an inline markdown pattern (e.g. **bold**, *italic*).
|
|
85
|
+
* Called when the user types the closing delimiter character.
|
|
86
|
+
*/
|
|
87
|
+
tryMatchInline(editor, blockId, insertedText, options = {}) {
|
|
88
|
+
if (insertedText.length !== 1) return null;
|
|
89
|
+
const handle = editor.getBlock(blockId);
|
|
90
|
+
if (!handle) return null;
|
|
91
|
+
const schema = editor.schema.resolve(handle.type);
|
|
92
|
+
if (!(0, import_pen_core.supportsInlineInputRules)(schema)) return null;
|
|
93
|
+
const fullText = handle.textContent();
|
|
94
|
+
const offset = this._resolveOffset(editor, blockId, options.offset);
|
|
95
|
+
if (offset == null) return null;
|
|
96
|
+
const textWithInsert = fullText.slice(0, offset) + insertedText + fullText.slice(offset);
|
|
97
|
+
const cursorAfterInsert = offset + insertedText.length;
|
|
98
|
+
for (const rule of this._inlineRules) {
|
|
99
|
+
if (insertedText !== rule.trigger.slice(-1)) continue;
|
|
100
|
+
if (!editor.schema.resolveInline(rule.markType)) continue;
|
|
101
|
+
const match = rule.pattern.exec(textWithInsert);
|
|
102
|
+
if (!match) continue;
|
|
103
|
+
const matchEnd = match.index + match[0].length;
|
|
104
|
+
if (matchEnd !== cursorAfterInsert) continue;
|
|
105
|
+
const innerText = match[1];
|
|
106
|
+
if (!innerText || innerText.length === 0) continue;
|
|
107
|
+
const matchStart = match.index;
|
|
108
|
+
const fullMatchLength = match[0].length;
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
type: "splice-text",
|
|
112
|
+
blockId,
|
|
113
|
+
from: matchStart,
|
|
114
|
+
to: matchStart + fullMatchLength,
|
|
115
|
+
insert: ""
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
type: "splice-text",
|
|
119
|
+
blockId,
|
|
120
|
+
from: matchStart,
|
|
121
|
+
to: matchStart,
|
|
122
|
+
insert: innerText,
|
|
123
|
+
marks: { [rule.markType]: true }
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
_resolveOffset(editor, blockId, offsetOverride) {
|
|
130
|
+
if (typeof offsetOverride === "number") {
|
|
131
|
+
return offsetOverride;
|
|
132
|
+
}
|
|
133
|
+
const sel = editor.selection;
|
|
134
|
+
if (!sel || sel.type !== "text" || !(0, import_pen_core.isCollapsed)(sel)) return null;
|
|
135
|
+
if (sel.anchor.blockId !== blockId) return null;
|
|
136
|
+
return sel.anchor.offset;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/defaultRules.ts
|
|
141
|
+
var CALLOUT_SEVERITY_MAP = {
|
|
142
|
+
note: "info",
|
|
143
|
+
info: "info",
|
|
144
|
+
warning: "warning",
|
|
145
|
+
error: "error"
|
|
146
|
+
};
|
|
147
|
+
var defaultBlockRules = [
|
|
148
|
+
headingRule(1, /^#\s$/),
|
|
149
|
+
headingRule(2, /^##\s$/),
|
|
150
|
+
headingRule(3, /^###\s$/),
|
|
151
|
+
headingRule(4, /^####\s$/),
|
|
152
|
+
headingRule(5, /^#####\s$/),
|
|
153
|
+
headingRule(6, /^######\s$/),
|
|
154
|
+
{
|
|
155
|
+
id: "input-rule:unordered-list",
|
|
156
|
+
match: /^[-*+]\s$/,
|
|
157
|
+
blockTypes: ["paragraph"],
|
|
158
|
+
handler: (_match, ctx) => convertBlockOps(ctx, "bulletListItem", _match[0].length)
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "input-rule:ordered-list",
|
|
162
|
+
match: /^(\d+)\.\s$/,
|
|
163
|
+
blockTypes: ["paragraph"],
|
|
164
|
+
handler: (match, ctx) => {
|
|
165
|
+
const start = Number.parseInt(match[1], 10);
|
|
166
|
+
return convertBlockOps(
|
|
167
|
+
ctx,
|
|
168
|
+
"numberedListItem",
|
|
169
|
+
match[0].length,
|
|
170
|
+
start > 1 ? { start } : {}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "input-rule:check-list",
|
|
176
|
+
match: /^\[[\sx]?\]\s$/i,
|
|
177
|
+
blockTypes: ["paragraph"],
|
|
178
|
+
handler: (match, ctx) => {
|
|
179
|
+
const checked = match[0].toLowerCase().includes("x");
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
type: "splice-text",
|
|
183
|
+
blockId: ctx.blockId,
|
|
184
|
+
from: 0,
|
|
185
|
+
to: 0 + match[0].length,
|
|
186
|
+
insert: ""
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
type: "set-props",
|
|
190
|
+
blockId: ctx.blockId,
|
|
191
|
+
props: { type: "checkListItem", checked }
|
|
192
|
+
}
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "input-rule:blockquote",
|
|
198
|
+
match: /^>\s$/,
|
|
199
|
+
blockTypes: ["paragraph"],
|
|
200
|
+
handler: (match, ctx) => convertBlockOps(ctx, "blockquote", match[0].length)
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: "input-rule:code-block",
|
|
204
|
+
match: /^```[\s\n]$/,
|
|
205
|
+
blockTypes: ["paragraph"],
|
|
206
|
+
handler: (match, ctx) => convertBlockOps(ctx, "codeBlock", match[0].length)
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "input-rule:divider",
|
|
210
|
+
match: /^(?:---|\*\*\*|___)\s$/,
|
|
211
|
+
blockTypes: ["paragraph"],
|
|
212
|
+
handler: (match, ctx) => [
|
|
213
|
+
{
|
|
214
|
+
type: "splice-text",
|
|
215
|
+
blockId: ctx.blockId,
|
|
216
|
+
from: 0,
|
|
217
|
+
to: 0 + match[0].length,
|
|
218
|
+
insert: ""
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
type: "set-props",
|
|
222
|
+
blockId: ctx.blockId,
|
|
223
|
+
props: { type: "divider" }
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "input-rule:callout",
|
|
229
|
+
match: /^>\s*\[!(\w+)\]\s$/i,
|
|
230
|
+
blockTypes: ["paragraph"],
|
|
231
|
+
handler: (match, ctx) => {
|
|
232
|
+
const raw = (match[1] ?? "info").toLowerCase();
|
|
233
|
+
const severity = CALLOUT_SEVERITY_MAP[raw] ?? "info";
|
|
234
|
+
return convertBlockOps(ctx, "callout", match[0].length, {
|
|
235
|
+
severity
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
];
|
|
240
|
+
function headingRule(level, match) {
|
|
241
|
+
return {
|
|
242
|
+
id: `input-rule:heading-${level}`,
|
|
243
|
+
match,
|
|
244
|
+
blockTypes: ["paragraph"],
|
|
245
|
+
handler: (m, ctx) => convertBlockOps(ctx, "heading", m[0].length, { level })
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function convertBlockOps(ctx, newType, deleteLength, newProps = {}) {
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
type: "splice-text",
|
|
252
|
+
blockId: ctx.blockId,
|
|
253
|
+
from: 0,
|
|
254
|
+
to: 0 + deleteLength,
|
|
255
|
+
insert: ""
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
type: "set-props",
|
|
259
|
+
blockId: ctx.blockId,
|
|
260
|
+
props: { type: newType, ...newProps }
|
|
261
|
+
}
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/inlineRules.ts
|
|
266
|
+
var defaultInlineRules = [
|
|
267
|
+
{
|
|
268
|
+
id: "inline-rule:bold",
|
|
269
|
+
trigger: "*",
|
|
270
|
+
pattern: /\*\*(.+?)\*\*$/,
|
|
271
|
+
markType: "bold"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "inline-rule:italic",
|
|
275
|
+
trigger: "*",
|
|
276
|
+
// Single * that isn't preceded by another * (to avoid eating bold patterns).
|
|
277
|
+
// Matches *text* at end of text-before-cursor.
|
|
278
|
+
pattern: /(?<!\*)\*([^*]+?)\*$/,
|
|
279
|
+
markType: "italic"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "inline-rule:code",
|
|
283
|
+
trigger: "`",
|
|
284
|
+
pattern: /`([^`]+?)`$/,
|
|
285
|
+
markType: "code"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "inline-rule:strikethrough",
|
|
289
|
+
trigger: "~",
|
|
290
|
+
pattern: /~~(.+?)~~$/,
|
|
291
|
+
markType: "strikethrough"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "inline-rule:highlight",
|
|
295
|
+
trigger: "=",
|
|
296
|
+
pattern: /==(.+?)==$/,
|
|
297
|
+
markType: "highlight"
|
|
298
|
+
}
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
// src/extension.ts
|
|
302
|
+
var AUTOFORMAT_EXTENSION_NAME = "autoformat";
|
|
303
|
+
var BYPASS_ORIGINS = /* @__PURE__ */ new Set([
|
|
304
|
+
"input-rule",
|
|
305
|
+
"collaborator",
|
|
306
|
+
"import",
|
|
307
|
+
"history",
|
|
308
|
+
"system"
|
|
309
|
+
]);
|
|
310
|
+
function autoformatExtension(config = {}) {
|
|
311
|
+
const engine = new InputRuleEngine();
|
|
312
|
+
if (!config.disableDefaults) {
|
|
313
|
+
for (const rule of defaultBlockRules) {
|
|
314
|
+
engine.register(rule);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!config.disableDefaultInlineRules) {
|
|
318
|
+
for (const rule of defaultInlineRules) {
|
|
319
|
+
engine.registerInline(rule);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (config.rules) {
|
|
323
|
+
for (const rule of config.rules) {
|
|
324
|
+
engine.register(rule);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (config.inlineRules) {
|
|
328
|
+
for (const rule of config.inlineRules) {
|
|
329
|
+
engine.registerInline(rule);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
let unsub = null;
|
|
333
|
+
const produced = /* @__PURE__ */ new WeakSet();
|
|
334
|
+
return {
|
|
335
|
+
name: AUTOFORMAT_EXTENSION_NAME,
|
|
336
|
+
version: "0.0.0",
|
|
337
|
+
activateClient: async (ctx) => {
|
|
338
|
+
const { editor } = ctx;
|
|
339
|
+
unsub?.();
|
|
340
|
+
unsub = editor.onBeforeApply(
|
|
341
|
+
(ops, options) => {
|
|
342
|
+
const origin = options.origin ?? "user";
|
|
343
|
+
if (BYPASS_ORIGINS.has(origin)) return ops;
|
|
344
|
+
if (produced.has(ops)) return ops;
|
|
345
|
+
const next = appendInputRuleTransforms(editor, engine, ops);
|
|
346
|
+
produced.add(next);
|
|
347
|
+
return next;
|
|
348
|
+
},
|
|
349
|
+
{ priority: 300 }
|
|
350
|
+
);
|
|
351
|
+
ctx.editor.internals.assignSlot(
|
|
352
|
+
import_pen_types.INPUT_RULES_ENGINE_SLOT_KEY,
|
|
353
|
+
engine
|
|
354
|
+
);
|
|
355
|
+
},
|
|
356
|
+
deactivateClient: async () => {
|
|
357
|
+
unsub?.();
|
|
358
|
+
unsub = null;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function appendInputRuleTransforms(editor, engine, ops) {
|
|
363
|
+
const transformedOps = [];
|
|
364
|
+
for (const op of ops) {
|
|
365
|
+
transformedOps.push(op);
|
|
366
|
+
if (op.type !== "splice-text" || typeof op.insert !== "string" || op.insert.length !== 1 || op.from !== op.to) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const blockResult = engine.tryMatch(editor, op.blockId, op.insert, {
|
|
370
|
+
offset: op.from
|
|
371
|
+
});
|
|
372
|
+
if (blockResult) {
|
|
373
|
+
transformedOps.push(...blockResult);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const inlineResult = engine.tryMatchInline(
|
|
377
|
+
editor,
|
|
378
|
+
op.blockId,
|
|
379
|
+
op.insert,
|
|
380
|
+
{
|
|
381
|
+
offset: op.from
|
|
382
|
+
}
|
|
383
|
+
);
|
|
384
|
+
if (inlineResult) {
|
|
385
|
+
transformedOps.push(...inlineResult);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return transformedOps;
|
|
389
|
+
}
|
|
390
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
391
|
+
0 && (module.exports = {
|
|
392
|
+
AUTOFORMAT_EXTENSION_NAME,
|
|
393
|
+
autoformatExtension,
|
|
394
|
+
defaultBlockRules,
|
|
395
|
+
defaultInlineRules
|
|
396
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as _input_pen_types from '@input/pen-types';
|
|
2
|
+
import { Extension, InputRule } from '@input/pen-types';
|
|
3
|
+
export { InputRule, InputRuleContext, InputRuleHandler } from '@input/pen-types';
|
|
4
|
+
|
|
5
|
+
interface InlineInputRule {
|
|
6
|
+
id: string;
|
|
7
|
+
trigger: string;
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
markType: string;
|
|
10
|
+
}
|
|
11
|
+
interface AutoformatConfig {
|
|
12
|
+
rules?: _input_pen_types.InputRule[];
|
|
13
|
+
inlineRules?: InlineInputRule[];
|
|
14
|
+
disableDefaults?: boolean;
|
|
15
|
+
disableDefaultInlineRules?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare const AUTOFORMAT_EXTENSION_NAME = "autoformat";
|
|
19
|
+
declare function autoformatExtension(config?: AutoformatConfig): Extension;
|
|
20
|
+
|
|
21
|
+
declare const defaultBlockRules: InputRule[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Inline markdown shortcuts that fire when the user types the closing delimiter.
|
|
25
|
+
*
|
|
26
|
+
* Each pattern captures the inner text as group 1 and must match ending
|
|
27
|
+
* exactly at the cursor position. The engine deletes the full match
|
|
28
|
+
* (including delimiters) and re-inserts the inner text with the mark applied.
|
|
29
|
+
*
|
|
30
|
+
* Patterns use negative lookbehind/lookahead where needed to avoid
|
|
31
|
+
* false positives (e.g. `**` inside a word).
|
|
32
|
+
*/
|
|
33
|
+
declare const defaultInlineRules: InlineInputRule[];
|
|
34
|
+
|
|
35
|
+
export { AUTOFORMAT_EXTENSION_NAME, type AutoformatConfig, type InlineInputRule, autoformatExtension, defaultBlockRules, defaultInlineRules };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as _input_pen_types from '@input/pen-types';
|
|
2
|
+
import { Extension, InputRule } from '@input/pen-types';
|
|
3
|
+
export { InputRule, InputRuleContext, InputRuleHandler } from '@input/pen-types';
|
|
4
|
+
|
|
5
|
+
interface InlineInputRule {
|
|
6
|
+
id: string;
|
|
7
|
+
trigger: string;
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
markType: string;
|
|
10
|
+
}
|
|
11
|
+
interface AutoformatConfig {
|
|
12
|
+
rules?: _input_pen_types.InputRule[];
|
|
13
|
+
inlineRules?: InlineInputRule[];
|
|
14
|
+
disableDefaults?: boolean;
|
|
15
|
+
disableDefaultInlineRules?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare const AUTOFORMAT_EXTENSION_NAME = "autoformat";
|
|
19
|
+
declare function autoformatExtension(config?: AutoformatConfig): Extension;
|
|
20
|
+
|
|
21
|
+
declare const defaultBlockRules: InputRule[];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Inline markdown shortcuts that fire when the user types the closing delimiter.
|
|
25
|
+
*
|
|
26
|
+
* Each pattern captures the inner text as group 1 and must match ending
|
|
27
|
+
* exactly at the cursor position. The engine deletes the full match
|
|
28
|
+
* (including delimiters) and re-inserts the inner text with the mark applied.
|
|
29
|
+
*
|
|
30
|
+
* Patterns use negative lookbehind/lookahead where needed to avoid
|
|
31
|
+
* false positives (e.g. `**` inside a word).
|
|
32
|
+
*/
|
|
33
|
+
declare const defaultInlineRules: InlineInputRule[];
|
|
34
|
+
|
|
35
|
+
export { AUTOFORMAT_EXTENSION_NAME, type AutoformatConfig, type InlineInputRule, autoformatExtension, defaultBlockRules, defaultInlineRules };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// src/extension.ts
|
|
2
|
+
import { INPUT_RULES_ENGINE_SLOT_KEY } from "@input/pen-types";
|
|
3
|
+
|
|
4
|
+
// src/engine.ts
|
|
5
|
+
import { isCollapsed, supportsInlineInputRules } from "@input/pen-core";
|
|
6
|
+
var InputRuleEngine = class {
|
|
7
|
+
_rules = [];
|
|
8
|
+
_inlineRules = [];
|
|
9
|
+
register(rule) {
|
|
10
|
+
const idx = this._rules.findIndex((r) => r.id === rule.id);
|
|
11
|
+
if (idx >= 0) {
|
|
12
|
+
this._rules[idx] = rule;
|
|
13
|
+
} else {
|
|
14
|
+
this._rules.push(rule);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
unregister(id) {
|
|
18
|
+
this._rules = this._rules.filter((r) => r.id !== id);
|
|
19
|
+
}
|
|
20
|
+
registerInline(rule) {
|
|
21
|
+
const idx = this._inlineRules.findIndex((r) => r.id === rule.id);
|
|
22
|
+
if (idx >= 0) {
|
|
23
|
+
this._inlineRules[idx] = rule;
|
|
24
|
+
} else {
|
|
25
|
+
this._inlineRules.push(rule);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
tryMatch(editor, blockId, insertedText, options = {}) {
|
|
29
|
+
if (insertedText !== " " && insertedText !== "\n") return null;
|
|
30
|
+
const handle = editor.getBlock(blockId);
|
|
31
|
+
if (!handle) return null;
|
|
32
|
+
const blockType = handle.type;
|
|
33
|
+
const fullText = handle.textContent();
|
|
34
|
+
const offset = this._resolveOffset(editor, blockId, options.offset);
|
|
35
|
+
if (offset == null) return null;
|
|
36
|
+
const textBefore = fullText.slice(0, offset) + insertedText;
|
|
37
|
+
const ctx = {
|
|
38
|
+
editor,
|
|
39
|
+
blockId,
|
|
40
|
+
blockType,
|
|
41
|
+
textBefore,
|
|
42
|
+
fullText
|
|
43
|
+
};
|
|
44
|
+
for (const rule of this._rules) {
|
|
45
|
+
if (rule.blockTypes && !rule.blockTypes.includes(blockType))
|
|
46
|
+
continue;
|
|
47
|
+
const match = textBefore.match(rule.match);
|
|
48
|
+
if (!match) continue;
|
|
49
|
+
const ops = rule.handler(match, ctx);
|
|
50
|
+
if (ops && ops.length > 0) return ops;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Attempts to match an inline markdown pattern (e.g. **bold**, *italic*).
|
|
56
|
+
* Called when the user types the closing delimiter character.
|
|
57
|
+
*/
|
|
58
|
+
tryMatchInline(editor, blockId, insertedText, options = {}) {
|
|
59
|
+
if (insertedText.length !== 1) return null;
|
|
60
|
+
const handle = editor.getBlock(blockId);
|
|
61
|
+
if (!handle) return null;
|
|
62
|
+
const schema = editor.schema.resolve(handle.type);
|
|
63
|
+
if (!supportsInlineInputRules(schema)) return null;
|
|
64
|
+
const fullText = handle.textContent();
|
|
65
|
+
const offset = this._resolveOffset(editor, blockId, options.offset);
|
|
66
|
+
if (offset == null) return null;
|
|
67
|
+
const textWithInsert = fullText.slice(0, offset) + insertedText + fullText.slice(offset);
|
|
68
|
+
const cursorAfterInsert = offset + insertedText.length;
|
|
69
|
+
for (const rule of this._inlineRules) {
|
|
70
|
+
if (insertedText !== rule.trigger.slice(-1)) continue;
|
|
71
|
+
if (!editor.schema.resolveInline(rule.markType)) continue;
|
|
72
|
+
const match = rule.pattern.exec(textWithInsert);
|
|
73
|
+
if (!match) continue;
|
|
74
|
+
const matchEnd = match.index + match[0].length;
|
|
75
|
+
if (matchEnd !== cursorAfterInsert) continue;
|
|
76
|
+
const innerText = match[1];
|
|
77
|
+
if (!innerText || innerText.length === 0) continue;
|
|
78
|
+
const matchStart = match.index;
|
|
79
|
+
const fullMatchLength = match[0].length;
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
type: "splice-text",
|
|
83
|
+
blockId,
|
|
84
|
+
from: matchStart,
|
|
85
|
+
to: matchStart + fullMatchLength,
|
|
86
|
+
insert: ""
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: "splice-text",
|
|
90
|
+
blockId,
|
|
91
|
+
from: matchStart,
|
|
92
|
+
to: matchStart,
|
|
93
|
+
insert: innerText,
|
|
94
|
+
marks: { [rule.markType]: true }
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
_resolveOffset(editor, blockId, offsetOverride) {
|
|
101
|
+
if (typeof offsetOverride === "number") {
|
|
102
|
+
return offsetOverride;
|
|
103
|
+
}
|
|
104
|
+
const sel = editor.selection;
|
|
105
|
+
if (!sel || sel.type !== "text" || !isCollapsed(sel)) return null;
|
|
106
|
+
if (sel.anchor.blockId !== blockId) return null;
|
|
107
|
+
return sel.anchor.offset;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// src/defaultRules.ts
|
|
112
|
+
var CALLOUT_SEVERITY_MAP = {
|
|
113
|
+
note: "info",
|
|
114
|
+
info: "info",
|
|
115
|
+
warning: "warning",
|
|
116
|
+
error: "error"
|
|
117
|
+
};
|
|
118
|
+
var defaultBlockRules = [
|
|
119
|
+
headingRule(1, /^#\s$/),
|
|
120
|
+
headingRule(2, /^##\s$/),
|
|
121
|
+
headingRule(3, /^###\s$/),
|
|
122
|
+
headingRule(4, /^####\s$/),
|
|
123
|
+
headingRule(5, /^#####\s$/),
|
|
124
|
+
headingRule(6, /^######\s$/),
|
|
125
|
+
{
|
|
126
|
+
id: "input-rule:unordered-list",
|
|
127
|
+
match: /^[-*+]\s$/,
|
|
128
|
+
blockTypes: ["paragraph"],
|
|
129
|
+
handler: (_match, ctx) => convertBlockOps(ctx, "bulletListItem", _match[0].length)
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "input-rule:ordered-list",
|
|
133
|
+
match: /^(\d+)\.\s$/,
|
|
134
|
+
blockTypes: ["paragraph"],
|
|
135
|
+
handler: (match, ctx) => {
|
|
136
|
+
const start = Number.parseInt(match[1], 10);
|
|
137
|
+
return convertBlockOps(
|
|
138
|
+
ctx,
|
|
139
|
+
"numberedListItem",
|
|
140
|
+
match[0].length,
|
|
141
|
+
start > 1 ? { start } : {}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "input-rule:check-list",
|
|
147
|
+
match: /^\[[\sx]?\]\s$/i,
|
|
148
|
+
blockTypes: ["paragraph"],
|
|
149
|
+
handler: (match, ctx) => {
|
|
150
|
+
const checked = match[0].toLowerCase().includes("x");
|
|
151
|
+
return [
|
|
152
|
+
{
|
|
153
|
+
type: "splice-text",
|
|
154
|
+
blockId: ctx.blockId,
|
|
155
|
+
from: 0,
|
|
156
|
+
to: 0 + match[0].length,
|
|
157
|
+
insert: ""
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: "set-props",
|
|
161
|
+
blockId: ctx.blockId,
|
|
162
|
+
props: { type: "checkListItem", checked }
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "input-rule:blockquote",
|
|
169
|
+
match: /^>\s$/,
|
|
170
|
+
blockTypes: ["paragraph"],
|
|
171
|
+
handler: (match, ctx) => convertBlockOps(ctx, "blockquote", match[0].length)
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "input-rule:code-block",
|
|
175
|
+
match: /^```[\s\n]$/,
|
|
176
|
+
blockTypes: ["paragraph"],
|
|
177
|
+
handler: (match, ctx) => convertBlockOps(ctx, "codeBlock", match[0].length)
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "input-rule:divider",
|
|
181
|
+
match: /^(?:---|\*\*\*|___)\s$/,
|
|
182
|
+
blockTypes: ["paragraph"],
|
|
183
|
+
handler: (match, ctx) => [
|
|
184
|
+
{
|
|
185
|
+
type: "splice-text",
|
|
186
|
+
blockId: ctx.blockId,
|
|
187
|
+
from: 0,
|
|
188
|
+
to: 0 + match[0].length,
|
|
189
|
+
insert: ""
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
type: "set-props",
|
|
193
|
+
blockId: ctx.blockId,
|
|
194
|
+
props: { type: "divider" }
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: "input-rule:callout",
|
|
200
|
+
match: /^>\s*\[!(\w+)\]\s$/i,
|
|
201
|
+
blockTypes: ["paragraph"],
|
|
202
|
+
handler: (match, ctx) => {
|
|
203
|
+
const raw = (match[1] ?? "info").toLowerCase();
|
|
204
|
+
const severity = CALLOUT_SEVERITY_MAP[raw] ?? "info";
|
|
205
|
+
return convertBlockOps(ctx, "callout", match[0].length, {
|
|
206
|
+
severity
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
function headingRule(level, match) {
|
|
212
|
+
return {
|
|
213
|
+
id: `input-rule:heading-${level}`,
|
|
214
|
+
match,
|
|
215
|
+
blockTypes: ["paragraph"],
|
|
216
|
+
handler: (m, ctx) => convertBlockOps(ctx, "heading", m[0].length, { level })
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function convertBlockOps(ctx, newType, deleteLength, newProps = {}) {
|
|
220
|
+
return [
|
|
221
|
+
{
|
|
222
|
+
type: "splice-text",
|
|
223
|
+
blockId: ctx.blockId,
|
|
224
|
+
from: 0,
|
|
225
|
+
to: 0 + deleteLength,
|
|
226
|
+
insert: ""
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: "set-props",
|
|
230
|
+
blockId: ctx.blockId,
|
|
231
|
+
props: { type: newType, ...newProps }
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/inlineRules.ts
|
|
237
|
+
var defaultInlineRules = [
|
|
238
|
+
{
|
|
239
|
+
id: "inline-rule:bold",
|
|
240
|
+
trigger: "*",
|
|
241
|
+
pattern: /\*\*(.+?)\*\*$/,
|
|
242
|
+
markType: "bold"
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "inline-rule:italic",
|
|
246
|
+
trigger: "*",
|
|
247
|
+
// Single * that isn't preceded by another * (to avoid eating bold patterns).
|
|
248
|
+
// Matches *text* at end of text-before-cursor.
|
|
249
|
+
pattern: /(?<!\*)\*([^*]+?)\*$/,
|
|
250
|
+
markType: "italic"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "inline-rule:code",
|
|
254
|
+
trigger: "`",
|
|
255
|
+
pattern: /`([^`]+?)`$/,
|
|
256
|
+
markType: "code"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "inline-rule:strikethrough",
|
|
260
|
+
trigger: "~",
|
|
261
|
+
pattern: /~~(.+?)~~$/,
|
|
262
|
+
markType: "strikethrough"
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: "inline-rule:highlight",
|
|
266
|
+
trigger: "=",
|
|
267
|
+
pattern: /==(.+?)==$/,
|
|
268
|
+
markType: "highlight"
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
// src/extension.ts
|
|
273
|
+
var AUTOFORMAT_EXTENSION_NAME = "autoformat";
|
|
274
|
+
var BYPASS_ORIGINS = /* @__PURE__ */ new Set([
|
|
275
|
+
"input-rule",
|
|
276
|
+
"collaborator",
|
|
277
|
+
"import",
|
|
278
|
+
"history",
|
|
279
|
+
"system"
|
|
280
|
+
]);
|
|
281
|
+
function autoformatExtension(config = {}) {
|
|
282
|
+
const engine = new InputRuleEngine();
|
|
283
|
+
if (!config.disableDefaults) {
|
|
284
|
+
for (const rule of defaultBlockRules) {
|
|
285
|
+
engine.register(rule);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!config.disableDefaultInlineRules) {
|
|
289
|
+
for (const rule of defaultInlineRules) {
|
|
290
|
+
engine.registerInline(rule);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (config.rules) {
|
|
294
|
+
for (const rule of config.rules) {
|
|
295
|
+
engine.register(rule);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (config.inlineRules) {
|
|
299
|
+
for (const rule of config.inlineRules) {
|
|
300
|
+
engine.registerInline(rule);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
let unsub = null;
|
|
304
|
+
const produced = /* @__PURE__ */ new WeakSet();
|
|
305
|
+
return {
|
|
306
|
+
name: AUTOFORMAT_EXTENSION_NAME,
|
|
307
|
+
version: "0.0.0",
|
|
308
|
+
activateClient: async (ctx) => {
|
|
309
|
+
const { editor } = ctx;
|
|
310
|
+
unsub?.();
|
|
311
|
+
unsub = editor.onBeforeApply(
|
|
312
|
+
(ops, options) => {
|
|
313
|
+
const origin = options.origin ?? "user";
|
|
314
|
+
if (BYPASS_ORIGINS.has(origin)) return ops;
|
|
315
|
+
if (produced.has(ops)) return ops;
|
|
316
|
+
const next = appendInputRuleTransforms(editor, engine, ops);
|
|
317
|
+
produced.add(next);
|
|
318
|
+
return next;
|
|
319
|
+
},
|
|
320
|
+
{ priority: 300 }
|
|
321
|
+
);
|
|
322
|
+
ctx.editor.internals.assignSlot(
|
|
323
|
+
INPUT_RULES_ENGINE_SLOT_KEY,
|
|
324
|
+
engine
|
|
325
|
+
);
|
|
326
|
+
},
|
|
327
|
+
deactivateClient: async () => {
|
|
328
|
+
unsub?.();
|
|
329
|
+
unsub = null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function appendInputRuleTransforms(editor, engine, ops) {
|
|
334
|
+
const transformedOps = [];
|
|
335
|
+
for (const op of ops) {
|
|
336
|
+
transformedOps.push(op);
|
|
337
|
+
if (op.type !== "splice-text" || typeof op.insert !== "string" || op.insert.length !== 1 || op.from !== op.to) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const blockResult = engine.tryMatch(editor, op.blockId, op.insert, {
|
|
341
|
+
offset: op.from
|
|
342
|
+
});
|
|
343
|
+
if (blockResult) {
|
|
344
|
+
transformedOps.push(...blockResult);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const inlineResult = engine.tryMatchInline(
|
|
348
|
+
editor,
|
|
349
|
+
op.blockId,
|
|
350
|
+
op.insert,
|
|
351
|
+
{
|
|
352
|
+
offset: op.from
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
if (inlineResult) {
|
|
356
|
+
transformedOps.push(...inlineResult);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return transformedOps;
|
|
360
|
+
}
|
|
361
|
+
export {
|
|
362
|
+
AUTOFORMAT_EXTENSION_NAME,
|
|
363
|
+
autoformatExtension,
|
|
364
|
+
defaultBlockRules,
|
|
365
|
+
defaultInlineRules
|
|
366
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@input/pen-autoformat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Markdown shortcut input rules for Pen — auto-format as you type",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/input-systems/pen#readme",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/input-systems/pen/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/input-systems/pen.git",
|
|
13
|
+
"directory": "packages/extensions/autoformat"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org/"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"import": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"require": {
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
28
|
+
"default": "./dist/index.cjs"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.mjs",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE.md"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@input/pen-core": "^0.1.0",
|
|
47
|
+
"@input/pen-types": "^0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"tsup": "^8.4.0",
|
|
51
|
+
"typescript": "^5.7.3",
|
|
52
|
+
"vitest": "^3.2.7"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsup",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint": "eslint .",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
60
|
+
}
|
|
61
|
+
}
|