Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bandicoot

Package Overview
Dependencies
Maintainers
5
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bandicoot - npm Package Compare versions

Comparing version 3.0.3 to 4.0.0

1102

dist/bandicoot.esm.js

@@ -1,2 +0,1102 @@

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=l.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(m.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function e(){if(o()){const a=window.getSelection();0<a.rangeCount&&(n.current=window.getSelection().getRangeAt(0)),l.fireSelectionChanged()}}function f(){const b=i();b!==r&&(s(b),a.save(b))}function g(){let a=m.current.innerHTML;if(0<l.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=l.serialize(b.body)}return a}function h(b,c){c?(m.current.innerHTML=l.sanitizeHTML(b,"setHTML"),a.autoFocus&&j()):(d(),m.current.innerHTML=l.sanitizeHTML(b,"setHTML"),j()),l.fireNewHTML()}function i(){return l.sanitizeHTML(g(),"getHTML")}function j(){m.current.focus(),p(!0)}function k(){return a.placeholderColor?"color: ".concat(a.placeholderColor,";"):window.chrome&&(window.chrome.webstore||window.chrome.runtime)?"color: rgb(117, 117, 117);":"opacity: 0.54;"}if("function"!=typeof a.sanitizeHTML)throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");const l=useContext(RichTextContext);l.sanitizeHTML=a.sanitizeHTML;const m=useRef(null),n=useRef(null),{isFocused:o,setFocused:p}=useSynchronousFocusState(),q=useRef(globalBandicootId++),[r,s]=useState(()=>l.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[t,u]=useState(!1);b&&(b.current={setHTML:h,resetEditor:function(){h("")},getHTML:i,focus:j}),useEffect(()=>(m.current.addEventListener("paste",c),()=>m.current.removeEventListener("paste",c)),[a.pasteFn]),useEffect(()=>(document.addEventListener("selectionchange",e),()=>document.removeEventListener("selectionchange",e))),useEffect(()=>{if(m.current)for(let a=m.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")},[m.current]),useEffect(()=>{if(a.save&&a.unchangedInterval&&m.current&&o()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(f,a.unchangedInterval)});return c.observe(m.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,m.current,o()]),useEffect(()=>{if(!1===o()){const a=setTimeout(()=>{l.fireBlur(),f()},100);return()=>{clearTimeout(a)}}},[o()]),useEffect(()=>{l.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(m.current&&document.activeElement!==m.current&&!m.current.contains(document.activeElement)){if(a.usePreviousRange&&n.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(n.current)}else m.current.focus();p(!0),setTimeout(e)}},l.getRangeFromBeforeBlur=()=>n.current,l.isFocused=o,l.getContentEditableElement=()=>m.current}),useEffect(()=>{!t&&a.initialHTML&&(u(!0),h(a.initialHTML,!0))},[t,h,l]),useEffect(()=>{if(a.placeholder){const a=document.createElement("style");return a.textContent=".bandicoot-id-".concat(q.current,":empty:before { content: attr(data-placeholder); ").concat(k()," }"),document.head.appendChild(a),()=>a.parentNode.removeChild(a)}},[a.placeholder,a.placeholderColor,q.current]);const v=a.style||{};return React.createElement("div",{contentEditable:!a.disabled,onBlur:()=>p(!1),onFocus:function(){p(!0);const a=window.getSelection();0<a.rangeCount&&(n.current=a.getRangeAt(0))},ref:m,className:a.className+" bandicoot-id-"+q.current,style:_objectSpread2({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},v),"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};function useTextAsImage(){let{processSerializedElement:a=noop$5,fontFamily:b=null}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:defaultOptions$2;const{performCommandWithValue:c}=useDocumentExecCommand("insertImage"),d=useContext(RichTextContext);return function(){useEffect(()=>{function a(){const a=d.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let c=0;c<a.length;c++){const d=a[c],e=textToUrl(d.dataset.textAsImage,d.previousElementSibling||d.nextElementSibling||d.parentElement,b),f=document.createElement("img");f.src=e,processImgElement(f,d.dataset.textAsImage),d.parentNode.replaceChild(f,d)}}return d.addNewHTMLListener(a),()=>d.removeNewHTMLListener(a)},[])}(),function(){useEffect(()=>{function b(b){const c=b.querySelectorAll("img[data-text-as-image]");for(let d=0;d<c.length;d++){const b=c[d],e=document.createElement("span");e.dataset.textAsImage=b.dataset.textAsImage,a(e,e.dataset.textAsImage),b.parentNode.replaceChild(e,b)}}return d.addSerializer(b),()=>d.removeSerializer(b)},[])}(),{insertTextAsImage(a){d.selectRangeFromBeforeBlur({usePreviousRange:!0});const e=textToUrl(a,getSelectedElement(),b);c(e);const f=document.querySelector("img[src=\"".concat(e,"\"]:not([data-text-as-image])"));processImgElement(f,a)}}}function textToUrl(a,b,d){const e=window.getComputedStyle(b),f=+e.fontSize.replace("px",""),g=+e.lineHeight.replace("px",""),h=d||e.fontFamily,i="bold ".concat(f,"px ").concat(h),j=document.createElement("div");j.style.font=i,j.style.position="absolute",j.style.visibility="hidden",j.style.whiteSpace="nowrap",j.textContent=a,document.body.appendChild(j);const k=document.createElement("canvas");k.width=j.clientWidth+1,k.height=g;const l=k.getContext("2d");return l.font=i,l.fillStyle="#00bf4b",l.textBaseline="bottom",l.fillText(a,0,g-3),document.body.removeChild(j),l.canvas.toDataURL()}function processImgElement(a,b){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,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) {
editorRef.current = {
setHTML,
resetEditor,
getHTML,
focus
};
}
function interceptPaste(event) {
// https://developer.mozilla.org/en-US/docs/Web/Events/paste
event.preventDefault();
event.stopPropagation();
let paste = richTextContext.sanitizeHTML((window.clipboardData || event.clipboardData).getData('text/html'), '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
};
function useTextAsImage() {
let {
processSerializedElement = noop$5,
fontFamily = null
} = 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, getSelectedElement(), fontFamily);
performCommandWithValue(url);
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])"));
processImgElement(imgElement, text);
}
};
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(spanEl.dataset.textAsImage, spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement, fontFamily);
const imgEl = document.createElement('img');
imgEl.src = url;
processImgElement(imgEl, spanEl.dataset.textAsImage);
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(text, referenceEl, fontFamily) {
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 = "bold ".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 = '#00bf4b';
c.textBaseline = 'bottom';
c.fillText(text, 0, currentLineHeight - 3);
document.body.removeChild(testDiv);
return c.canvas.toDataURL();
}
function processImgElement(imgElement, text) {
imgElement.style.verticalAlign = 'bottom';
imgElement.dataset.textAsImage = text;
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,1124 @@

(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,b,d){const e=window.getComputedStyle(b),f=+e.fontSize.replace("px",""),g=+e.lineHeight.replace("px",""),h=d||e.fontFamily,i="bold ".concat(f,"px ").concat(h),j=document.createElement("div");j.style.font=i,j.style.position="absolute",j.style.visibility="hidden",j.style.whiteSpace="nowrap",j.textContent=a,document.body.appendChild(j);const k=document.createElement("canvas");k.width=j.clientWidth+1,k.height=g;const l=k.getContext("2d");return l.font=i,l.fillStyle="#00bf4b",l.textBaseline="bottom",l.fillText(a,0,g-3),document.body.removeChild(j),l.canvas.toDataURL()}function m(a,b){a.style.verticalAlign="bottom",a.dataset.textAsImage=b,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=o.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(p.current);const b=window.getSelection();b.removeAllRanges(),b.addRange(a),document.execCommand("removeFormat"),document.execCommand("delete")}function h(){if(s()){const a=window.getSelection();0<a.rangeCount&&(r.current=window.getSelection().getRangeAt(0)),o.fireSelectionChanged()}}function i(){const b=l();b!==x&&(y(b),a.save(b))}function j(){let a=p.current.innerHTML;if(0<o.numSerializers()){const b=new DOMParser().parseFromString(a,"text/html");a=o.serialize(b.body)}return a}function k(b,c){c?(p.current.innerHTML=o.sanitizeHTML(b,"setHTML"),a.autoFocus&&m()):(e(),p.current.innerHTML=o.sanitizeHTML(b,"setHTML"),m()),o.fireNewHTML()}function l(){return o.sanitizeHTML(j(),"getHTML")}function m(){p.current.focus(),v(!0)}function n(){return a.placeholderColor?"color: ".concat(a.placeholderColor,";"):window.chrome&&(window.chrome.webstore||window.chrome.runtime)?"color: rgb(117, 117, 117);":"opacity: 0.54;"}if("function"!=typeof a.sanitizeHTML)throw Error("RichTextEditor must be passed a sanitizeHTML function as a prop");const o=b.useContext(t);o.sanitizeHTML=a.sanitizeHTML;const p=b.useRef(null),r=b.useRef(null),{isFocused:s,setFocused:v}=g(),w=b.useRef(u++),[x,y]=b.useState(()=>o.sanitizeHTML(a.initialHTML,"initialSetLastSavedHTML")),[z,A]=b.useState(!1);c&&(c.current={setHTML:k,resetEditor:function(){k("")},getHTML:l,focus:m}),b.useEffect(()=>(p.current.addEventListener("paste",d),()=>p.current.removeEventListener("paste",d)),[a.pasteFn]),b.useEffect(()=>(document.addEventListener("selectionchange",h),()=>document.removeEventListener("selectionchange",h))),b.useEffect(()=>{if(p.current)for(let a=p.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")},[p.current]),b.useEffect(()=>{if(a.save&&a.unchangedInterval&&p.current&&s()){let b;const c=new MutationObserver(()=>{clearTimeout(b),b=setTimeout(i,a.unchangedInterval)});return c.observe(p.current,{attributes:!0,childList:!0,subtree:!0,characterData:!0}),()=>{c.disconnect(),clearTimeout(b)}}},[a.unchangedInterval,a.save,p.current,s()]),b.useEffect(()=>{if(!1===s()){const a=setTimeout(()=>{o.fireBlur(),i()},100);return()=>{clearTimeout(a)}}},[s()]),b.useEffect(()=>{o.selectRangeFromBeforeBlur=function(){let a=0<arguments.length&&arguments[0]!==void 0?arguments[0]:{usePreviousRange:!1};if(p.current&&document.activeElement!==p.current&&!p.current.contains(document.activeElement)){if(a.usePreviousRange&&r.current){const a=window.getSelection();a.removeAllRanges(),a.addRange(r.current)}else p.current.focus();v(!0),setTimeout(h)}},o.getRangeFromBeforeBlur=()=>r.current,o.isFocused=s,o.getContentEditableElement=()=>p.current}),b.useEffect(()=>{!z&&a.initialHTML&&(A(!0),k(a.initialHTML,!0))},[z,k,o]),b.useEffect(()=>{if(a.placeholder){const a=document.createElement("style");return a.textContent=".bandicoot-id-".concat(w.current,":empty:before { content: attr(data-placeholder); ").concat(n()," }"),document.head.appendChild(a),()=>a.parentNode.removeChild(a)}},[a.placeholder,a.placeholderColor,w.current]);const B=a.style||{};return q.createElement("div",{contentEditable:!a.disabled,onBlur:()=>v(!1),onFocus:function(){v(!0);const a=window.getSelection();0<a.rangeCount&&(r.current=a.getRangeAt(0))},ref:p,className:a.className+" bandicoot-id-"+w.current,style:f({wordBreak:"break-word",wordWrap:"break-word",overflowWrap:"break-word"},B),"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};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:a=G,fontFamily:c=null}=0<arguments.length&&void 0!==arguments[0]?arguments[0]:H;const{performCommandWithValue:d}=h("insertImage"),e=b.useContext(t);return function(){b.useEffect(()=>{function a(){const a=e.getContentEditableElement().querySelectorAll("span[data-text-as-image]");for(let b=0;b<a.length;b++){const d=a[b],e=l(d.dataset.textAsImage,d.previousElementSibling||d.nextElementSibling||d.parentElement,c),f=document.createElement("img");f.src=e,m(f,d.dataset.textAsImage),d.parentNode.replaceChild(f,d)}}return e.addNewHTMLListener(a),()=>e.removeNewHTMLListener(a)},[])}(),function(){b.useEffect(()=>{function b(b){const c=b.querySelectorAll("img[data-text-as-image]");for(let d=0;d<c.length;d++){const b=c[d],e=document.createElement("span");e.dataset.textAsImage=b.dataset.textAsImage,a(e,e.dataset.textAsImage),b.parentNode.replaceChild(e,b)}}return e.addSerializer(b),()=>e.removeSerializer(b)},[])}(),{insertTextAsImage(a){e.selectRangeFromBeforeBlur({usePreviousRange:!0});const b=l(a,n(),c);d(b);const f=document.querySelector("img[src=\"".concat(b,"\"]:not([data-text-as-image])"));m(f,a)}}},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) {
editorRef.current = {
setHTML,
resetEditor,
getHTML,
focus
};
}
function interceptPaste(event) {
// https://developer.mozilla.org/en-US/docs/Web/Events/paste
event.preventDefault();
event.stopPropagation();
let paste = richTextContext.sanitizeHTML((window.clipboardData || event.clipboardData).getData('text/html'), '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
};
function useTextAsImage() {
let {
processSerializedElement = noop$5,
fontFamily = null
} = 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, getSelectedElement(), fontFamily);
performCommandWithValue(url);
const imgElement = document.querySelector("img[src=\"".concat(url, "\"]:not([data-text-as-image])"));
processImgElement(imgElement, text);
}
};
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(spanEl.dataset.textAsImage, spanEl.previousElementSibling || spanEl.nextElementSibling || spanEl.parentElement, fontFamily);
const imgEl = document.createElement('img');
imgEl.src = url;
processImgElement(imgEl, spanEl.dataset.textAsImage);
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(text, referenceEl, fontFamily) {
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 = "bold ".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 = '#00bf4b';
c.textBaseline = 'bottom';
c.fillText(text, 0, currentLineHeight - 3);
document.body.removeChild(testDiv);
return c.canvas.toDataURL();
}
function processImgElement(imgElement, text) {
imgElement.style.verticalAlign = 'bottom';
imgElement.dataset.textAsImage = text;
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

2

package.json
{
"name": "bandicoot",
"version": "3.0.3",
"version": "4.0.0",
"description": "React rich text editor",

@@ -5,0 +5,0 @@ "main": "dist/bandicoot.umd.js",

@@ -28,3 +28,3 @@ declare module "bandicoot" {

placeholder?: string;
placeholderColor?: string;
placeholderStyle?: object;
ref?: React.RefObject<HTMLElement>;

@@ -31,0 +31,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc