{"id":13758274,"url":"https://github.com/HubSpot/draft-extend","last_synced_at":"2025-05-10T07:30:47.961Z","repository":{"id":9900846,"uuid":"63554433","full_name":"HubSpot/draft-extend","owner":"HubSpot","description":"Build extensible Draft.js editors with configurable plugins and integrated serialization.","archived":false,"fork":false,"pushed_at":"2023-02-25T23:23:21.000Z","size":334,"stargazers_count":116,"open_issues_count":19,"forks_count":20,"subscribers_count":151,"default_branch":"master","last_synced_at":"2025-04-02T03:41:43.478Z","etag":null,"topics":["draft","draft-js","javascript","react","rich-text-editor"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HubSpot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-07-17T22:03:54.000Z","updated_at":"2024-05-03T15:34:06.000Z","dependencies_parsed_at":"2024-06-18T17:03:55.437Z","dependency_job_id":"29b87167-f8f1-4866-abcc-700ee7a6889b","html_url":"https://github.com/HubSpot/draft-extend","commit_stats":{"total_commits":116,"total_committers":13,"mean_commits":8.923076923076923,"dds":0.5258620689655172,"last_synced_commit":"b3772c816804d63d469742f68465672b81d73588"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HubSpot%2Fdraft-extend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HubSpot%2Fdraft-extend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HubSpot%2Fdraft-extend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HubSpot%2Fdraft-extend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HubSpot","download_url":"https://codeload.github.com/HubSpot/draft-extend/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253324113,"owners_count":21890847,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["draft","draft-js","javascript","react","rich-text-editor"],"created_at":"2024-08-03T13:00:23.962Z","updated_at":"2025-05-10T07:30:47.724Z","avatar_url":"https://github.com/HubSpot.png","language":"JavaScript","readme":"# draft-extend\n\n[![npm version](https://badge.fury.io/js/draft-extend.svg)](https://www.npmjs.com/package/draft-extend) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\n*Build extensible [Draft.js](http://draftjs.org) editors with configurable plugins and integrated serialization*\n\n***\n\n###### Jump to:\n- [Overview](#overview)\n- [Examples](#examples)\n- [Editor](#editor)\n- [compose](#compose)\n- [Building Plugins](building-plugins.md)\n\n***\n\n## Overview\nDraft Extend is a platform to build a full-featured Draft.js editor using modular plugins that can integrate with [draft-convert](http://github.com/HubSpot/draft-convert) to serialize with HTML. The higher-order function API makes it extremely easy to use any number of plugins for rendering and conversion.\n\n#### Usage:\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport {EditorState} from 'draft-js';\nimport {Editor, compose} from 'draft-extend';\nimport {convertFromHTML, convertToHTML} from 'draft-convert';\n\nconst plugins = compose(\n    FirstPlugin,\n    SecondPlugin,\n    ThirdPlugin\n);\n\nconst EditorWithPlugins = plugins(Editor); // Rich text editor component with plugin functionality\nconst toHTML = plugins(convertFromHTML); // function to convert from HTML including plugin functionality\nconst fromHTML = plugins(convertToHTML); // function to convert to HTML including plugin functionality\n\nconst MyEditor = React.createClass({\n    getInitialState() {\n        return {\n            editorState: EditorState.createWithContent(fromHTML('\u003cdiv\u003e\u003c/div\u003e'))\n        };\n    },\n\n    onChange(editorState) {\n        const html = toHTML(editorState.getCurrentContent());\n        console.log(html); // don't actually convert to HTML on every change!\n        this.setState({editorState});\n    },\n\n    render() {\n        return (\n            \u003cEditorWithPlugins\n                editorState={this.state.editorState}\n                onChange={this.onChange}\n            /\u003e\n        );\n    }\n});\n\nReactDOM.render(\n    \u003cMyEditor /\u003e,\n    document.querySelector('.app')\n);\n```\n\n***\n\n## Examples\n\nExamples of how to build plugins of different types are included in the [example](example/) directory. To run the examples locally:\n\n1. run `npm install` in the `draft-extend` directory\n2. open any HTML file in the `examples` directory in your web browser - no local server is necessary\n\n***\n\n## Editor\n**Editor component on which to extend functionality with plugins created by [`createPlugin`](#createplugin).**\n\n#### Props\nThe most important two props are:\n- `editorState` - Draft.js `EditorState` instance to be rendered.\n- `onChange: function(editorState: EditorState): void` - Like with vanilla Draft.js, function called on any editor change passing the `EditorState`.\n\nOther props are used by plugins composed around `Editor`. See [Building Plugins](building-plugins.md) for more information. **These should generally not be used outside of the context of a plugin**:\n- `buttons`: `Array\u003cComponent\u003e` Array of React components to add to the controls of the editor.\n- `overlays`: `Array\u003cComponent\u003e` Array of React components to add as overlays to the editor.\n- `decorators`: `Array\u003cDraftDecorator\u003e` Array of Draft.js decorator objects used to render the EditorState. They are added to the EditorState as a CompositeDecorator within the component and are of shape `{strategy, component}`.\n- `baseDecorator`: `DraftDecoratorType` Replacement decorator object to override the built-in `CompositeDecorator`'s behavior. See the \"Beyond CompositeDecorator\" section on [this page of the Draft.js docs](https://draftjs.org/docs/advanced-topics-decorators.html#content) for more information.\n- `styleMap`: `Object` Object map from Draft.js inline style type to style object. Used for the Draft.js Editor's `customStyleMap` prop.\n\nAll other props are passed down to the [Draft.js `Editor` component](https://facebook.github.io/draft-js/docs/api-reference-editor.html) and to any buttons and overlays added by plugins.\n\n***\n\n## compose\nSince the API of plugins is based around composition, a basic `compose` function is provided to make it easy to apply plugins to the component as well as conversion functions and provides a single source of truth for plugin configuration.\n```javascript\n// without compose\nconst EditorWithPlugins = FirstPlugin(SecondPlugin(ThirdPlugin(Editor)));\nconst fromHTML = FirstPlugin(SecondPlugin(ThirdPlugin(convertFromHTML)));\nconst toHTML = FirstPlugin(SecondPlugin(ThirdPlugin(convertToHTML)));\n\n// with compose\nconst plugins = compose(\n    FirstPlugin,\n    SecondPlugin,\n    ThirdPlugin\n);\n\nconst EditorWithPlugins = plugins(Editor);\nconst toHTML = plugins(convertToHTML);\nconst fromHTML = plugins(convertFromHTML);\n```\n\n***\n\n## KeyCommandController\n**Higher-order component to consolidate key command listeners across the component tree**\n\nAn increasingly common pattern for rich text editors is a toolbar detached from the main `Editor` component. This toolbar will be outside of the `Editor` component subtree, but will often need to respond to key commands that would otherwise be encapsulated by the `Editor`. `KeyCommandController` is a higher-order component that allows the subscription to key commands to move up the React tree so that components outside that subtree may listen and emit changes to editor state. `KeyCommandController`. It may be used with any component, but a good example is the `Toolbar` component:\n\n```javascript\nimport {Editor, Toolbar, KeyCommandController, compose} from 'draft-extend';\n\nconst plugins = compose(\n  FirstPlugin,\n  SecondPlugin\n);\n\nconst WrappedEditor = plugins(Editor);\nconst WrappedToolbar = plugins(Toolbar);\n\nconst Parent = ({editorState, onChange, handleKeyCommand, addKeyCommandListener, removeKeyCommandListener}) =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cWrappedEditor\n        editorState={editorState}\n        onChange={onChange}\n        handleKeyCommand={handleKeyCommand}\n        addKeyCommandListener={addKeyCommandListener}\n        removeKeyCommandListener={removeKeyCommandListener}\n      /\u003e\n      \u003cWrappedToolbar\n        editorState={editorState}\n        onChange={onChange}\n        addKeyCommandListener={addKeyCommandListener}\n        removeKeyCommandListener={removeKeyCommandListener}\n      /\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default KeyCommandController(Parent);\n```\n\n`KeyCommandController` provides the final `handleKeyCommand` to use in the `Editor` component as well as subscribe/unsubscribe functions. As long as these props are passed from some common parent wrapped with `KeyCommandController` that also receives `editorState` and `onChange` props, other components may subscribe and emit chagnes to the editor state.\n\nAdditionally, `KeyCommandController`s are composable and will defer to the highest parent instance. That is, if a `KeyCommandController` receives `handleKeyCommand`, `addKeyCommandListener`, and `removeKeyCommandListener` props (presumably from another controller) it will delegate to that controller's record of subscribed functions, keeping all listeners in one place.\n","funding_links":[],"categories":["Plugins and Decorators Built for Draft.js"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHubSpot%2Fdraft-extend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHubSpot%2Fdraft-extend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHubSpot%2Fdraft-extend/lists"}