{"id":15649166,"url":"https://github.com/thibaudcolas/draftjs-conductor","last_synced_at":"2025-04-05T12:06:25.639Z","repository":{"id":29426811,"uuid":"121653315","full_name":"thibaudcolas/draftjs-conductor","owner":"thibaudcolas","description":"📝✨ Little Draft.js helpers to make rich text editors “just work”","archived":false,"fork":false,"pushed_at":"2025-03-21T11:15:02.000Z","size":39921,"stargazers_count":40,"open_issues_count":14,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-29T11:45:53.278Z","etag":null,"topics":["draft-js","draftjs-utils","wagtail","wysiwyg"],"latest_commit_sha":null,"homepage":"https://thibaudcolas.github.io/draftjs-conductor","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thibaudcolas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"docs/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/SECURITY.md","support":"docs/SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-02-15T16:37:22.000Z","updated_at":"2025-03-08T05:23:16.000Z","dependencies_parsed_at":"2023-02-15T15:01:53.439Z","dependency_job_id":"f22ec807-5cff-4397-bc72-593337bb0e77","html_url":"https://github.com/thibaudcolas/draftjs-conductor","commit_stats":{"total_commits":748,"total_committers":6,"mean_commits":"124.66666666666667","dds":0.5120320855614973,"last_synced_commit":"628c63e0a5818aa171e7a48d5dbb03c4f1664c81"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thibaudcolas%2Fdraftjs-conductor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thibaudcolas%2Fdraftjs-conductor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thibaudcolas%2Fdraftjs-conductor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thibaudcolas%2Fdraftjs-conductor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thibaudcolas","download_url":"https://codeload.github.com/thibaudcolas/draftjs-conductor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332604,"owners_count":20921853,"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-js","draftjs-utils","wagtail","wysiwyg"],"created_at":"2024-10-03T12:28:28.957Z","updated_at":"2025-04-05T12:06:25.606Z","avatar_url":"https://github.com/thibaudcolas.png","language":"TypeScript","readme":"# [Draft.js conductor](https://thibaudcolas.github.io/draftjs-conductor/) [\u003cimg src=\"https://raw.githubusercontent.com/thibaudcolas/draftail.org/main/.github/draftail-logo.svg?sanitize=true\" width=\"90\" height=\"90\" align=\"right\"\u003e](https://www.draftail.org/)\n\n[![npm](https://img.shields.io/npm/v/draftjs-conductor.svg)](https://www.npmjs.com/package/draftjs-conductor) [![Build status](https://github.com/thibaudcolas/draftjs-conductor/workflows/CI/badge.svg)](https://github.com/thibaudcolas/draftjs-conductor/actions) [![Coverage Status](https://coveralls.io/repos/github/thibaudcolas/draftjs-conductor/badge.svg)](https://coveralls.io/github/thibaudcolas/draftjs-conductor)\n\n\u003e 📝✨ Little [Draft.js](https://facebook.github.io/draft-js/) helpers to make rich text editors _just work_. Built for [Draftail](https://www.draftail.org/).\n\n[![Photoshop’s Magic Wand selection tool applied on a WYSIWYG editor interface](https://thibaudcolas.github.io/draftjs-conductor/wysiwyg-magic-wand.png)](https://thibaudcolas.github.io/draftjs-conductor)\n\nCheck out the [online demo](https://thibaudcolas.github.io/draftjs-conductor)!\n\n## Features\n\n- [Infinite list nesting](#infinite-list-nesting)\n- [Idempotent copy-paste between editors](#idempotent-copy-paste-between-editors)\n- [Editor state data conversion helpers](#editor-state-data-conversion-helpers)\n\n---\n\n### Infinite list nesting\n\nBy default, Draft.js only provides support for [5 list levels](https://github.com/facebook/draft-js/blob/232791a4e92d94a52c869f853f9869367bdabdac/src/component/contents/DraftEditorContents-core.react.js#L58-L62) for bulleted and numbered lists. While this is often more than enough, some editors need to go further.\n\nInstead of manually writing and maintaining the list nesting styles, use those little helpers:\n\n```js\nimport { getListNestingStyles, blockDepthStyleFn } from \"draftjs-conductor\";\n\n\u003cstyle\u003e\n  {getListNestingStyles(6)}\n\u003c/style\u003e\n\u003cEditor blockStyleFn={blockDepthStyleFn} /\u003e\n```\n\n`getListNestingStyles` will generate the necessary CSS for your editor’s lists. `blockDepthStyleFn` will then apply classes to blocks based on their depth, so the styles take effect. Voilà!\n\nIf your editor’s maximum list nesting depth never changes, pre-render the styles as a fragment for better performance:\n\n```js\nconst listNestingStyles = \u003cstyle\u003e{getListNestingStyles(6)}\u003c/style\u003e;\n```\n\nYou can also leverage [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo) to speed up re-renders even if `max` was to change:\n\n```js\nconst NestingStyles = React.memo(ListNestingStyles);\n\n\u003cstyle\u003e\n  \u003cNestingStyles max={max} /\u003e\n\u003c/style\u003e;\n```\n\nRelevant Draft.js issues:\n\n- [maxDepth param is greater than 4 in RichUtils.onTab – facebook/draft-js#997](https://github.com/facebook/draft-js/issues/997)\n- Still problematic: [Nested list styles above 4 levels are not retained when copy-pasting between Draft instances. – facebook/draft-js#1605 (comment)](https://github.com/facebook/draft-js/pull/1605#pullrequestreview-87340460)\n\n---\n\n### Idempotent copy-paste between editors\n\nThe default Draft.js copy-paste handlers lose a lot of the formatting when copy-pasting between Draft.js editors. While this might be ok for some use cases, sites with multiple editors on the same page need them to reliably support copy-paste.\n\nRelevant Draft.js issues:\n\n- [Ability to retain pasted custom entities - facebook/draft-js#380](https://github.com/facebook/draft-js/issues/380)\n- [Copy/paste between editors – facebook/draft-js#787](https://github.com/facebook/draft-js/issues/787)\n- [Extra newlines added to text pasted between two Draft editors – facebook/draft-js#1389](https://github.com/facebook/draft-js/issues/1389)\n- [Copy/paste between editors strips soft returns – facebook/draft-js#1154](https://github.com/facebook/draft-js/issues/1154)\n- [Sequential unstyled blocks are merged into the same block on paste – facebook/draft-js#738](https://github.com/facebook/draft-js/issues/738)\n- [Nested list styles are not retained when copy-pasting between Draft instances. – facebook/draft-js#1163](https://github.com/facebook/draft-js/issues/1163)\n- [Nested list styles above 4 levels are not retained when copy-pasting between Draft instances. – facebook/draft-js#1605 (comment)](https://github.com/facebook/draft-js/pull/1605#pullrequestreview-87340460)\n- [Merged `\u003cp\u003e` tags on paste – facebook/draft-js#523 (comment)](https://github.com/facebook/draft-js/issues/523#issuecomment-371098488)\n\nAll of those problems can be fixed with this library, which overrides the `copy` and `cut` events to transfer more of the editor’s content, and introduces a function to use with the Draft.js [`handlePastedText`](https://draftjs.org/docs/api-reference-editor#handlepastedtext) to retrieve the pasted content.\n\n**This will paste all copied content, even if the target editor might not support it.** To ensure only supported content is retained, use filters like [draftjs-filters](https://github.com/thibaudcolas/draftjs-filters).\n\nNote: IE11 isn’t supported, as it doesn't support storing HTML in the clipboard, and we also use the [`Element.closest`](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest) API.\n\n#### With draft.js 0.11 and above\n\nHere’s how to use the copy/cut override, and the paste handler:\n\n```js\nimport {\n  onDraftEditorCopy,\n  onDraftEditorCut,\n  handleDraftEditorPastedText,\n} from \"draftjs-conductor\";\n\nclass MyEditor extends Component {\n  constructor(props: Props) {\n    super(props);\n\n    this.state = {\n      editorState: EditorState.createEmpty(),\n    };\n\n    this.onChange = this.onChange.bind(this);\n    this.handlePastedText = this.handlePastedText.bind(this);\n  }\n\n  onChange(nextState: EditorState) {\n    this.setState({ editorState: nextState });\n  }\n\n  handlePastedText(\n    text: string,\n    html: string | null,\n    editorState: EditorState,\n  ) {\n    let newState = handleDraftEditorPastedText(html, editorState);\n\n    if (newState) {\n      this.onChange(newState);\n      return true;\n    }\n\n    return false;\n  }\n\n  render() {\n    const { editorState } = this.state;\n\n    return (\n      \u003cEditor\n        editorState={editorState}\n        onChange={this.onChange}\n        onCopy={onDraftEditorCopy}\n        onCut={onDraftEditorCut}\n        handlePastedText={this.handlePastedText}\n      /\u003e\n    );\n  }\n}\n```\n\nThe copy/cut event handlers will ensure the clipboard contains a full representation of the Draft.js content state on copy/cut, while `handleDraftEditorPastedText` retrieves Draft.js content state from the clipboard. Voilà! This also changes the HTML clipboard content to be more semantic, with less styles copied to other word processors/editors.\n\nYou can also use `getDraftEditorPastedContent` method and set new EditorState by yourself. It is useful when you need to do some transformation with content (for example filtering unsupported styles), before past it in the state.\n\n#### With draft.js 0.10\n\nThe above code relies on the `onCopy` and `onCut` event handlers, only available from Draft.js v0.11.0 onwards. For Draft.js v0.10.5, use `registerCopySource` instead, providing a `ref` to the editor:\n\n```js\nimport {\n  registerCopySource,\n  handleDraftEditorPastedText,\n} from \"draftjs-conductor\";\n\nclass MyEditor extends Component {\n  componentDidMount() {\n    this.copySource = registerCopySource(this.editorRef);\n  }\n\n  componentWillUnmount() {\n    if (this.copySource) {\n      this.copySource.unregister();\n    }\n  }\n\n  render() {\n    const { editorState } = this.state;\n\n    return (\n      \u003cEditor\n        ref={(ref) =\u003e {\n          this.editorRef = ref;\n        }}\n        editorState={editorState}\n        onChange={this.onChange}\n        handlePastedText={this.handlePastedText}\n      /\u003e\n    );\n  }\n}\n```\n\n#### With draft-js-plugins\n\nThe setup is slightly different with `draft-js-plugins` (and React hooks) – we need to use the provided `getEditorRef` method:\n\n```tsx\n// reference to the editor\nconst editor = useRef\u003cEditor\u003e(null);\n\n// register code for copying\nuseEffect(() =\u003e {\n  let unregisterCopySource: undefined | unregisterObject = undefined;\n  if (editor.current !== null) {\n    unregisterCopySource = registerCopySource(\n      editor.current.getEditorRef() as any,\n    );\n  }\n  return () =\u003e {\n    unregisterCopySource?.unregister();\n  };\n});\n```\n\nSee [#115](https://github.com/thibaudcolas/draftjs-conductor/issues/115) for further details.\n\n### Editor state data conversion helpers\n\nDraft.js has its own data conversion helpers, [`convertFromRaw`](https://draftjs.org/docs/api-reference-data-conversion#convertfromraw) and [`convertToRaw`](https://draftjs.org/docs/api-reference-data-conversion#converttoraw), which work really well, but aren’t providing that good of an API when initialising or persisting the content of an editor.\n\nWe provide two helper methods to simplify the initialisation and serialisation of content. **`createEditorStateFromRaw`** combines [`EditorState.createWithContent`](https://draftjs.org/docs/api-reference-editor-state#createwithcontent), [`EditorState.createEmpty`](https://draftjs.org/docs/api-reference-editor-state#createempty) and [`convertFromRaw`](https://draftjs.org/docs/api-reference-data-conversion#convertfromraw) as a single method:\n\n```js\nimport { createEditorStateFromRaw } from \"draftjs-conductor\";\n\n// Initialise with `null` if there’s no preexisting state.\nconst editorState = createEditorStateFromRaw(null);\n// Initialise with the raw content state otherwise\nconst editorState = createEditorStateFromRaw({ entityMap: {}, blocks: [] });\n// Optionally use a decorator, like with Draft.js APIs.\nconst editorState = createEditorStateFromRaw(null, decorator);\n```\n\nTo save content, **`serialiseEditorStateToRaw`** combines [`convertToRaw`](https://draftjs.org/docs/api-reference-data-conversion#converttoraw) with checks for empty content – so empty content is saved as `null`, rather than a single text block with empty text as would be the case otherwise.\n\n```js\nimport { serialiseEditorStateToRaw } from \"draftjs-conductor\";\n\n// Content will be `null` if there’s no textual content, or RawDraftContentState otherwise.\nconst content = serialiseEditorStateToRaw(editorState);\n```\n\n## Contributing\n\nSee anything you like in here? Anything missing? We welcome all support, whether on bug reports, feature requests, code, design, reviews, tests, documentation, and more. Please have a look at our [contribution guidelines](docs/CONTRIBUTING.md).\n\n## Credits\n\nView the full list of [contributors](https://github.com/thibaudcolas/draftjs-conductor/graphs/contributors). [MIT](LICENSE) licensed. Website content available as [CC0](https://creativecommons.org/publicdomain/zero/1.0/). Image credit: [FirefoxEmoji](https://github.com/mozilla/fxemoji).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthibaudcolas%2Fdraftjs-conductor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthibaudcolas%2Fdraftjs-conductor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthibaudcolas%2Fdraftjs-conductor/lists"}