npm install textarea-markdown-editor
There are some breaking changes since v1.0.0 released, checkout the release note
Textarea Markdown is a simple markdown editor using only <textarea/>
. It extends textarea by adding formatting features like shortcuts, list-wrapping, invoked commands and other to make user experience better 🙃
Essentially this library just provides the textarea Component. You can choose any markdown parser, create your own layout, and use your own textarea component that is styled and behaves however you like
- Lists wrapping
- Auto formatting pasted links
- Indent tabulation
- Keyboard shortcuts handling
- 17 built-in customizable commands
import React, { Fragment, useRef, useState } from 'react';
import TextareaMarkdown, { TextareaMarkdownRef } from 'textarea-markdown-editor';
function App() {
const [value, setValue] = useState('');
const ref = useRef<TextareaMarkdownRef>(null);
return (
<Fragment>
<button onClick={() => ref.current?.trigger('bold')}>Bold</button>
<br />
<TextareaMarkdown ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
</Fragment>
);
}
ℹ️ Ref instance provide the trigger
function to invoke commands
You can use custom textarea Component. Just wrap it with TextareaMarkdown.Wrapper
import React, { useRef, useState } from 'react';
import TextareaMarkdown, { TextareaMarkdownRef } from 'textarea-markdown-editor';
import TextareaAutosize from 'react-textarea-autosize';
function App() {
const [value, setValue] = useState('');
const ref = useRef<TextareaMarkdownRef>(null);
return (
<TextareaMarkdown.Wrapper ref={ref}>
<TextareaAutosize value={value} onChange={(e) => setValue(e.target.value)} />
</TextareaMarkdown.Wrapper>
);
}
ℹ️ This solution will not create any real dom wrapper
You can specify or overwrite shortcuts for built-in commands or create your own
import React, { useRef, useState } from 'react';
import TextareaMarkdown, { CommandHandler, TextareaMarkdownRef } from 'textarea-markdown-editor';
/** Inserts 🙃 at the current position and select it */
const emojiCommandHandler: CommandHandler = ({ cursor }) => {
// MARKER - means a cursor position, or a selection range if specified two markers
cursor.insert(`${cursor.MARKER}🙃${cursor.MARKER}`);
};
function App() {
const [value, setValue] = useState('');
const ref = useRef<TextareaMarkdownRef>(null);
return (
<Fragment>
<button onClick={() => ref.current?.trigger('insert-emoji')}>Insert 🙃</button>
<br />
<TextareaMarkdown
ref={ref}
value={value}
onChange={(e) => setValue(e.target.value)}
commands={[
{
name: 'code',
shortcut: ['command+/', 'ctrl+/'],
shortcutPreventDefault: true,
},
{
name: 'insert-emoji',
handler: emojiCommandHandler,
},
]}
/>
</Fragment>
);
}
ℹ️ Note that mutation element.value
will not trigger change
event on textarea element. Use cursor.setValue(...)
or other method of Cursor
.
ℹ️ Mousetrap.js is used under the hood for shortcuts handling. It is great solution with simple and intuitive api. You can read more about combination in the documentation
import { bootstrapTextareaMarkdown } from 'textarea-markdown-editor/dist/bootstrap';
const textarea = document.querySelector('textarea'); // element can be obtained from anywhere, this is just an example;
const { trigger, dispose } = bootstrapTextareaMarkdown(textarea, {
options: {}, // optional options config
commands: [], // optional commands configs
});
ℹ️ Checkout sandbox example
ℹ️ Although this is possible, this feature is more of a workaround, since the library was originally written to be used with react, your package manager probably will warn you about missing peer-dependencies
👀 You can find more examples here
- TextareaMarkdownProps
- Command
- CommandHandler
- Built-in commands
- TextareaMarkdownOptions
- TextareaMarkdownRef
ℹ️ TextareaMarkdown
accepts all props which native textarea supports
options
TextareaMarkdownOptions
Options config
commands
Command
[]
Array of commands configuration
Name | Type | Description |
---|---|---|
name | TType |
Built-in or custom command name |
shortcut? | string | string [] |
Shortcut combinations (Mousetrap.js) |
shortcutPreventDefault? | boolean |
Toggle key event prevent default:false |
handler? | CommandHandler |
Handler function for custom commands |
enable? | boolean |
Toggle command enabling |
export type CommandHandler = (context: CommandHandlerContext) => void | Promise<void>;
export type CommandHandlerContext = {
textarea: HTMLTextAreaElement;
cursor: Cursor;
keyEvent?: KeyboardEvent;
clipboardEvent?: ClipboardEvent;
options: TextareaMarkdownOptions;
};
Name | Description | Shortcut |
---|---|---|
bold | Inserts or wraps bold markup | ctrl/command+b |
italic | Inserts or wraps italic markup | ctrl/command+i |
strike-through | Inserts or wraps strike-through markup | ctrl/command+shift+x |
link | Inserts or wraps link markup | |
image | Inserts or wraps image markup | |
unordered-list | Inserts or wraps unordered list markup | |
ordered-list | Inserts or wraps ordered list markup | |
code-block | Inserts or wraps code block markup | |
code-inline | Inserts or wraps inline code markup | |
code | Inserts or wraps inline or block code markup dependent of selected | |
block-quotes | Inserts or wraps block-quotes markup | |
h1 | Inserts h1 headline | |
h2 | Inserts h2 headline | |
h3 | Inserts h3 headline | |
h4 | Inserts h4 headline | |
h5 | Inserts h5 headline | |
h6 | Inserts h6 headline |
Name | Type | Description |
---|---|---|
preferredBoldSyntax | "**" | "__" |
Preferred bold wrap syntax default: '**' |
preferredItalicSyntax | "*" | "_" |
Preferred italic wrap syntax default: '*' |
preferredUnorderedListSyntax | "-" | "*" | "+" |
Preferred unordered list prefix default: '-' |
enableIndentExtension | boolean |
Will handle tab and shift+tab keystrokes, on which will insert/remove indentation instead of the default behavior default:true |
enableLinkPasteExtension | boolean |
Will handle paste event, on which will wrap pasted with link/image markup if pasted is URL default:true |
enablePrefixWrappingExtension | boolean |
Will handle enter keystroke, on which will wrap current list sequence if needed default:true |
enableProperLineRemoveBehaviorExtension | boolean |
Will handle command/ctrl+backspace keystrokes, on which will remove only a current line instead of the default behavior default:true |
customPrefixWrapping | (PrefixWrappingConfig | string )[] |
Array of custom prefixes, that need to be wrapped. (Will not work with enablePrefixWrappingExtension:false ) |
blockQuotesPlaceholder | string |
default: 'quote' |
boldPlaceholder | string |
default: 'bold' |
codeBlockPlaceholder | string |
default: 'code block' |
codeInlinePlaceholder | string |
default: 'code' |
headlinePlaceholder | string | (level : number ) => string |
default: (lvl) => 'headline ' + lvl |
imageTextPlaceholder | string |
Used inside default image markup ![<example>](...) default: 'example' |
imageUrlPlaceholder | string |
Used inside default image markup ![...](<image.png>) default: 'image.png' |
italicPlaceholder | string |
default: 'italic' |
linkTextPlaceholder | string |
Used inside default link markup [<example>](...) default: 'example' |
linkUrlPlaceholder | string |
Used inside default image markup ![...](<url>) default: 'url' |
orderedListPlaceholder | string |
default: 'ordered list' |
strikeThroughPlaceholder | string |
default: 'strike through' |
unorderedListPlaceholder | string |
default: 'unordered list' |
ℹ️ Extends HTMLTextAreaElement
instance
trigger: (command: string) => void;
cursor: Cursor