@thomasfosterau/effect-richtext 0.0.0 → 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.
Files changed (176) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +117 -1
  3. package/dist/_virtual/_rolldown/runtime.js +12 -0
  4. package/dist/commands/commands.d.ts +323 -0
  5. package/dist/commands/commands.js +722 -0
  6. package/dist/commands/internal.js +43 -0
  7. package/dist/commands/keymaps.d.ts +46 -0
  8. package/dist/commands/keymaps.js +64 -0
  9. package/dist/commands.d.ts +3 -0
  10. package/dist/commands.js +3 -0
  11. package/dist/compat/commands.d.ts +250 -0
  12. package/dist/compat/commands.js +275 -0
  13. package/dist/compat/internal/core.d.ts +20 -0
  14. package/dist/compat/internal/core.js +148 -0
  15. package/dist/compat/internal/props.js +82 -0
  16. package/dist/compat/keymap.d.ts +21 -0
  17. package/dist/compat/keymap.js +38 -0
  18. package/dist/compat/model.d.ts +893 -0
  19. package/dist/compat/model.js +1212 -0
  20. package/dist/compat/orderedmap.d.ts +74 -0
  21. package/dist/compat/orderedmap.js +124 -0
  22. package/dist/compat/schema-basic.d.ts +38 -0
  23. package/dist/compat/schema-basic.js +238 -0
  24. package/dist/compat/schema-list.d.ts +81 -0
  25. package/dist/compat/schema-list.js +156 -0
  26. package/dist/compat/state.d.ts +415 -0
  27. package/dist/compat/state.js +689 -0
  28. package/dist/compat/test-builder.d.ts +98 -0
  29. package/dist/compat/test-builder.js +197 -0
  30. package/dist/compat/transform.d.ts +458 -0
  31. package/dist/compat/transform.js +803 -0
  32. package/dist/compat/view.d.ts +411 -0
  33. package/dist/compat/view.js +409 -0
  34. package/dist/dropcursor.d.ts +46 -0
  35. package/dist/dropcursor.js +176 -0
  36. package/dist/extensions/defaults.d.ts +716 -0
  37. package/dist/extensions/defaults.js +209 -0
  38. package/dist/extensions/editor.d.ts +246 -0
  39. package/dist/extensions/editor.js +271 -0
  40. package/dist/extensions/extension.d.ts +375 -0
  41. package/dist/extensions/extension.js +233 -0
  42. package/dist/extensions.d.ts +4 -0
  43. package/dist/extensions.js +4 -0
  44. package/dist/gapcursor.d.ts +66 -0
  45. package/dist/gapcursor.js +273 -0
  46. package/dist/history.d.ts +178 -0
  47. package/dist/history.js +412 -0
  48. package/dist/html.d.ts +158 -0
  49. package/dist/html.js +164 -0
  50. package/dist/inputrules.d.ts +271 -0
  51. package/dist/inputrules.js +321 -0
  52. package/dist/internal/attempt.js +15 -0
  53. package/dist/internal/json.js +29 -0
  54. package/dist/keymap/keymap.d.ts +130 -0
  55. package/dist/keymap/keymap.js +159 -0
  56. package/dist/keymap.d.ts +2 -0
  57. package/dist/keymap.js +1 -0
  58. package/dist/markdown/errors.d.ts +23 -0
  59. package/dist/markdown/errors.js +26 -0
  60. package/dist/markdown/from_markdown.d.ts +185 -0
  61. package/dist/markdown/from_markdown.js +266 -0
  62. package/dist/markdown/schema.d.ts +522 -0
  63. package/dist/markdown/schema.js +292 -0
  64. package/dist/markdown/to_markdown.d.ts +251 -0
  65. package/dist/markdown/to_markdown.js +413 -0
  66. package/dist/markdown.d.ts +5 -0
  67. package/dist/markdown.js +5 -0
  68. package/dist/model/attrsCodec.js +48 -0
  69. package/dist/model/comparedeep.js +17 -0
  70. package/dist/model/content.d.ts +232 -0
  71. package/dist/model/content.js +630 -0
  72. package/dist/model/diff.js +66 -0
  73. package/dist/model/dom.d.ts +3 -0
  74. package/dist/model/errors.d.ts +90 -0
  75. package/dist/model/errors.js +76 -0
  76. package/dist/model/fragment.d.ts +131 -0
  77. package/dist/model/fragment.js +262 -0
  78. package/dist/model/from_dom.d.ts +299 -0
  79. package/dist/model/from_dom.js +618 -0
  80. package/dist/model/json.d.ts +37 -0
  81. package/dist/model/json.js +126 -0
  82. package/dist/model/mark.d.ts +73 -0
  83. package/dist/model/mark.js +121 -0
  84. package/dist/model/node.d.ts +267 -0
  85. package/dist/model/node.js +434 -0
  86. package/dist/model/replace.d.ts +78 -0
  87. package/dist/model/replace.js +207 -0
  88. package/dist/model/resolvedpos.d.ts +196 -0
  89. package/dist/model/resolvedpos.js +305 -0
  90. package/dist/model/schema.d.ts +740 -0
  91. package/dist/model/schema.js +451 -0
  92. package/dist/model/to_dom.d.ts +169 -0
  93. package/dist/model/to_dom.js +238 -0
  94. package/dist/model.d.ts +12 -0
  95. package/dist/model.js +12 -0
  96. package/dist/placeholder.d.ts +53 -0
  97. package/dist/placeholder.js +79 -0
  98. package/dist/reactive/observer.d.ts +54 -0
  99. package/dist/reactive/observer.js +61 -0
  100. package/dist/reactive/selectors.d.ts +22 -0
  101. package/dist/reactive/selectors.js +61 -0
  102. package/dist/reactive/stateref.d.ts +72 -0
  103. package/dist/reactive/stateref.js +71 -0
  104. package/dist/reactive/streams.d.ts +83 -0
  105. package/dist/reactive/streams.js +85 -0
  106. package/dist/reactive.d.ts +16 -0
  107. package/dist/reactive.js +26 -0
  108. package/dist/schema-basic.d.ts +448 -0
  109. package/dist/schema-basic.js +269 -0
  110. package/dist/schema-list/commands.d.ts +66 -0
  111. package/dist/schema-list/commands.js +203 -0
  112. package/dist/schema-list.d.ts +92 -0
  113. package/dist/schema-list.js +100 -0
  114. package/dist/state/errors.d.ts +39 -0
  115. package/dist/state/errors.js +32 -0
  116. package/dist/state/plugin.d.ts +232 -0
  117. package/dist/state/plugin.js +82 -0
  118. package/dist/state/selection.d.ts +297 -0
  119. package/dist/state/selection.js +486 -0
  120. package/dist/state/state.d.ts +212 -0
  121. package/dist/state/state.js +489 -0
  122. package/dist/state/transaction.d.ts +145 -0
  123. package/dist/state/transaction.js +195 -0
  124. package/dist/state.d.ts +6 -0
  125. package/dist/state.js +6 -0
  126. package/dist/test-builder.d.ts +3582 -0
  127. package/dist/test-builder.js +239 -0
  128. package/dist/transform/attr_step.d.ts +60 -0
  129. package/dist/transform/attr_step.js +105 -0
  130. package/dist/transform/errors.d.ts +50 -0
  131. package/dist/transform/errors.js +35 -0
  132. package/dist/transform/map.d.ts +182 -0
  133. package/dist/transform/map.js +280 -0
  134. package/dist/transform/mark.js +98 -0
  135. package/dist/transform/mark_set_step.js +69 -0
  136. package/dist/transform/mark_step.d.ts +113 -0
  137. package/dist/transform/mark_step.js +225 -0
  138. package/dist/transform/replace.d.ts +18 -0
  139. package/dist/transform/replace.js +381 -0
  140. package/dist/transform/replace_step.d.ts +109 -0
  141. package/dist/transform/replace_step.js +175 -0
  142. package/dist/transform/step.d.ts +157 -0
  143. package/dist/transform/step.js +168 -0
  144. package/dist/transform/structure.d.ts +89 -0
  145. package/dist/transform/structure.js +350 -0
  146. package/dist/transform/transform.d.ts +248 -0
  147. package/dist/transform/transform.js +373 -0
  148. package/dist/transform.d.ts +10 -0
  149. package/dist/transform.js +10 -0
  150. package/dist/view/browser.js +39 -0
  151. package/dist/view/capturekeys.js +282 -0
  152. package/dist/view/clipboard.d.ts +8 -0
  153. package/dist/view/clipboard.js +245 -0
  154. package/dist/view/decoration.d.ts +339 -0
  155. package/dist/view/decoration.js +566 -0
  156. package/dist/view/dom.d.ts +18 -0
  157. package/dist/view/dom.js +140 -0
  158. package/dist/view/domchange.js +295 -0
  159. package/dist/view/domcoords.d.ts +22 -0
  160. package/dist/view/domcoords.js +448 -0
  161. package/dist/view/domobserver.d.ts +47 -0
  162. package/dist/view/domobserver.js +328 -0
  163. package/dist/view/errors.d.ts +18 -0
  164. package/dist/view/errors.js +21 -0
  165. package/dist/view/input.d.ts +58 -0
  166. package/dist/view/input.js +704 -0
  167. package/dist/view/selection.js +198 -0
  168. package/dist/view/view.d.ts +699 -0
  169. package/dist/view/view.js +623 -0
  170. package/dist/view/viewdesc.d.ts +236 -0
  171. package/dist/view/viewdesc.js +1205 -0
  172. package/dist/view-css.d.ts +43 -0
  173. package/dist/view-css.js +124 -0
  174. package/dist/view.d.ts +6 -0
  175. package/dist/view.js +4 -0
  176. package/package.json +200 -4
