UNPKG

24.1 kBTypeScriptView Raw
1import type { AnyFunction, CommandFunction, CommandFunctionProps, EmptyShape, Fragment, FromToProps, LiteralUnion, MarkType, NodeType, PrimitiveSelection, ProsemirrorAttributes, ProsemirrorNode, RemirrorContentType, Static, Transaction } from '@remirror/core-types';
2import { RemoveMarkProps, ReplaceTextProps, ToggleBlockItemProps } from '@remirror/core-utils';
3import { Mark } from '@remirror/pm/model';
4import type { EditorView } from '@remirror/pm/view';
5import { InsertTextOptions, ToggleMarkProps } from '../commands';
6import { AnyExtension, ChainedFromExtensions, CommandNames, CommandsFromExtensions, Helper, PlainExtension, UiCommandNames } from '../extension';
7import type { CreateExtensionPlugin, ExtensionCommandReturn, FocusType, StateUpdateLifecycleProps } from '../types';
8import { CommandDecoratorOptions } from './builtin-decorators';
9export interface CommandOptions {
10 /**
11 * The className that is added to all tracker positions
12 *
13 * '@defaultValue 'remirror-tracker-position'
14 */
15 trackerClassName?: Static<string>;
16 /**
17 * The default element that is used for all trackers.
18 *
19 * @defaultValue 'span'
20 */
21 trackerNodeName?: Static<string>;
22}
23/**
24 * Generate chained and unchained commands for making changes to the editor.
25 *
26 * @remarks
27 *
28 * Typically actions are used to create interactive menus. For example a menu
29 * can use a command to toggle bold formatting or to undo the last action.
30 *
31 * @category Builtin Extension
32 */
33export declare class CommandsExtension extends PlainExtension<CommandOptions> {
34 get name(): "commands";
35 /**
36 * The current transaction which allows for making commands chainable.
37 *
38 * It is shared by all the commands helpers and can even be used in the
39 * [[`KeymapExtension`]].
40 *
41 * @internal
42 */
43 get transaction(): Transaction;
44 /**
45 * This is the holder for the shared transaction which is shared by commands
46 * in order to support chaining.
47 *
48 * @internal
49 */
50 private _transaction?;
51 /**
52 * Track the decorated command data.
53 */
54 private readonly decorated;
55 onCreate(): void;
56 /**
57 * Attach commands once the view is attached.
58 */
59 onView(view: EditorView): void;
60 /**
61 * Update the cached transaction whenever the state is updated.
62 */
63 onStateUpdate({ state }: StateUpdateLifecycleProps): void;
64 /**
65 * Create a plugin that solely exists to track forced updates via the
66 * generated plugin key.
67 */
68 createPlugin(): CreateExtensionPlugin;
69 /**
70 * Enable custom commands to be used within the editor by users.
71 *
72 * This is preferred to the initial idea of setting commands on the
73 * manager or even as a prop. The problem is that there's no typechecking
74 * and it should be just fine to add your custom commands here to see the
75 * dispatched immediately.
76 *
77 * To use it, firstly define the command.
78 *
79 * ```ts
80 * import { CommandFunction } from 'remirror';
81 *
82 * const myCustomCommand: CommandFunction = ({ tr, dispatch }) => {
83 * dispatch?.(tr.insertText('My Custom Command'));
84 *
85 * return true;
86 * }
87 * ```
88 *
89 * And then use it within the component.
90 *
91 * ```ts
92 * import React, { useCallback } from 'react';
93 * import { useRemirror } from '@remirror/react';
94 *
95 * const MyEditorButton = () => {
96 * const { commands } = useRemirror();
97 * const onClick = useCallback(() => {
98 * commands.customDispatch(myCustomCommand);
99 * }, [commands])
100 *
101 * return <button onClick={onClick}>Custom Command</button>
102 * }
103 * ```
104 *
105 * An alternative is to use a custom command directly from a
106 * `prosemirror-*` library. This can be accomplished in the following way.
107 *
108 *
109 * ```ts
110 * import { joinDown } from 'prosemirror-commands';
111 * import { convertCommand } from 'remirror';
112 *
113 * const MyEditorButton = () => {
114 * const { commands } = useRemirror();
115 * const onClick = useCallback(() => {
116 * commands.customDispatch(convertCommand(joinDown));
117 * }, [commands]);
118 *
119 * return <button onClick={onClick}>Custom Command</button>;
120 * };
121 * ```
122 */
123 customDispatch(command: CommandFunction): CommandFunction;
124 /**
125 * Insert text into the dom at the current location by default. If a
126 * promise is provided instead of text the resolved value will be inserted
127 * at the tracked position.
128 */
129 insertText(text: string | (() => Promise<string>), options?: InsertTextOptions): CommandFunction;
130 /**
131 * Select the text within the provided range.
132 *
133 * Here are some ways it can be used.
134 *
135 * ```ts
136 * // Set to the end of the document.
137 * commands.selectText('end');
138 *
139 * // Set the selection to the start of the document.
140 * commands.selectText('start');
141 *
142 * // Select all the text in the document.
143 * commands.selectText('all')
144 *
145 * // Select a range of text. It's up to you to make sure the selected
146 * // range is valid.
147 * commands.selectText({ from: 10, to: 15 });
148 *
149 * // Specify the anchor and range in the selection.
150 * commands.selectText({ anchor: 10, head: 15 });
151 *
152 * // Set to a specific position.
153 * commands.selectText(10);
154 *
155 * // Use a ProseMirror selection
156 * commands.selectText(TextSelection.near(state.doc.resolve(10)))
157 * ```
158 *
159 * Although this is called `selectText` you can provide your own selection
160 * option which can be any type of selection.
161 */
162 selectText(selection: PrimitiveSelection, options?: {
163 forceUpdate?: boolean;
164 }): CommandFunction;
165 /**
166 * Select the link at the current location.
167 */
168 selectMark(type: string | MarkType): CommandFunction;
169 /**
170 * Delete the provided range or current selection.
171 */
172 delete(range?: FromToProps): CommandFunction;
173 /**
174 * Fire an empty update to trigger an update to all decorations, and state
175 * that may not yet have run.
176 *
177 * This can be used in extensions to trigger updates when certain options that
178 * affect the editor state have changed.
179 *
180 * @param action - provide an action which is called just before the empty
181 * update is dispatched (only when dispatch is available). This can be used in
182 * chainable editor scenarios when you want to lazily invoke an action at the
183 * point the update is about to be applied.
184 */
185 emptyUpdate(action?: () => void): CommandFunction;
186 /**
187 * Force an update of the specific updatable ProseMirror props.
188 *
189 * This command is always available as a builtin command.
190 *
191 * @category Builtin Command
192 */
193 forceUpdate(...keys: UpdatableViewProps[]): CommandFunction;
194 /**
195 * Update the attributes for the node at the specified `pos` in the
196 * editor.
197 *
198 * @category Builtin Command
199 */
200 updateNodeAttributes<Type extends object>(pos: number, attrs: ProsemirrorAttributes<Type>): CommandFunction;
201 /**
202 * Set the content of the editor while preserving history.
203 *
204 * Under the hood this is replacing the content in the document with the new
205 * state.doc of the provided content.
206 *
207 * If the content is a string you will need to ensure you have the proper
208 * string handler set up in the editor.
209 */
210 setContent(content: RemirrorContentType, selection?: PrimitiveSelection): CommandFunction;
211 /**
212 * Reset the content of the editor while preserving the history.
213 *
214 * This means that undo and redo will still be active since the doc is replaced with a new doc.
215 */
216 resetContent(): CommandFunction;
217 /**
218 * Fire an update to remove the current range selection. The cursor will
219 * be placed at the anchor of the current range selection.
220 *
221 * A range selection is a non-empty text selection.
222 *
223 * @category Builtin Command
224 */
225 emptySelection(): CommandFunction;
226 /**
227 * Insert a new line into the editor.
228 *
229 * Depending on editor setup and where the cursor is placed this may have
230 * differing impacts.
231 *
232 * @category Builtin Command
233 */
234 insertNewLine(): CommandFunction;
235 /**
236 * Insert a node into the editor with the provided content.
237 *
238 * @category Builtin Command
239 */
240 insertNode(node: string | NodeType | ProsemirrorNode | Fragment, options?: InsertNodeOptions): CommandFunction;
241 /**
242 * Set the focus for the editor.
243 *
244 * If using this with chaining this should only be placed at the end of
245 * the chain. It can cause hard to debug issues when used in the middle of
246 * a chain.
247 *
248 * ```tsx
249 * import { useCallback } from 'react';
250 * import { useRemirrorContext } from '@remirror/react';
251 *
252 * const MenuButton = () => {
253 * const { chain } = useRemirrorContext();
254 * const onClick = useCallback(() => {
255 * chain
256 * .toggleBold()
257 * .focus('end')
258 * .run();
259 * }, [chain])
260 *
261 * return <button onClick={onClick}>Bold</button>
262 * }
263 * ```
264 */
265 focus(position?: FocusType): CommandFunction;
266 /**
267 * Blur focus from the editor and also update the selection at the same
268 * time.
269 */
270 blur(position?: PrimitiveSelection): CommandFunction;
271 /**
272 * Set the block type of the current selection or the provided range.
273 *
274 * @param nodeType - the node type to create
275 * @param attrs - the attributes to add to the node type
276 * @param selection - the position in the document to set the block node
277 * @param preserveAttrs - when true preserve the attributes at the provided selection
278 */
279 setBlockNodeType(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection, preserveAttrs?: boolean): CommandFunction;
280 /**
281 * Toggle between wrapping an inactive node with the provided node type, and
282 * lifting it up into it's parent.
283 *
284 * @param nodeType - the node type to toggle
285 * @param attrs - the attrs to use for the node
286 * @param selection - the selection point in the editor to perform the action
287 */
288 toggleWrappingNode(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection): CommandFunction;
289 /**
290 * Toggle a block between the provided type and toggleType.
291 */
292 toggleBlockNodeItem(toggleProps: ToggleBlockItemProps): CommandFunction;
293 /**
294 * Wrap the selection or the provided text in a node of the given type with the
295 * given attributes.
296 */
297 wrapInNode(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, range?: FromToProps | undefined): CommandFunction;
298 /**
299 * Removes a mark from the current selection or provided range.
300 */
301 applyMark(markType: string | MarkType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection): CommandFunction;
302 /**
303 * Removes a mark from the current selection or provided range.
304 */
305 toggleMark(props: ToggleMarkProps): CommandFunction;
306 /**
307 * Removes a mark from the current selection or provided range.
308 */
309 removeMark(props: RemoveMarkProps): CommandFunction;
310 /**
311 * Set the meta data to attach to the editor on the next update.
312 */
313 setMeta(name: string, value: unknown): CommandFunction;
314 /**
315 * Select all text in the editor.
316 */
317 selectAll(): CommandFunction;
318 /**
319 * Copy the selected content for non empty selections.
320 */
321 copy(): CommandFunction;
322 /**
323 * Select all text in the editor.
324 */
325 paste(): CommandFunction;
326 /**
327 * Cut the selected content.
328 */
329 cut(): CommandFunction;
330 /**
331 * Replaces text with an optional appended string at the end. The replacement
332 * can be text, or a custom node.
333 *
334 * @param props - see [[`ReplaceTextProps`]]
335 */
336 replaceText(props: ReplaceTextProps): CommandFunction;
337 /**
338 * Get the all the decorated commands available on the editor instance.
339 */
340 getAllCommandOptions(): Helper<Record<string, WithName<CommandDecoratorOptions>>>;
341 /**
342 * Get the options that were passed into the provided command.
343 */
344 getCommandOptions(name: string): Helper<WithName<CommandDecoratorOptions> | undefined>;
345 /**
346 * A short hand way of getting the `view`, `state`, `tr` and `dispatch`
347 * methods.
348 */
349 getCommandProp(): Helper<Required<CommandFunctionProps>>;
350 /**
351 * Update the command options via a shallow merge of the provided options. If
352 * no options are provided the entry is deleted.
353 *
354 * @internal
355 */
356 updateDecorated(name: string, options?: Partial<WithName<CommandDecoratorOptions>>): void;
357 /**
358 * Needed on iOS since `requestAnimationFrame` doesn't breaks the focus
359 * implementation.
360 */
361 private handleIosFocus;
362 /**
363 * Focus the editor after a slight delay.
364 */
365 private delayedFocus;
366 /**
367 * A helper for forcing through updates in the view layer. The view layer can
368 * check for the meta data of the transaction with
369 * `manager.store.getForcedUpdate(tr)`. If that has a value then it should use
370 * the unique symbol to update the key.
371 */
372 private readonly forceUpdateTransaction;
373 /**
374 * Check for a forced update in the transaction. This pulls the meta data
375 * from the transaction and if it is true then it was a forced update.
376 *
377 * ```ts
378 * import { CommandsExtension } from 'remirror/extensions';
379 *
380 * const commandsExtension = manager.getExtension(CommandsExtension);
381 * log(commandsExtension.getForcedUpdates(tr))
382 * ```
383 *
384 * This can be used for updating:
385 *
386 * - `nodeViews`
387 * - `editable` status of the editor
388 * - `attributes` - for the top level node
389 *
390 * @internal
391 */
392 private getForcedUpdates;
393 /**
394 * Get the command metadata.
395 */
396 private getCommandMeta;
397 private setCommandMeta;
398 /**
399 * Add the commands from the provided `commands` property to the `chained`,
400 * `original` and `unchained` objects.
401 */
402 private addCommands;
403 /**
404 * Create an unchained command method.
405 */
406 private unchainedFactory;
407 /**
408 * Create the unchained command.
409 */
410 private createUnchainedCommand;
411 /**
412 * Create a chained command method.
413 */
414 private chainedFactory;
415}
416export interface InsertNodeOptions {
417 attrs?: ProsemirrorAttributes;
418 marks?: Array<Mark | string | MarkType>;
419 /**
420 * The content to insert.
421 */
422 content?: Fragment | ProsemirrorNode | ProsemirrorNode[] | string;
423 /**
424 * @deprecated use selection property instead.
425 */
426 range?: FromToProps;
427 /**
428 * Set the selection where the command should occur.
429 */
430 selection?: PrimitiveSelection;
431 /**
432 * Set this to true to replace an empty parent block with this content (if the
433 * content is a block node).
434 */
435 replaceEmptyParentBlock?: boolean;
436}
437/**
438 * Provides the list of Prosemirror EditorView props that should be updated/
439 */
440export type ForcedUpdateMeta = UpdatableViewProps[];
441export type UpdatableViewProps = 'attributes' | 'editable';
442export interface CommandExtensionMeta {
443 forcedUpdates?: UpdatableViewProps[];
444}
445/**
446 * A type with a name property.
447 */
448type WithName<Type> = Type & {
449 name: string;
450};
451declare global {
452 namespace Remirror {
453 interface ManagerStore<Extension extends AnyExtension> {
454 /**
455 * Get the forced updates from the provided transaction.
456 */
457 getForcedUpdates: (tr: Transaction) => ForcedUpdateMeta;
458 /**
459 * Enables the use of custom commands created by extensions which extend
460 * the functionality of your editor in an expressive way.
461 *
462 * @remarks
463 *
464 * Commands are synchronous and immediately dispatched. This means that
465 * they can be used to create menu items when the functionality you need
466 * is already available by the commands.
467 *
468 * ```ts
469 * if (commands.toggleBold.isEnabled()) {
470 * commands.toggleBold();
471 * }
472 * ```
473 */
474 commands: CommandsFromExtensions<Extension>;
475 /**
476 * Chainable commands for composing functionality together in quaint and
477 * beautiful ways
478 *
479 * @remarks
480 *
481 * You can use this property to create expressive and complex commands
482 * that build up the transaction until it can be run.
483 *
484 * The way chainable commands work is by adding multiple steps to a shared
485 * transaction which is then dispatched when the `run` command is called.
486 * This requires making sure that commands within your code use the `tr`
487 * that is provided rather than the `state.tr` property. `state.tr`
488 * creates a new transaction which is not shared by the other steps in a
489 * chainable command.
490 *
491 * The aim is to make as many commands as possible chainable as explained
492 * [here](https://github.com/remirror/remirror/issues/418#issuecomment-666922209).
493 *
494 * There are certain commands that can't be made chainable.
495 *
496 * - undo
497 * - redo
498 *
499 * ```ts
500 * chain
501 * .toggleBold()
502 * .insertText('Hi')
503 * .setSelection('all')
504 * .run();
505 * ```
506 *
507 * The `run()` method ends the chain and dispatches the command.
508 */
509 chain: ChainedFromExtensions<Extension>;
510 }
511 interface BaseExtension {
512 /**
513 * `ExtensionCommands`
514 *
515 * This pseudo property makes it easier to infer Generic types of this
516 * class.
517 *
518 * @internal
519 */
520 ['~C']: this['createCommands'] extends AnyFunction ? ReturnType<this['createCommands']> : EmptyShape;
521 /**
522 * @experimental
523 *
524 * Stores all the command names for this decoration that have been added
525 * as decorators to the extension instance. This is used by the
526 * `CommandsExtension` to pick the commands and store meta data attached
527 * to each command.
528 *
529 * @internal
530 */
531 decoratedCommands?: Record<string, CommandDecoratorOptions>;
532 /**
533 * Create and register commands for that can be called within the editor.
534 *
535 * These are typically used to create menu's actions and as a direct
536 * response to user actions.
537 *
538 * @remarks
539 *
540 * The `createCommands` method should return an object with each key being
541 * unique within the editor. To ensure that this is the case it is
542 * recommended that the keys of the command are namespaced with the name
543 * of the extension.
544 *
545 * ```ts
546 * import { ExtensionFactory } from '@remirror/core';
547 *
548 * const MyExtension = ExtensionFactory.plain({
549 * name: 'myExtension',
550 * version: '1.0.0',
551 * createCommands() {
552 * return {
553 * haveFun() {
554 * return ({ state, dispatch }) => {
555 * if (dispatch) {
556 * dispatch(tr.insertText('Have fun!'));
557 * }
558 *
559 * return true; // True return signifies that this command is enabled.
560 * }
561 * },
562 * }
563 * }
564 * })
565 * ```
566 *
567 * The actions available in this case would be `undoHistory` and
568 * `redoHistory`. It is unlikely that any other extension would override
569 * these commands.
570 *
571 * Another benefit of commands is that they are picked up by typescript
572 * and can provide code completion for consumers of the extension.
573 */
574 createCommands?(): ExtensionCommandReturn;
575 }
576 interface ExtensionStore {
577 /**
578 * A property containing all the available commands in the editor.
579 *
580 * This should only be accessed after the `onView` lifecycle method
581 * otherwise it will throw an error. If you want to use it in the
582 * `createCommands` function then make sure it is used within the returned
583 * function scope and not in the outer scope.
584 */
585 commands: CommandsFromExtensions<Extensions | (AnyExtension & {
586 _T: false;
587 })>;
588 /**
589 * A method that returns an object with all the chainable commands
590 * available to be run.
591 *
592 * @remarks
593 *
594 * Each chainable command mutates the states transaction so after running
595 * all your commands. you should dispatch the desired transaction.
596 *
597 * This should only be called when the view has been initialized (i.e.)
598 * within the `createCommands` method calls.
599 *
600 * ```ts
601 * import { ExtensionFactory } from '@remirror/core';
602 *
603 * const MyExtension = ExtensionFactory.plain({
604 * name: 'myExtension',
605 * version: '1.0.0',
606 * createCommands: () => {
607 * // This will throw since it can only be called within the returned
608 * methods.
609 * const chain = this.store.chain; // ❌
610 *
611 * return {
612 * // This is good 😋
613 * haveFun() {
614 * return ({ state, dispatch }) =>
615 * this.store.chain.insertText('fun!').run(); ✅
616 * },
617 * }
618 * }
619 * })
620 * ```
621 *
622 * This should only be accessed after the `EditorView` has been fully
623 * attached to the `RemirrorManager`.
624 *
625 * The chain can also be called as a function with a custom `tr`
626 * parameter. This allows you to provide a custom transaction to use
627 * within the chainable commands.
628 *
629 * Use the command at the beginning of the command chain to override the
630 * shared transaction.
631 *
632 * There are times when you want to be sure of the transaction which is
633 * being updated.
634 *
635 * To restore the previous transaction call the `restore` chained method.
636 *
637 * @param tr - the transaction to set
638 */
639 chain: ChainedFromExtensions<Extensions | (AnyExtension & {
640 _T: false;
641 })>;
642 }
643 interface AllExtensions {
644 commands: CommandsExtension;
645 }
646 /**
647 * The command names for all core extensions.
648 */
649 type AllCommandNames = LiteralUnion<CommandNames<Remirror.Extensions>, string>;
650 /**
651 * The command names for all core extensions.
652 */
653 type AllUiCommandNames = LiteralUnion<UiCommandNames<Remirror.Extensions>, string>;
654 }
655}
656export {};
657
\No newline at end of file