UNPKG

bandicoot

Version:

React rich text editor

3 lines (2 loc) 20.6 kB
import React,{useRef,useEffect,useCallback,forwardRef,useContext,useState,useLayoutEffect}from"react";function ownKeys(a,b){var c=Object.keys(a);if(Object.getOwnPropertySymbols){var d=Object.getOwnPropertySymbols(a);b&&(d=d.filter(function(b){return Object.getOwnPropertyDescriptor(a,b).enumerable})),c.push.apply(c,d)}return c}function _objectSpread2(a){for(var b,c=1;c<arguments.length;c++)b=null==arguments[c]?{}:arguments[c],c%2?ownKeys(Object(b),!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(Object(b)).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function _defineProperty(a,b,c){return b=_toPropertyKey(b),b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function _extends(){return _extends=Object.assign?Object.assign.bind():function(a){for(var b,c=1;c<arguments.length;c++)for(var d in b=arguments[c],b)Object.prototype.hasOwnProperty.call(b,d)&&(a[d]=b[d]);return a},_extends.apply(this,arguments)}function _toPrimitive(a,b){if("object"!=typeof a||null===a)return a;var c=a[Symbol.toPrimitive];if(c!==void 0){var d=c.call(a,b||"default");if("object"!=typeof d)return d;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===b?String:Number)(a)}function _toPropertyKey(a){var b=_toPrimitive(a,"string");return"symbol"==typeof b?b:b+""}function RichTextContainer(a){const b=useRef(Object.assign({},defaultContextValue)),c=b.current,d=useRef([]),e=useRef([]),f=useRef([]),g=useRef([]);return useEffect(()=>{const a=c.getContentEditableElement();a&&a.innerHTML&&c.fireNewHTML()},[c]),c.addSelectionChangedListener=useCallback(a=>{d.current.push(a)},[]),c.removeSelectionChangedListener=useCallback(a=>{d.current=d.current.filter(b=>b!==a)},[]),c.fireSelectionChanged=useCallback(()=>{d.current.forEach(a=>a())},[]),c.addBlurListener=useCallback(a=>{e.current.push(a)},[]),c.removeBlurListener=useCallback(a=>{e.current=e.current.filter(b=>b!==a)},[]),c.fireBlur=useCallback(()=>{e.current.forEach(a=>a())},[]),c.addNewHTMLListener=useCallback(a=>{f.current.push(a)},[]),c.removeNewHTMLListener=useCallback(a=>{f.current=f.current.filter(b=>b!==a)},[]),c.fireNewHTML=useCallback(()=>{f.current.forEach(a=>a())},[]),c.numSerializers=useCallback(()=>g.current.length,[]),c.addSerializer=useCallback(a=>{g.current.push(a)},[]),c.removeSerializer=useCallback(a=>{g.current=g.current.filter(b=>b!==a)},[]),c.serialize=useCallback(a=>(g.current.forEach(b=>b(a)),a.innerHTML),[]),React.createElement(RichTextContext.Provider,{value:c},a.children)}const noop=()=>{},defaultContextValue={addSelectionChangedListener:noop,removeSelectionChangedListener:noop,fireSelectionChanged:noop,selectRangeFromBeforeBlur:noop,getRangeBeforeBlur:noop,addBlurListener:noop,removeBlurListener:noop,fireBlur:noop,addNewHTMLListener:noop,removeNewHTMLListener:noop,fireNewHTML:noop,isFocused:noop,getContentEditableElement:noop,numSerializers:()=>0,addSerializer:noop,removeSerializer:noop,serialize:noop,sanitizeHTML:noop},RichTextContext=React.createContext(defaultContextValue),noop$1=()=>{},noopWithReturn=a=>a;let globalBandicootId=0;const RichTextEditor=forwardRef((a,b)=>{function c(b){b.preventDefault(),b.stopPropagation();const c=b.clipboardData||window.clipboardData,d=!c.types.includes("text/html"),e=c.getData(d?"text":"text/html");let f=n.sanitizeHTML(e,"pasteHTML"),g=a.pasteFn(f);!1!==g&&document.execCommand(d?"insertText":"insertHTML",null,g)}function d(){e(),document.execCommand("removeFormat"),document.execCommand("delete")}function e(a){const b=document.createRange();b.selectNodeContents(o.current),a&&b.collapse(!1);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)}function f(){if(q()){const a=window.getSelection();0<a.rangeCount&&(p.current=window.getSelection().getRangeAt(0)),n.fireSelectionChanged()}}function g(){const b=k();b!==t&&(u(b),a.save(b))}function h(){var a;let b=(null===o||void 0===o||null===(a=o.current)||void 0===a?void 0:a.innerHTML)||"";if(0<n.numSerializers()){const a=new DOMParser().parseFromString(b,"text/html");b=n.serialize(a.body)}return b}function i(b,c){c?(o.current.innerHTML=n.sanitizeHTML(b,"setHTML"),a.autoFocus&&l()):(d(),o.current.innerHTML=n.sanitizeHTML(b,"setHTML"),l()),n.fireNewHTML()}function j(){i("")}function k(){return n.sanitizeHTML(h(),"getHTML")}function l(){e(!0),r(!0)}function m(){return!window.chrome||!window.chrome.webstore&&!window.chrome.runtime?{opacity:"0.54"}:{color:"rgb(117, 117, 117)"}}if("function"!=typeof a.sanitizeHTML)throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");const n=useContext(RichTextContext);n.sanitizeHTML=a.sanitizeHTML;const o=useRef(null),p=useRef(null),{isFocused:q,setFocused:r}=useSynchronousFocusState(),s=useRef(globalBandicootId++),[t,u]=useState(()=>n.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[v,w]=useState(!1);if(b){const a={setHTML:i,resetEditor:j,getHTML:k,focus:l};"function"==typeof b?b({current:a}):b.current=a}useLayoutEffect(()=>(o.current.addEventListener("paste",c),()=>o.current.removeEventListener("paste",c)),[a.pasteFn]),useLayoutEffect(()=>(document.addEventListener("selectionchange",f),()=>document.removeEventListener("selectionchange",f)),[n.fireSelectionChanged]),useEffect(()=>{if(o.current)for(let a=o.current;a.parentNode;)a=a.parentNode,"SPAN"===a.tagName&&console.warn("A span tag has been detected in the parents of <RichTextEditor>. This has been known to cause issues. https://github.com/CanopyTax/bandicoot/issues/69")},[o.current]),useEffect(()=>{if(a.save&&a.unchangedInterval&&o.current&&q()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(g,a.unchangedInterval)});return c.observe(o.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,o.current,q()]),useEffect(()=>{if(!1===q()){const a=setTimeout(()=>{n.fireBlur(),g()},100);return()=>{clearTimeout(a)}}},[q()]),useEffect(()=>{n.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(o.current&&document.activeElement!==o.current&&!o.current.contains(document.activeElement)){if(a.usePreviousRange&&p.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(p.current)}else o.current.focus();r(!0),setTimeout(f)}},n.getRangeFromBeforeBlur=()=>p.current,n.isFocused=q,n.getContentEditableElement=()=>o.current}),useEffect(()=>{!v&&a.initialHTML&&(w(!0),i(a.initialHTML,!0))},[v,i,n]),useEffect(()=>{if(a.placeholder){const b=document.createElement("style");b.textContent=".bandicoot-id-".concat(s.current,":empty:before { content: attr(data-placeholder); }"),document.head.appendChild(b);const c=Array.prototype.slice.call(document.styleSheets).find(a=>a.ownerNode===b);if(c){const b=c.cssRules[0].style,d=a.placeholderStyle?a.placeholderStyle:m();for(let c in d)b[c]=a.placeholderStyle?a.placeholderStyle[c]:d[c]}return()=>b.parentNode.removeChild(b)}},[a.placeholder,a.placeholderStyle,s.current]);const x=a.style||{};return React.createElement("div",{contentEditable:!a.disabled,onBlur:()=>r(!1),onFocus:function(){r(!0);const a=window.getSelection();0<a.rangeCount&&(p.current=a.getRangeAt(0))},onInput:a.onInput,onKeyDown:a.onKeyDown,ref:o,className:a.className+" bandicoot-id-"+s.current,style:_objectSpread2({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},x),"data-placeholder":a.placeholder,tabIndex:a.tabIndex})});function useSynchronousFocusState(){const a=useRef(null),[b,c]=useState(!1);return{isFocused:function(){return a.current},setFocused:function(d){a.current=d,c(!b)}}}RichTextEditor.defaultProps={className:"",initialHTML:"",save:noop$1,placeholder:"",pasteFn:noopWithReturn};function useDocumentExecCommand(a){const b=useContext(RichTextContext);return{performCommand(c){b.selectRangeFromBeforeBlur(),document.execCommand(a)},performCommandWithValue(c){b.selectRangeFromBeforeBlur();document.execCommand(a,null,c)}}}function useFormatBlock(){const{performCommandWithValue:a}=useDocumentExecCommand("formatBlock");return{formatBlock(b){b===document.queryCommandValue("formatBlock")?a("div"):a(b)}}}const defaultActiveInfo={isActive:!1,value:!1};function useDocumentQueryCommandState(a,b){function c(){const c=document.queryCommandValue(a),d=b?b===c:document.queryCommandState(a);(d!==e.isActive||c!==e.value)&&f({isActive:d,value:c})}function d(){f(defaultActiveInfo)}const[e,f]=useState(defaultActiveInfo),g=useContext(RichTextContext);return useEffect(()=>(g.addSelectionChangedListener(c),()=>g.removeSelectionChangedListener(c)),[e,f]),useEffect(()=>(g.addBlurListener(d),()=>g.removeBlurListener(d)),[e,f]),{isActive:e.isActive,activeValue:e.value}}function useFontSize(a){let{defaultFontSize:c="14px",fontSizes:b}=a;if(7<b.length)throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");const[d,e]=useState(c),[f,g]=useState(null),{performCommandWithValue:h}=useDocumentExecCommand("fontSize"),i=useContext(RichTextContext),j=function(){return useEffect(()=>{function a(){const a=window.getSelection();if((!f||a.anchorNode!==f.anchorNode||a.anchorOffset!==f.anchorOffset||a.focusNode!==f.focusNode||a.focusOffset!==f.focusOffset)&&(g({anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),0<a.rangeCount)){let b=a.getRangeAt(0).startContainer;1!==b.nodeType&&(b=b.parentElement);const c=window.getComputedStyle(b).fontSize,f=c;f!==d&&e(f)}}return i.addSelectionChangedListener(a),()=>i.removeSelectionChangedListener(a)},[d,e,f]),d}();return function(){useEffect(()=>{const a=b.reduce((a,b,c)=>"".concat(a," font[size=\"").concat(c+1,"\"] {font-size: ").concat(b,"}"),""),c=document.createElement("style");return c.textContent=a,document.head.appendChild(c),()=>document.head.removeChild(c)},[b]),useEffect(()=>{function a(a){const c=a.querySelectorAll("font");for(let d=0;d<c.length;d++){const a=c[d],e=+a.getAttribute("size");if(e>b.length)throw Error("Cannot find fontSize for integer size '".concat(e,"'"));const f=b[e-1];a.removeAttribute("size"),a.style.fontSize=f,a.dataset.integerSize=e}}return i.addSerializer(a),()=>i.removeSerializer(a)},[b]),useEffect(()=>{function a(){const a=i.getContentEditableElement().querySelectorAll("font");for(let c=0;c<a.length;c++){const d=a[c],e=d.style.fontSize,f=b.findIndex(a=>a===e)+1;0<f&&(d.style.fontSize="",d.setAttribute("size",f))}}return i.addNewHTMLListener(a),()=>i.removeNewHTMLListener(a)},[b])}(),{currentlySelectedFontSize:j,setSize(a){const c=b.findIndex(b=>b===a)+1;if(0>=c)throw Error("Cannot set font size since '".concat(a,"' was not passed in the fontSizes array"));e(a),h(c)}}}const noop$2=()=>{},defaultAcceptImgTypes=".jpg, .png, image/*",defaultOpts={processImgElement:noop$2,fileBlobToUrl:defaultFileBlobToUrl,acceptImgTypes:defaultAcceptImgTypes};function useImage(){function a(a){a.style.cursor="pointer",a.style.maxWidth="100%",c(a)}function b(a){a.querySelectorAll("img").forEach(a=>{i.current[a.src]&&(a.src=i.current[a.src])})}let{processImgElement:c=noop$2,fileBlobToUrl:d=defaultFileBlobToUrl,acceptImgTypes:e=defaultAcceptImgTypes}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOpts;const{performCommandWithValue:f}=useDocumentExecCommand("insertImage"),g=useContext(RichTextContext),h=useRef(null),i=React.useRef({});return function(){useEffect(()=>{function b(){const b=g.getContentEditableElement().querySelectorAll("img:not([data-text-as-image])");b.forEach(a)}return g.addNewHTMLListener(b),()=>g.removeNewHTMLListener(b)},[c])}(),function(){useEffect(()=>{h.current=document.createElement("input");const b=h.current;b.type="file",b.accept=e,b.multiple=!1,b.addEventListener("change",()=>{b.files&&0<b.files.length&&d(b.files[0],c=>{f(c);const d=document.querySelector("img[src=\"".concat(c,"\"]"));if(d.src&&d.src.startsWith("blob:")){const a=new FileReader;a.addEventListener("load",()=>{i.current[d.src]=a.result}),a.readAsDataURL(b.files[0])}a(d)})})},[d,c,e])}(),function(){useEffect(()=>(g.addSerializer(b),()=>g.removeSerializer(b)))}(),{chooseFile(a){h.current.click()},removeImage(a){const b=document.createRange();b.selectNode(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b),document.execCommand("delete")}}}function defaultFileBlobToUrl(a,b){b(URL.createObjectURL(a))}let tempId=0;const noop$3=()=>{},defaultOptions={processAnchorElement:noop$3};function useLink(){function a(a,d){c.selectRangeFromBeforeBlur({usePreviousRange:!0});const f="rte-link-temp-id-".concat(tempId++);e("<a href=\"".concat(a,"\" id=\"").concat(f,"\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(d,"</a>"));const g=document.getElementById(f);g.removeAttribute("id"),b(g)}let{processAnchorElement:b=noop$3}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOptions;const c=useContext(RichTextContext),{performCommand:d}=useDocumentExecCommand("unlink"),{performCommandWithValue:e}=useDocumentExecCommand("insertHTML");return function(){useEffect(()=>{function a(){const a=c.getContentEditableElement().querySelectorAll("a");a.forEach(b)}return c.addNewHTMLListener(a),()=>c.removeNewHTMLListener(a)},[b])}(),{getTextFromBeforeBlur:function(){const a=c.getRangeFromBeforeBlur();return a?a.toString():null},selectEntireLink:function(a){const b=document.createRange();b.selectNodeContents(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)},unlink:function(){d(),(window.navigator.userAgent.includes("Edge/14")||window.navigator.userAgent.includes("Edge/15")||window.navigator.userAgent.includes("Edge/16")||window.navigator.userAgent.includes("Edge/17"))&&document.execCommand("removeFormat")},insertLink:a}}let tempId$1=0;const noop$4=()=>{},defaultOptions$1={processContentEditableFalseElement:noop$4};function useContentEditableFalse(){function a(a){if(a.removeAttribute("id"),a.contentEditable=!1,a.addEventListener("click",()=>selectRangeAfterNode(a)),!a.previousSibling&&a.parentElement===d.getContentEditableElement()){const b=document.createElement("span");a.parentElement.insertBefore(b,a)}if(!a.nextSibling&&a.parentElement===d.getContentEditableElement()){const b=document.createElement("span");a.insertAdjacentElement("afterend",b)}selectRangeAfterNode(a),b(a)}let{processContentEditableFalseElement:b=noop$4}=0<arguments.length&&arguments[0]!==void 0?arguments[0]:defaultOptions$1;const{performCommandWithValue:c}=useDocumentExecCommand("insertHTML"),d=useContext(RichTextContext);return{insertContentEditableFalseElement(b){const e="rte-ce-false-temp-id-"+tempId$1++,f="<span id=\"".concat(e,"\">").concat(d.sanitizeHTML(b,"insertContentEditableFalseHTML"),"</span>");c(f);const g=document.getElementById(e);a(g)}}}function selectRangeAfterNode(a){const b=document.createRange();b.setStartAfter(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)}const noop$5=()=>{},defaultOptions$2={processSerializedElement:noop$5,fontFamily:null,fillStyle:"#00bf4b",fontWeight:"bold",textBottom:null,backgroundColor:"transparent"};function useTextAsImage(){let{processSerializedElement:f=noop$5,fontFamily:a,fillStyle:b,fontWeight:c,textBottom:d,backgroundColor:e}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOptions$2;const{performCommandWithValue:g}=useDocumentExecCommand("insertImage"),h=useContext(RichTextContext);return function(){useEffect(()=>{function f(){const f=h.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let g=0;g<f.length;g++){const h=f[g],i=textToUrl({text:h.dataset.textAsImage,referenceEl:h.previousElementSibling||h.nextElementSibling||h.parentElement,fontFamily:a,fontWeight:c,fillStyle:b,textBottom:d}),j=document.createElement("img");j.src=i,processImgElement(j,h.dataset.textAsImage,e),h.parentNode.replaceChild(j,h)}}return h.addNewHTMLListener(f),()=>h.removeNewHTMLListener(f)})}(),function(){useEffect(()=>{function a(a){const b=a.querySelectorAll("img[data-text-as-image]");for(let c=0;c<b.length;c++){const a=b[c],d=document.createElement("span");d.dataset.textAsImage=a.dataset.textAsImage,f(d,d.dataset.textAsImage),a.parentNode.replaceChild(d,a)}}return h.addSerializer(a),()=>h.removeSerializer(a)},[])}(),{insertTextAsImage(f){h.selectRangeFromBeforeBlur({usePreviousRange:!0});const i=textToUrl({text:f,referenceEl:getSelectedElement(),fontFamily:a,fontWeight:c,fillStyle:b,textBottom:d});g(i);const j=document.querySelector("img[src=\"".concat(i,"\"]:not([data-text-as-image])"));processImgElement(j,f,e)}}}function textToUrl(a){let{text:b,referenceEl:d,fontFamily:e,fillStyle:f,fontWeight:g,textBottom:h}=a;const i=window.getComputedStyle(d),j=+i.fontSize.replace("px",""),k=+i.lineHeight.replace("px",""),l=e||i.fontFamily,m="".concat(g||"normal"," ").concat(j,"px ").concat(l),n=document.createElement("div");n.style.font=m,n.style.position="absolute",n.style.visibility="hidden",n.style.whiteSpace="nowrap",n.textContent=b,document.body.appendChild(n);const o=document.createElement("canvas");o.width=n.clientWidth+1,o.height=k;const p=o.getContext("2d");p.font=m,p.fillStyle=f||defaultOptions$2.fillStyle,p.textBaseline="bottom";return p.fillText(b,0,h||k-3),document.body.removeChild(n),p.canvas.toDataURL()}function processImgElement(a,b,c){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,a.style.backgroundColor=c||defaultOptions$2.backgroundColor,a.addEventListener("click",b=>{const c=a.getBoundingClientRect(),d=c.left+c.width/2,e=document.createRange();b.x<d?e.setStartBefore(a):e.setStartAfter(a);const f=window.getSelection();f.removeAllRanges(),f.addRange(e)})}function getSelectedElement(){let a=getSelection().getRangeAt(0).commonAncestorContainer;return 1===a.nodeType?a:a.parentElement}function useElementDeletionDetection(a,b){const c=useContext(RichTextContext);useEffect(()=>{if(a){function d(){a.isConnected||a._bandicoot_delete_callback_called||(a._bandicoot_delete_callback_called=!0,b(a))}return c.addSelectionChangedListener(d),()=>c.removeSelectionChangedListener(d)}},[a,b])}function styleInject(a,b){void 0===b&&(b={});var c=b.insertAt;if(a&&"undefined"!=typeof document){var d=document.head||document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css","top"===c?d.firstChild?d.insertBefore(e,d.firstChild):d.appendChild(e):d.appendChild(e),e.styleSheet?e.styleSheet.cssText=a:e.appendChild(document.createTextNode(a))}}var css=".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}",style={bandicootButton:"icon-button_bandicootButton__2IZP5"};styleInject(".icon-button_bandicootButton__2IZP5 {\n background-color: transparent;\n border: none;\n border-radius: 4px;\n}\n.icon-button_bandicootButton__2IZP5:hover {\n transition: background-color .25s ease-in-out;\n background-color: rgba(0, 0, 0, 0.05);\n}\n.icon-button_bandicootButton__2IZP5 svg {\n fill: currentcolor;\n}");function IconButton(a){return React.createElement("button",_extends({},a,{className:"\n ".concat(style.bandicootButton,"\n ").concat(a.isActive?"active-control-button":"","\n ").concat(a.className||"","\n "),onClick:a.onClick}),a.children)}BoldIcon.defaultProps={width:24,height:24};function BoldIcon(a){return React.createElement("svg",{width:a.width,height:a.height,xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",viewBox:"0 0 317.41 317.41"},React.createElement("path",{d:"M281.4 158.7c21.7-15.1 36-40.3 36-68.7 0-46.2-37.6-83.7-83.7-83.7h-40H115 45 0v30h45v245H0v30h45 70 78.7 40c46.2 0 83.8-37.6 83.8-83.7C317.4 199 303.2 173.9 281.4 158.7zM193.7 36.2c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115v-107.5H193.7zM115 173.7h78.7c29.6 0 53.8 24.1 53.8 53.8s-24.1 53.8-53.7 53.8H115V173.7z"}))}function BoldButton(a){const{performCommand:b}=useDocumentExecCommand("bold"),{isActive:c}=useDocumentQueryCommandState("bold");return React.createElement(IconButton,{onClick:b,isActive:c},React.createElement(BoldIcon,a))}export{BoldButton,RichTextContainer,RichTextContext,RichTextEditor,useContentEditableFalse,useDocumentExecCommand,useDocumentQueryCommandState,useElementDeletionDetection,useFontSize,useFormatBlock,useImage,useLink,useTextAsImage}; //# sourceMappingURL=bandicoot.esm.js.map