index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { DraftailEditor } from 'draftail';
  4. import { Provider } from 'react-redux';
  5. import { gettext } from '../../utils/gettext';
  6. import Icon from '../Icon/Icon';
  7. export { default as Link } from './decorators/Link';
  8. export { default as Document } from './decorators/Document';
  9. export { default as ImageBlock } from './blocks/ImageBlock';
  10. export { default as EmbedBlock } from './blocks/EmbedBlock';
  11. import {
  12. ModalWorkflowSource,
  13. ImageModalWorkflowSource,
  14. EmbedModalWorkflowSource,
  15. LinkModalWorkflowSource,
  16. DocumentModalWorkflowSource,
  17. } from './sources/ModalWorkflowSource';
  18. import Tooltip from './Tooltip/Tooltip';
  19. import TooltipEntity from './decorators/TooltipEntity';
  20. import EditorFallback from './EditorFallback/EditorFallback';
  21. import CommentableEditor from './CommentableEditor/CommentableEditor';
  22. // 1024x1024 SVG path rendering of the "↵" character, that renders badly in MS Edge.
  23. const BR_ICON =
  24. 'M.436 633.471l296.897-296.898v241.823h616.586V94.117h109.517v593.796H297.333v242.456z';
  25. /**
  26. * Registry for client-side code of Draftail plugins.
  27. */
  28. const PLUGINS = {};
  29. const registerPlugin = (plugin) => {
  30. PLUGINS[plugin.type] = plugin;
  31. return PLUGINS;
  32. };
  33. /**
  34. * Wraps a style/block/entity type’s icon with an icon font implementation,
  35. * so Draftail can use icon fonts in its toolbar.
  36. */
  37. export const wrapWagtailIcon = (type) => {
  38. const isIconFont = type.icon && typeof type.icon === 'string';
  39. if (isIconFont) {
  40. return Object.assign(type, {
  41. icon: <Icon name={type.icon} />,
  42. });
  43. }
  44. return type;
  45. };
  46. /**
  47. * Initialises the DraftailEditor for a given field.
  48. * @param {string} selector
  49. * @param {Object} options
  50. * @param {Element} currentScript
  51. */
  52. const initEditor = (selector, options, currentScript) => {
  53. // document.currentScript is not available in IE11. Use a fallback instead.
  54. const context = currentScript ? currentScript.parentNode : document.body;
  55. // If the field is not in the current context, look for it in the whole body.
  56. // Fallback for sequence.js jQuery eval-ed scripts running in document.head.
  57. const field =
  58. context.querySelector(selector) || document.body.querySelector(selector);
  59. const editorWrapper = document.createElement('div');
  60. editorWrapper.className = 'Draftail-Editor__wrapper';
  61. editorWrapper.setAttribute('data-draftail-editor-wrapper', true);
  62. field.parentNode.appendChild(editorWrapper);
  63. const serialiseInputValue = (rawContentState) => {
  64. field.rawContentState = rawContentState;
  65. field.value = JSON.stringify(rawContentState);
  66. };
  67. const blockTypes = options.blockTypes || [];
  68. const inlineStyles = options.inlineStyles || [];
  69. let entityTypes = options.entityTypes || [];
  70. entityTypes = entityTypes.map(wrapWagtailIcon).map((type) => {
  71. const plugin = PLUGINS[type.type];
  72. // Override the properties defined in the JS plugin: Python should be the source of truth.
  73. return Object.assign({}, plugin, type);
  74. });
  75. const enableHorizontalRule = options.enableHorizontalRule
  76. ? {
  77. description: gettext('Horizontal line'),
  78. }
  79. : false;
  80. const rawContentState = JSON.parse(field.value);
  81. field.rawContentState = rawContentState;
  82. const editorRef = (ref) => {
  83. // Bind editor instance to its field so it can be accessed imperatively elsewhere.
  84. field.draftailEditor = ref;
  85. };
  86. const sharedProps = {
  87. rawContentState: rawContentState,
  88. onSave: serialiseInputValue,
  89. placeholder: gettext('Write here…'),
  90. spellCheck: true,
  91. enableLineBreak: {
  92. description: gettext('Line break'),
  93. icon: BR_ICON,
  94. },
  95. showUndoControl: { description: gettext('Undo') },
  96. showRedoControl: { description: gettext('Redo') },
  97. maxListNesting: 4,
  98. stripPastedStyles: false,
  99. ...options,
  100. blockTypes: blockTypes.map(wrapWagtailIcon),
  101. inlineStyles: inlineStyles.map(wrapWagtailIcon),
  102. entityTypes,
  103. enableHorizontalRule,
  104. };
  105. const styles = getComputedStyle(document.documentElement);
  106. const colors = {
  107. standardHighlight: styles.getPropertyValue('--color-primary-light'),
  108. overlappingHighlight: styles.getPropertyValue('--color-primary-lighter'),
  109. focusedHighlight: styles.getPropertyValue('--color-primary'),
  110. };
  111. // If the field has a valid contentpath - ie is not an InlinePanel or under a ListBlock -
  112. // and the comments system is initialized then use CommentableEditor, otherwise plain DraftailEditor
  113. const contentPath = window.comments?.getContentPath(field) || '';
  114. const editor =
  115. window.comments?.commentApp && contentPath !== '' ? (
  116. <Provider store={window.comments.commentApp.store}>
  117. <CommentableEditor
  118. editorRef={editorRef}
  119. commentApp={window.comments.commentApp}
  120. fieldNode={field.parentNode}
  121. contentPath={contentPath}
  122. colorConfig={colors}
  123. isCommentShortcut={window.comments.isCommentShortcut}
  124. {...sharedProps}
  125. />
  126. </Provider>
  127. ) : (
  128. <DraftailEditor ref={editorRef} {...sharedProps} />
  129. );
  130. ReactDOM.render(
  131. <EditorFallback field={field}>{editor}</EditorFallback>,
  132. editorWrapper,
  133. );
  134. };
  135. export default {
  136. initEditor,
  137. registerPlugin,
  138. // Components exposed for third-party reuse.
  139. ModalWorkflowSource,
  140. ImageModalWorkflowSource,
  141. EmbedModalWorkflowSource,
  142. LinkModalWorkflowSource,
  143. DocumentModalWorkflowSource,
  144. Tooltip,
  145. TooltipEntity,
  146. };