@@ -0,0 +1,321 @@
1
+ import { Plugin } from "./state/plugin.js";
2
+ import { TextSelection } from "./state/selection.js";
3
+ import { canJoin, findWrappingOrNull } from "./transform/structure.js";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Option from "effect/Option";
6
+ import * as Predicate from "effect/Predicate";
7
+ // src/inputrules.ts
8
+ /**
9
+ * Input rules: regular expressions describing a piece of text that,
10
+ * when typed, causes something to happen. Ported from
11
+ * `prosemirror-inputrules`.
12
+ *
13
+ * ```ts skip-type-checking
14
+ * const plugin = inputRules({
15
+ * rules: [...smartQuotes, emDash, wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote)]
16
+ * })
17
+ * ```
18
+ *
19
+ * Rule handlers may return a transaction (or `null`), like in
20
+ * ProseMirror, but also an `Option<Transaction>` or an
21
+ * `Effect<Option<Transaction>, E, R>`. The errors and requirements of
22
+ * effectful rules end up in the plugin's type.
23
+ *
24
+ * @since 0.1.0
25
+ */
26
+ /**
27
+ * @since 0.1.0
28
+ * @category symbols
29
+ */
30
+ const InputRuleTypeId = Symbol.for("effect-richtext/inputrules/InputRule");
31
+ /**
32
+ * Input rules are regular expressions describing a piece of text that,
33
+ * when typed, causes something to happen. This might be changing two
34
+ * dashes into an emdash, wrapping a paragraph starting with `"> "` into
35
+ * a blockquote, or something entirely different.
36
+ *
37
+ * @since 0.1.0
38
+ * @category models
39
+ */
40
+ var InputRule = class {
41
+ /**
42
+ * Create an input rule. The rule applies when the user typed something
43
+ * and the text directly in front of the cursor matches `match`, which
44
+ * should end with `$`.
45
+ *
46
+ * The `handler` can be a string, in which case the matched text, or
47
+ * the first matched group in the regexp, is replaced by that string.
48
+ *
49
+ * Or it can be a function, which will be called with the match array
50
+ * produced by `RegExp.exec`, as well as the start and end of the
51
+ * matched range, and which can return a transaction that describes the
52
+ * rule's effect, or `null` to indicate the input was not handled. It
53
+ * may also return an `Option` of a transaction, or an Effect producing
54
+ * one.
55
+ */
56
+ constructor(match, handler, options = {}) {
57
+ this.match = match;
58
+ this.handler = Predicate.isString(handler) ? stringHandler(handler) : handler;
59
+ this.undoable = options.undoable !== false;
60
+ this.inCode = options.inCode || false;
61
+ this.inCodeMark = options.inCodeMark !== false;
62
+ }
63
+ };
64
+ function stringHandler(string) {
65
+ return (state, match, start, end) => {
66
+ let insert = string;
67
+ if (match[1]) {
68
+ const offset = match[0].lastIndexOf(match[1]);
69
+ insert += match[0].slice(offset + match[1].length);
70
+ start += offset;
71
+ const cutOff = start - end;
72
+ if (cutOff > 0) {
73
+ insert = match[0].slice(offset - cutOff, offset) + insert;
74
+ start = end;
75
+ }
76
+ }
77
+ return state.tr.insertTextUnsafe(insert, start, end);
78
+ };
79
+ }
80
+ const MAX_MATCH = 500;
81
+ /**
82
+ * Create an input rules plugin. When enabled, it will cause text input
83
+ * that matches any of the given rules to trigger the rule's action.
84
+ *
85
+ * Rules are tried in order. Rules whose handler returns an Effect run
86
+ * with the view's context: when such an effect completes synchronously,
87
+ * it behaves exactly like a plain rule. When it is asynchronous, the
88
+ * input is considered handled; when it eventually finds no transaction
89
+ * (or fails), the typed text is inserted, and when it produces a
90
+ * transaction, it is dispatched—both only if the editor state didn't
91
+ * change in the meantime.
92
+ *
93
+ * @since 0.1.0
94
+ * @category constructors
95
+ */
96
+ const inputRules = ({ rules }) => {
97
+ const plugin = new Plugin({
98
+ state: {
99
+ init: () => Option.none(),
100
+ apply(tr, prev) {
101
+ const stored = tr.getMeta(plugin);
102
+ if (Option.isSome(stored)) return stored;
103
+ return tr.selectionSet || tr.docChanged ? Option.none() : prev;
104
+ }
105
+ },
106
+ props: {
107
+ handleTextInput(view, from, to, text, deflt) {
108
+ return run(view, from, to, text, rules, plugin, deflt);
109
+ },
110
+ handleDOMEvents: { compositionend: (view) => {
111
+ setTimeout(() => {
112
+ if (view.isDestroyed) return;
113
+ const sel = view.state.selection;
114
+ const $cursor = sel instanceof TextSelection ? sel.$cursorOrNull : null;
115
+ if ($cursor) view.runHandler(run(view, $cursor.pos, $cursor.pos, "", rules, plugin, void 0));
116
+ });
117
+ return false;
118
+ } }
119
+ },
120
+ isInputRules: true
121
+ });
122
+ return plugin;
123
+ };
124
+ const hasCodeMark = (marks) => marks.some((m) => m.type.spec.code);
125
+ function run(view, from, to, text, rules, plugin, deflt) {
126
+ if (view.composing) return false;
127
+ const state = view.state, $from = state.doc.resolveUnsafe(from);
128
+ return tryRules({
129
+ view,
130
+ state,
131
+ $from,
132
+ from,
133
+ to,
134
+ text,
135
+ textBefore: $from.parent.textBetween(Math.max(0, $from.parentOffset - MAX_MATCH), $from.parentOffset, null, "") + text,
136
+ rules,
137
+ plugin,
138
+ deflt,
139
+ sync: true
140
+ }, 0);
141
+ }
142
+ function tryRules(cx, start) {
143
+ const { $from, rules, state, text, textBefore } = cx;
144
+ for (let i = start; i < rules.length; i++) {
145
+ const rule = rules[i];
146
+ if (!rule.inCodeMark && hasCodeMark($from.marks())) continue;
147
+ if ($from.parent.type.spec.code) {
148
+ if (!rule.inCode) continue;
149
+ } else if (rule.inCode === "only") continue;
150
+ const match = rule.match.exec(textBefore);
151
+ if (!match || match[0].length < text.length) continue;
152
+ const startPos = cx.from - (match[0].length - text.length);
153
+ if (!rule.inCodeMark) {
154
+ let hasMark = false;
155
+ state.doc.nodesBetween(startPos, $from.pos, (node) => {
156
+ if (node.isInline && hasCodeMark(node.marks)) hasMark = true;
157
+ });
158
+ if (hasMark) continue;
159
+ }
160
+ const result = rule.handler(state, match, startPos, cx.to);
161
+ if (Effect.isEffect(result)) return runEffectRule(cx, rule, result, i + 1);
162
+ const tr = Option.isOption(result) ? Option.getOrNull(result) : result;
163
+ if (!tr) continue;
164
+ return applyRule(cx, rule, tr);
165
+ }
166
+ return false;
167
+ }
168
+ function applyRule(cx, rule, tr) {
169
+ if (rule.undoable) tr.setMeta(cx.plugin, {
170
+ transform: tr,
171
+ from: cx.from,
172
+ to: cx.to,
173
+ text: cx.text
174
+ });
175
+ return Effect.as(cx.view.dispatch(tr), true);
176
+ }
177
+ function fallback(cx) {
178
+ if (cx.sync || !cx.deflt || cx.view.isDestroyed || cx.view.state !== cx.state) return Effect.succeed(false);
179
+ return Effect.as(cx.view.dispatch(cx.deflt()), true);
180
+ }
181
+ function runEffectRule(cx, rule, effect, next) {
182
+ if (cx.sync) queueMicrotask(() => cx.sync = false);
183
+ return effect.pipe(Effect.flatMap((result) => {
184
+ if (Option.isSome(result)) {
185
+ if (cx.view.isDestroyed || cx.view.state !== cx.state) return Effect.succeed(true);
186
+ return applyRule(cx, rule, result.value);
187
+ }
188
+ const rest = tryRules(cx, next);
189
+ if (Effect.isEffect(rest)) return rest;
190
+ return fallback(cx);
191
+ }), Effect.tapCause(() => fallback(cx)));
192
+ }
193
+ /**
194
+ * This is a command that will undo an input rule, if applying such a
195
+ * rule was the last thing that the user did.
196
+ *
197
+ * @since 0.1.0
198
+ * @category commands
199
+ */
200
+ const undoInputRule = (state, dispatch) => Effect.suspend(() => {
201
+ const plugins = state.plugins;
202
+ for (let i = 0; i < plugins.length; i++) {
203
+ const plugin = plugins[i];
204
+ if (!plugin.spec.isInputRules) continue;
205
+ const undoable = Option.getOrNull(Option.flatten(plugin.getState(state)));
206
+ if (!undoable) continue;
207
+ if (!dispatch) return Effect.succeed(true);
208
+ const tr = state.tr, toUndo = undoable.transform;
209
+ for (let j = toUndo.steps.length - 1; j >= 0; j--) tr.stepUnsafe(toUndo.steps[j].invert(toUndo.docs[j]));
210
+ if (undoable.text) {
211
+ const marks = tr.doc.resolveUnsafe(undoable.from).marks();
212
+ tr.replaceWithUnsafe(undoable.from, undoable.to, state.schema.textUnsafe(undoable.text, marks));
213
+ } else tr.deleteUnsafe(undoable.from, undoable.to);
214
+ return Effect.as(dispatch(tr), true);
215
+ }
216
+ return Effect.succeed(false);
217
+ });
218
+ const resolveAttrs = (getAttrs, match) => Predicate.isFunction(getAttrs) ? getAttrs(match) : getAttrs;
219
+ /**
220
+ * Build an input rule for automatically wrapping a textblock when a
221
+ * given string is typed. The `regexp` argument is directly passed
222
+ * through to the `InputRule` constructor. You'll probably want the
223
+ * regexp to start with `^`, so that the pattern can only occur at the
224
+ * start of a textblock.
225
+ *
226
+ * `nodeType` is the type of node to wrap in. If it needs attributes,
227
+ * you can either pass them directly, or pass a function that will
228
+ * compute them from the regular expression match.
229
+ *
230
+ * By default, if there's a node with the same type above the newly
231
+ * wrapped node, the rule will try to join those two nodes. You can pass
232
+ * a join predicate, which takes a regular expression match and the node
233
+ * before the wrapped node, and can return a boolean to indicate whether
234
+ * a join should happen.
235
+ *
236
+ * @since 0.1.0
237
+ * @category constructors
238
+ */
239
+ const wrappingInputRule = (regexp, nodeType, getAttrs = null, joinPredicate) => new InputRule(regexp, (state, match, start, end) => {
240
+ const attrs = resolveAttrs(getAttrs, match);
241
+ const tr = state.tr.deleteUnsafe(start, end);
242
+ const range = tr.doc.resolveUnsafe(start).blockRangeOrNull();
243
+ const wrapping = range && findWrappingOrNull(range, nodeType, attrs);
244
+ if (!wrapping) return null;
245
+ tr.wrapUnsafe(range, wrapping);
246
+ const before = tr.doc.resolveUnsafe(start - 1).nodeBeforeOrNull;
247
+ if (before && before.type === nodeType && canJoin(tr.doc, start - 1) && (!joinPredicate || joinPredicate(match, before))) tr.joinUnsafe(start - 1);
248
+ return tr;
249
+ });
250
+ /**
251
+ * Build an input rule that changes the type of a textblock when the
252
+ * matched text is typed into it. You'll usually want to start your
253
+ * regexp with `^` so that it is only matched at the start of a
254
+ * textblock. The optional `getAttrs` parameter can be used to compute
255
+ * the new node's attributes, and works the same as in the
256
+ * `wrappingInputRule` function.
257
+ *
258
+ * @since 0.1.0
259
+ * @category constructors
260
+ */
261
+ const textblockTypeInputRule = (regexp, nodeType, getAttrs = null) => new InputRule(regexp, (state, match, start, end) => {
262
+ const $start = state.doc.resolveUnsafe(start);
263
+ const attrs = resolveAttrs(getAttrs, match);
264
+ if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), nodeType)) return null;
265
+ return state.tr.deleteUnsafe(start, end).setBlockTypeUnsafe(start, start, nodeType, attrs);
266
+ });
267
+ /**
268
+ * Converts double dashes to an emdash.
269
+ *
270
+ * @since 0.1.0
271
+ * @category rules
272
+ */
273
+ const emDash = new InputRule(/--$/, "—", { inCodeMark: false });
274
+ /**
275
+ * Converts three dots to an ellipsis character.
276
+ *
277
+ * @since 0.1.0
278
+ * @category rules
279
+ */
280
+ const ellipsis = new InputRule(/\.\.\.$/, "…", { inCodeMark: false });
281
+ /**
282
+ * “Smart” opening double quotes.
283
+ *
284
+ * @since 0.1.0
285
+ * @category rules
286
+ */
287
+ const openDoubleQuote = new InputRule(/(?:^|[\s{[(<'"‘“])(")$/, "“", { inCodeMark: false });
288
+ /**
289
+ * “Smart” closing double quotes.
290
+ *
291
+ * @since 0.1.0
292
+ * @category rules
293
+ */
294
+ const closeDoubleQuote = new InputRule(/"$/, "”", { inCodeMark: false });
295
+ /**
296
+ * “Smart” opening single quotes.
297
+ *
298
+ * @since 0.1.0
299
+ * @category rules
300
+ */
301
+ const openSingleQuote = new InputRule(/(?:^|[\s{[(<'"‘“])(')$/, "‘", { inCodeMark: false });
302
+ /**
303
+ * “Smart” closing single quotes.
304
+ *
305
+ * @since 0.1.0
306
+ * @category rules
307
+ */
308
+ const closeSingleQuote = new InputRule(/'$/, "’", { inCodeMark: false });
309
+ /**
310
+ * Smart-quote related input rules.
311
+ *
312
+ * @since 0.1.0
313
+ * @category rules
314
+ */
315
+ const smartQuotes = [
316
+ openDoubleQuote,
317
+ closeDoubleQuote,
318
+ openSingleQuote,
319
+ closeSingleQuote
320
+ ];
321
+ export { InputRule, InputRuleTypeId, closeDoubleQuote, closeSingleQuote, ellipsis, emDash, inputRules, openDoubleQuote, openSingleQuote, smartQuotes, textblockTypeInputRule, undoInputRule, wrappingInputRule };
@@ -0,0 +1,15 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Result from "effect/Result";
3
+ // src/internal/attempt.ts
4
+ /**
5
+ * Lift a synchronous computation that signals expected failure by
6
+ * throwing a tagged error (as recognised by `isExpected`) into an
7
+ * Effect. Other exceptions become defects.
8
+ *
9
+ * @internal
10
+ */
11
+ const attempt = (f, isExpected) => Effect.suspend(() => Result.match(Result.try(f), {
12
+ onSuccess: Effect.succeed,
13
+ onFailure: (e) => isExpected(e) ? Effect.fail(e) : Effect.die(e)
14
+ }));
15
+ export { attempt };
@@ -0,0 +1,29 @@
1
+ import * as Option from "effect/Option";
2
+ import * as Schema from "effect/Schema";
3
+ // src/internal/json.ts
4
+ const UnknownFromJsonString = /* @__PURE__ */ Schema.fromJsonString(Schema.Unknown);
5
+ const JsonFromJsonString = /* @__PURE__ */ Schema.fromJsonString(Schema.Json);
6
+ const encodeJsonString = /* @__PURE__ */ Schema.encodeOption(UnknownFromJsonString);
7
+ const decodeJsonString = /* @__PURE__ */ Schema.decodeOption(JsonFromJsonString);
8
+ /**
9
+ * Serialize a value as a JSON string (calling `toJSON` where defined, as
10
+ * `JSON.stringify` does), or `None` when it has no JSON form (`undefined`,
11
+ * a function, a cycle, a `BigInt`).
12
+ *
13
+ * @internal
14
+ */
15
+ const stringifyJson = (value) => encodeJsonString(value);
16
+ /**
17
+ * Parse a JSON string, or `None` when it is not valid JSON.
18
+ *
19
+ * @internal
20
+ */
21
+ const parseJson = (text) => decodeJsonString(text);
22
+ /**
23
+ * Render a value for an error or debug message: its JSON form, or
24
+ * `String(value)` when it has none.
25
+ *
26
+ * @internal
27
+ */
28
+ const showJson = (value) => Option.getOrElse(stringifyJson(value), () => String(value));
29
+ export { parseJson, showJson, stringifyJson };
@@ -0,0 +1,130 @@
1
+ import { AnySchema } from "../model/schema.js";
2
+ import "../model.js";
3
+ import "../state.js";
4
+ import { Command } from "../state/transaction.js";
5
+ import { Plugin } from "../state/plugin.js";
6
+ import { EditorView } from "../view/view.js";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Schema from "effect/Schema";
9
+ declare const KeymapError_base: Schema.Class<KeymapError, Schema.TaggedStruct<"KeymapError", {
10
+ readonly message: Schema.String;
11
+ }>, import("effect/Cause").YieldableError>;
12
+ /**
13
+ * An invalid keymap: a key name with an unrecognized modifier, or two
14
+ * bindings for the same key.
15
+ *
16
+ * @since 0.1.0
17
+ * @category errors
18
+ */
19
+ export declare class KeymapError extends KeymapError_base {}
20
+ /**
21
+ * A set of key bindings, mapping key names to commands.
22
+ *
23
+ * @since 0.1.0
24
+ * @category models
25
+ */
26
+ export interface Bindings {
27
+ readonly [key: string]: Command<any, any, any>;
28
+ }
29
+ /**
30
+ * The union of the error types of the commands in a set of bindings.
31
+ *
32
+ * @since 0.1.0
33
+ * @category type-level
34
+ */
35
+ export type BindingsError<B extends Bindings> = { [K in keyof B]: B[K] extends ((...args: any) => Effect.Effect<any, infer E, any>) ? E : never; }[keyof B];
36
+ /**
37
+ * The union of the requirements of the commands in a set of bindings.
38
+ *
39
+ * @since 0.1.0
40
+ * @category type-level
41
+ */
42
+ export type BindingsRequirements<B extends Bindings> = { [K in keyof B]: B[K] extends ((...args: any) => Effect.Effect<any, any, infer R>) ? R : never; }[keyof B];
43
+ /**
44
+ * The editor view a keydown handler receives. Its `dispatch` is passed
45
+ * to bound commands, and the view itself as their third argument.
46
+ *
47
+ * @since 0.1.0
48
+ * @category models
49
+ */
50
+ export type KeymapView<Sc extends AnySchema = AnySchema> = EditorView<Sc, any, any>;
51
+ /**
52
+ * A keydown handler, as created by `keydownHandler`.
53
+ *
54
+ * @since 0.1.0
55
+ * @category models
56
+ */
57
+ export type KeydownHandler<E = never, R = never> = (view: KeymapView<any>, event: KeyboardEvent) => Effect.Effect<boolean, E, R>;
58
+ /**
59
+ * Normalize a key name (such as `"Mod-Shift-a"`) into the canonical
60
+ * form used for lookups (`"Shift-Ctrl-a"` on non-Mac platforms).
61
+ * Throws a `KeymapError` for unrecognized modifiers.
62
+ *
63
+ * @since 0.1.0
64
+ * @category utils
65
+ */
66
+ export declare function normalizeKeyName(name: string): string;
67
+ /**
68
+ * Normalize the key names of a set of bindings. Throws a `KeymapError`
69
+ * for invalid or duplicate key names.
70
+ *
71
+ * @internal
72
+ */
73
+ export declare function normalizeBindings<A>(map: {
74
+ readonly [key: string]: A;
75
+ }): Record<string, A>;
76
+ /**
77
+ * Find the bindings matching a keydown event, in the order in which
78
+ * they should be tried.
79
+ *
80
+ * @internal
81
+ */
82
+ export declare function matchBindings<A>(map: Record<string, A>, event: KeyboardEvent): Array<A>;
83
+ /**
84
+ * Create a keymap plugin for the given set of bindings.
85
+ *
86
+ * Bindings should map key names to commands, which will be called with
87
+ * `(state, dispatch, view)` arguments, and should succeed with `true`
88
+ * when they've handled the key. Note that the view argument isn't part
89
+ * of the command protocol, but can be used as an escape hatch if a
90
+ * binding needs to directly interact with the UI.
91
+ *
92
+ * Key names may be strings like `"Shift-Ctrl-Enter"`—a key identifier
93
+ * prefixed with zero or more modifiers. Key identifiers are based on
94
+ * the strings that can appear in `KeyEvent.key`. Use lowercase letters
95
+ * to refer to letter keys (or uppercase letters if you want shift to be
96
+ * held). You may use `"Space"` as an alias for the `" "` name.
97
+ *
98
+ * Modifiers can be given in any order. `Shift-` (or `s-`), `Alt-` (or
99
+ * `a-`), `Ctrl-` (or `c-` or `Control-`) and `Cmd-` (or `m-` or
100
+ * `Meta-`) are recognized. For characters that are created by holding
101
+ * shift, the `Shift-` prefix is implied, and should not be added
102
+ * explicitly.
103
+ *
104
+ * You can use `Mod-` as a shorthand for `Cmd-` on Mac and `Ctrl-` on
105
+ * other platforms.
106
+ *
107
+ * You can add multiple keymap plugins to an editor. The order in which
108
+ * they appear determines their precedence (the ones early in the array
109
+ * get to dispatch first).
110
+ *
111
+ * The plugin's error and requirement types are the unions of those of
112
+ * the bound commands. Throws a `KeymapError` when a key name is invalid
113
+ * or bound twice.
114
+ *
115
+ * @since 0.1.0
116
+ * @category constructors
117
+ */
118
+ export declare const keymap: <const B extends Bindings>(bindings: B) => Plugin<undefined, AnySchema, BindingsError<B>, BindingsRequirements<B>>;
119
+ /**
120
+ * Given a set of bindings (using the same format as `keymap`), return a
121
+ * keydown handler that handles them. The handler succeeds with `true`
122
+ * when one of the matching commands handled the key.
123
+ *
124
+ * Throws a `KeymapError` when a key name is invalid or bound twice.
125
+ *
126
+ * @since 0.1.0
127
+ * @category constructors
128
+ */
129
+ export declare const keydownHandler: <const B extends Bindings>(bindings: B) => KeydownHandler<BindingsError<B>, BindingsRequirements<B>>;
130
+ export {};
@@ -0,0 +1,159 @@
1
+ import { Plugin } from "../state/plugin.js";
2
+ import * as Arr from "effect/Array";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Schema from "effect/Schema";
6
+ import { base, keyName } from "w3c-keyname";
7
+ // src/keymap/keymap.ts
8
+ /**
9
+ * Keymaps: plugins that bind key names to commands, ported from
10
+ * `prosemirror-keymap`.
11
+ *
12
+ * Ported from prosemirror-keymap (MIT, Marijn Haverbeke and others).
13
+ *
14
+ * @since 0.1.0
15
+ */
16
+ const mac = !Predicate.isUndefined(globalThis.navigator) && /Mac|iP(hone|[oa]d)/.test(navigator.platform);
17
+ const windows = !Predicate.isUndefined(globalThis.navigator) && /Win/.test(navigator.platform);
18
+ /**
19
+ * An invalid keymap: a key name with an unrecognized modifier, or two
20
+ * bindings for the same key.
21
+ *
22
+ * @since 0.1.0
23
+ * @category errors
24
+ */
25
+ var KeymapError = class extends Schema.TaggedError()("KeymapError", { message: Schema.String }) {};
26
+ /**
27
+ * Normalize a key name (such as `"Mod-Shift-a"`) into the canonical
28
+ * form used for lookups (`"Shift-Ctrl-a"` on non-Mac platforms).
29
+ * Throws a `KeymapError` for unrecognized modifiers.
30
+ *
31
+ * @since 0.1.0
32
+ * @category utils
33
+ */
34
+ function normalizeKeyName(name) {
35
+ const parts = name.split(/-(?!$)/);
36
+ let result = parts[parts.length - 1];
37
+ if (result === "Space") result = " ";
38
+ let alt = false, ctrl = false, shift = false, meta = false;
39
+ for (let i = 0; i < parts.length - 1; i++) {
40
+ const mod = parts[i];
41
+ if (/^(cmd|meta|m)$/i.test(mod)) meta = true;
42
+ else if (/^a(lt)?$/i.test(mod)) alt = true;
43
+ else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true;
44
+ else if (/^s(hift)?$/i.test(mod)) shift = true;
45
+ else if (/^mod$/i.test(mod)) {
46
+ if (mac) meta = true;
47
+ else ctrl = true;
48
+ } else throw new KeymapError({ message: "Unrecognized modifier name: " + mod });
49
+ }
50
+ if (alt) result = "Alt-" + result;
51
+ if (ctrl) result = "Ctrl-" + result;
52
+ if (meta) result = "Meta-" + result;
53
+ if (shift) result = "Shift-" + result;
54
+ return result;
55
+ }
56
+ /**
57
+ * Normalize the key names of a set of bindings. Throws a `KeymapError`
58
+ * for invalid or duplicate key names.
59
+ *
60
+ * @internal
61
+ */
62
+ function normalizeBindings(map) {
63
+ const copy = Object.create(null);
64
+ for (const prop in map) {
65
+ const norm = normalizeKeyName(prop);
66
+ if (Object.prototype.hasOwnProperty.call(copy, norm)) throw new KeymapError({ message: "Multiple bindings for key " + norm + " in a single keymap" });
67
+ copy[norm] = map[prop];
68
+ }
69
+ return copy;
70
+ }
71
+ function modifiers(name, event, shift = true) {
72
+ if (event.altKey) name = "Alt-" + name;
73
+ if (event.ctrlKey) name = "Ctrl-" + name;
74
+ if (event.metaKey) name = "Meta-" + name;
75
+ if (shift && event.shiftKey) name = "Shift-" + name;
76
+ return name;
77
+ }
78
+ /**
79
+ * Find the bindings matching a keydown event, in the order in which
80
+ * they should be tried.
81
+ *
82
+ * @internal
83
+ */
84
+ function matchBindings(map, event) {
85
+ const name = keyName(event);
86
+ const candidates = [];
87
+ const direct = map[modifiers(name, event)];
88
+ if (direct) candidates.push(direct);
89
+ if (name.length === 1 && name !== " ") {
90
+ if (event.shiftKey) {
91
+ const noShift = map[modifiers(name, event, false)];
92
+ if (noShift) candidates.push(noShift);
93
+ }
94
+ let baseName;
95
+ if ((event.altKey || event.metaKey || event.ctrlKey) && !(windows && event.ctrlKey && event.altKey) && (baseName = base[event.keyCode]) && baseName !== name) {
96
+ const fromCode = map[modifiers(baseName, event)];
97
+ if (fromCode) candidates.push(fromCode);
98
+ }
99
+ }
100
+ return candidates;
101
+ }
102
+ const succeedFalse = Effect.succeed(false);
103
+ const succeedTrue = Effect.succeed(true);
104
+ /**
105
+ * Create a keymap plugin for the given set of bindings.
106
+ *
107
+ * Bindings should map key names to commands, which will be called with
108
+ * `(state, dispatch, view)` arguments, and should succeed with `true`
109
+ * when they've handled the key. Note that the view argument isn't part
110
+ * of the command protocol, but can be used as an escape hatch if a
111
+ * binding needs to directly interact with the UI.
112
+ *
113
+ * Key names may be strings like `"Shift-Ctrl-Enter"`—a key identifier
114
+ * prefixed with zero or more modifiers. Key identifiers are based on
115
+ * the strings that can appear in `KeyEvent.key`. Use lowercase letters
116
+ * to refer to letter keys (or uppercase letters if you want shift to be
117
+ * held). You may use `"Space"` as an alias for the `" "` name.
118
+ *
119
+ * Modifiers can be given in any order. `Shift-` (or `s-`), `Alt-` (or
120
+ * `a-`), `Ctrl-` (or `c-` or `Control-`) and `Cmd-` (or `m-` or
121
+ * `Meta-`) are recognized. For characters that are created by holding
122
+ * shift, the `Shift-` prefix is implied, and should not be added
123
+ * explicitly.
124
+ *
125
+ * You can use `Mod-` as a shorthand for `Cmd-` on Mac and `Ctrl-` on
126
+ * other platforms.
127
+ *
128
+ * You can add multiple keymap plugins to an editor. The order in which
129
+ * they appear determines their precedence (the ones early in the array
130
+ * get to dispatch first).
131
+ *
132
+ * The plugin's error and requirement types are the unions of those of
133
+ * the bound commands. Throws a `KeymapError` when a key name is invalid
134
+ * or bound twice.
135
+ *
136
+ * @since 0.1.0
137
+ * @category constructors
138
+ */
139
+ const keymap = (bindings) => new Plugin({ props: { handleKeyDown: keydownHandler(bindings) } });
140
+ /**
141
+ * Given a set of bindings (using the same format as `keymap`), return a
142
+ * keydown handler that handles them. The handler succeeds with `true`
143
+ * when one of the matching commands handled the key.
144
+ *
145
+ * Throws a `KeymapError` when a key name is invalid or bound twice.
146
+ *
147
+ * @since 0.1.0
148
+ * @category constructors
149
+ */
150
+ const keydownHandler = (bindings) => {
151
+ const map = normalizeBindings(bindings);
152
+ return (view, event) => {
153
+ const candidates = matchBindings(map, event);
154
+ if (Arr.isArrayEmpty(candidates)) return succeedFalse;
155
+ const loop = (i) => i === candidates.length ? succeedFalse : Effect.flatMap(Effect.suspend(() => candidates[i](view.state, view.dispatch, view)), (handled) => handled ? succeedTrue : loop(i + 1));
156
+ return loop(0);
157
+ };
158
+ };
159
+ export { KeymapError, keydownHandler, keymap, matchBindings, normalizeBindings, normalizeKeyName };
@@ -0,0 +1,2 @@
1
+ import { Bindings, BindingsError, BindingsRequirements, KeydownHandler, KeymapError, KeymapView, keydownHandler, keymap, matchBindings, normalizeBindings, normalizeKeyName } from "./keymap/keymap.js";
2
+ export { Bindings, BindingsError, BindingsRequirements, KeydownHandler, KeymapError, KeymapView, keydownHandler, keymap, matchBindings, normalizeBindings, normalizeKeyName };
package/dist/keymap.js ADDED
@@ -0,0 +1 @@
1
+ export { KeymapError, keydownHandler, keymap, matchBindings, normalizeBindings, normalizeKeyName } from "./keymap/keymap.js";
@@ -0,0 +1,23 @@
1
+ import * as Schema from "effect/Schema";
2
+ declare const MarkdownError_base: Schema.Class<MarkdownError, Schema.TaggedStruct<"MarkdownError", {
3
+ readonly message: Schema.String;
4
+ }>, import("effect/Cause").YieldableError>;
5
+ /**
6
+ * A Markdown error: the tokenizer produced a token type the parser has
7
+ * no handler for, a parser was configured with an invalid parsing
8
+ * spec, or (in strict mode) the serializer met a node or mark type it
9
+ * has no serializer for.
10
+ *
11
+ * Parsing reports unsupported tokens in the Effect error channel; the
12
+ * other cases are programming errors and are thrown.
13
+ *
14
+ * @since 0.1.0
15
+ * @category errors
16
+ */
17
+ export declare class MarkdownError extends MarkdownError_base {}
18
+ /**
19
+ * @since 0.1.0
20
+ * @category guards
21
+ */
22
+ export declare const isMarkdownError: (u: unknown) => u is MarkdownError;
23
+ export {};