@remirror/core
Version:
Where your quest to create a world class editing experience begins.
657 lines (656 loc) • 24.1 kB
TypeScript
import type { AnyFunction, CommandFunction, CommandFunctionProps, EmptyShape, Fragment, FromToProps, LiteralUnion, MarkType, NodeType, PrimitiveSelection, ProsemirrorAttributes, ProsemirrorNode, RemirrorContentType, Static, Transaction } from '@remirror/core-types';
import { RemoveMarkProps, ReplaceTextProps, ToggleBlockItemProps } from '@remirror/core-utils';
import { Mark } from '@remirror/pm/model';
import type { EditorView } from '@remirror/pm/view';
import { InsertTextOptions, ToggleMarkProps } from '../commands';
import { AnyExtension, ChainedFromExtensions, CommandNames, CommandsFromExtensions, Helper, PlainExtension, UiCommandNames } from '../extension';
import type { CreateExtensionPlugin, ExtensionCommandReturn, FocusType, StateUpdateLifecycleProps } from '../types';
import { CommandDecoratorOptions } from './builtin-decorators';
export interface CommandOptions {
/**
* The className that is added to all tracker positions
*
* '@defaultValue 'remirror-tracker-position'
*/
trackerClassName?: Static<string>;
/**
* The default element that is used for all trackers.
*
* @defaultValue 'span'
*/
trackerNodeName?: Static<string>;
}
/**
* Generate chained and unchained commands for making changes to the editor.
*
* @remarks
*
* Typically actions are used to create interactive menus. For example a menu
* can use a command to toggle bold formatting or to undo the last action.
*
* @category Builtin Extension
*/
export declare class CommandsExtension extends PlainExtension<CommandOptions> {
get name(): "commands";
/**
* The current transaction which allows for making commands chainable.
*
* It is shared by all the commands helpers and can even be used in the
* [[`KeymapExtension`]].
*
* @internal
*/
get transaction(): Transaction;
/**
* This is the holder for the shared transaction which is shared by commands
* in order to support chaining.
*
* @internal
*/
private _transaction?;
/**
* Track the decorated command data.
*/
private readonly decorated;
onCreate(): void;
/**
* Attach commands once the view is attached.
*/
onView(view: EditorView): void;
/**
* Update the cached transaction whenever the state is updated.
*/
onStateUpdate({ state }: StateUpdateLifecycleProps): void;
/**
* Create a plugin that solely exists to track forced updates via the
* generated plugin key.
*/
createPlugin(): CreateExtensionPlugin;
/**
* Enable custom commands to be used within the editor by users.
*
* This is preferred to the initial idea of setting commands on the
* manager or even as a prop. The problem is that there's no typechecking
* and it should be just fine to add your custom commands here to see the
* dispatched immediately.
*
* To use it, firstly define the command.
*
* ```ts
* import { CommandFunction } from 'remirror';
*
* const myCustomCommand: CommandFunction = ({ tr, dispatch }) => {
* dispatch?.(tr.insertText('My Custom Command'));
*
* return true;
* }
* ```
*
* And then use it within the component.
*
* ```ts
* import React, { useCallback } from 'react';
* import { useRemirror } from '@remirror/react';
*
* const MyEditorButton = () => {
* const { commands } = useRemirror();
* const onClick = useCallback(() => {
* commands.customDispatch(myCustomCommand);
* }, [commands])
*
* return <button onClick={onClick}>Custom Command</button>
* }
* ```
*
* An alternative is to use a custom command directly from a
* `prosemirror-*` library. This can be accomplished in the following way.
*
*
* ```ts
* import { joinDown } from 'prosemirror-commands';
* import { convertCommand } from 'remirror';
*
* const MyEditorButton = () => {
* const { commands } = useRemirror();
* const onClick = useCallback(() => {
* commands.customDispatch(convertCommand(joinDown));
* }, [commands]);
*
* return <button onClick={onClick}>Custom Command</button>;
* };
* ```
*/
customDispatch(command: CommandFunction): CommandFunction;
/**
* Insert text into the dom at the current location by default. If a
* promise is provided instead of text the resolved value will be inserted
* at the tracked position.
*/
insertText(text: string | (() => Promise<string>), options?: InsertTextOptions): CommandFunction;
/**
* Select the text within the provided range.
*
* Here are some ways it can be used.
*
* ```ts
* // Set to the end of the document.
* commands.selectText('end');
*
* // Set the selection to the start of the document.
* commands.selectText('start');
*
* // Select all the text in the document.
* commands.selectText('all')
*
* // Select a range of text. It's up to you to make sure the selected
* // range is valid.
* commands.selectText({ from: 10, to: 15 });
*
* // Specify the anchor and range in the selection.
* commands.selectText({ anchor: 10, head: 15 });
*
* // Set to a specific position.
* commands.selectText(10);
*
* // Use a ProseMirror selection
* commands.selectText(TextSelection.near(state.doc.resolve(10)))
* ```
*
* Although this is called `selectText` you can provide your own selection
* option which can be any type of selection.
*/
selectText(selection: PrimitiveSelection, options?: {
forceUpdate?: boolean;
}): CommandFunction;
/**
* Select the link at the current location.
*/
selectMark(type: string | MarkType): CommandFunction;
/**
* Delete the provided range or current selection.
*/
delete(range?: FromToProps): CommandFunction;
/**
* Fire an empty update to trigger an update to all decorations, and state
* that may not yet have run.
*
* This can be used in extensions to trigger updates when certain options that
* affect the editor state have changed.
*
* @param action - provide an action which is called just before the empty
* update is dispatched (only when dispatch is available). This can be used in
* chainable editor scenarios when you want to lazily invoke an action at the
* point the update is about to be applied.
*/
emptyUpdate(action?: () => void): CommandFunction;
/**
* Force an update of the specific updatable ProseMirror props.
*
* This command is always available as a builtin command.
*
* @category Builtin Command
*/
forceUpdate(...keys: UpdatableViewProps[]): CommandFunction;
/**
* Update the attributes for the node at the specified `pos` in the
* editor.
*
* @category Builtin Command
*/
updateNodeAttributes<Type extends object>(pos: number, attrs: ProsemirrorAttributes<Type>): CommandFunction;
/**
* Set the content of the editor while preserving history.
*
* Under the hood this is replacing the content in the document with the new
* state.doc of the provided content.
*
* If the content is a string you will need to ensure you have the proper
* string handler set up in the editor.
*/
setContent(content: RemirrorContentType, selection?: PrimitiveSelection): CommandFunction;
/**
* Reset the content of the editor while preserving the history.
*
* This means that undo and redo will still be active since the doc is replaced with a new doc.
*/
resetContent(): CommandFunction;
/**
* Fire an update to remove the current range selection. The cursor will
* be placed at the anchor of the current range selection.
*
* A range selection is a non-empty text selection.
*
* @category Builtin Command
*/
emptySelection(): CommandFunction;
/**
* Insert a new line into the editor.
*
* Depending on editor setup and where the cursor is placed this may have
* differing impacts.
*
* @category Builtin Command
*/
insertNewLine(): CommandFunction;
/**
* Insert a node into the editor with the provided content.
*
* @category Builtin Command
*/
insertNode(node: string | NodeType | ProsemirrorNode | Fragment, options?: InsertNodeOptions): CommandFunction;
/**
* Set the focus for the editor.
*
* If using this with chaining this should only be placed at the end of
* the chain. It can cause hard to debug issues when used in the middle of
* a chain.
*
* ```tsx
* import { useCallback } from 'react';
* import { useRemirrorContext } from '@remirror/react';
*
* const MenuButton = () => {
* const { chain } = useRemirrorContext();
* const onClick = useCallback(() => {
* chain
* .toggleBold()
* .focus('end')
* .run();
* }, [chain])
*
* return <button onClick={onClick}>Bold</button>
* }
* ```
*/
focus(position?: FocusType): CommandFunction;
/**
* Blur focus from the editor and also update the selection at the same
* time.
*/
blur(position?: PrimitiveSelection): CommandFunction;
/**
* Set the block type of the current selection or the provided range.
*
* @param nodeType - the node type to create
* @param attrs - the attributes to add to the node type
* @param selection - the position in the document to set the block node
* @param preserveAttrs - when true preserve the attributes at the provided selection
*/
setBlockNodeType(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection, preserveAttrs?: boolean): CommandFunction;
/**
* Toggle between wrapping an inactive node with the provided node type, and
* lifting it up into it's parent.
*
* @param nodeType - the node type to toggle
* @param attrs - the attrs to use for the node
* @param selection - the selection point in the editor to perform the action
*/
toggleWrappingNode(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection): CommandFunction;
/**
* Toggle a block between the provided type and toggleType.
*/
toggleBlockNodeItem(toggleProps: ToggleBlockItemProps): CommandFunction;
/**
* Wrap the selection or the provided text in a node of the given type with the
* given attributes.
*/
wrapInNode(nodeType: string | NodeType, attrs?: ProsemirrorAttributes, range?: FromToProps | undefined): CommandFunction;
/**
* Removes a mark from the current selection or provided range.
*/
applyMark(markType: string | MarkType, attrs?: ProsemirrorAttributes, selection?: PrimitiveSelection): CommandFunction;
/**
* Removes a mark from the current selection or provided range.
*/
toggleMark(props: ToggleMarkProps): CommandFunction;
/**
* Removes a mark from the current selection or provided range.
*/
removeMark(props: RemoveMarkProps): CommandFunction;
/**
* Set the meta data to attach to the editor on the next update.
*/
setMeta(name: string, value: unknown): CommandFunction;
/**
* Select all text in the editor.
*/
selectAll(): CommandFunction;
/**
* Copy the selected content for non empty selections.
*/
copy(): CommandFunction;
/**
* Select all text in the editor.
*/
paste(): CommandFunction;
/**
* Cut the selected content.
*/
cut(): CommandFunction;
/**
* Replaces text with an optional appended string at the end. The replacement
* can be text, or a custom node.
*
* @param props - see [[`ReplaceTextProps`]]
*/
replaceText(props: ReplaceTextProps): CommandFunction;
/**
* Get the all the decorated commands available on the editor instance.
*/
getAllCommandOptions(): Helper<Record<string, WithName<CommandDecoratorOptions>>>;
/**
* Get the options that were passed into the provided command.
*/
getCommandOptions(name: string): Helper<WithName<CommandDecoratorOptions> | undefined>;
/**
* A short hand way of getting the `view`, `state`, `tr` and `dispatch`
* methods.
*/
getCommandProp(): Helper<Required<CommandFunctionProps>>;
/**
* Update the command options via a shallow merge of the provided options. If
* no options are provided the entry is deleted.
*
* @internal
*/
updateDecorated(name: string, options?: Partial<WithName<CommandDecoratorOptions>>): void;
/**
* Needed on iOS since `requestAnimationFrame` doesn't breaks the focus
* implementation.
*/
private handleIosFocus;
/**
* Focus the editor after a slight delay.
*/
private delayedFocus;
/**
* A helper for forcing through updates in the view layer. The view layer can
* check for the meta data of the transaction with
* `manager.store.getForcedUpdate(tr)`. If that has a value then it should use
* the unique symbol to update the key.
*/
private readonly forceUpdateTransaction;
/**
* Check for a forced update in the transaction. This pulls the meta data
* from the transaction and if it is true then it was a forced update.
*
* ```ts
* import { CommandsExtension } from 'remirror/extensions';
*
* const commandsExtension = manager.getExtension(CommandsExtension);
* log(commandsExtension.getForcedUpdates(tr))
* ```
*
* This can be used for updating:
*
* - `nodeViews`
* - `editable` status of the editor
* - `attributes` - for the top level node
*
* @internal
*/
private getForcedUpdates;
/**
* Get the command metadata.
*/
private getCommandMeta;
private setCommandMeta;
/**
* Add the commands from the provided `commands` property to the `chained`,
* `original` and `unchained` objects.
*/
private addCommands;
/**
* Create an unchained command method.
*/
private unchainedFactory;
/**
* Create the unchained command.
*/
private createUnchainedCommand;
/**
* Create a chained command method.
*/
private chainedFactory;
}
export interface InsertNodeOptions {
attrs?: ProsemirrorAttributes;
marks?: Array<Mark | string | MarkType>;
/**
* The content to insert.
*/
content?: Fragment | ProsemirrorNode | ProsemirrorNode[] | string;
/**
* @deprecated use selection property instead.
*/
range?: FromToProps;
/**
* Set the selection where the command should occur.
*/
selection?: PrimitiveSelection;
/**
* Set this to true to replace an empty parent block with this content (if the
* content is a block node).
*/
replaceEmptyParentBlock?: boolean;
}
/**
* Provides the list of Prosemirror EditorView props that should be updated/
*/
export type ForcedUpdateMeta = UpdatableViewProps[];
export type UpdatableViewProps = 'attributes' | 'editable';
export interface CommandExtensionMeta {
forcedUpdates?: UpdatableViewProps[];
}
/**
* A type with a name property.
*/
type WithName<Type> = Type & {
name: string;
};
declare global {
namespace Remirror {
interface ManagerStore<Extension extends AnyExtension> {
/**
* Get the forced updates from the provided transaction.
*/
getForcedUpdates: (tr: Transaction) => ForcedUpdateMeta;
/**
* Enables the use of custom commands created by extensions which extend
* the functionality of your editor in an expressive way.
*
* @remarks
*
* Commands are synchronous and immediately dispatched. This means that
* they can be used to create menu items when the functionality you need
* is already available by the commands.
*
* ```ts
* if (commands.toggleBold.isEnabled()) {
* commands.toggleBold();
* }
* ```
*/
commands: CommandsFromExtensions<Extension>;
/**
* Chainable commands for composing functionality together in quaint and
* beautiful ways
*
* @remarks
*
* You can use this property to create expressive and complex commands
* that build up the transaction until it can be run.
*
* The way chainable commands work is by adding multiple steps to a shared
* transaction which is then dispatched when the `run` command is called.
* This requires making sure that commands within your code use the `tr`
* that is provided rather than the `state.tr` property. `state.tr`
* creates a new transaction which is not shared by the other steps in a
* chainable command.
*
* The aim is to make as many commands as possible chainable as explained
* [here](https://github.com/remirror/remirror/issues/418#issuecomment-666922209).
*
* There are certain commands that can't be made chainable.
*
* - undo
* - redo
*
* ```ts
* chain
* .toggleBold()
* .insertText('Hi')
* .setSelection('all')
* .run();
* ```
*
* The `run()` method ends the chain and dispatches the command.
*/
chain: ChainedFromExtensions<Extension>;
}
interface BaseExtension {
/**
* `ExtensionCommands`
*
* This pseudo property makes it easier to infer Generic types of this
* class.
*
* @internal
*/
['~C']: this['createCommands'] extends AnyFunction ? ReturnType<this['createCommands']> : EmptyShape;
/**
* @experimental
*
* Stores all the command names for this decoration that have been added
* as decorators to the extension instance. This is used by the
* `CommandsExtension` to pick the commands and store meta data attached
* to each command.
*
* @internal
*/
decoratedCommands?: Record<string, CommandDecoratorOptions>;
/**
* Create and register commands for that can be called within the editor.
*
* These are typically used to create menu's actions and as a direct
* response to user actions.
*
* @remarks
*
* The `createCommands` method should return an object with each key being
* unique within the editor. To ensure that this is the case it is
* recommended that the keys of the command are namespaced with the name
* of the extension.
*
* ```ts
* import { ExtensionFactory } from '@remirror/core';
*
* const MyExtension = ExtensionFactory.plain({
* name: 'myExtension',
* version: '1.0.0',
* createCommands() {
* return {
* haveFun() {
* return ({ state, dispatch }) => {
* if (dispatch) {
* dispatch(tr.insertText('Have fun!'));
* }
*
* return true; // True return signifies that this command is enabled.
* }
* },
* }
* }
* })
* ```
*
* The actions available in this case would be `undoHistory` and
* `redoHistory`. It is unlikely that any other extension would override
* these commands.
*
* Another benefit of commands is that they are picked up by typescript
* and can provide code completion for consumers of the extension.
*/
createCommands?(): ExtensionCommandReturn;
}
interface ExtensionStore {
/**
* A property containing all the available commands in the editor.
*
* This should only be accessed after the `onView` lifecycle method
* otherwise it will throw an error. If you want to use it in the
* `createCommands` function then make sure it is used within the returned
* function scope and not in the outer scope.
*/
commands: CommandsFromExtensions<Extensions | (AnyExtension & {
_T: false;
})>;
/**
* A method that returns an object with all the chainable commands
* available to be run.
*
* @remarks
*
* Each chainable command mutates the states transaction so after running
* all your commands. you should dispatch the desired transaction.
*
* This should only be called when the view has been initialized (i.e.)
* within the `createCommands` method calls.
*
* ```ts
* import { ExtensionFactory } from '@remirror/core';
*
* const MyExtension = ExtensionFactory.plain({
* name: 'myExtension',
* version: '1.0.0',
* createCommands: () => {
* // This will throw since it can only be called within the returned
* methods.
* const chain = this.store.chain; // ❌
*
* return {
* // This is good 😋
* haveFun() {
* return ({ state, dispatch }) =>
* this.store.chain.insertText('fun!').run(); ✅
* },
* }
* }
* })
* ```
*
* This should only be accessed after the `EditorView` has been fully
* attached to the `RemirrorManager`.
*
* The chain can also be called as a function with a custom `tr`
* parameter. This allows you to provide a custom transaction to use
* within the chainable commands.
*
* Use the command at the beginning of the command chain to override the
* shared transaction.
*
* There are times when you want to be sure of the transaction which is
* being updated.
*
* To restore the previous transaction call the `restore` chained method.
*
* @param tr - the transaction to set
*/
chain: ChainedFromExtensions<Extensions | (AnyExtension & {
_T: false;
})>;
}
interface AllExtensions {
commands: CommandsExtension;
}
/**
* The command names for all core extensions.
*/
type AllCommandNames = LiteralUnion<CommandNames<Remirror.Extensions>, string>;
/**
* The command names for all core extensions.
*/
type AllUiCommandNames = LiteralUnion<UiCommandNames<Remirror.Extensions>, string>;
}
}
export {};