@lesjoursfr/edith 2.1.2 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ .edith{border:1px solid #212529;border-radius:.25rem;padding:5px}.edith,.edith-toolbar{background-color:#212529}.edith-btn{background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem;color:#212529;cursor:pointer;display:inline-block;font-size:1rem;font-weight:900;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;user-select:none;vertical-align:middle}.edith-btn:disabled{color:#b7bec5;cursor:not-allowed}.edith-btn-group{display:inline-flex;position:relative;vertical-align:middle}.edith-btn-group:not(:first-child){margin-left:10px}.edith-btn-group :not(:first-child){margin-left:-1px}.edith-btn-group .edith-btn:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.edith-btn-group .edith-btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.edith-btn-nbsp:before{content:" ";display:block;height:16px;width:12px}.edith-tooltip{background:#000;border-radius:4px;color:#fff;font-size:13px;font-weight:700;padding:4px 8px;z-index:10}.edith-tooltip .arrow,.edith-tooltip .arrow:before{background:inherit;height:8px;position:absolute;width:8px}.edith-tooltip .arrow{visibility:hidden}.edith-tooltip .arrow:before{content:"";transform:rotate(45deg);visibility:visible}.edith-tooltip[data-popper-placement^=top]>.arrow{bottom:-4px}.edith-tooltip[data-popper-placement^=bottom]>.arrow{top:-4px}.edith-tooltip[data-popper-placement^=left]>.arrow{right:-4px}.edith-tooltip[data-popper-placement^=right]>.arrow{left:-4px}.edith-editing-area{background-color:#fff;border-radius:.25rem;margin-top:5px;padding:5px}.edith-code,.edith-visual{height:100%;outline:none;overflow:auto}.edith-hidden{display:none}.edith-visual{color:#212529}.edith-visual .edith-nbsp{color:#b7bec5}.edith-modal{background:#fff;border:2px solid #ced4da;border-radius:10px;left:calc(50% - 200px);position:fixed;top:20%;width:400px;z-index:10}.edith-modal .edith-modal-header{border-bottom:1px solid #ced4da;color:#000;font-size:20px;font-weight:700;line-height:1.4;padding:5px 10px}.edith-modal .edith-modal-content{color:#212529;margin:10px}.edith-modal .edith-modal-input{display:flex;flex-wrap:wrap;margin:10px 0}.edith-modal .edith-modal-input input,.edith-modal .edith-modal-input label{width:100%}.edith-modal .edith-modal-input input{appearance:none;background-clip:padding-box;background-color:#fff;border:1px solid #bfbfbf;border-radius:.25rem;font-size:1rem;font-weight:400;line-height:1.5;outline:0;padding:.375rem .75rem;width:100%}.edith-modal .edith-modal-input label{font-size:16px;font-weight:700;margin-bottom:5px}.edith-modal .edith-modal-checkbox{margin:10px 0}.edith-modal .edith-modal-checkbox label{display:flex}.edith-modal .edith-modal-checkbox input{appearance:none;background-color:#fff;background-position:50%;background-repeat:no-repeat;background-size:contain;border:1px solid #bfbfbf;border-radius:.25em;height:1em;margin-top:.25em;vertical-align:top;width:1em}.edith-modal .edith-modal-checkbox input:checked{background-color:#0d6efd;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3E%3C/svg%3E");border-color:#0d6efd}.edith-modal .edith-modal-footer{border-top:1px solid #ced4da;display:flex;justify-content:flex-end;padding:5px 10px}.edith-modal .edith-modal-footer :not(:last-child){margin-right:10px}.edith-modal .edith-modal-cancel,.edith-modal .edith-modal-submit{border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;user-select:none;vertical-align:middle}.edith-modal .edith-modal-cancel{background-color:#fff;border-color:#212529;color:#212529}.edith-modal .edith-modal-submit{background-color:#0d6efd;border-color:#0d6efd;color:#fff}
package/build/edith.js ADDED
@@ -0,0 +1 @@
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("CodeMirror"),require("Popper")):"function"==typeof define&&define.amd?define(["CodeMirror","Popper"],e):"object"==typeof exports?exports.Edith=e(require("CodeMirror"),require("Popper")):t.Edith=e(t.CodeMirror,t.Popper)}(self,(function(t,e){return function(){"use strict";var n={704:function(e){e.exports=t},613:function(t){t.exports=e}},i={};function o(t){var e=i[t];if(void 0!==e)return e.exports;var s=i[t]={exports:{}};return n[t](s,s.exports,o),s.exports}o.d=function(t,e){for(var n in e)o.o(e,n)&&!o.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var s={};return function(){function t(t,e){return n(t)&&t.tagName===e.toUpperCase()}function e(t){return t.nodeType===Node.TEXT_NODE}function n(t){return t.nodeType===Node.ELEMENT_NODE}function i(t,e){return t.getAttribute(e)}function r(t,e){return"string"==typeof e?t.tagName===e.toUpperCase():e.some((e=>t.tagName===e.toUpperCase()))}function a(t,e){return t.classList.contains(e)}function l(t,{innerHTML:e,textContent:n,attributes:i}={}){const o=document.createElement(t);if(i)for(const t in i)Object.hasOwnProperty.call(i,t)&&o.setAttribute(t,i[t]);return"string"==typeof e?o.innerHTML=e:"string"==typeof n&&(o.textContent=n),o}function c(t,e){return t.replaceWith(e),e}function d(t){const e=[...t.childNodes];return t.replaceWith(...e),e}function h(t){const e=document.createTextNode(t.textContent??"");return t.replaceWith(e),e}function u(t){return["AREA","BASE","BR","COL","EMBED","HR","IMG","INPUT","KEYGEN","LINK","META","PARAM","SOURCE","TRACK","WBR"].includes(t)}function p(t,e){for(const n of[...t.childNodes])e(n)&&n.remove()}function f(t,e){if(e(t))t.remove();else for(const n of[...t.childNodes])f(n,e)}function m(t){p(t,(t=>function(t){return t.nodeType===Node.COMMENT_NODE}(t)))}function b(t,e){for(const n of t.getAttributeNames())void 0===e[n]&&t.removeAttribute(n);for(const n of Object.keys(e))t.setAttribute(n,e[n])}function g(t){const e=t.getAttribute("style")||"";return(r(t,"b")&&e.match(/font-weight\s*:\s*(normal|400);/)||r(t,"i")&&e.match(/font-style\s*:\s*normal;/)||r(t,["u","s"])&&e.match(/text-decoration\s*:\s*none;/))&&(t=c(t,l("span",{attributes:{style:e},innerHTML:t.innerHTML}))),e.match(/font-weight\s*:\s*(bold|700|800|900);/)?t=c(t,l("b",{innerHTML:`<span style="${e.replace(/font-weight\s*:\s*(bold|700|800|900);/,"")}">${t.innerHTML}</span>`})):e.match(/font-style\s*:\s*italic;/)?t=c(t,l("i",{innerHTML:`<span style="${e.replace(/font-style\s*:\s*italic;/,"")}">${t.innerHTML}</span>`})):e.match(/text-decoration\s*:\s*underline;/)?t=c(t,l("u",{innerHTML:`<span style="${e.replace(/text-decoration\s*:\s*underline;/,"")}">${t.innerHTML}</span>`})):e.match(/text-decoration\s*:\s*line-through;/)&&(t=c(t,l("s",{innerHTML:`<span style="${e.replace(/text-decoration\s*:\s*line-through;/,"")}">${t.innerHTML}</span>`}))),t}function v(e,n){const i=e.childNodes;for(;i.length>0&&t(i[0],n);)i[0].remove();for(;i.length>0&&t(i[i.length-1],n);)i[i.length-1].remove()}o.r(s),o.d(s,{Edith:function(){return J},EdithButton:function(){return z}});let y=0;function E(t){const[e,...n]=t.split(".");return{type:e,ns:n??null}}function w(t,e,n){void 0===t.ljbtEvents&&(t.ljbtEvents={});for(const i of e.split(" ")){const{type:e,ns:o}=E(i),s=(++y).toString(10);t.addEventListener(e,n),t.ljbtEvents[s]={type:e,ns:o,handler:n}}}function C(t,e,n){void 0===t.ljbtEvents&&(t.ljbtEvents={});for(const i of e.split(" ")){const{type:e,ns:o}=E(i);for(const[i,s]of Object.entries(t.ljbtEvents))s.type!==e&&"*"!==e||null!==o&&!s.ns?.includes(o[0])||void 0!==n&&("function"!=typeof n||n!==s.handler)||(delete t.ljbtEvents[i],t.removeEventListener(s.type,s.handler))}}function x(){const t=window.getSelection();return{sel:t,range:t.rangeCount?t.getRangeAt(0):void 0}}function T(t){const e=document.createRange(),n=window.getSelection();e.setStart(t,1),e.collapse(!0),n.removeAllRanges(),n.addRange(e)}function k(t){const e=document.createRange(),n=window.getSelection();e.setStartAfter(t),e.collapse(!0),n.removeAllRanges(),n.addRange(e)}function N(t){if(1===t.length&&n(t[0])&&u(t[0].tagName))return void k(t[0]);const e=document.createRange(),i=window.getSelection();e.setStartBefore(t[0]),e.setEndAfter(t[t.length-1]),i.removeAllRanges(),i.addRange(e)}function M(t){const{range:e}=x();return void 0!==e&&(t.contains(e.startContainer)&&t.contains(e.endContainer))}function A(t,e){const n=e.parentNode,i=t.cloneRange();i.setStart(n,0);const o=i.extractContents(),s=document.createTextNode("​");return o.append(s),n.prepend(o),T(s),s}function L(t,e){const i=e.parentNode,o=new Range;o.selectNodeContents(i),o.setEnd(t.startContainer,t.startOffset);const s=new Range;s.selectNodeContents(i),s.setStart(t.endContainer,t.endOffset);const r=o.extractContents(),a=s.extractContents();i.prepend(r),i.append(a);let l=n(t.commonAncestorContainer)?t.commonAncestorContainer:t.commonAncestorContainer.parentNode;for(;l.tagName!==e.tagName;)l=l.parentNode;const c=d(l);return N(c),c[0].parentNode}function S(t,e={}){const{sel:i,range:o}=x();if(void 0===o)return;if(o.collapsed){let n=i.anchorNode.parentNode;for(;!a(n,"edith-visual");){if(r(n,t))return A(o,n);n=n.parentNode}return function(t,e,n={}){const i=document.createElement(e);return"a"===e?i.textContent=n.textContent||"lien":i.innerHTML="​",t.insertNode(i),"a"===e&&i.insertAdjacentText("afterend"," "),T(i),i}(o,t,e)}let s=o.commonAncestorContainer;for(;!n(s)||!a(s,"edith-visual");){if(n(s)&&r(s,t))return L(o,s);s=s.parentNode}for(const e of[...s.getElementsByTagName(t)])if(i.containsNode(e,!0)){return N(d(e)),s.normalize(),s}const l=document.createElement(t);return l.appendChild(o.extractContents()),o.insertNode(l),p(s,(t=>n(t)&&!u(t.tagName)&&(null===t.textContent||0===t.textContent.length))),function(t){const e=document.createRange(),n=window.getSelection();e.selectNodeContents(t),e.collapse(!1),n.removeAllRanges(),n.addRange(e)}(l),l}function H(t,e){for(let n of[...t.children]){if(r(n,"span")&&a(n,"edith-nbsp")){b(n,{class:"edith-nbsp",contenteditable:"false"}),n.innerHTML="¶";continue}n.hasAttribute("style")&&(n=g(n)),e[n.tagName]&&(n=c(n,l("span",{attributes:{style:n.getAttribute("style")||""},innerHTML:n.innerHTML})));const t={...e};if(r(n,["b","i","q","u","s"])&&(t[n.tagName]=!0),H(n,t),r(n,"a")){const t={};n.hasAttribute("href")&&(t.href=n.getAttribute("href")),n.hasAttribute("target")&&(t.target=n.getAttribute("target")),b(n,t)}else if(r(n,["b","i","q","u","s","br","sup"]))b(n,{});else if(r(n,["style","meta","link"]))n.remove();else if(r(n,"p")){if(null===n.textContent||0===n.textContent.trim().length){n.remove();continue}b(n,{}),v(n,"br")}else d(n)}}function O(t,n){const i=document.createElement("div");return i.innerHTML=t,H(i,n),i.normalize(),p(i,(t=>e(t)&&(null===t.textContent||0===t.textContent.trim().length))),i.innerHTML=i.innerHTML.replace(/\s*&nbsp;\s*/g," ").replace(/\s+/g," ").replace(/(<\/b>[\n\r\s]*<b>|<\/i>[\n\r\s]*<i>|<\/u>[\n\r\s]*<u>|<\/s>[\n\r\s]*<s>)/g," "),m(i),i}var R,j;!function(t){t.modeChanged="edith-mode-changed",t.initialized="edith-initialized"}(R||(R={}));class D{buffer=[];constructor(){}push(t){this.buffer.push(t),this.buffer.length>20&&this.buffer.shift()}pop(){return 0===this.buffer.length?null:this.buffer.pop()}}!function(t){t[t.Visual=1]="Visual",t[t.Code=2]="Code"}(j||(j={}));const I={height:80,resizable:!1,toolbar:[["style",["bold","italic","underline","strikethrough"]]],buttons:{},initialContent:""};var V=o(613);class z{el;ctx;icon;title;onclick;showOnCodeView;popperEl;popper;constructor(t,e){this.ctx=t,this.icon=e.icon,this.title=e.title,this.onclick=e.onclick,this.showOnCodeView=!0===e.showOnCodeView}click(t){t.preventDefault(),this.onclick(this.ctx,t)}showTooltip(){if(void 0!==this.popper)return;this.popperEl=l("div",{textContent:this.title,attributes:{class:"edith-tooltip"}});const t=l("div",{attributes:{class:"arrow","data-popper-arrow":""}});this.popperEl.append(t),this.ctx.toolbar.append(this.popperEl),this.popper=(0,V.createPopper)(this.el,this.popperEl,{placement:"bottom",modifiers:[{name:"arrow",options:{padding:5}},{name:"offset",options:{offset:[0,8]}}]})}hideTooltip(){void 0!==this.popper&&(this.popper.destroy(),this.popper=void 0,this.popperEl?.remove())}onEditorModeChange(t){t.detail.mode===j.Code?this.el.setAttribute("disabled","disabled"):this.el.removeAttribute("disabled")}render(){return this.el=l("button",{attributes:{class:`edith-btn ${this.icon}`,type:"button"}}),this.el.onclick=this.click.bind(this),this.el.onmouseenter=this.showTooltip.bind(this),this.el.onmouseleave=this.hideTooltip.bind(this),!0!==this.showOnCodeView&&this.ctx.on(R.modeChanged,this.onEditorModeChange.bind(this)),this.el}}const P=Object.freeze({bold:t=>new z(t,{icon:"fa-solid fa-bold",title:"Gras",onclick:t=>{t.editor.wrapInsideTag("b")}}),italic:t=>new z(t,{icon:"fa-solid fa-italic",title:"Italique",onclick:t=>{t.editor.wrapInsideTag("i")}}),underline:t=>new z(t,{icon:"fa-solid fa-underline",title:"Souligner",onclick:t=>{t.editor.wrapInsideTag("u")}}),strikethrough:t=>new z(t,{icon:"fa-solid fa-strikethrough",title:"Barrer",onclick:t=>{t.editor.wrapInsideTag("s")}}),subscript:t=>new z(t,{icon:"fa-solid fa-subscript",title:"Indice",onclick:t=>{t.editor.wrapInsideTag("sub")}}),superscript:t=>new z(t,{icon:"fa-solid fa-superscript",title:"Exposant",onclick:t=>{t.editor.wrapInsideTag("sup")}}),nbsp:t=>new z(t,{icon:"edith-btn-nbsp",title:"Ajouter une espace insécable",onclick:t=>{t.editor.replaceByHtml('<span class="edith-nbsp" contenteditable="false">¶</span>')}}),clear:t=>new z(t,{icon:"fa-solid fa-eraser",title:"Effacer la mise en forme",onclick:t=>{t.editor.clearStyle()}}),link:t=>new z(t,{icon:"fa-solid fa-link",title:"Lien",onclick:t=>{t.editor.insertLink()}}),codeview:t=>new z(t,{icon:"fa-solid fa-code",title:"Afficher le code HTML",onclick:t=>{t.editor.toggleCodeView()},showOnCodeView:!0})});var B,$=o(704);function q(t,e,n={}){let i,o,s,r,a,l=0;const c=!!n.leading,d="maxWait"in n,h=d?Math.max(n.maxWait||0,e):void 0,u=!("trailing"in n)||!!n.trailing;function p(e){const n=i,r=o;return i=o=void 0,l=e,s=t.apply(r,n),s}function f(t,e){return setTimeout(t,e)}function m(t){const n=t-a;return void 0===a||n>=e||n<0||d&&t-l>=h}function b(){const t=Date.now();if(m(t))return function(t){if(r=void 0,u&&i)return p(t);return i=o=void 0,s}(t);r=f(b,function(t){const n=t-l,i=e-(t-a);return d?Math.min(i,h-n):i}(t))}return function(...t){const n=Date.now(),h=m(n);if(i=t,o=this,a=n,h){if(void 0===r)return function(t){return l=t,r=f(b,e),c?p(t):s}(a);if(d)return r=f(b,e),p(a)}return void 0===r&&(r=f(b,e)),s}}function K(t){const e=document.createElement("div");e.setAttribute("class","edith-modal-input");const n=document.createElement("label");n.textContent=t.label;const i=document.createElement("input");return i.setAttribute("name",t.name),i.setAttribute("type","text"),null!==t.initialState&&(i.value=t.initialState.toString()),e.append(n),e.append(i),e}function W(t){const e=document.createElement("div");e.setAttribute("class","edith-modal-checkbox");const n=document.createElement("label");n.textContent=t.label;const i=document.createElement("input");return i.setAttribute("name",t.name),i.setAttribute("type","checkbox"),t.initialState&&(i.checked=!0),n.prepend(i),e.append(n),e}function _(t,e,n=null){return{fieldType:B.input,label:t,name:e,initialState:n}}function U(t,e,n=!1){return{fieldType:B.checkbox,label:t,name:e,initialState:n}}!function(t){t[t.input=1]="input",t[t.checkbox=2]="checkbox"}(B||(B={}));class F{el;ctx;title;fields;callback;constructor(t,e){this.ctx=t,this.title=e.title,this.fields=e.fields||[],this.callback=e.callback}cancel(t){t.preventDefault(),this.callback(null),this.close()}submit(t){t.preventDefault();const e={};for(const t of this.el.querySelectorAll("input"))n="name",t.hasAttribute(n)&&(e[i(t,"name")]="checkbox"===i(t,"type")?t.checked:t.value);var n;this.callback(e),this.close()}close(){this.el.remove()}show(){this.el=l("div",{attributes:{class:"edith-modal"}});const t=l("div",{attributes:{class:"edith-modal-header"}}),e=l("span",{textContent:this.title,attributes:{class:"edith-modal-title"}});t.append(e);const n=l("div",{attributes:{class:"edith-modal-content"}});for(const t of this.fields)switch(t.fieldType){case B.input:n.append(K(t));break;case B.checkbox:n.append(W(t));break;default:throw new Error(`Unknown fieldType ${t.fieldType}`)}const i=l("div",{attributes:{class:"edith-modal-footer"}}),o=l("button",{textContent:"Annuler",attributes:{class:"edith-modal-cancel",type:"button"}});i.append(o);const s=l("button",{textContent:"Valider",attributes:{class:"edith-modal-submit",type:"button"}});return i.append(s),this.el.append(t),this.el.append(n),this.el.append(i),this.ctx.modals.append(this.el),o.onclick=this.cancel.bind(this),s.onclick=this.submit.bind(this),this.el}}class G{el;ctx;content;height;resizable;mode;visualEditor;codeEditor;codeMirror;history;throttledSnapshots;constructor(t,e){this.ctx=t,this.content=e.initialContent,this.height=e.height,this.resizable=e.resizable,this.mode=j.Visual,this.history=new D,this.throttledSnapshots=function(t,e,n={}){return q(t,e,{leading:!("leading"in n)||!!n.leading,trailing:!("trailing"in n)||!!n.trailing,maxWait:e})}((()=>this.takeSnapshot()),3e3,{leading:!1,trailing:!0}),this.content=this.content.replace(/&nbsp;/g,'<span class="edith-nbsp" contenteditable="false">¶</span>')}render(){this.el=l("div",{attributes:{class:"edith-editing-area",style:this.resizable?`min-height: ${this.height}px; resize: vertical`:`height: ${this.height}px`}}),this.visualEditor=l("div",{innerHTML:this.content,attributes:{class:"edith-visual",contenteditable:"true",style:this.resizable?`min-height: ${this.height-10}px`:`height: ${this.height-10}px`}}),this.el.append(this.visualEditor),this.codeEditor=l("div",{attributes:{class:"edith-code edith-hidden"}}),this.el.append(this.codeEditor);const t=this.onKeyEvent.bind(this);this.visualEditor.addEventListener("keydown",t),this.visualEditor.addEventListener("keyup",t);const e=this.onPasteEvent.bind(this);return this.visualEditor.addEventListener("paste",e),this.el}getVisualEditorElement(){return this.visualEditor}getCodeEditorElement(){return this.codeEditor}setContent(t){t=t.replace(/&nbsp;/g,'<span class="edith-nbsp" contenteditable="false">¶</span>'),this.mode===j.Visual?this.visualEditor.innerHTML=t:this.codeMirror.dispatch({changes:{from:0,to:this.codeMirror.state.doc.length,insert:t}})}getContent(){const t=this.mode===j.Visual?this.visualEditor.innerHTML:this.codeMirror.state.doc.toJSON().map((t=>t.trim())).join("\n");if("<p><br></p>"===t)return"";const e=l("div",{innerHTML:t});f(e,(t=>n(t)&&!u(t.tagName)&&(null===t.textContent||0===t.textContent.length)));for(const t of e.querySelectorAll("[style]"))t.removeAttribute("style");for(const t of e.querySelectorAll("span"))0===t.attributes.length&&d(t);return e.innerHTML.replace(/\u200B/gi,"").replace(/<\/p>\s*<p>/gi,"<br>").replace(/(<p>|<\/p>)/gi,"").replace(/<span[^>]+class="edith-nbsp"[^>]*>[^<]*<\/span>/gi,"&nbsp;").replace(/(?:<br\s?\/?>)+$/gi,"")}takeSnapshot(){this.history.push(this.visualEditor.innerHTML)}restoreSnapshot(){this.visualEditor.innerHTML=this.history.pop()??""}wrapInsideTag(t){M(this.visualEditor)&&(S(t),this.takeSnapshot())}replaceByHtml(t){M(this.visualEditor)&&(!function(t){const{sel:e,range:n}=x();if(void 0===n)return;const i=document.createDocumentFragment(),o=l("div",{innerHTML:t});i.append(...o.childNodes);const s=i.childNodes[i.childNodes.length-1];e.deleteFromDocument(),n.insertNode(i),k(s)}(t),this.takeSnapshot())}clearStyle(){!function(){const{sel:t,range:e}=x();if(void 0!==e&&n(e.commonAncestorContainer))for(const n of[...e.commonAncestorContainer.children])t.containsNode(n,!0)&&h(n)}(),this.takeSnapshot()}insertLink(){const{sel:t,range:e}=x();if(void 0===e)return;const i=new F(this.ctx,{title:"Insérer un lien",fields:[_("Texte à afficher","text",e.toString()),_("URL du lien","href"),U("Ouvrir dans une nouvelle fenêtre","openInNewTab",!0)],callback:i=>{null!==i&&(function(t){const e=window.getSelection();e.removeAllRanges(),void 0!==t.range&&e.addRange(t.range)}({sel:t,range:e}),function(t,e,i){const o=S("a",{textContent:t});if(void 0!==o)n(o)&&(o.setAttribute("href",e),!0===i&&o.setAttribute("target","_blank"))}(i.text,i.href,i.openInNewTab))}});i.show()}toggleCodeView(){if(this.mode===j.Visual){this.mode=j.Code,this.visualEditor.classList.add("edith-hidden"),this.codeEditor.classList.remove("edith-hidden");const t=document.createElement("div");this.codeEditor.append(t),this.codeMirror=new $.EditorView({doc:this.visualEditor.innerHTML,extensions:[$.basicSetup,$.EditorView.lineWrapping,(0,$.html)({matchClosingTags:!0,autoCloseTags:!0})],parent:t})}else this.mode=j.Visual,this.codeEditor.classList.add("edith-hidden"),this.visualEditor.classList.remove("edith-hidden"),this.visualEditor.innerHTML=this.codeMirror.state.doc.toJSON().map((t=>t.trim())).join("\n"),this.codeMirror.destroy(),this.codeMirror=void 0,this.codeEditor.innerHTML="";this.ctx.trigger(R.modeChanged,{mode:this.mode})}onKeyEvent(t){(t.metaKey||t.ctrlKey?this._processKeyEventWithMeta(t):this._processKeyEvent(t))&&(t.preventDefault(),t.stopPropagation())}_processKeyEvent(t){return 13===t.keyCode?("keydown"===t.type&&this.replaceByHtml("<br />"),!0):(this.throttledSnapshots(),!1)}_processKeyEventWithMeta(t){switch(t.keyCode){case 13:return"keydown"===t.type&&this.replaceByHtml("<br />"),!0;case 32:return"keydown"===t.type&&this.replaceByHtml('<span class="edith-nbsp" contenteditable="false">¶</span>'),!0;case 66:return"keydown"===t.type&&this.wrapInsideTag("b"),!0;case 73:return"keydown"===t.type&&this.wrapInsideTag("i"),!0;case 85:return"keydown"===t.type&&this.wrapInsideTag("u"),!0;case 83:return"keydown"===t.type&&this.wrapInsideTag("s"),!0;case 90:return"keydown"===t.type&&this.restoreSnapshot(),!0}return!1}onPasteEvent(t){t.preventDefault(),t.stopPropagation();const{sel:n,range:i}=x();if(void 0===i||null===t.clipboardData)return;const o=document.createDocumentFragment();if(t.clipboardData.types.includes("text/html")){let i=e(n.anchorNode)?n.anchorNode.parentNode:n.anchorNode;const s={B:!1,I:!1,U:!1,S:!1,Q:!1};for(;null!==i&&!a(i,"edith-visual");)r(i,["b","i","u","s","q"])&&(s[(i.tagName,"Q")]=!0),i=i.parentNode;let l=t.clipboardData.getData("text/html").replace(/[\r\n]+/g," ");/^<html>\s*<body>/.test(l)||(l="<html><body>"+l+"</body></html>");const c=O(l,s);o.append(...c.childNodes)}else{const e=t.clipboardData.getData("text/plain").split(/[\r\n]+/g);for(let t=0;t<e.length;t++)0!==t&&o.append(document.createElement("br")),o.append(document.createTextNode(e[t]))}n.deleteFromDocument(),i.insertNode(o)}destroy(){this.codeMirror?.destroy(),this.codeMirror=void 0,this.el.remove()}}class J{element;toolbar;editor;modals;constructor(t,e){var n,i;this.element=t,n=this.element,"string"==typeof(i="edith")?n.classList.add(i):n.classList.add(...i),this.toolbar=l("div",{attributes:{class:"edith-toolbar"}}),this.element.append(this.toolbar);const o=e.buttons??I.buttons,s=e.toolbar??I.toolbar;for(const{0:t,1:e}of s){const n=document.createElement("div");n.setAttribute("id",t),n.setAttribute("class","edith-btn-group"),this.toolbar.append(n);for(const t of e){const e=o[t]??P[t];n.append(e(this).render())}}this.editor=new G(this,{initialContent:e.initialContent??I.initialContent,height:e.height??I.height,resizable:e.resizable??I.resizable}),this.element.append(this.editor.render()),this.modals=l("div",{attributes:{class:"edith-modals"}}),this.element.append(this.modals),this.element.edith=this,this.trigger(R.initialized)}on(t,e){!function(t,e,n){if(t instanceof NodeList)for(const i of t)w(i,e,n);else w(t,e,n)}(this.element,t,e)}off(t,e){!function(t,e,n){if(t instanceof NodeList)for(const i of t)C(i,e,n);else C(t,e,n)}(this.element,t,e)}trigger(t,e){!function(t,e,n){t.dispatchEvent("string"==typeof e?new CustomEvent(e,{detail:n}):e)}(this.element,t,e)}setContent(t){this.editor.setContent(t)}getContent(){return this.editor.getContent()}destroy(){var t,e;t=this.element,"string"==typeof(e="edith")?t.classList.remove(e):t.classList.remove(...e),this.modals.remove(),this.editor.destroy(),this.toolbar.remove(),this.element.remove()}}}(),s}()}));
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Replace the current selection by the given HTML code.
3
+ * @param {string} html the HTML code
4
+ */
5
+ export declare function replaceSelectionByHtml(html: string): void;
6
+ /**
7
+ * Wrap the current selection inside a new node.
8
+ * @param {string} tag the tag name of the node
9
+ * @param {object} options optional parameters
10
+ * @param {string} options.textContent the text content of the node
11
+ * @returns {HTMLElement|Text} the created node or the root node
12
+ */
13
+ export declare function wrapInsideTag<K extends keyof HTMLElementTagNameMap>(tag: K, options?: {
14
+ textContent?: string;
15
+ }): HTMLElement | Text | undefined;
16
+ /**
17
+ * Wrap the current selection inside a link.
18
+ * @param {string} text the text of the link
19
+ * @param {string} href the href of the link
20
+ * @param {boolean} targetBlank add target="_blank" attribute or not
21
+ * @returns {HTMLElement|Text} the created node
22
+ */
23
+ export declare function wrapInsideLink(text: string, href: string, targetBlank: boolean): HTMLElement | Text | undefined;
24
+ /**
25
+ * Clear the style in the current selection.
26
+ */
27
+ export declare function clearSelectionStyle(): void;
28
+ /**
29
+ * Clean the DOM content of the node
30
+ * @param {HTMLElement} root the node to process
31
+ * @param {object} style active styles for the root
32
+ */
33
+ export declare function cleanDomContent(root: HTMLElement, style: {
34
+ [keyof: string]: boolean;
35
+ }): void;
36
+ /**
37
+ * Clean the given HTML code.
38
+ * @param {string} html the HTML code to clean
39
+ * @param {object} style active styles
40
+ * @returns {HTMLElement} the cleaned HTML code
41
+ */
42
+ export declare function cleanPastedHtml(html: string, style: {
43
+ [keyof: string]: boolean;
44
+ }): HTMLElement;
@@ -0,0 +1,327 @@
1
+ import { createNodeWith, hasClass, hasTagName, isHTMLElement, isSelfClosing, removeCommentNodes, removeEmptyTextNodes, removeNodes, replaceNodeStyleByTag, replaceNodeWith, resetAttributesTo, textifyNode, trimTag, unwrapNode, } from "@lesjoursfr/browser-tools";
2
+ import { getSelection, moveCursorAfterNode, moveCursorInsideNode, selectNodeContents, selectNodes } from "./range.js";
3
+ /**
4
+ * Split the node at the caret position.
5
+ * @param {Range} range the caret position
6
+ * @param {HTMLElement} node the node to split
7
+ * @returns {Text} the created text node with the caret inside
8
+ */
9
+ function splitNodeAtCaret(range, node) {
10
+ // Get the node's parent
11
+ const parent = node.parentNode;
12
+ // Clone the current range & move the starting point to the beginning of the parent's node
13
+ const beforeCaret = range.cloneRange();
14
+ beforeCaret.setStart(parent, 0);
15
+ // Extract the content before the caret
16
+ const frag = beforeCaret.extractContents();
17
+ // Add a TextNode
18
+ const textNode = document.createTextNode("\u200B");
19
+ frag.append(textNode);
20
+ // Add back the content into the node's parent
21
+ parent.prepend(frag);
22
+ // Move the cursor in the created TextNode
23
+ moveCursorInsideNode(textNode);
24
+ // Return the inserted TextNode
25
+ return textNode;
26
+ }
27
+ /**
28
+ * Extract the selection from the node.
29
+ * @param {Range} range the selection to extract
30
+ * @param {HTMLElement} node the node to split
31
+ * @param {string} tag the tag to remove
32
+ * @returns {HTMLElement} the created node
33
+ */
34
+ function extractSelectionFromNode(range, node) {
35
+ // Get the node's parent
36
+ const parent = node.parentNode;
37
+ // Clone the current range & move the starting point to the beginning of the parent's node
38
+ const beforeSelection = new Range();
39
+ beforeSelection.selectNodeContents(parent);
40
+ beforeSelection.setEnd(range.startContainer, range.startOffset);
41
+ const afterSelection = new Range();
42
+ afterSelection.selectNodeContents(parent);
43
+ afterSelection.setStart(range.endContainer, range.endOffset);
44
+ // Extract the content of the selection
45
+ const fragBefore = beforeSelection.extractContents();
46
+ const fragAfter = afterSelection.extractContents();
47
+ // Add back the content into the node's parent
48
+ parent.prepend(fragBefore);
49
+ parent.append(fragAfter);
50
+ // Remove the parent from the selection
51
+ let current = !isHTMLElement(range.commonAncestorContainer)
52
+ ? range.commonAncestorContainer.parentNode
53
+ : range.commonAncestorContainer;
54
+ while (current.tagName !== node.tagName) {
55
+ // Take the parent
56
+ current = current.parentNode;
57
+ }
58
+ const innerNodes = unwrapNode(current);
59
+ // Preserve the selection
60
+ selectNodes(innerNodes);
61
+ // Return the inserted TextNode
62
+ return innerNodes[0].parentNode;
63
+ }
64
+ /**
65
+ * Create a node at the caret position.
66
+ * @param {Range} range the caret position
67
+ * @param {string} tag the tag name of the node
68
+ * @param {object} options optional parameters
69
+ * @param {string} options.textContent the text content of the node
70
+ * @returns {HTMLElement} the created node with the caret inside
71
+ */
72
+ function insertTagAtCaret(range, tag, options = {}) {
73
+ // Create the tag
74
+ const node = document.createElement(tag);
75
+ // Add a zero-width char or the word "lien" to create a valid cursor position inside the element
76
+ if (tag === "a") {
77
+ node.textContent = options.textContent || "lien";
78
+ }
79
+ else {
80
+ node.innerHTML = "\u200B";
81
+ }
82
+ // Insert the tag at the cursor position
83
+ range.insertNode(node);
84
+ // Add an extra space after the tag if it's a link
85
+ if (tag === "a") {
86
+ node.insertAdjacentText("afterend", " ");
87
+ }
88
+ // Move the cursor inside the created tag
89
+ moveCursorInsideNode(node);
90
+ // Return the inserted tag
91
+ return node;
92
+ }
93
+ /**
94
+ * Replace the current selection by the given HTML code.
95
+ * @param {string} html the HTML code
96
+ */
97
+ export function replaceSelectionByHtml(html) {
98
+ // Get the caret position
99
+ const { sel, range } = getSelection();
100
+ // Check if the user has selected something
101
+ if (range === undefined) {
102
+ return;
103
+ }
104
+ // Create the fragment to insert
105
+ const frag = document.createDocumentFragment();
106
+ // Create the nodes to insert
107
+ const el = createNodeWith("div", { innerHTML: html });
108
+ frag.append(...el.childNodes);
109
+ const lastNode = frag.childNodes[frag.childNodes.length - 1];
110
+ // Replace the current selection by the pasted content
111
+ sel.deleteFromDocument();
112
+ range.insertNode(frag);
113
+ // Preserve the selection
114
+ moveCursorAfterNode(lastNode);
115
+ }
116
+ /**
117
+ * Wrap the current selection inside a new node.
118
+ * @param {string} tag the tag name of the node
119
+ * @param {object} options optional parameters
120
+ * @param {string} options.textContent the text content of the node
121
+ * @returns {HTMLElement|Text} the created node or the root node
122
+ */
123
+ export function wrapInsideTag(tag, options = {}) {
124
+ // Get the caret position
125
+ const { sel, range } = getSelection();
126
+ // Check if the user has selected something
127
+ if (range === undefined) {
128
+ return;
129
+ }
130
+ // Check if there is a Selection
131
+ if (range.collapsed) {
132
+ // Check if a parent element has the same tag name
133
+ let parent = sel.anchorNode.parentNode;
134
+ while (!hasClass(parent, "edith-visual")) {
135
+ if (hasTagName(parent, tag)) {
136
+ // One of the parent has the same tag name
137
+ // Split the parent at the caret & insert a TextNode
138
+ return splitNodeAtCaret(range, parent);
139
+ }
140
+ // Take the parent
141
+ parent = parent.parentNode;
142
+ }
143
+ // We just have to insert a new Node at the caret position
144
+ return insertTagAtCaret(range, tag, options);
145
+ }
146
+ // There is a selection
147
+ // Check if a parent element has the same tag name
148
+ let parent = range.commonAncestorContainer;
149
+ while (!isHTMLElement(parent) || !hasClass(parent, "edith-visual")) {
150
+ if (isHTMLElement(parent) && hasTagName(parent, tag)) {
151
+ // One of the parent has the same tag name
152
+ // Extract the selection from the parent
153
+ return extractSelectionFromNode(range, parent);
154
+ }
155
+ // Take the parent
156
+ parent = parent.parentNode;
157
+ }
158
+ // Try to replace all elements with the same tag name in the selection
159
+ for (const el of [...parent.getElementsByTagName(tag)]) {
160
+ // Check if the the Element Intersect the Selection
161
+ if (sel.containsNode(el, true)) {
162
+ // Unwrap the node
163
+ const innerNodes = unwrapNode(el);
164
+ // Return the node
165
+ selectNodes(innerNodes);
166
+ parent.normalize();
167
+ return parent;
168
+ }
169
+ }
170
+ // Nothing was replaced
171
+ // Wrap the selection inside the given tag
172
+ const node = document.createElement(tag);
173
+ node.appendChild(range.extractContents());
174
+ range.insertNode(node);
175
+ // Remove empty tags
176
+ removeNodes(parent, (el) => {
177
+ return isHTMLElement(el) && !isSelfClosing(el.tagName) && (el.textContent === null || el.textContent.length === 0);
178
+ });
179
+ // Return the node
180
+ selectNodeContents(node);
181
+ return node;
182
+ }
183
+ /**
184
+ * Wrap the current selection inside a link.
185
+ * @param {string} text the text of the link
186
+ * @param {string} href the href of the link
187
+ * @param {boolean} targetBlank add target="_blank" attribute or not
188
+ * @returns {HTMLElement|Text} the created node
189
+ */
190
+ export function wrapInsideLink(text, href, targetBlank) {
191
+ // Wrap the selection inside a link
192
+ const tag = wrapInsideTag("a", { textContent: text });
193
+ // Check if we have a tag
194
+ if (tag === undefined) {
195
+ return;
196
+ }
197
+ // Check if it's a Text node
198
+ if (!isHTMLElement(tag)) {
199
+ return tag;
200
+ }
201
+ // Add an href Attribute
202
+ tag.setAttribute("href", href);
203
+ // Create a target="_blank" attribute if required
204
+ if (targetBlank === true) {
205
+ tag.setAttribute("target", "_blank");
206
+ }
207
+ // Return the tag
208
+ return tag;
209
+ }
210
+ /**
211
+ * Clear the style in the current selection.
212
+ */
213
+ export function clearSelectionStyle() {
214
+ // Get the caret position
215
+ const { sel, range } = getSelection();
216
+ // Check if there is something to do
217
+ if (range === undefined || !isHTMLElement(range.commonAncestorContainer)) {
218
+ return;
219
+ }
220
+ // Try to replace all non-text elements by their text
221
+ for (const el of [...range.commonAncestorContainer.children]) {
222
+ // Check if the the Element Intersect the Selection
223
+ if (sel.containsNode(el, true)) {
224
+ // Replace the node by its text
225
+ textifyNode(el);
226
+ }
227
+ }
228
+ }
229
+ /**
230
+ * Clean the DOM content of the node
231
+ * @param {HTMLElement} root the node to process
232
+ * @param {object} style active styles for the root
233
+ */
234
+ export function cleanDomContent(root, style) {
235
+ // Iterate through children
236
+ for (let el of [...root.children]) {
237
+ // Check if the span is an edith-nbsp
238
+ if (hasTagName(el, "span") && hasClass(el, "edith-nbsp")) {
239
+ // Ensure that we have a clean element
240
+ resetAttributesTo(el, { class: "edith-nbsp", contenteditable: "false" });
241
+ el.innerHTML = "¶";
242
+ continue;
243
+ }
244
+ // Check if there is a style attribute on the current node
245
+ if (el.hasAttribute("style")) {
246
+ // Replace the style attribute by tags
247
+ el = replaceNodeStyleByTag(el);
248
+ }
249
+ // Check if the Tag Match a Parent Tag
250
+ if (style[el.tagName]) {
251
+ el = replaceNodeWith(el, createNodeWith("span", { attributes: { style: el.getAttribute("style") || "" }, innerHTML: el.innerHTML }));
252
+ }
253
+ // Save the Current Style Tag
254
+ const newTags = { ...style };
255
+ if (hasTagName(el, ["b", "i", "q", "u", "s"])) {
256
+ newTags[el.tagName] = true;
257
+ }
258
+ // Clean Children
259
+ cleanDomContent(el, newTags);
260
+ // Keep only href & target attributes for <a> tags
261
+ if (hasTagName(el, "a")) {
262
+ const linkAttributes = {};
263
+ if (el.hasAttribute("href")) {
264
+ linkAttributes.href = el.getAttribute("href");
265
+ }
266
+ if (el.hasAttribute("target")) {
267
+ linkAttributes.target = el.getAttribute("target");
268
+ }
269
+ resetAttributesTo(el, linkAttributes);
270
+ continue;
271
+ }
272
+ // Remove all tag attributes for tags in the allowed list
273
+ if (hasTagName(el, ["b", "i", "q", "u", "s", "br", "sup"])) {
274
+ resetAttributesTo(el, {});
275
+ continue;
276
+ }
277
+ // Remove useless tags
278
+ if (hasTagName(el, ["style", "meta", "link"])) {
279
+ el.remove();
280
+ continue;
281
+ }
282
+ // Check if it's a <p> tag
283
+ if (hasTagName(el, "p")) {
284
+ // Check if the element contains text
285
+ if (el.textContent === null || el.textContent.trim().length === 0) {
286
+ // Remove the node
287
+ el.remove();
288
+ continue;
289
+ }
290
+ // Remove all tag attributes
291
+ resetAttributesTo(el, {});
292
+ // Remove leading & trailing <br>
293
+ trimTag(el, "br");
294
+ // Return
295
+ continue;
296
+ }
297
+ // Unwrap the node
298
+ unwrapNode(el);
299
+ }
300
+ }
301
+ /**
302
+ * Clean the given HTML code.
303
+ * @param {string} html the HTML code to clean
304
+ * @param {object} style active styles
305
+ * @returns {HTMLElement} the cleaned HTML code
306
+ */
307
+ export function cleanPastedHtml(html, style) {
308
+ // Create a new div with the HTML content
309
+ const result = document.createElement("div");
310
+ result.innerHTML = html;
311
+ // Clean the HTML content
312
+ cleanDomContent(result, style);
313
+ result.normalize();
314
+ // Clean empty text nodes
315
+ removeEmptyTextNodes(result);
316
+ // Fix extra stuff in the HTML code :
317
+ // - Clean spaces
318
+ // - Merge siblings tags
319
+ result.innerHTML = result.innerHTML
320
+ .replace(/\s*&nbsp;\s*/g, " ")
321
+ .replace(/\s+/g, " ")
322
+ .replace(/(<\/b>[\n\r\s]*<b>|<\/i>[\n\r\s]*<i>|<\/u>[\n\r\s]*<u>|<\/s>[\n\r\s]*<s>)/g, " ");
323
+ // Clean comment nodes
324
+ removeCommentNodes(result);
325
+ // Return Cleaned HTML
326
+ return result;
327
+ }
@@ -0,0 +1,4 @@
1
+ export declare enum Events {
2
+ modeChanged = "edith-mode-changed",
3
+ initialized = "edith-initialized"
4
+ }
@@ -0,0 +1,5 @@
1
+ export var Events;
2
+ (function (Events) {
3
+ Events["modeChanged"] = "edith-mode-changed";
4
+ Events["initialized"] = "edith-initialized";
5
+ })(Events || (Events = {}));
@@ -0,0 +1,14 @@
1
+ export declare class History {
2
+ private buffer;
3
+ constructor();
4
+ /**
5
+ * Add a new snapshot to the history.
6
+ * @param {string} doc the element to save
7
+ */
8
+ push(doc: string): void;
9
+ /**
10
+ * Get the last saved element
11
+ * @returns {(string|null)} the last saved element or null
12
+ */
13
+ pop(): string | null;
14
+ }
@@ -0,0 +1,24 @@
1
+ export class History {
2
+ buffer = [];
3
+ constructor() { }
4
+ /**
5
+ * Add a new snapshot to the history.
6
+ * @param {string} doc the element to save
7
+ */
8
+ push(doc) {
9
+ this.buffer.push(doc);
10
+ if (this.buffer.length > 20) {
11
+ this.buffer.shift();
12
+ }
13
+ }
14
+ /**
15
+ * Get the last saved element
16
+ * @returns {(string|null)} the last saved element or null
17
+ */
18
+ pop() {
19
+ if (this.buffer.length === 0) {
20
+ return null;
21
+ }
22
+ return this.buffer.pop();
23
+ }
24
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./edit.js";
2
+ export * from "./events.js";
3
+ export * from "./history.js";
4
+ export * from "./mode.js";
5
+ export * from "./range.js";
@@ -0,0 +1,5 @@
1
+ export * from "./edit.js";
2
+ export * from "./events.js";
3
+ export * from "./history.js";
4
+ export * from "./mode.js";
5
+ export * from "./range.js";
@@ -0,0 +1,4 @@
1
+ export declare enum EditorModes {
2
+ Visual = 1,
3
+ Code = 2
4
+ }
@@ -0,0 +1,5 @@
1
+ export var EditorModes;
2
+ (function (EditorModes) {
3
+ EditorModes[EditorModes["Visual"] = 1] = "Visual";
4
+ EditorModes[EditorModes["Code"] = 2] = "Code";
5
+ })(EditorModes || (EditorModes = {}));
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @typedef {Object} CurrentSelection
3
+ * @property {Selection} sel the current selection
4
+ * @property {(Range|undefined)} range the current range
5
+ */
6
+ export type CurrentSelection = {
7
+ sel: Selection;
8
+ range?: Range;
9
+ };
10
+ /**
11
+ * Get the current selection.
12
+ * @returns {CurrentSelection} the current selection
13
+ */
14
+ export declare function getSelection(): CurrentSelection;
15
+ /**
16
+ * Restore the given selection.
17
+ * @param {CurrentSelection} selection the selection to restore
18
+ */
19
+ export declare function restoreSelection(selection: CurrentSelection): void;
20
+ /**
21
+ * Move the cursor inside the node.
22
+ * @param {ChildNode} target the targeted node
23
+ */
24
+ export declare function moveCursorInsideNode(target: ChildNode): void;
25
+ /**
26
+ * Move the cursor after the node.
27
+ * @param {ChildNode} target the targeted node
28
+ */
29
+ export declare function moveCursorAfterNode(target: ChildNode): void;
30
+ /**
31
+ * Select the node's content.
32
+ * @param {ChildNode} target the targeted node
33
+ */
34
+ export declare function selectNodeContents(target: ChildNode): void;
35
+ /**
36
+ * Select the given Nodes.
37
+ * @param {Array<ChildNode>} nodes The list of Nodes to select.
38
+ */
39
+ export declare function selectNodes(nodes: ChildNode[]): void;
40
+ /**
41
+ * Check if the current selection is inside the given node.
42
+ * @param {ChildNode} node the targeted node
43
+ * @returns {boolean} true if the selection is inside
44
+ */
45
+ export declare function isSelectionInsideNode(node: ChildNode): boolean;
@@ -0,0 +1,86 @@
1
+ import { isHTMLElement, isSelfClosing } from "@lesjoursfr/browser-tools";
2
+ /**
3
+ * Get the current selection.
4
+ * @returns {CurrentSelection} the current selection
5
+ */
6
+ export function getSelection() {
7
+ const sel = window.getSelection();
8
+ return { sel, range: sel.rangeCount ? sel.getRangeAt(0) : undefined };
9
+ }
10
+ /**
11
+ * Restore the given selection.
12
+ * @param {CurrentSelection} selection the selection to restore
13
+ */
14
+ export function restoreSelection(selection) {
15
+ const sel = window.getSelection();
16
+ sel.removeAllRanges();
17
+ if (selection.range !== undefined) {
18
+ sel.addRange(selection.range);
19
+ }
20
+ }
21
+ /**
22
+ * Move the cursor inside the node.
23
+ * @param {ChildNode} target the targeted node
24
+ */
25
+ export function moveCursorInsideNode(target) {
26
+ const range = document.createRange();
27
+ const sel = window.getSelection();
28
+ range.setStart(target, 1);
29
+ range.collapse(true);
30
+ sel.removeAllRanges();
31
+ sel.addRange(range);
32
+ }
33
+ /**
34
+ * Move the cursor after the node.
35
+ * @param {ChildNode} target the targeted node
36
+ */
37
+ export function moveCursorAfterNode(target) {
38
+ const range = document.createRange();
39
+ const sel = window.getSelection();
40
+ range.setStartAfter(target);
41
+ range.collapse(true);
42
+ sel.removeAllRanges();
43
+ sel.addRange(range);
44
+ }
45
+ /**
46
+ * Select the node's content.
47
+ * @param {ChildNode} target the targeted node
48
+ */
49
+ export function selectNodeContents(target) {
50
+ const range = document.createRange();
51
+ const sel = window.getSelection();
52
+ range.selectNodeContents(target);
53
+ range.collapse(false);
54
+ sel.removeAllRanges();
55
+ sel.addRange(range);
56
+ }
57
+ /**
58
+ * Select the given Nodes.
59
+ * @param {Array<ChildNode>} nodes The list of Nodes to select.
60
+ */
61
+ export function selectNodes(nodes) {
62
+ // Check if we just have a self-closing tag
63
+ if (nodes.length === 1 && isHTMLElement(nodes[0]) && isSelfClosing(nodes[0].tagName)) {
64
+ moveCursorAfterNode(nodes[0]); // Move the cursor after the Node
65
+ return;
66
+ }
67
+ // Select Nodes
68
+ const range = document.createRange();
69
+ const sel = window.getSelection();
70
+ range.setStartBefore(nodes[0]);
71
+ range.setEndAfter(nodes[nodes.length - 1]);
72
+ sel.removeAllRanges();
73
+ sel.addRange(range);
74
+ }
75
+ /**
76
+ * Check if the current selection is inside the given node.
77
+ * @param {ChildNode} node the targeted node
78
+ * @returns {boolean} true if the selection is inside
79
+ */
80
+ export function isSelectionInsideNode(node) {
81
+ const { range } = getSelection();
82
+ if (range === undefined) {
83
+ return false;
84
+ }
85
+ return node.contains(range.startContainer) && node.contains(range.endContainer);
86
+ }