Comparing version 4.2.0 to 4.2.1
@@ -1,2 +0,1144 @@ | ||
import React,{useRef,useEffect,forwardRef,useContext,useState}from"react";function _defineProperty(a,b,c){return 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||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 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(b,!0).forEach(function(c){_defineProperty(a,c,b[c])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):ownKeys(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}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=a=>{d.current.push(a)},c.removeSelectionChangedListener=a=>{d.current=d.current.filter(b=>b!==a)},c.fireSelectionChanged=()=>{d.current.forEach(a=>a())},c.addBlurListener=a=>{e.current.push(a)},c.removeBlurListener=a=>{e.current=e.current.filter(b=>b!==a)},c.fireBlur=()=>{e.current.forEach(a=>a())},c.addNewHTMLListener=a=>{f.current.push(a)},c.removeNewHTMLListener=a=>{f.current=f.current.filter(b=>b!==a)},c.fireNewHTML=()=>{f.current.forEach(a=>a())},c.numSerializers=()=>g.current.length,c.addSerializer=a=>{g.current.push(a)},c.removeSerializer=a=>{g.current=g.current.filter(b=>b!==a)},c.serialize=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();let c=m.sanitizeHTML((window.clipboardData||b.clipboardData).getData("text/html"),"pasteHTML"),d=a.pasteFn(c);!1!==d&&document.execCommand("insertHTML",null,d)}function d(){const a=document.createRange();a.selectNodeContents(n.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function e(){if(p()){const a=window.getSelection();0<a.rangeCount&&(o.current=window.getSelection().getRangeAt(0)),m.fireSelectionChanged()}}function f(){const b=j();b!==s&&(t(b),a.save(b))}function g(){let a=n.current.innerHTML;if(0<m.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=m.serialize(b.body)}return a}function h(b,c){c?(n.current.innerHTML=m.sanitizeHTML(b,"setHTML"),a.autoFocus&&k()):(d(),n.current.innerHTML=m.sanitizeHTML(b,"setHTML"),k()),m.fireNewHTML()}function i(){h("")}function j(){return m.sanitizeHTML(g(),"getHTML")}function k(){n.current.focus(),q(!0)}function l(){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 m=useContext(RichTextContext);m.sanitizeHTML=a.sanitizeHTML;const n=useRef(null),o=useRef(null),{isFocused:p,setFocused:q}=useSynchronousFocusState(),r=useRef(globalBandicootId++),[s,t]=useState(()=>m.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[u,v]=useState(!1);if(b){const a={setHTML:h,resetEditor:i,getHTML:j,focus:k};"function"==typeof b?b({current:a}):b.current=a}useEffect(()=>(n.current.addEventListener("paste",c),()=>n.current.removeEventListener("paste",c)),[a.pasteFn]),useEffect(()=>(document.addEventListener("selectionchange",e),()=>document.removeEventListener("selectionchange",e))),useEffect(()=>{if(n.current)for(let a=n.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")},[n.current]),useEffect(()=>{if(a.save&&a.unchangedInterval&&n.current&&p()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(f,a.unchangedInterval)});return c.observe(n.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,n.current,p()]),useEffect(()=>{if(!1===p()){const a=setTimeout(()=>{m.fireBlur(),f()},100);return()=>{clearTimeout(a)}}},[p()]),useEffect(()=>{m.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(n.current&&document.activeElement!==n.current&&!n.current.contains(document.activeElement)){if(a.usePreviousRange&&o.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(o.current)}else n.current.focus();q(!0),setTimeout(e)}},m.getRangeFromBeforeBlur=()=>o.current,m.isFocused=p,m.getContentEditableElement=()=>n.current}),useEffect(()=>{!u&&a.initialHTML&&(v(!0),h(a.initialHTML,!0))},[u,h,m]),useEffect(()=>{if(a.placeholder){const b=document.createElement("style");b.textContent=".bandicoot-id-".concat(r.current,":empty:before { content: attr(data-placeholder); }"),document.head.appendChild(b);const c=Array.prototype.slice.call(document.styleSheets).find(a=>a.ownerNode===b),d=c.cssRules[0].style,e=a.placeholderStyle?a.placeholderStyle:l();for(let b in e)d[b]=a.placeholderStyle?a.placeholderStyle[b]:e[b];return()=>b.parentNode.removeChild(b)}},[a.placeholder,a.placeholderStyle,r.current]);const w=a.style||{};return React.createElement("div",{contentEditable:!a.disabled,onBlur:()=>q(!1),onFocus:function(){q(!0);const a=window.getSelection();0<a.rangeCount&&(o.current=a.getRangeAt(0))},ref:n,className:a.className+" bandicoot-id-"+r.current,style:_objectSpread2({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},w),"data-placeholder":a.placeholder})});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",textTop: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," ").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}; | ||
import React, { useRef, useEffect, forwardRef, useContext, useState } from 'react'; | ||
function _defineProperty(obj, key, value) { | ||
if (key in obj) { | ||
Object.defineProperty(obj, key, { | ||
value: value, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} else { | ||
obj[key] = value; | ||
} | ||
return obj; | ||
} | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function ownKeys(object, enumerableOnly) { | ||
var keys = Object.keys(object); | ||
if (Object.getOwnPropertySymbols) { | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
keys.push.apply(keys, symbols); | ||
} | ||
return keys; | ||
} | ||
function _objectSpread2(target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
if (i % 2) { | ||
ownKeys(source, true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(source).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
} | ||
return target; | ||
} | ||
function RichTextContainer(props) { | ||
const contextValueRef = useRef(Object.assign({}, defaultContextValue)); | ||
const contextValue = contextValueRef.current; | ||
const selectionChangedListenersRef = useRef([]); | ||
const blurListenersRef = useRef([]); | ||
const newHTMLListenersRef = useRef([]); | ||
const serializers = useRef([]); | ||
useEffect(() => { | ||
const contentEditableElement = contextValue.getContentEditableElement(); | ||
if (contentEditableElement && contentEditableElement.innerHTML) { | ||
contextValue.fireNewHTML(); | ||
} | ||
}, [contextValue]); | ||
contextValue.addSelectionChangedListener = listener => { | ||
selectionChangedListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeSelectionChangedListener = listener => { | ||
selectionChangedListenersRef.current = selectionChangedListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireSelectionChanged = () => { | ||
selectionChangedListenersRef.current.forEach(listener => listener()); | ||
}; | ||
contextValue.addBlurListener = listener => { | ||
blurListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeBlurListener = listener => { | ||
blurListenersRef.current = blurListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireBlur = () => { | ||
blurListenersRef.current.forEach(listener => listener()); | ||
}; | ||
contextValue.addNewHTMLListener = listener => { | ||
newHTMLListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeNewHTMLListener = listener => { | ||
newHTMLListenersRef.current = newHTMLListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireNewHTML = () => { | ||
newHTMLListenersRef.current.forEach(l => l()); | ||
}; | ||
contextValue.numSerializers = () => serializers.current.length; | ||
contextValue.addSerializer = serializer => { | ||
serializers.current.push(serializer); | ||
}; | ||
contextValue.removeSerializer = serializer => { | ||
serializers.current = serializers.current.filter(s => s !== serializer); | ||
}; | ||
contextValue.serialize = dom => { | ||
serializers.current.forEach(serializer => serializer(dom)); | ||
return dom.innerHTML; | ||
}; | ||
return React.createElement(RichTextContext.Provider, { | ||
value: contextValue | ||
}, props.children); | ||
} | ||
const noop = () => {}; | ||
const 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 | ||
}; | ||
const RichTextContext = React.createContext(defaultContextValue); | ||
const noop$1 = () => {}; | ||
const noopWithReturn = value => value; | ||
let globalBandicootId = 0; | ||
const RichTextEditor = forwardRef((props, editorRef) => { | ||
if (typeof props.sanitizeHTML !== "function") { | ||
throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop"); | ||
} | ||
const richTextContext = useContext(RichTextContext); | ||
richTextContext.sanitizeHTML = props.sanitizeHTML; | ||
const divRef = useRef(null); | ||
const selectionRangeBeforeBlurRef = useRef(null); | ||
const { | ||
isFocused, | ||
setFocused | ||
} = useSynchronousFocusState(); | ||
const bandicootId = useRef(globalBandicootId++); | ||
const [lastSavedHTML, setLastSavedHTML] = useState(() => richTextContext.sanitizeHTML(props.initialHTML, 'initialSetLastSavedHTML')); | ||
const [hasSetInitialHTML, setHasSetInitialHTML] = useState(false); | ||
if (editorRef) { | ||
const current = { | ||
setHTML, | ||
resetEditor, | ||
getHTML, | ||
focus | ||
}; | ||
if (typeof editorRef === 'function') { | ||
editorRef({ | ||
current | ||
}); | ||
} else { | ||
editorRef.current = current; | ||
} | ||
} | ||
function interceptPaste(event) { | ||
// https://developer.mozilla.org/en-US/docs/Web/Events/paste | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
const clipboardData = event.clipboardData || window.clipboardData; | ||
const pasteData = clipboardData.getData('text/html') || clipboardData.getData('text'); | ||
let paste = richTextContext.sanitizeHTML(pasteData, 'pasteHTML'); | ||
let newPaste = props.pasteFn(paste); | ||
if (newPaste !== false) { | ||
document.execCommand('insertHTML', null, newPaste); | ||
} | ||
} | ||
useEffect(() => { | ||
divRef.current.addEventListener('paste', interceptPaste); | ||
return () => divRef.current.removeEventListener('paste', interceptPaste); | ||
}, [props.pasteFn]); | ||
function emptyEditor() { | ||
// do it with selection and execCommand so it can be undone with Ctrl Z | ||
const range = document.createRange(); | ||
range.selectNodeContents(divRef.current); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
document.execCommand('removeFormat'); | ||
document.execCommand('delete'); | ||
} | ||
useEffect(() => { | ||
document.addEventListener('selectionchange', handleSelectionChange); | ||
return () => document.removeEventListener('selectionchange', handleSelectionChange); | ||
}); | ||
useEffect(() => { | ||
if (divRef.current) { | ||
let el = divRef.current; | ||
while (el.parentNode) { | ||
el = el.parentNode; | ||
if (el.tagName === 'SPAN') 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'); | ||
} | ||
} | ||
}, [divRef.current]); | ||
useEffect(() => { | ||
if (props.save && props.unchangedInterval && divRef.current && isFocused()) { | ||
const mutationConfig = { | ||
attributes: true, | ||
childList: true, | ||
subtree: true, | ||
characterData: true | ||
}; | ||
let timeout; | ||
const observer = new MutationObserver(() => { | ||
clearTimeout(timeout); | ||
timeout = setTimeout(save, props.unchangedInterval); | ||
}); | ||
observer.observe(divRef.current, mutationConfig); | ||
return () => { | ||
observer.disconnect(); | ||
clearTimeout(timeout); | ||
}; | ||
} | ||
}, [props.unchangedInterval, props.save, divRef.current, isFocused()]); | ||
useEffect(() => { | ||
// Clicking on bandicoot richtext buttons triggers a blur event that will setFocus to false we want to delay the | ||
// save event that is triggered by blur events. 100ms is arbitrary. Whenever react rerenders the rich-text-editor | ||
// due to focused state changing we need to either clear the blurTimeout to prevent a save action from firing | ||
// (in the case of a quick refocus triggered by the rich text buttons) or fire a save event after waiting 100ms | ||
if (isFocused() === false) { | ||
const timeout = setTimeout(() => { | ||
richTextContext.fireBlur(); | ||
save(); | ||
}, 100); | ||
return () => { | ||
clearTimeout(timeout); | ||
}; | ||
} | ||
}, [isFocused()]); | ||
useEffect(() => { | ||
richTextContext.selectRangeFromBeforeBlur = function () { | ||
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { | ||
usePreviousRange: false | ||
}; | ||
if (divRef.current && document.activeElement !== divRef.current && !divRef.current.contains(document.activeElement)) { | ||
if (options.usePreviousRange && selectionRangeBeforeBlurRef.current) { | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(selectionRangeBeforeBlurRef.current); | ||
} else { | ||
divRef.current.focus(); | ||
} | ||
setFocused(true); // We are calling handleSelectionChange manually because calling divRef.current.focus() programatically does not trigger | ||
// a focus event like it normally would if a user did it. And we need the bandicoot hooks to know that the selection has changed, | ||
// which is done by calling fireSelectionChanged (which is what handleSelectionChange does). | ||
// | ||
// And we're using setTimeout because if we don't wait a tick of the event loop, the browser will tell us the old | ||
// command state from before we focused. | ||
setTimeout(handleSelectionChange); | ||
} | ||
}; | ||
richTextContext.getRangeFromBeforeBlur = () => { | ||
return selectionRangeBeforeBlurRef.current; | ||
}; | ||
richTextContext.isFocused = isFocused; | ||
richTextContext.getContentEditableElement = () => divRef.current; | ||
}); | ||
useEffect(() => { | ||
if (!hasSetInitialHTML && props.initialHTML) { | ||
setHasSetInitialHTML(true); | ||
setHTML(props.initialHTML, true); | ||
} | ||
}, [hasSetInitialHTML, setHTML, richTextContext]); | ||
useEffect(() => { | ||
if (props.placeholder) { | ||
const styleElement = document.createElement('style'); | ||
styleElement.textContent = ".bandicoot-id-".concat(bandicootId.current, ":empty:before { content: attr(data-placeholder); }"); | ||
document.head.appendChild(styleElement); | ||
const styleSheet = Array.prototype.slice.call(document.styleSheets).find(s => s.ownerNode === styleElement); | ||
const styleDeclaration = styleSheet.cssRules[0].style; | ||
const styles = props.placeholderStyle ? props.placeholderStyle : getDefaultPlaceholderStyles(); | ||
for (let propName in styles) { | ||
styleDeclaration[propName] = props.placeholderStyle ? props.placeholderStyle[propName] : styles[propName]; | ||
} | ||
return () => styleElement.parentNode.removeChild(styleElement); | ||
} | ||
}, [props.placeholder, props.placeholderStyle, bandicootId.current]); | ||
const divStyles = props.style || {}; | ||
return React.createElement("div", { | ||
contentEditable: !props.disabled, | ||
onBlur: () => setFocused(false), | ||
onFocus: onFocus, | ||
ref: divRef, | ||
className: props.className + " bandicoot-id-" + bandicootId.current, | ||
style: _objectSpread2({ | ||
wordBreak: 'break-word', | ||
wordWrap: 'break-word', | ||
overflowWrap: 'break-word' | ||
}, divStyles), | ||
"data-placeholder": props.placeholder | ||
}); | ||
function onFocus() { | ||
setFocused(true); | ||
const selection = window.getSelection(); | ||
if (selection.rangeCount > 0) { | ||
selectionRangeBeforeBlurRef.current = selection.getRangeAt(0); | ||
} | ||
} | ||
function handleSelectionChange(evt) { | ||
if (isFocused()) { | ||
const selection = window.getSelection(); | ||
if (selection.rangeCount > 0) { | ||
selectionRangeBeforeBlurRef.current = window.getSelection().getRangeAt(0); | ||
} | ||
richTextContext.fireSelectionChanged(); | ||
} | ||
} | ||
function save() { | ||
const html = getHTML(); | ||
if (html !== lastSavedHTML) { | ||
setLastSavedHTML(html); | ||
props.save(html); | ||
} | ||
} | ||
function serialize() { | ||
let html = divRef.current.innerHTML; | ||
if (richTextContext.numSerializers() > 0) { | ||
const dom = new DOMParser().parseFromString(html, 'text/html'); | ||
html = richTextContext.serialize(dom.body); | ||
} | ||
return html; | ||
} | ||
function setHTML(html, isInitialMount) { | ||
// both emptyEditor() and focus() result in unwanted autofocus on contentEditable on first render | ||
// if the consumer wants autofocus, give us the prop | ||
if (!isInitialMount) { | ||
emptyEditor(); | ||
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML'); | ||
focus(); | ||
} else { | ||
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML'); | ||
if (props.autoFocus) focus(); | ||
} | ||
richTextContext.fireNewHTML(); | ||
} | ||
function resetEditor() { | ||
setHTML(''); | ||
} | ||
function getHTML() { | ||
return richTextContext.sanitizeHTML(serialize(), 'getHTML'); | ||
} | ||
function focus() { | ||
divRef.current.focus(); | ||
setFocused(true); | ||
} | ||
function getDefaultPlaceholderStyles() { | ||
if (!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) { | ||
return { | ||
color: 'rgb(117, 117, 117)' | ||
}; // default chrome style | ||
} else { | ||
return { | ||
opacity: '0.54' | ||
}; // firefox | ||
} | ||
} | ||
}); // This hook allows you to change the focused value synchronously instead of queuing it | ||
// up with a set state. This is necessary since the selectionchange listener depends on | ||
// the blur and focus listeners to update the state synchronously (before the selectionchange | ||
// listener is fired) | ||
function useSynchronousFocusState() { | ||
const focusedRef = useRef(null); // To be able to trigger a re-render when the ref value changes synchronously | ||
const [bool, setBool] = useState(false); | ||
return { | ||
isFocused, | ||
setFocused | ||
}; | ||
function isFocused() { | ||
return focusedRef.current; | ||
} | ||
function setFocused(val) { | ||
focusedRef.current = val; | ||
setBool(!bool); | ||
} | ||
} | ||
RichTextEditor.defaultProps = { | ||
className: '', | ||
initialHTML: '', | ||
save: noop$1, | ||
placeholder: '', | ||
pasteFn: noopWithReturn | ||
}; | ||
function useDocumentExecCommand(commandName) { | ||
const richTextContext = useContext(RichTextContext); | ||
return { | ||
performCommand(evt) { | ||
richTextContext.selectRangeFromBeforeBlur(); | ||
document.execCommand(commandName); | ||
}, | ||
performCommandWithValue(value) { | ||
richTextContext.selectRangeFromBeforeBlur(); | ||
const showDefaultUI = null; | ||
document.execCommand(commandName, showDefaultUI, value); | ||
} | ||
}; | ||
} | ||
function useFormatBlock() { | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('formatBlock'); | ||
return { | ||
formatBlock(value) { | ||
if (value === document.queryCommandValue('formatBlock')) { | ||
performCommandWithValue('div'); | ||
} else { | ||
performCommandWithValue(value); | ||
} | ||
} | ||
}; | ||
} | ||
const defaultActiveInfo = { | ||
isActive: false, | ||
value: false | ||
}; | ||
function useDocumentQueryCommandState(commandName, activeValueMatch) { | ||
const [activeInfo, setActiveInfo] = useState(defaultActiveInfo); | ||
const richTextContext = useContext(RichTextContext); | ||
useEffect(() => { | ||
richTextContext.addSelectionChangedListener(recheckActive); | ||
return () => richTextContext.removeSelectionChangedListener(recheckActive); | ||
}, [activeInfo, setActiveInfo]); | ||
useEffect(() => { | ||
richTextContext.addBlurListener(setInactive); | ||
return () => richTextContext.removeBlurListener(setInactive); | ||
}, [activeInfo, setActiveInfo]); | ||
function recheckActive() { | ||
const actualActiveValue = document.queryCommandValue(commandName); | ||
const isActuallyActive = activeValueMatch ? activeValueMatch === actualActiveValue : document.queryCommandState(commandName); | ||
if (isActuallyActive !== activeInfo.isActive || actualActiveValue !== activeInfo.value) { | ||
setActiveInfo({ | ||
isActive: isActuallyActive, | ||
value: actualActiveValue | ||
}); | ||
} | ||
} | ||
function setInactive() { | ||
setActiveInfo(defaultActiveInfo); | ||
} | ||
return { | ||
isActive: activeInfo.isActive, | ||
activeValue: activeInfo.value | ||
}; | ||
} | ||
function useFontSize(_ref) { | ||
let { | ||
defaultFontSize = '14px', | ||
fontSizes | ||
} = _ref; | ||
if (fontSizes.length > 7) { | ||
throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)"); | ||
} | ||
const [fontSize, setFontSize] = useState(defaultFontSize); | ||
const [lastSelection, setLastSelection] = useState(null); | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('fontSize'); | ||
const richTextContext = useContext(RichTextContext); | ||
const currentlySelectedFontSize = useCurrentlySelectedFontSize(); | ||
useSizeOverrides(); | ||
return { | ||
currentlySelectedFontSize, | ||
setSize(fontSize) { | ||
const integerSize = fontSizes.findIndex(size => size === fontSize) + 1; | ||
if (integerSize <= 0) { | ||
throw Error("Cannot set font size since '".concat(fontSize, "' was not passed in the fontSizes array")); | ||
} | ||
setFontSize(fontSize); | ||
performCommandWithValue(integerSize); | ||
} | ||
}; | ||
function useCurrentlySelectedFontSize() { | ||
useEffect(() => { | ||
richTextContext.addSelectionChangedListener(selectionChanged); | ||
return () => richTextContext.removeSelectionChangedListener(selectionChanged); | ||
function selectionChanged() { | ||
const selection = window.getSelection(); // If the selection has 'actually' changed (i.e. not just due to the editor bluring and focusing) | ||
if (!lastSelection || selection.anchorNode !== lastSelection.anchorNode || selection.anchorOffset !== lastSelection.anchorOffset || selection.focusNode !== lastSelection.focusNode || selection.focusOffset !== lastSelection.focusOffset) { | ||
setLastSelection({ | ||
anchorNode: selection.anchorNode, | ||
anchorOffset: selection.anchorOffset, | ||
focusNode: selection.focusNode, | ||
focusOffset: selection.focusOffset | ||
}); // If there is no selection, we won't change the font size | ||
if (selection.rangeCount > 0) { | ||
let selectionNode = selection.getRangeAt(0).startContainer; | ||
if (selectionNode.nodeType !== 1) { | ||
// we've got a text node or comment node or other type of node that's not an element | ||
selectionNode = selectionNode.parentElement; | ||
} | ||
const stringFontSize = window.getComputedStyle(selectionNode).fontSize; | ||
const newSize = stringFontSize; | ||
if (newSize !== fontSize) { | ||
setFontSize(newSize); | ||
} | ||
} | ||
} | ||
} | ||
}, [fontSize, setFontSize, lastSelection]); | ||
return fontSize; | ||
} | ||
function useSizeOverrides() { | ||
useEffect(() => { | ||
const cssString = fontSizes.reduce((acc, style, index) => { | ||
return "".concat(acc, " font[size=\"").concat(index + 1, "\"] {font-size: ").concat(style, "}"); | ||
}, ''); | ||
const styleEl = document.createElement('style'); | ||
styleEl.textContent = cssString; | ||
document.head.appendChild(styleEl); | ||
return () => document.head.removeChild(styleEl); | ||
}, [fontSizes]); | ||
useEffect(() => { | ||
richTextContext.addSerializer(serializer); | ||
return () => richTextContext.removeSerializer(serializer); | ||
function serializer(dom) { | ||
const fontEls = dom.querySelectorAll('font'); | ||
for (let i = 0; i < fontEls.length; i++) { | ||
const fontEl = fontEls[i]; | ||
const integerSize = Number(fontEl.getAttribute('size')); | ||
if (integerSize > fontSizes.length) { | ||
throw Error("Cannot find fontSize for integer size '".concat(integerSize, "'")); | ||
} | ||
const size = fontSizes[integerSize - 1]; | ||
fontEl.removeAttribute('size'); | ||
fontEl.style.fontSize = size; | ||
fontEl.dataset.integerSize = integerSize; | ||
} | ||
} | ||
}, [fontSizes]); | ||
useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const fontEls = richTextContext.getContentEditableElement().querySelectorAll('font'); | ||
for (let i = 0; i < fontEls.length; i++) { | ||
const fontEl = fontEls[i]; | ||
const cssFontSize = fontEl.style.fontSize; | ||
const integerSize = fontSizes.findIndex(size => size === cssFontSize) + 1; | ||
if (integerSize > 0) { | ||
fontEl.style.fontSize = ''; | ||
fontEl.setAttribute('size', integerSize); | ||
} | ||
} | ||
} | ||
}, [fontSizes]); | ||
} | ||
} | ||
const noop$2 = () => {}; | ||
const defaultAcceptImgTypes = '.jpg, .png, image/*'; | ||
const defaultOpts = { | ||
processImgElement: noop$2, | ||
fileBlobToUrl: defaultFileBlobToUrl, | ||
acceptImgTypes: defaultAcceptImgTypes | ||
}; | ||
function useImage() { | ||
let { | ||
processImgElement = noop$2, | ||
fileBlobToUrl = defaultFileBlobToUrl, | ||
acceptImgTypes = defaultAcceptImgTypes | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOpts; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertImage'); | ||
const richTextContext = useContext(RichTextContext); | ||
const fileInputRef = useRef(null); | ||
const dataUrls = React.useRef({}); | ||
useNewHtmlHandler(); | ||
useFileChooserInput(); | ||
useSerializer(); | ||
return { | ||
chooseFile(evt) { | ||
fileInputRef.current.click(); | ||
}, | ||
removeImage(imgElement) { | ||
const range = document.createRange(); | ||
range.selectNode(imgElement); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
document.execCommand('delete'); | ||
} | ||
}; | ||
function handleImageElement(imgElement) { | ||
imgElement.style.cursor = 'pointer'; | ||
imgElement.style.maxWidth = '100%'; | ||
processImgElement(imgElement); | ||
} | ||
function useNewHtmlHandler() { | ||
useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const imgElements = richTextContext.getContentEditableElement().querySelectorAll('img:not([data-text-as-image])'); | ||
imgElements.forEach(handleImageElement); | ||
} | ||
}, [processImgElement]); | ||
} | ||
function useFileChooserInput() { | ||
useEffect(() => { | ||
fileInputRef.current = document.createElement('input'); | ||
const fileInputElement = fileInputRef.current; | ||
fileInputElement.type = 'file'; | ||
fileInputElement.accept = acceptImgTypes; | ||
fileInputElement.multiple = false; | ||
fileInputElement.addEventListener('change', () => { | ||
if (fileInputElement.files && fileInputElement.files.length > 0) { | ||
fileBlobToUrl(fileInputElement.files[0], imgUrl => { | ||
performCommandWithValue(imgUrl); | ||
const imgElement = document.querySelector("img[src=\"".concat(imgUrl, "\"]")); | ||
if (imgElement.src && imgElement.src.startsWith('blob:')) { | ||
const reader = new FileReader(); | ||
reader.addEventListener('load', () => { | ||
dataUrls.current[imgElement.src] = reader.result; | ||
}); | ||
reader.readAsDataURL(fileInputElement.files[0]); | ||
} | ||
handleImageElement(imgElement); | ||
}); | ||
} | ||
}); | ||
}, [fileBlobToUrl, processImgElement, acceptImgTypes]); | ||
} | ||
function useSerializer() { | ||
useEffect(() => { | ||
richTextContext.addSerializer(serializer); | ||
return () => richTextContext.removeSerializer(serializer); | ||
}); | ||
} | ||
function serializer(dom) { | ||
dom.querySelectorAll('img').forEach(imgEl => { | ||
if (dataUrls.current[imgEl.src]) { | ||
imgEl.src = dataUrls.current[imgEl.src]; | ||
} | ||
}); | ||
} | ||
} | ||
function defaultFileBlobToUrl(file, cbk) { | ||
cbk(URL.createObjectURL(file)); | ||
} | ||
let tempId = 0; | ||
const noop$3 = () => {}; | ||
const defaultOptions = { | ||
processAnchorElement: noop$3 | ||
}; | ||
function useLink() { | ||
let { | ||
processAnchorElement = noop$3 | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions; | ||
const richTextContext = useContext(RichTextContext); | ||
const { | ||
performCommand | ||
} = useDocumentExecCommand('unlink'); | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertHTML'); | ||
useNewHtmlHandler(); | ||
return { | ||
getTextFromBeforeBlur, | ||
selectEntireLink, | ||
unlink, | ||
insertLink | ||
}; | ||
function getTextFromBeforeBlur() { | ||
const range = richTextContext.getRangeFromBeforeBlur(); | ||
if (range) { | ||
return range.toString(); | ||
} else { | ||
return null; | ||
} | ||
} | ||
function selectEntireLink(anchorElement) { | ||
const range = document.createRange(); | ||
range.selectNodeContents(anchorElement); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
function insertLink(link, displayedText) { | ||
richTextContext.selectRangeFromBeforeBlur({ | ||
usePreviousRange: true | ||
}); | ||
const id = "rte-link-temp-id-".concat(tempId++); | ||
performCommandWithValue("<a href=\"".concat(link, "\" id=\"").concat(id, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(displayedText, "</a>")); | ||
const anchorElement = document.getElementById(id); | ||
anchorElement.removeAttribute('id'); | ||
processAnchorElement(anchorElement); | ||
} | ||
function useNewHtmlHandler() { | ||
useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const anchorElements = richTextContext.getContentEditableElement().querySelectorAll('a'); | ||
anchorElements.forEach(processAnchorElement); | ||
} | ||
}, [processAnchorElement]); | ||
} | ||
function unlink() { | ||
performCommand(); | ||
if (window.navigator.userAgent.includes('Edge/14') || window.navigator.userAgent.includes('Edge/15') || window.navigator.userAgent.includes('Edge/16') || window.navigator.userAgent.includes('Edge/17')) { | ||
// Older versions of Edge remove the <a> when you unlink, but keep the text blue and underlined so it looks like a link. | ||
// Ideally we'd be super smart about working around this, but for now I'm just removing all rich text formatting from the | ||
// text that used to be a link when you've got an older version of Edge. | ||
document.execCommand('removeFormat'); | ||
} | ||
} | ||
} | ||
let tempId$1 = 0; | ||
const noop$4 = () => {}; | ||
const defaultOptions$1 = { | ||
processContentEditableFalseElement: noop$4 | ||
}; | ||
function useContentEditableFalse() { | ||
let { | ||
processContentEditableFalseElement = noop$4 | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$1; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertHTML'); | ||
const richTextContext = useContext(RichTextContext); | ||
return { | ||
insertContentEditableFalseElement(innerHTML) { | ||
const id = "rte-ce-false-temp-id-" + tempId$1++; | ||
const htmlToInsert = "<span id=\"".concat(id, "\">").concat(richTextContext.sanitizeHTML(innerHTML, 'insertContentEditableFalseHTML'), "</span>"); | ||
performCommandWithValue(htmlToInsert); | ||
const contentEditableFalseElement = document.getElementById(id); | ||
handleContentEditableFalseElement(contentEditableFalseElement); | ||
} | ||
}; | ||
function handleContentEditableFalseElement(contentEditableFalseElement) { | ||
contentEditableFalseElement.removeAttribute('id'); | ||
contentEditableFalseElement.contentEditable = false; | ||
contentEditableFalseElement.addEventListener('click', () => selectRangeAfterNode(contentEditableFalseElement)); // if we are inserting this at the start of the rich text content editable container, we need to make sure a | ||
// cursor still appears and works when you select the beginning of the rich text container. This is done with | ||
// an empty span that *is* contentEditable | ||
if (!contentEditableFalseElement.previousSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) { | ||
const editableElementBefore = document.createElement('span'); | ||
contentEditableFalseElement.parentElement.insertBefore(editableElementBefore, contentEditableFalseElement); | ||
} // if we are inserting this at the end of the rich text content editable container, we need to make sure a | ||
// cursor still appears and works when you select the end of the rich text container. This is done with | ||
// an empty span that *is* contentEditable | ||
if (!contentEditableFalseElement.nextSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) { | ||
const editableElementAfter = document.createElement('span'); | ||
contentEditableFalseElement.insertAdjacentElement('afterend', editableElementAfter); | ||
} | ||
selectRangeAfterNode(contentEditableFalseElement); | ||
processContentEditableFalseElement(contentEditableFalseElement); | ||
} | ||
} | ||
function selectRangeAfterNode(node) { | ||
const range = document.createRange(); | ||
range.setStartAfter(node); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
const noop$5 = () => {}; | ||
const defaultOptions$2 = { | ||
processSerializedElement: noop$5, | ||
fontFamily: null, | ||
fillStyle: '#00bf4b', | ||
fontWeight: 'bold', | ||
textBottom: null, | ||
backgroundColor: 'transparent' | ||
}; | ||
function useTextAsImage() { | ||
let { | ||
processSerializedElement = noop$5, | ||
fontFamily, | ||
fillStyle, | ||
fontWeight, | ||
textBottom, | ||
backgroundColor | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$2; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertImage'); | ||
const richTextContext = useContext(RichTextContext); | ||
useNewHtmlHandler(); | ||
useSerializer(); | ||
return { | ||
insertTextAsImage(text) { | ||
richTextContext.selectRangeFromBeforeBlur({ | ||
usePreviousRange: true | ||
}); | ||
const url = textToUrl({ | ||
text, | ||
referenceEl: getSelectedElement(), | ||
fontFamily, | ||
fontWeight, | ||
fillStyle, | ||
textBottom | ||
}); | ||
performCommandWithValue(url); | ||
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])")); | ||
processImgElement(imgElement, text, backgroundColor); | ||
} | ||
}; | ||
function useNewHtmlHandler() { | ||
useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const spanEls = richTextContext.getContentEditableElement().querySelectorAll('span[data-text-as-image]'); | ||
for (let i = 0; i < spanEls.length; i++) { | ||
const spanEl = spanEls[i]; | ||
const url = textToUrl({ | ||
text: spanEl.dataset.textAsImage, | ||
referenceEl: spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement, | ||
fontFamily, | ||
fontWeight, | ||
fillStyle, | ||
textBottom | ||
}); | ||
const imgEl = document.createElement('img'); | ||
imgEl.src = url; | ||
processImgElement(imgEl, spanEl.dataset.textAsImage, backgroundColor); | ||
spanEl.parentNode.replaceChild(imgEl, spanEl); | ||
} | ||
} | ||
}); | ||
} | ||
function useSerializer() { | ||
useEffect(() => { | ||
richTextContext.addSerializer(htmlSerializer); | ||
return () => richTextContext.removeSerializer(htmlSerializer); | ||
function htmlSerializer(dom) { | ||
const textAsImage = dom.querySelectorAll('img[data-text-as-image]'); | ||
for (let i = 0; i < textAsImage.length; i++) { | ||
const imgEl = textAsImage[i]; | ||
const spanEl = document.createElement('span'); | ||
spanEl.dataset.textAsImage = imgEl.dataset.textAsImage; | ||
processSerializedElement(spanEl, spanEl.dataset.textAsImage); | ||
imgEl.parentNode.replaceChild(spanEl, imgEl); | ||
} | ||
} | ||
}, []); | ||
} | ||
} | ||
function textToUrl(_ref) { | ||
let { | ||
text, | ||
referenceEl, | ||
fontFamily, | ||
fillStyle, | ||
fontWeight, | ||
textBottom | ||
} = _ref; | ||
const computedStyle = window.getComputedStyle(referenceEl); | ||
const currentFontSize = Number(computedStyle.fontSize.replace('px', '')); | ||
const currentLineHeight = Number(computedStyle.lineHeight.replace('px', '')); | ||
const currentFontFamily = fontFamily || computedStyle.fontFamily; | ||
const font = "".concat(fontWeight, " ").concat(currentFontSize, "px ").concat(currentFontFamily); | ||
const testDiv = document.createElement('div'); | ||
testDiv.style.font = font; | ||
testDiv.style.position = 'absolute'; | ||
testDiv.style.visibility = 'hidden'; | ||
testDiv.style.whiteSpace = 'nowrap'; | ||
testDiv.textContent = text; | ||
document.body.appendChild(testDiv); | ||
const canvas = document.createElement('canvas'); | ||
canvas.width = testDiv.clientWidth + 1; | ||
canvas.height = currentLineHeight; | ||
const c = canvas.getContext('2d'); | ||
c.font = font; | ||
c.fillStyle = fillStyle || defaultOptions$2.fillStyle; | ||
c.textBaseline = 'bottom'; | ||
const yPosition = textBottom || currentLineHeight - 3; | ||
c.fillText(text, 0, yPosition); | ||
document.body.removeChild(testDiv); | ||
return c.canvas.toDataURL(); | ||
} | ||
function processImgElement(imgElement, text, backgroundColor) { | ||
imgElement.style.verticalAlign = 'bottom'; | ||
imgElement.dataset.textAsImage = text; | ||
imgElement.style.backgroundColor = backgroundColor || defaultOptions$2.backgroundColor; | ||
imgElement.addEventListener('click', evt => { | ||
const rect = imgElement.getBoundingClientRect(); | ||
const middlePoint = rect.left + rect.width / 2; | ||
const range = document.createRange(); | ||
if (evt.x < middlePoint) { | ||
range.setStartBefore(imgElement); | ||
} else { | ||
range.setStartAfter(imgElement); | ||
} | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
}); | ||
} | ||
function getSelectedElement() { | ||
let result = getSelection().getRangeAt(0).commonAncestorContainer; | ||
return result.nodeType === 1 ? result : result.parentElement; | ||
} | ||
function useElementDeletionDetection(domElement, cbk) { | ||
const richTextContext = useContext(RichTextContext); | ||
useEffect(() => { | ||
if (domElement) { | ||
richTextContext.addSelectionChangedListener(checkElementConnected); | ||
return () => richTextContext.removeSelectionChangedListener(checkElementConnected); | ||
function checkElementConnected() { | ||
if (!domElement.isConnected && !domElement._bandicoot_delete_callback_called) { | ||
domElement._bandicoot_delete_callback_called = true; | ||
cbk(domElement); | ||
} | ||
} | ||
} | ||
}, [domElement, cbk]); | ||
} | ||
function styleInject(css, ref) { | ||
if (ref === void 0) ref = {}; | ||
var insertAt = ref.insertAt; | ||
if (!css || typeof document === 'undefined') { | ||
return; | ||
} | ||
var head = document.head || document.getElementsByTagName('head')[0]; | ||
var style = document.createElement('style'); | ||
style.type = 'text/css'; | ||
if (insertAt === 'top') { | ||
if (head.firstChild) { | ||
head.insertBefore(style, head.firstChild); | ||
} else { | ||
head.appendChild(style); | ||
} | ||
} else { | ||
head.appendChild(style); | ||
} | ||
if (style.styleSheet) { | ||
style.styleSheet.cssText = css; | ||
} else { | ||
style.appendChild(document.createTextNode(css)); | ||
} | ||
} | ||
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}"; | ||
var style = {"bandicootButton":"icon-button_bandicootButton__2IZP5"}; | ||
styleInject(css); | ||
function IconButton(props) { | ||
return React.createElement("button", _extends({}, props, { | ||
className: "\n ".concat(style.bandicootButton, "\n ").concat(props.isActive ? 'active-control-button' : '', "\n ").concat(props.className || '', "\n "), | ||
onClick: props.onClick | ||
}), props.children); | ||
} | ||
BoldIcon.defaultProps = { | ||
width: 24, | ||
height: 24 | ||
}; | ||
function BoldIcon(props) { | ||
return React.createElement("svg", { | ||
width: props.width, | ||
height: props.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(props) { | ||
const { | ||
performCommand | ||
} = useDocumentExecCommand('bold'); | ||
const { | ||
isActive | ||
} = useDocumentQueryCommandState('bold'); | ||
return React.createElement(IconButton, { | ||
onClick: performCommand, | ||
isActive: isActive | ||
}, React.createElement(BoldIcon, props)); | ||
} | ||
export { BoldButton, RichTextContainer, RichTextContext, RichTextEditor, useContentEditableFalse, useDocumentExecCommand, useDocumentQueryCommandState, useElementDeletionDetection, useFontSize, useFormatBlock, useImage, useLink, useTextAsImage }; | ||
//# sourceMappingURL=bandicoot.esm.js.map |
@@ -1,2 +0,1166 @@ | ||
(function(a,b){"object"==typeof exports&&"undefined"!=typeof module?b(exports,require("react")):"function"==typeof define&&define.amd?define(["exports","react"],b):(a=a||self,b(a.bandicoot={},a.React))})(this,function(a,b){'use strict';function c(a,b,c){return b in a?Object.defineProperty(a,b,{value:c,enumerable:!0,configurable:!0,writable:!0}):a[b]=c,a}function d(){return d=Object.assign||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},d.apply(this,arguments)}function e(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 f(a){for(var b,d=1;d<arguments.length;d++)b=null==arguments[d]?{}:arguments[d],d%2?e(b,!0).forEach(function(d){c(a,d,b[d])}):Object.getOwnPropertyDescriptors?Object.defineProperties(a,Object.getOwnPropertyDescriptors(b)):e(b).forEach(function(c){Object.defineProperty(a,c,Object.getOwnPropertyDescriptor(b,c))});return a}function g(){const a=b.useRef(null),[c,d]=b.useState(!1);return{isFocused:function(){return a.current},setFocused:function(b){a.current=b,d(!c)}}}function h(a){const c=b.useContext(t);return{performCommand(b){c.selectRangeFromBeforeBlur(),document.execCommand(a)},performCommandWithValue(b){c.selectRangeFromBeforeBlur();document.execCommand(a,null,b)}}}function i(a,c){function d(){const b=document.queryCommandValue(a),d=c?c===b:document.queryCommandState(a);(d!==f.isActive||b!==f.value)&&g({isActive:d,value:b})}function e(){g(w)}const[f,g]=b.useState(w),h=b.useContext(t);return b.useEffect(()=>(h.addSelectionChangedListener(d),()=>h.removeSelectionChangedListener(d)),[f,g]),b.useEffect(()=>(h.addBlurListener(e),()=>h.removeBlurListener(e)),[f,g]),{isActive:f.isActive,activeValue:f.value}}function j(a,b){b(URL.createObjectURL(a))}function k(a){const b=document.createRange();b.setStartAfter(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b)}function l(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," ").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||H.fillStyle,p.textBaseline="bottom";return p.fillText(b,0,h||k-3),document.body.removeChild(n),p.canvas.toDataURL()}function m(a,b,c){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,a.style.backgroundColor=c||H.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 n(){let a=getSelection().getRangeAt(0).commonAncestorContainer;return 1===a.nodeType?a:a.parentElement}function o(a){return q.createElement("button",d({},a,{className:"\n ".concat(I.bandicootButton,"\n ").concat(a.isActive?"active-control-button":"","\n ").concat(a.className||"","\n "),onClick:a.onClick}),a.children)}function p(a){return q.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"},q.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"}))}var q="default"in b?b["default"]:b;const r=()=>{},s={addSelectionChangedListener:r,removeSelectionChangedListener:r,fireSelectionChanged:r,selectRangeFromBeforeBlur:r,getRangeBeforeBlur:r,addBlurListener:r,removeBlurListener:r,fireBlur:r,addNewHTMLListener:r,removeNewHTMLListener:r,fireNewHTML:r,isFocused:r,getContentEditableElement:r,numSerializers:()=>0,addSerializer:r,removeSerializer:r,serialize:r,sanitizeHTML:r},t=q.createContext(s);let u=0;const v=b.forwardRef((a,c)=>{function d(b){b.preventDefault(),b.stopPropagation();let c=p.sanitizeHTML((window.clipboardData||b.clipboardData).getData("text/html"),"pasteHTML"),d=a.pasteFn(c);!1!==d&&document.execCommand("insertHTML",null,d)}function e(){const a=document.createRange();a.selectNodeContents(r.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function h(){if(v()){const a=window.getSelection();0<a.rangeCount&&(s.current=window.getSelection().getRangeAt(0)),p.fireSelectionChanged()}}function i(){const b=m();b!==y&&(z(b),a.save(b))}function j(){let a=r.current.innerHTML;if(0<p.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=p.serialize(b.body)}return a}function k(b,c){c?(r.current.innerHTML=p.sanitizeHTML(b,"setHTML"),a.autoFocus&&n()):(e(),r.current.innerHTML=p.sanitizeHTML(b,"setHTML"),n()),p.fireNewHTML()}function l(){k("")}function m(){return p.sanitizeHTML(j(),"getHTML")}function n(){r.current.focus(),w(!0)}function o(){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 p=b.useContext(t);p.sanitizeHTML=a.sanitizeHTML;const r=b.useRef(null),s=b.useRef(null),{isFocused:v,setFocused:w}=g(),x=b.useRef(u++),[y,z]=b.useState(()=>p.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[A,B]=b.useState(!1);if(c){const a={setHTML:k,resetEditor:l,getHTML:m,focus:n};"function"==typeof c?c({current:a}):c.current=a}b.useEffect(()=>(r.current.addEventListener("paste",d),()=>r.current.removeEventListener("paste",d)),[a.pasteFn]),b.useEffect(()=>(document.addEventListener("selectionchange",h),()=>document.removeEventListener("selectionchange",h))),b.useEffect(()=>{if(r.current)for(let a=r.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")},[r.current]),b.useEffect(()=>{if(a.save&&a.unchangedInterval&&r.current&&v()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(i,a.unchangedInterval)});return c.observe(r.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,r.current,v()]),b.useEffect(()=>{if(!1===v()){const a=setTimeout(()=>{p.fireBlur(),i()},100);return()=>{clearTimeout(a)}}},[v()]),b.useEffect(()=>{p.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(r.current&&document.activeElement!==r.current&&!r.current.contains(document.activeElement)){if(a.usePreviousRange&&s.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(s.current)}else r.current.focus();w(!0),setTimeout(h)}},p.getRangeFromBeforeBlur=()=>s.current,p.isFocused=v,p.getContentEditableElement=()=>r.current}),b.useEffect(()=>{!A&&a.initialHTML&&(B(!0),k(a.initialHTML,!0))},[A,k,p]),b.useEffect(()=>{if(a.placeholder){const b=document.createElement("style");b.textContent=".bandicoot-id-".concat(x.current,":empty:before { content: attr(data-placeholder); }"),document.head.appendChild(b);const c=Array.prototype.slice.call(document.styleSheets).find(a=>a.ownerNode===b),d=c.cssRules[0].style,e=a.placeholderStyle?a.placeholderStyle:o();for(let b in e)d[b]=a.placeholderStyle?a.placeholderStyle[b]:e[b];return()=>b.parentNode.removeChild(b)}},[a.placeholder,a.placeholderStyle,x.current]);const C=a.style||{};return q.createElement("div",{contentEditable:!a.disabled,onBlur:()=>w(!1),onFocus:function(){w(!0);const a=window.getSelection();0<a.rangeCount&&(s.current=a.getRangeAt(0))},ref:r,className:a.className+" bandicoot-id-"+x.current,style:f({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},C),"data-placeholder":a.placeholder})});v.defaultProps={className:"",initialHTML:"",save:()=>{},placeholder:"",pasteFn:a=>a};const w={isActive:!1,value:!1},x=()=>{},y=".jpg, .png, image/*",z={processImgElement:x,fileBlobToUrl:j,acceptImgTypes:y};let A=0;const B=()=>{},C={processAnchorElement:B};let D=0;const E=()=>{},F={processContentEditableFalseElement:E},G=()=>{},H={processSerializedElement:G,fontFamily:null,fillStyle:"#00bf4b",fontWeight:"bold",textTop:null,backgroundColor:"transparent"};var I={bandicootButton:"icon-button_bandicootButton__2IZP5"};(function(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))}})(".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}"),p.defaultProps={width:24,height:24},a.BoldButton=function(a){const{performCommand:b}=h("bold"),{isActive:c}=i("bold");return q.createElement(o,{onClick:b,isActive:c},q.createElement(p,a))},a.RichTextContainer=function(a){const c=b.useRef(Object.assign({},s)),d=c.current,e=b.useRef([]),f=b.useRef([]),g=b.useRef([]),h=b.useRef([]);return b.useEffect(()=>{const a=d.getContentEditableElement();a&&a.innerHTML&&d.fireNewHTML()},[d]),d.addSelectionChangedListener=a=>{e.current.push(a)},d.removeSelectionChangedListener=a=>{e.current=e.current.filter(b=>b!==a)},d.fireSelectionChanged=()=>{e.current.forEach(a=>a())},d.addBlurListener=a=>{f.current.push(a)},d.removeBlurListener=a=>{f.current=f.current.filter(b=>b!==a)},d.fireBlur=()=>{f.current.forEach(a=>a())},d.addNewHTMLListener=a=>{g.current.push(a)},d.removeNewHTMLListener=a=>{g.current=g.current.filter(b=>b!==a)},d.fireNewHTML=()=>{g.current.forEach(a=>a())},d.numSerializers=()=>h.current.length,d.addSerializer=a=>{h.current.push(a)},d.removeSerializer=a=>{h.current=h.current.filter(b=>b!==a)},d.serialize=a=>(h.current.forEach(b=>b(a)),a.innerHTML),q.createElement(t.Provider,{value:d},a.children)},a.RichTextContext=t,a.RichTextEditor=v,a.useContentEditableFalse=function(){function a(a){if(a.removeAttribute("id"),a.contentEditable=!1,a.addEventListener("click",()=>k(a)),!a.previousSibling&&a.parentElement===e.getContentEditableElement()){const b=document.createElement("span");a.parentElement.insertBefore(b,a)}if(!a.nextSibling&&a.parentElement===e.getContentEditableElement()){const b=document.createElement("span");a.insertAdjacentElement("afterend",b)}k(a),c(a)}let{processContentEditableFalseElement:c=E}=0<arguments.length&&arguments[0]!==void 0?arguments[0]:F;const{performCommandWithValue:d}=h("insertHTML"),e=b.useContext(t);return{insertContentEditableFalseElement(b){const c="rte-ce-false-temp-id-"+D++,f="<span id=\"".concat(c,"\">").concat(e.sanitizeHTML(b,"insertContentEditableFalseHTML"),"</span>");d(f);const g=document.getElementById(c);a(g)}}},a.useDocumentExecCommand=h,a.useDocumentQueryCommandState=i,a.useElementDeletionDetection=function(a,c){const d=b.useContext(t);b.useEffect(()=>{if(a){function b(){a.isConnected||a._bandicoot_delete_callback_called||(a._bandicoot_delete_callback_called=!0,c(a))}return d.addSelectionChangedListener(b),()=>d.removeSelectionChangedListener(b)}},[a,c])},a.useFontSize=function(a){let{defaultFontSize:d="14px",fontSizes:c}=a;if(7<c.length)throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)");const[e,f]=b.useState(d),[g,i]=b.useState(null),{performCommandWithValue:j}=h("fontSize"),k=b.useContext(t),l=function(){return b.useEffect(()=>{function a(){const a=window.getSelection();if((!g||a.anchorNode!==g.anchorNode||a.anchorOffset!==g.anchorOffset||a.focusNode!==g.focusNode||a.focusOffset!==g.focusOffset)&&(i({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,d=c;d!==e&&f(d)}}return k.addSelectionChangedListener(a),()=>k.removeSelectionChangedListener(a)},[e,f,g]),e}();return function(){b.useEffect(()=>{const a=c.reduce((a,b,c)=>"".concat(a," font[size=\"").concat(c+1,"\"] {font-size: ").concat(b,"}"),""),b=document.createElement("style");return b.textContent=a,document.head.appendChild(b),()=>document.head.removeChild(b)},[c]),b.useEffect(()=>{function a(a){const b=a.querySelectorAll("font");for(let d=0;d<b.length;d++){const a=b[d],e=+a.getAttribute("size");if(e>c.length)throw Error("Cannot find fontSize for integer size '".concat(e,"'"));const f=c[e-1];a.removeAttribute("size"),a.style.fontSize=f,a.dataset.integerSize=e}}return k.addSerializer(a),()=>k.removeSerializer(a)},[c]),b.useEffect(()=>{function a(){const a=k.getContentEditableElement().querySelectorAll("font");for(let b=0;b<a.length;b++){const d=a[b],e=d.style.fontSize,f=c.findIndex(a=>a===e)+1;0<f&&(d.style.fontSize="",d.setAttribute("size",f))}}return k.addNewHTMLListener(a),()=>k.removeNewHTMLListener(a)},[c])}(),{currentlySelectedFontSize:l,setSize(a){const b=c.findIndex(b=>b===a)+1;if(0>=b)throw Error("Cannot set font size since '".concat(a,"' was not passed in the fontSizes array"));f(a),j(b)}}},a.useFormatBlock=function(){const{performCommandWithValue:a}=h("formatBlock");return{formatBlock(b){b===document.queryCommandValue("formatBlock")?a("div"):a(b)}}},a.useImage=function(){function a(a){a.style.cursor="pointer",a.style.maxWidth="100%",d(a)}function c(a){a.querySelectorAll("img").forEach(a=>{l.current[a.src]&&(a.src=l.current[a.src])})}let{processImgElement:d=x,fileBlobToUrl:e=j,acceptImgTypes:f=y}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:z;const{performCommandWithValue:g}=h("insertImage"),i=b.useContext(t),k=b.useRef(null),l=q.useRef({});return function(){b.useEffect(()=>{function b(){const b=i.getContentEditableElement().querySelectorAll("img:not([data-text-as-image])");b.forEach(a)}return i.addNewHTMLListener(b),()=>i.removeNewHTMLListener(b)},[d])}(),function(){b.useEffect(()=>{k.current=document.createElement("input");const b=k.current;b.type="file",b.accept=f,b.multiple=!1,b.addEventListener("change",()=>{b.files&&0<b.files.length&&e(b.files[0],c=>{g(c);const d=document.querySelector("img[src=\"".concat(c,"\"]"));if(d.src&&d.src.startsWith("blob:")){const a=new FileReader;a.addEventListener("load",()=>{l.current[d.src]=a.result}),a.readAsDataURL(b.files[0])}a(d)})})},[e,d,f])}(),function(){b.useEffect(()=>(i.addSerializer(c),()=>i.removeSerializer(c)))}(),{chooseFile(a){k.current.click()},removeImage(a){const b=document.createRange();b.selectNode(a);const c=window.getSelection();c.removeAllRanges(),c.addRange(b),document.execCommand("delete")}}},a.useLink=function(){function a(a,b){d.selectRangeFromBeforeBlur({usePreviousRange:!0});const e="rte-link-temp-id-".concat(A++);f("<a href=\"".concat(a,"\" id=\"").concat(e,"\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(b,"</a>"));const g=document.getElementById(e);g.removeAttribute("id"),c(g)}let{processAnchorElement:c=B}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:C;const d=b.useContext(t),{performCommand:e}=h("unlink"),{performCommandWithValue:f}=h("insertHTML");return function(){b.useEffect(()=>{function a(){const a=d.getContentEditableElement().querySelectorAll("a");a.forEach(c)}return d.addNewHTMLListener(a),()=>d.removeNewHTMLListener(a)},[c])}(),{getTextFromBeforeBlur:function(){const a=d.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(){e(),(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}},a.useTextAsImage=function(){let{processSerializedElement:g=G,fontFamily:a,fillStyle:c,fontWeight:d,textBottom:e,backgroundColor:f}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:H;const{performCommandWithValue:i}=h("insertImage"),j=b.useContext(t);return function(){b.useEffect(()=>{function b(){const b=j.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let g=0;g<b.length;g++){const h=b[g],i=l({text:h.dataset.textAsImage,referenceEl:h.previousElementSibling||h.nextElementSibling||h.parentElement,fontFamily:a,fontWeight:d,fillStyle:c,textBottom:e}),j=document.createElement("img");j.src=i,m(j,h.dataset.textAsImage,f),h.parentNode.replaceChild(j,h)}}return j.addNewHTMLListener(b),()=>j.removeNewHTMLListener(b)})}(),function(){b.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,g(d,d.dataset.textAsImage),a.parentNode.replaceChild(d,a)}}return j.addSerializer(a),()=>j.removeSerializer(a)},[])}(),{insertTextAsImage(b){j.selectRangeFromBeforeBlur({usePreviousRange:!0});const g=l({text:b,referenceEl:n(),fontFamily:a,fontWeight:d,fillStyle:c,textBottom:e});i(g);const h=document.querySelector("img[src=\"".concat(g,"\"]:not([data-text-as-image])"));m(h,b,f)}}},Object.defineProperty(a,"__esModule",{value:!0})}); | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : | ||
(global = global || self, factory(global.bandicoot = {}, global.React)); | ||
}(this, (function (exports, React) { 'use strict'; | ||
var React__default = 'default' in React ? React['default'] : React; | ||
function _defineProperty(obj, key, value) { | ||
if (key in obj) { | ||
Object.defineProperty(obj, key, { | ||
value: value, | ||
enumerable: true, | ||
configurable: true, | ||
writable: true | ||
}); | ||
} else { | ||
obj[key] = value; | ||
} | ||
return obj; | ||
} | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function ownKeys(object, enumerableOnly) { | ||
var keys = Object.keys(object); | ||
if (Object.getOwnPropertySymbols) { | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
keys.push.apply(keys, symbols); | ||
} | ||
return keys; | ||
} | ||
function _objectSpread2(target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
if (i % 2) { | ||
ownKeys(source, true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(source).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
} | ||
return target; | ||
} | ||
function RichTextContainer(props) { | ||
const contextValueRef = React.useRef(Object.assign({}, defaultContextValue)); | ||
const contextValue = contextValueRef.current; | ||
const selectionChangedListenersRef = React.useRef([]); | ||
const blurListenersRef = React.useRef([]); | ||
const newHTMLListenersRef = React.useRef([]); | ||
const serializers = React.useRef([]); | ||
React.useEffect(() => { | ||
const contentEditableElement = contextValue.getContentEditableElement(); | ||
if (contentEditableElement && contentEditableElement.innerHTML) { | ||
contextValue.fireNewHTML(); | ||
} | ||
}, [contextValue]); | ||
contextValue.addSelectionChangedListener = listener => { | ||
selectionChangedListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeSelectionChangedListener = listener => { | ||
selectionChangedListenersRef.current = selectionChangedListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireSelectionChanged = () => { | ||
selectionChangedListenersRef.current.forEach(listener => listener()); | ||
}; | ||
contextValue.addBlurListener = listener => { | ||
blurListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeBlurListener = listener => { | ||
blurListenersRef.current = blurListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireBlur = () => { | ||
blurListenersRef.current.forEach(listener => listener()); | ||
}; | ||
contextValue.addNewHTMLListener = listener => { | ||
newHTMLListenersRef.current.push(listener); | ||
}; | ||
contextValue.removeNewHTMLListener = listener => { | ||
newHTMLListenersRef.current = newHTMLListenersRef.current.filter(l => l !== listener); | ||
}; | ||
contextValue.fireNewHTML = () => { | ||
newHTMLListenersRef.current.forEach(l => l()); | ||
}; | ||
contextValue.numSerializers = () => serializers.current.length; | ||
contextValue.addSerializer = serializer => { | ||
serializers.current.push(serializer); | ||
}; | ||
contextValue.removeSerializer = serializer => { | ||
serializers.current = serializers.current.filter(s => s !== serializer); | ||
}; | ||
contextValue.serialize = dom => { | ||
serializers.current.forEach(serializer => serializer(dom)); | ||
return dom.innerHTML; | ||
}; | ||
return React__default.createElement(RichTextContext.Provider, { | ||
value: contextValue | ||
}, props.children); | ||
} | ||
const noop = () => {}; | ||
const 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 | ||
}; | ||
const RichTextContext = React__default.createContext(defaultContextValue); | ||
const noop$1 = () => {}; | ||
const noopWithReturn = value => value; | ||
let globalBandicootId = 0; | ||
const RichTextEditor = React.forwardRef((props, editorRef) => { | ||
if (typeof props.sanitizeHTML !== "function") { | ||
throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop"); | ||
} | ||
const richTextContext = React.useContext(RichTextContext); | ||
richTextContext.sanitizeHTML = props.sanitizeHTML; | ||
const divRef = React.useRef(null); | ||
const selectionRangeBeforeBlurRef = React.useRef(null); | ||
const { | ||
isFocused, | ||
setFocused | ||
} = useSynchronousFocusState(); | ||
const bandicootId = React.useRef(globalBandicootId++); | ||
const [lastSavedHTML, setLastSavedHTML] = React.useState(() => richTextContext.sanitizeHTML(props.initialHTML, 'initialSetLastSavedHTML')); | ||
const [hasSetInitialHTML, setHasSetInitialHTML] = React.useState(false); | ||
if (editorRef) { | ||
const current = { | ||
setHTML, | ||
resetEditor, | ||
getHTML, | ||
focus | ||
}; | ||
if (typeof editorRef === 'function') { | ||
editorRef({ | ||
current | ||
}); | ||
} else { | ||
editorRef.current = current; | ||
} | ||
} | ||
function interceptPaste(event) { | ||
// https://developer.mozilla.org/en-US/docs/Web/Events/paste | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
const clipboardData = event.clipboardData || window.clipboardData; | ||
const pasteData = clipboardData.getData('text/html') || clipboardData.getData('text'); | ||
let paste = richTextContext.sanitizeHTML(pasteData, 'pasteHTML'); | ||
let newPaste = props.pasteFn(paste); | ||
if (newPaste !== false) { | ||
document.execCommand('insertHTML', null, newPaste); | ||
} | ||
} | ||
React.useEffect(() => { | ||
divRef.current.addEventListener('paste', interceptPaste); | ||
return () => divRef.current.removeEventListener('paste', interceptPaste); | ||
}, [props.pasteFn]); | ||
function emptyEditor() { | ||
// do it with selection and execCommand so it can be undone with Ctrl Z | ||
const range = document.createRange(); | ||
range.selectNodeContents(divRef.current); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
document.execCommand('removeFormat'); | ||
document.execCommand('delete'); | ||
} | ||
React.useEffect(() => { | ||
document.addEventListener('selectionchange', handleSelectionChange); | ||
return () => document.removeEventListener('selectionchange', handleSelectionChange); | ||
}); | ||
React.useEffect(() => { | ||
if (divRef.current) { | ||
let el = divRef.current; | ||
while (el.parentNode) { | ||
el = el.parentNode; | ||
if (el.tagName === 'SPAN') 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'); | ||
} | ||
} | ||
}, [divRef.current]); | ||
React.useEffect(() => { | ||
if (props.save && props.unchangedInterval && divRef.current && isFocused()) { | ||
const mutationConfig = { | ||
attributes: true, | ||
childList: true, | ||
subtree: true, | ||
characterData: true | ||
}; | ||
let timeout; | ||
const observer = new MutationObserver(() => { | ||
clearTimeout(timeout); | ||
timeout = setTimeout(save, props.unchangedInterval); | ||
}); | ||
observer.observe(divRef.current, mutationConfig); | ||
return () => { | ||
observer.disconnect(); | ||
clearTimeout(timeout); | ||
}; | ||
} | ||
}, [props.unchangedInterval, props.save, divRef.current, isFocused()]); | ||
React.useEffect(() => { | ||
// Clicking on bandicoot richtext buttons triggers a blur event that will setFocus to false we want to delay the | ||
// save event that is triggered by blur events. 100ms is arbitrary. Whenever react rerenders the rich-text-editor | ||
// due to focused state changing we need to either clear the blurTimeout to prevent a save action from firing | ||
// (in the case of a quick refocus triggered by the rich text buttons) or fire a save event after waiting 100ms | ||
if (isFocused() === false) { | ||
const timeout = setTimeout(() => { | ||
richTextContext.fireBlur(); | ||
save(); | ||
}, 100); | ||
return () => { | ||
clearTimeout(timeout); | ||
}; | ||
} | ||
}, [isFocused()]); | ||
React.useEffect(() => { | ||
richTextContext.selectRangeFromBeforeBlur = function () { | ||
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { | ||
usePreviousRange: false | ||
}; | ||
if (divRef.current && document.activeElement !== divRef.current && !divRef.current.contains(document.activeElement)) { | ||
if (options.usePreviousRange && selectionRangeBeforeBlurRef.current) { | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(selectionRangeBeforeBlurRef.current); | ||
} else { | ||
divRef.current.focus(); | ||
} | ||
setFocused(true); // We are calling handleSelectionChange manually because calling divRef.current.focus() programatically does not trigger | ||
// a focus event like it normally would if a user did it. And we need the bandicoot hooks to know that the selection has changed, | ||
// which is done by calling fireSelectionChanged (which is what handleSelectionChange does). | ||
// | ||
// And we're using setTimeout because if we don't wait a tick of the event loop, the browser will tell us the old | ||
// command state from before we focused. | ||
setTimeout(handleSelectionChange); | ||
} | ||
}; | ||
richTextContext.getRangeFromBeforeBlur = () => { | ||
return selectionRangeBeforeBlurRef.current; | ||
}; | ||
richTextContext.isFocused = isFocused; | ||
richTextContext.getContentEditableElement = () => divRef.current; | ||
}); | ||
React.useEffect(() => { | ||
if (!hasSetInitialHTML && props.initialHTML) { | ||
setHasSetInitialHTML(true); | ||
setHTML(props.initialHTML, true); | ||
} | ||
}, [hasSetInitialHTML, setHTML, richTextContext]); | ||
React.useEffect(() => { | ||
if (props.placeholder) { | ||
const styleElement = document.createElement('style'); | ||
styleElement.textContent = ".bandicoot-id-".concat(bandicootId.current, ":empty:before { content: attr(data-placeholder); }"); | ||
document.head.appendChild(styleElement); | ||
const styleSheet = Array.prototype.slice.call(document.styleSheets).find(s => s.ownerNode === styleElement); | ||
const styleDeclaration = styleSheet.cssRules[0].style; | ||
const styles = props.placeholderStyle ? props.placeholderStyle : getDefaultPlaceholderStyles(); | ||
for (let propName in styles) { | ||
styleDeclaration[propName] = props.placeholderStyle ? props.placeholderStyle[propName] : styles[propName]; | ||
} | ||
return () => styleElement.parentNode.removeChild(styleElement); | ||
} | ||
}, [props.placeholder, props.placeholderStyle, bandicootId.current]); | ||
const divStyles = props.style || {}; | ||
return React__default.createElement("div", { | ||
contentEditable: !props.disabled, | ||
onBlur: () => setFocused(false), | ||
onFocus: onFocus, | ||
ref: divRef, | ||
className: props.className + " bandicoot-id-" + bandicootId.current, | ||
style: _objectSpread2({ | ||
wordBreak: 'break-word', | ||
wordWrap: 'break-word', | ||
overflowWrap: 'break-word' | ||
}, divStyles), | ||
"data-placeholder": props.placeholder | ||
}); | ||
function onFocus() { | ||
setFocused(true); | ||
const selection = window.getSelection(); | ||
if (selection.rangeCount > 0) { | ||
selectionRangeBeforeBlurRef.current = selection.getRangeAt(0); | ||
} | ||
} | ||
function handleSelectionChange(evt) { | ||
if (isFocused()) { | ||
const selection = window.getSelection(); | ||
if (selection.rangeCount > 0) { | ||
selectionRangeBeforeBlurRef.current = window.getSelection().getRangeAt(0); | ||
} | ||
richTextContext.fireSelectionChanged(); | ||
} | ||
} | ||
function save() { | ||
const html = getHTML(); | ||
if (html !== lastSavedHTML) { | ||
setLastSavedHTML(html); | ||
props.save(html); | ||
} | ||
} | ||
function serialize() { | ||
let html = divRef.current.innerHTML; | ||
if (richTextContext.numSerializers() > 0) { | ||
const dom = new DOMParser().parseFromString(html, 'text/html'); | ||
html = richTextContext.serialize(dom.body); | ||
} | ||
return html; | ||
} | ||
function setHTML(html, isInitialMount) { | ||
// both emptyEditor() and focus() result in unwanted autofocus on contentEditable on first render | ||
// if the consumer wants autofocus, give us the prop | ||
if (!isInitialMount) { | ||
emptyEditor(); | ||
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML'); | ||
focus(); | ||
} else { | ||
divRef.current.innerHTML = richTextContext.sanitizeHTML(html, 'setHTML'); | ||
if (props.autoFocus) focus(); | ||
} | ||
richTextContext.fireNewHTML(); | ||
} | ||
function resetEditor() { | ||
setHTML(''); | ||
} | ||
function getHTML() { | ||
return richTextContext.sanitizeHTML(serialize(), 'getHTML'); | ||
} | ||
function focus() { | ||
divRef.current.focus(); | ||
setFocused(true); | ||
} | ||
function getDefaultPlaceholderStyles() { | ||
if (!!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime)) { | ||
return { | ||
color: 'rgb(117, 117, 117)' | ||
}; // default chrome style | ||
} else { | ||
return { | ||
opacity: '0.54' | ||
}; // firefox | ||
} | ||
} | ||
}); // This hook allows you to change the focused value synchronously instead of queuing it | ||
// up with a set state. This is necessary since the selectionchange listener depends on | ||
// the blur and focus listeners to update the state synchronously (before the selectionchange | ||
// listener is fired) | ||
function useSynchronousFocusState() { | ||
const focusedRef = React.useRef(null); // To be able to trigger a re-render when the ref value changes synchronously | ||
const [bool, setBool] = React.useState(false); | ||
return { | ||
isFocused, | ||
setFocused | ||
}; | ||
function isFocused() { | ||
return focusedRef.current; | ||
} | ||
function setFocused(val) { | ||
focusedRef.current = val; | ||
setBool(!bool); | ||
} | ||
} | ||
RichTextEditor.defaultProps = { | ||
className: '', | ||
initialHTML: '', | ||
save: noop$1, | ||
placeholder: '', | ||
pasteFn: noopWithReturn | ||
}; | ||
function useDocumentExecCommand(commandName) { | ||
const richTextContext = React.useContext(RichTextContext); | ||
return { | ||
performCommand(evt) { | ||
richTextContext.selectRangeFromBeforeBlur(); | ||
document.execCommand(commandName); | ||
}, | ||
performCommandWithValue(value) { | ||
richTextContext.selectRangeFromBeforeBlur(); | ||
const showDefaultUI = null; | ||
document.execCommand(commandName, showDefaultUI, value); | ||
} | ||
}; | ||
} | ||
function useFormatBlock() { | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('formatBlock'); | ||
return { | ||
formatBlock(value) { | ||
if (value === document.queryCommandValue('formatBlock')) { | ||
performCommandWithValue('div'); | ||
} else { | ||
performCommandWithValue(value); | ||
} | ||
} | ||
}; | ||
} | ||
const defaultActiveInfo = { | ||
isActive: false, | ||
value: false | ||
}; | ||
function useDocumentQueryCommandState(commandName, activeValueMatch) { | ||
const [activeInfo, setActiveInfo] = React.useState(defaultActiveInfo); | ||
const richTextContext = React.useContext(RichTextContext); | ||
React.useEffect(() => { | ||
richTextContext.addSelectionChangedListener(recheckActive); | ||
return () => richTextContext.removeSelectionChangedListener(recheckActive); | ||
}, [activeInfo, setActiveInfo]); | ||
React.useEffect(() => { | ||
richTextContext.addBlurListener(setInactive); | ||
return () => richTextContext.removeBlurListener(setInactive); | ||
}, [activeInfo, setActiveInfo]); | ||
function recheckActive() { | ||
const actualActiveValue = document.queryCommandValue(commandName); | ||
const isActuallyActive = activeValueMatch ? activeValueMatch === actualActiveValue : document.queryCommandState(commandName); | ||
if (isActuallyActive !== activeInfo.isActive || actualActiveValue !== activeInfo.value) { | ||
setActiveInfo({ | ||
isActive: isActuallyActive, | ||
value: actualActiveValue | ||
}); | ||
} | ||
} | ||
function setInactive() { | ||
setActiveInfo(defaultActiveInfo); | ||
} | ||
return { | ||
isActive: activeInfo.isActive, | ||
activeValue: activeInfo.value | ||
}; | ||
} | ||
function useFontSize(_ref) { | ||
let { | ||
defaultFontSize = '14px', | ||
fontSizes | ||
} = _ref; | ||
if (fontSizes.length > 7) { | ||
throw Error("Browsers only support up to 7 font sizes with document.execCommand('fontSize', null, size)"); | ||
} | ||
const [fontSize, setFontSize] = React.useState(defaultFontSize); | ||
const [lastSelection, setLastSelection] = React.useState(null); | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('fontSize'); | ||
const richTextContext = React.useContext(RichTextContext); | ||
const currentlySelectedFontSize = useCurrentlySelectedFontSize(); | ||
useSizeOverrides(); | ||
return { | ||
currentlySelectedFontSize, | ||
setSize(fontSize) { | ||
const integerSize = fontSizes.findIndex(size => size === fontSize) + 1; | ||
if (integerSize <= 0) { | ||
throw Error("Cannot set font size since '".concat(fontSize, "' was not passed in the fontSizes array")); | ||
} | ||
setFontSize(fontSize); | ||
performCommandWithValue(integerSize); | ||
} | ||
}; | ||
function useCurrentlySelectedFontSize() { | ||
React.useEffect(() => { | ||
richTextContext.addSelectionChangedListener(selectionChanged); | ||
return () => richTextContext.removeSelectionChangedListener(selectionChanged); | ||
function selectionChanged() { | ||
const selection = window.getSelection(); // If the selection has 'actually' changed (i.e. not just due to the editor bluring and focusing) | ||
if (!lastSelection || selection.anchorNode !== lastSelection.anchorNode || selection.anchorOffset !== lastSelection.anchorOffset || selection.focusNode !== lastSelection.focusNode || selection.focusOffset !== lastSelection.focusOffset) { | ||
setLastSelection({ | ||
anchorNode: selection.anchorNode, | ||
anchorOffset: selection.anchorOffset, | ||
focusNode: selection.focusNode, | ||
focusOffset: selection.focusOffset | ||
}); // If there is no selection, we won't change the font size | ||
if (selection.rangeCount > 0) { | ||
let selectionNode = selection.getRangeAt(0).startContainer; | ||
if (selectionNode.nodeType !== 1) { | ||
// we've got a text node or comment node or other type of node that's not an element | ||
selectionNode = selectionNode.parentElement; | ||
} | ||
const stringFontSize = window.getComputedStyle(selectionNode).fontSize; | ||
const newSize = stringFontSize; | ||
if (newSize !== fontSize) { | ||
setFontSize(newSize); | ||
} | ||
} | ||
} | ||
} | ||
}, [fontSize, setFontSize, lastSelection]); | ||
return fontSize; | ||
} | ||
function useSizeOverrides() { | ||
React.useEffect(() => { | ||
const cssString = fontSizes.reduce((acc, style, index) => { | ||
return "".concat(acc, " font[size=\"").concat(index + 1, "\"] {font-size: ").concat(style, "}"); | ||
}, ''); | ||
const styleEl = document.createElement('style'); | ||
styleEl.textContent = cssString; | ||
document.head.appendChild(styleEl); | ||
return () => document.head.removeChild(styleEl); | ||
}, [fontSizes]); | ||
React.useEffect(() => { | ||
richTextContext.addSerializer(serializer); | ||
return () => richTextContext.removeSerializer(serializer); | ||
function serializer(dom) { | ||
const fontEls = dom.querySelectorAll('font'); | ||
for (let i = 0; i < fontEls.length; i++) { | ||
const fontEl = fontEls[i]; | ||
const integerSize = Number(fontEl.getAttribute('size')); | ||
if (integerSize > fontSizes.length) { | ||
throw Error("Cannot find fontSize for integer size '".concat(integerSize, "'")); | ||
} | ||
const size = fontSizes[integerSize - 1]; | ||
fontEl.removeAttribute('size'); | ||
fontEl.style.fontSize = size; | ||
fontEl.dataset.integerSize = integerSize; | ||
} | ||
} | ||
}, [fontSizes]); | ||
React.useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const fontEls = richTextContext.getContentEditableElement().querySelectorAll('font'); | ||
for (let i = 0; i < fontEls.length; i++) { | ||
const fontEl = fontEls[i]; | ||
const cssFontSize = fontEl.style.fontSize; | ||
const integerSize = fontSizes.findIndex(size => size === cssFontSize) + 1; | ||
if (integerSize > 0) { | ||
fontEl.style.fontSize = ''; | ||
fontEl.setAttribute('size', integerSize); | ||
} | ||
} | ||
} | ||
}, [fontSizes]); | ||
} | ||
} | ||
const noop$2 = () => {}; | ||
const defaultAcceptImgTypes = '.jpg, .png, image/*'; | ||
const defaultOpts = { | ||
processImgElement: noop$2, | ||
fileBlobToUrl: defaultFileBlobToUrl, | ||
acceptImgTypes: defaultAcceptImgTypes | ||
}; | ||
function useImage() { | ||
let { | ||
processImgElement = noop$2, | ||
fileBlobToUrl = defaultFileBlobToUrl, | ||
acceptImgTypes = defaultAcceptImgTypes | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOpts; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertImage'); | ||
const richTextContext = React.useContext(RichTextContext); | ||
const fileInputRef = React.useRef(null); | ||
const dataUrls = React__default.useRef({}); | ||
useNewHtmlHandler(); | ||
useFileChooserInput(); | ||
useSerializer(); | ||
return { | ||
chooseFile(evt) { | ||
fileInputRef.current.click(); | ||
}, | ||
removeImage(imgElement) { | ||
const range = document.createRange(); | ||
range.selectNode(imgElement); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
document.execCommand('delete'); | ||
} | ||
}; | ||
function handleImageElement(imgElement) { | ||
imgElement.style.cursor = 'pointer'; | ||
imgElement.style.maxWidth = '100%'; | ||
processImgElement(imgElement); | ||
} | ||
function useNewHtmlHandler() { | ||
React.useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const imgElements = richTextContext.getContentEditableElement().querySelectorAll('img:not([data-text-as-image])'); | ||
imgElements.forEach(handleImageElement); | ||
} | ||
}, [processImgElement]); | ||
} | ||
function useFileChooserInput() { | ||
React.useEffect(() => { | ||
fileInputRef.current = document.createElement('input'); | ||
const fileInputElement = fileInputRef.current; | ||
fileInputElement.type = 'file'; | ||
fileInputElement.accept = acceptImgTypes; | ||
fileInputElement.multiple = false; | ||
fileInputElement.addEventListener('change', () => { | ||
if (fileInputElement.files && fileInputElement.files.length > 0) { | ||
fileBlobToUrl(fileInputElement.files[0], imgUrl => { | ||
performCommandWithValue(imgUrl); | ||
const imgElement = document.querySelector("img[src=\"".concat(imgUrl, "\"]")); | ||
if (imgElement.src && imgElement.src.startsWith('blob:')) { | ||
const reader = new FileReader(); | ||
reader.addEventListener('load', () => { | ||
dataUrls.current[imgElement.src] = reader.result; | ||
}); | ||
reader.readAsDataURL(fileInputElement.files[0]); | ||
} | ||
handleImageElement(imgElement); | ||
}); | ||
} | ||
}); | ||
}, [fileBlobToUrl, processImgElement, acceptImgTypes]); | ||
} | ||
function useSerializer() { | ||
React.useEffect(() => { | ||
richTextContext.addSerializer(serializer); | ||
return () => richTextContext.removeSerializer(serializer); | ||
}); | ||
} | ||
function serializer(dom) { | ||
dom.querySelectorAll('img').forEach(imgEl => { | ||
if (dataUrls.current[imgEl.src]) { | ||
imgEl.src = dataUrls.current[imgEl.src]; | ||
} | ||
}); | ||
} | ||
} | ||
function defaultFileBlobToUrl(file, cbk) { | ||
cbk(URL.createObjectURL(file)); | ||
} | ||
let tempId = 0; | ||
const noop$3 = () => {}; | ||
const defaultOptions = { | ||
processAnchorElement: noop$3 | ||
}; | ||
function useLink() { | ||
let { | ||
processAnchorElement = noop$3 | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions; | ||
const richTextContext = React.useContext(RichTextContext); | ||
const { | ||
performCommand | ||
} = useDocumentExecCommand('unlink'); | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertHTML'); | ||
useNewHtmlHandler(); | ||
return { | ||
getTextFromBeforeBlur, | ||
selectEntireLink, | ||
unlink, | ||
insertLink | ||
}; | ||
function getTextFromBeforeBlur() { | ||
const range = richTextContext.getRangeFromBeforeBlur(); | ||
if (range) { | ||
return range.toString(); | ||
} else { | ||
return null; | ||
} | ||
} | ||
function selectEntireLink(anchorElement) { | ||
const range = document.createRange(); | ||
range.selectNodeContents(anchorElement); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
function insertLink(link, displayedText) { | ||
richTextContext.selectRangeFromBeforeBlur({ | ||
usePreviousRange: true | ||
}); | ||
const id = "rte-link-temp-id-".concat(tempId++); | ||
performCommandWithValue("<a href=\"".concat(link, "\" id=\"").concat(id, "\" target=\"_blank\" rel=\"noopener noreferrer\">").concat(displayedText, "</a>")); | ||
const anchorElement = document.getElementById(id); | ||
anchorElement.removeAttribute('id'); | ||
processAnchorElement(anchorElement); | ||
} | ||
function useNewHtmlHandler() { | ||
React.useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const anchorElements = richTextContext.getContentEditableElement().querySelectorAll('a'); | ||
anchorElements.forEach(processAnchorElement); | ||
} | ||
}, [processAnchorElement]); | ||
} | ||
function unlink() { | ||
performCommand(); | ||
if (window.navigator.userAgent.includes('Edge/14') || window.navigator.userAgent.includes('Edge/15') || window.navigator.userAgent.includes('Edge/16') || window.navigator.userAgent.includes('Edge/17')) { | ||
// Older versions of Edge remove the <a> when you unlink, but keep the text blue and underlined so it looks like a link. | ||
// Ideally we'd be super smart about working around this, but for now I'm just removing all rich text formatting from the | ||
// text that used to be a link when you've got an older version of Edge. | ||
document.execCommand('removeFormat'); | ||
} | ||
} | ||
} | ||
let tempId$1 = 0; | ||
const noop$4 = () => {}; | ||
const defaultOptions$1 = { | ||
processContentEditableFalseElement: noop$4 | ||
}; | ||
function useContentEditableFalse() { | ||
let { | ||
processContentEditableFalseElement = noop$4 | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$1; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertHTML'); | ||
const richTextContext = React.useContext(RichTextContext); | ||
return { | ||
insertContentEditableFalseElement(innerHTML) { | ||
const id = "rte-ce-false-temp-id-" + tempId$1++; | ||
const htmlToInsert = "<span id=\"".concat(id, "\">").concat(richTextContext.sanitizeHTML(innerHTML, 'insertContentEditableFalseHTML'), "</span>"); | ||
performCommandWithValue(htmlToInsert); | ||
const contentEditableFalseElement = document.getElementById(id); | ||
handleContentEditableFalseElement(contentEditableFalseElement); | ||
} | ||
}; | ||
function handleContentEditableFalseElement(contentEditableFalseElement) { | ||
contentEditableFalseElement.removeAttribute('id'); | ||
contentEditableFalseElement.contentEditable = false; | ||
contentEditableFalseElement.addEventListener('click', () => selectRangeAfterNode(contentEditableFalseElement)); // if we are inserting this at the start of the rich text content editable container, we need to make sure a | ||
// cursor still appears and works when you select the beginning of the rich text container. This is done with | ||
// an empty span that *is* contentEditable | ||
if (!contentEditableFalseElement.previousSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) { | ||
const editableElementBefore = document.createElement('span'); | ||
contentEditableFalseElement.parentElement.insertBefore(editableElementBefore, contentEditableFalseElement); | ||
} // if we are inserting this at the end of the rich text content editable container, we need to make sure a | ||
// cursor still appears and works when you select the end of the rich text container. This is done with | ||
// an empty span that *is* contentEditable | ||
if (!contentEditableFalseElement.nextSibling && contentEditableFalseElement.parentElement === richTextContext.getContentEditableElement()) { | ||
const editableElementAfter = document.createElement('span'); | ||
contentEditableFalseElement.insertAdjacentElement('afterend', editableElementAfter); | ||
} | ||
selectRangeAfterNode(contentEditableFalseElement); | ||
processContentEditableFalseElement(contentEditableFalseElement); | ||
} | ||
} | ||
function selectRangeAfterNode(node) { | ||
const range = document.createRange(); | ||
range.setStartAfter(node); | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
} | ||
const noop$5 = () => {}; | ||
const defaultOptions$2 = { | ||
processSerializedElement: noop$5, | ||
fontFamily: null, | ||
fillStyle: '#00bf4b', | ||
fontWeight: 'bold', | ||
textBottom: null, | ||
backgroundColor: 'transparent' | ||
}; | ||
function useTextAsImage() { | ||
let { | ||
processSerializedElement = noop$5, | ||
fontFamily, | ||
fillStyle, | ||
fontWeight, | ||
textBottom, | ||
backgroundColor | ||
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultOptions$2; | ||
const { | ||
performCommandWithValue | ||
} = useDocumentExecCommand('insertImage'); | ||
const richTextContext = React.useContext(RichTextContext); | ||
useNewHtmlHandler(); | ||
useSerializer(); | ||
return { | ||
insertTextAsImage(text) { | ||
richTextContext.selectRangeFromBeforeBlur({ | ||
usePreviousRange: true | ||
}); | ||
const url = textToUrl({ | ||
text, | ||
referenceEl: getSelectedElement(), | ||
fontFamily, | ||
fontWeight, | ||
fillStyle, | ||
textBottom | ||
}); | ||
performCommandWithValue(url); | ||
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])")); | ||
processImgElement(imgElement, text, backgroundColor); | ||
} | ||
}; | ||
function useNewHtmlHandler() { | ||
React.useEffect(() => { | ||
richTextContext.addNewHTMLListener(newHtml); | ||
return () => richTextContext.removeNewHTMLListener(newHtml); | ||
function newHtml() { | ||
const spanEls = richTextContext.getContentEditableElement().querySelectorAll('span[data-text-as-image]'); | ||
for (let i = 0; i < spanEls.length; i++) { | ||
const spanEl = spanEls[i]; | ||
const url = textToUrl({ | ||
text: spanEl.dataset.textAsImage, | ||
referenceEl: spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement, | ||
fontFamily, | ||
fontWeight, | ||
fillStyle, | ||
textBottom | ||
}); | ||
const imgEl = document.createElement('img'); | ||
imgEl.src = url; | ||
processImgElement(imgEl, spanEl.dataset.textAsImage, backgroundColor); | ||
spanEl.parentNode.replaceChild(imgEl, spanEl); | ||
} | ||
} | ||
}); | ||
} | ||
function useSerializer() { | ||
React.useEffect(() => { | ||
richTextContext.addSerializer(htmlSerializer); | ||
return () => richTextContext.removeSerializer(htmlSerializer); | ||
function htmlSerializer(dom) { | ||
const textAsImage = dom.querySelectorAll('img[data-text-as-image]'); | ||
for (let i = 0; i < textAsImage.length; i++) { | ||
const imgEl = textAsImage[i]; | ||
const spanEl = document.createElement('span'); | ||
spanEl.dataset.textAsImage = imgEl.dataset.textAsImage; | ||
processSerializedElement(spanEl, spanEl.dataset.textAsImage); | ||
imgEl.parentNode.replaceChild(spanEl, imgEl); | ||
} | ||
} | ||
}, []); | ||
} | ||
} | ||
function textToUrl(_ref) { | ||
let { | ||
text, | ||
referenceEl, | ||
fontFamily, | ||
fillStyle, | ||
fontWeight, | ||
textBottom | ||
} = _ref; | ||
const computedStyle = window.getComputedStyle(referenceEl); | ||
const currentFontSize = Number(computedStyle.fontSize.replace('px', '')); | ||
const currentLineHeight = Number(computedStyle.lineHeight.replace('px', '')); | ||
const currentFontFamily = fontFamily || computedStyle.fontFamily; | ||
const font = "".concat(fontWeight, " ").concat(currentFontSize, "px ").concat(currentFontFamily); | ||
const testDiv = document.createElement('div'); | ||
testDiv.style.font = font; | ||
testDiv.style.position = 'absolute'; | ||
testDiv.style.visibility = 'hidden'; | ||
testDiv.style.whiteSpace = 'nowrap'; | ||
testDiv.textContent = text; | ||
document.body.appendChild(testDiv); | ||
const canvas = document.createElement('canvas'); | ||
canvas.width = testDiv.clientWidth + 1; | ||
canvas.height = currentLineHeight; | ||
const c = canvas.getContext('2d'); | ||
c.font = font; | ||
c.fillStyle = fillStyle || defaultOptions$2.fillStyle; | ||
c.textBaseline = 'bottom'; | ||
const yPosition = textBottom || currentLineHeight - 3; | ||
c.fillText(text, 0, yPosition); | ||
document.body.removeChild(testDiv); | ||
return c.canvas.toDataURL(); | ||
} | ||
function processImgElement(imgElement, text, backgroundColor) { | ||
imgElement.style.verticalAlign = 'bottom'; | ||
imgElement.dataset.textAsImage = text; | ||
imgElement.style.backgroundColor = backgroundColor || defaultOptions$2.backgroundColor; | ||
imgElement.addEventListener('click', evt => { | ||
const rect = imgElement.getBoundingClientRect(); | ||
const middlePoint = rect.left + rect.width / 2; | ||
const range = document.createRange(); | ||
if (evt.x < middlePoint) { | ||
range.setStartBefore(imgElement); | ||
} else { | ||
range.setStartAfter(imgElement); | ||
} | ||
const selection = window.getSelection(); | ||
selection.removeAllRanges(); | ||
selection.addRange(range); | ||
}); | ||
} | ||
function getSelectedElement() { | ||
let result = getSelection().getRangeAt(0).commonAncestorContainer; | ||
return result.nodeType === 1 ? result : result.parentElement; | ||
} | ||
function useElementDeletionDetection(domElement, cbk) { | ||
const richTextContext = React.useContext(RichTextContext); | ||
React.useEffect(() => { | ||
if (domElement) { | ||
richTextContext.addSelectionChangedListener(checkElementConnected); | ||
return () => richTextContext.removeSelectionChangedListener(checkElementConnected); | ||
function checkElementConnected() { | ||
if (!domElement.isConnected && !domElement._bandicoot_delete_callback_called) { | ||
domElement._bandicoot_delete_callback_called = true; | ||
cbk(domElement); | ||
} | ||
} | ||
} | ||
}, [domElement, cbk]); | ||
} | ||
function styleInject(css, ref) { | ||
if (ref === void 0) ref = {}; | ||
var insertAt = ref.insertAt; | ||
if (!css || typeof document === 'undefined') { | ||
return; | ||
} | ||
var head = document.head || document.getElementsByTagName('head')[0]; | ||
var style = document.createElement('style'); | ||
style.type = 'text/css'; | ||
if (insertAt === 'top') { | ||
if (head.firstChild) { | ||
head.insertBefore(style, head.firstChild); | ||
} else { | ||
head.appendChild(style); | ||
} | ||
} else { | ||
head.appendChild(style); | ||
} | ||
if (style.styleSheet) { | ||
style.styleSheet.cssText = css; | ||
} else { | ||
style.appendChild(document.createTextNode(css)); | ||
} | ||
} | ||
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}"; | ||
var style = {"bandicootButton":"icon-button_bandicootButton__2IZP5"}; | ||
styleInject(css); | ||
function IconButton(props) { | ||
return React__default.createElement("button", _extends({}, props, { | ||
className: "\n ".concat(style.bandicootButton, "\n ").concat(props.isActive ? 'active-control-button' : '', "\n ").concat(props.className || '', "\n "), | ||
onClick: props.onClick | ||
}), props.children); | ||
} | ||
BoldIcon.defaultProps = { | ||
width: 24, | ||
height: 24 | ||
}; | ||
function BoldIcon(props) { | ||
return React__default.createElement("svg", { | ||
width: props.width, | ||
height: props.height, | ||
xmlns: "http://www.w3.org/2000/svg", | ||
xmlnsXlink: "http://www.w3.org/1999/xlink", | ||
viewBox: "0 0 317.41 317.41" | ||
}, React__default.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(props) { | ||
const { | ||
performCommand | ||
} = useDocumentExecCommand('bold'); | ||
const { | ||
isActive | ||
} = useDocumentQueryCommandState('bold'); | ||
return React__default.createElement(IconButton, { | ||
onClick: performCommand, | ||
isActive: isActive | ||
}, React__default.createElement(BoldIcon, props)); | ||
} | ||
exports.BoldButton = BoldButton; | ||
exports.RichTextContainer = RichTextContainer; | ||
exports.RichTextContext = RichTextContext; | ||
exports.RichTextEditor = RichTextEditor; | ||
exports.useContentEditableFalse = useContentEditableFalse; | ||
exports.useDocumentExecCommand = useDocumentExecCommand; | ||
exports.useDocumentQueryCommandState = useDocumentQueryCommandState; | ||
exports.useElementDeletionDetection = useElementDeletionDetection; | ||
exports.useFontSize = useFontSize; | ||
exports.useFormatBlock = useFormatBlock; | ||
exports.useImage = useImage; | ||
exports.useLink = useLink; | ||
exports.useTextAsImage = useTextAsImage; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
}))); | ||
//# sourceMappingURL=bandicoot.umd.js.map |
{ | ||
"name": "bandicoot", | ||
"version": "4.2.0", | ||
"version": "4.2.1", | ||
"description": "React rich text editor", | ||
@@ -5,0 +5,0 @@ "main": "dist/bandicoot.umd.js", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
229635
2092
1