@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.
- package/build/edith.css +1 -0
- package/build/edith.js +1 -0
- package/dist/core/edit.d.ts +44 -0
- package/dist/core/edit.js +327 -0
- package/dist/core/events.d.ts +4 -0
- package/dist/core/events.js +5 -0
- package/dist/core/history.d.ts +14 -0
- package/dist/core/history.js +24 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/mode.d.ts +4 -0
- package/dist/core/mode.js +5 -0
- package/dist/core/range.d.ts +45 -0
- package/dist/core/range.js +86 -0
- package/dist/css/edith.scss +283 -0
- package/dist/edith-options.d.ts +17 -0
- package/dist/edith-options.js +56 -0
- package/dist/edith.d.ts +30 -0
- package/dist/edith.js +77 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ui/button.d.ts +25 -0
- package/dist/ui/button.js +166 -0
- package/dist/ui/editor.d.ts +37 -0
- package/dist/ui/editor.js +323 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/modal.d.ts +32 -0
- package/dist/ui/modal.js +145 -0
- package/package.json +12 -9
- package/src/core/edit.ts +94 -2
- package/src/core/events.ts +0 -144
- package/src/core/index.ts +0 -2
- package/src/core/range.ts +1 -1
- package/src/edith.ts +2 -1
- package/src/ui/button.ts +2 -1
- package/src/ui/editor.ts +12 -9
- package/src/ui/modal.ts +1 -1
- package/src/core/dom.ts +0 -584
- package/src/core/throttle.ts +0 -172
package/build/edith.css
ADDED
|
@@ -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* \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(/ /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(/ /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," ").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* \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,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,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
|
+
}
|