Plugin Directory

Changeset 3142172


Ignore:
Timestamp:
08/27/2024 09:42:05 AM (16 months ago)
Author:
Webaxones
Message:

Update to version 1.8.0 from GitHub

Location:
consistency
Files:
20 added
6 deleted
42 edited
1 copied

Legend:

Unmodified
Added
Removed
  • consistency/tags/1.8.0/build/index.asset.php

    r3135255 r3142172  
    1 <?php return array('dependencies' => array('react', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-i18n', 'wp-notices', 'wp-plugins'), 'version' => '8a8d2697904a0e5c0985');
     1<?php return array('dependencies' => array('react', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom-ready', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-rich-text'), 'version' => '525eca4ceb38a5bd7f50');
  • consistency/tags/1.8.0/build/index.js

    r3135255 r3142172  
    1 (()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})}};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);const t=window.wp.plugins,s=window.wp.data,n=window.wp.domReady;var o=e.n(n);const c=window.React,a=window.wp.i18n,r=window.wp.editPost,i=window.wp.components,l=()=>(0,c.createElement)(i.Icon,{icon:(0,c.createElement)("svg",{version:"1.1",id:"consistency-plugin",x:"0px",y:"0px",width:"24px",height:"24px",viewBox:"0 0 24 24",enableBackground:"new 0 0 24 24"},(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"4",y1:"20",x2:"7",y2:"20"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"14",y1:"20",x2:"21",y2:"20"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"6.9",y1:"15",x2:"13.8",y2:"15"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"10.2",y1:"6.3",x2:"16",y2:"20"}),(0,c.createElement)("polyline",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",points:"5,20 11,4 13,4 20,20 "}))}),u=window.wp.coreData,p=window.wp.notices,d=e=>{const{settingSlug:t,settingName:n,settingDescription:o}=e,{currentUser:r}=(0,s.useSelect)((e=>({currentUser:e(u.store).getCurrentUser()})),[]),l=r&&r.id,[d,g]=(0,u.useEntityProp)("root","user","meta",l),{saveEditedEntityRecord:y}=(0,s.useDispatch)(u.store),{createNotice:h}=(0,s.useDispatch)(p.store);return(0,c.createElement)(i.ToggleControl,{label:n,help:(0,c.createElement)("span",{dangerouslySetInnerHTML:o}),checked:d?.consistency_plugin_user_settings?.find((e=>e.slug===t))?.value||!1,onChange:e=>{let s=d?.consistency_plugin_user_settings.map((s=>t===s.slug?{...s,value:e}:s));s?.find((e=>e.slug===t))||s.push({slug:t,value:e}),g({...d,consistency_plugin_user_settings:s}),y("root","user",l,{...d,meta:s}),h((0,a.__)("info","consistency"),e?sprintf((0,a.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,a.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}})},g=()=>(0,c.createElement)(i.Panel,{className:"UserSettingPanel"},(0,c.createElement)(i.PanelHeader,null,(0,c.createElement)("strong",null,(0,a.__)("Settings for my account","consistency")),(0,c.createElement)("br",null)),(0,c.createElement)("div",{style:{padding:16}},(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(d,{settingSlug:"on_the_fly",settingName:(0,a.__)("On-the-fly autocorrect","consistency"),settingDescription:{__html:(0,a.__)("Enable/disable on-the-fly autocorrect","consistency")}})),(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(d,{settingSlug:"on_paste",settingName:(0,a.__)("On paste autocorrect","consistency"),settingDescription:{__html:(0,a.__)("Enable/disable autocorrect on paste","consistency")}})))),y=["regularToCurlyQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces","regularToGermanQuotes","regularToGermanBookStyleQuotes"],h=["core/paragraph","core/heading","core/quote","core/list-item","core/read-more"],{getBlock:m}=(0,s.select)("core/block-editor"),{updateBlock:b}=(0,s.dispatch)("core/block-editor"),_=[{slug:"quote",incompatibleWith:[]},{slug:"2hyphens",incompatibleWith:[]},{slug:"3hyphens",incompatibleWith:[]},{slug:"4hyphens",incompatibleWith:[]},{slug:"ordinalNumberSuffix",incompatibleWith:[]},{slug:"regularToCurlyQuotes",incompatibleWith:["regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanBookStyleQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotesWithoutSpaces",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes"]},{slug:"curlyToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes"]},{slug:"breakingSpace",incompatibleWith:["spaceBefore"]},{slug:"noSpaceBefore",incompatibleWith:["spaceBefore"]},{slug:"spaceBefore",incompatibleWith:["breakingSpace","noSpaceBefore"]},{slug:"noBreakingSpaceAfter",incompatibleWith:[]},{slug:"noNonBreakingSpaceAfter",incompatibleWith:[]},{slug:"capitalizeFirstSentenceLetter",incompatibleWith:[]},{slug:"etcThreeDots",incompatibleWith:[]},{slug:"etcTwoDots",incompatibleWith:[]},{slug:"etcEllipsis",incompatibleWith:[]},{slug:"ellipsis",incompatibleWith:[]},{slug:"symbolInACircle",incompatibleWith:[]},{slug:"symbolInSmallCapsAndSuperscriptStyle",incompatibleWith:[]},{slug:"fractions",incompatibleWith:[]},{slug:"percentages",incompatibleWith:[]}],{getBlockName:f,getBlockAttributes:k}=(0,s.select)("core/block-editor"),w=e=>{const t=T();return!(void 0===localesByRules||!localesByRules.hasOwnProperty(e))&&localesByRules[e].includes(t)},S=e=>{const t=_.find((t=>t.slug===e));return!!t&&t.incompatibleWith.some((e=>x()?.find((t=>t.slug===e))?.value))},{getEntityRecord:v}=(0,s.select)("core"),E=()=>{const e=v("root","site");return e?.consistency_plugin_localization_management||!0},x=()=>{const e=(()=>{const e=v("root","site");return e?.consistency_plugin_settings||[]})();return E()?e.filter((e=>w(e.slug))):e},T=()=>{const e=v("root","site");return e?.language||"en_US"},C=()=>{const e=T(),t=E()?(0,a.__)(` (${e} locale)`,"consistency"):(0,a.__)(" (all locales)","consistency");return(0,c.createElement)("span",{style:{fontWeight:"normal",fontStyle:"italic",fontSize:"smaller"}},t)},F=e=>{const{settingSlug:t,settingName:n,settingDescription:o}=e,{createNotice:r}=(0,s.useDispatch)(p.store);if(E()&&!w(t))return"";const[l,d]=(0,u.useEntityProp)("root","site","consistency_plugin_settings",void 0),{saveEditedEntityRecord:g}=(0,s.useDispatch)(u.store);return(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(i.ToggleControl,{label:n,help:(0,c.createElement)("span",{dangerouslySetInnerHTML:o}),checked:l?.find((e=>e.slug===t))?.value||!1,disabled:S(t),onChange:e=>{let s=l.map((s=>t===s.slug?{...s,value:e}:s));d(s),g("root","site",void 0,s),r((0,a.__)("info","consistency"),e?sprintf((0,a.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,a.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}}))},Q=[{slug:"quote",name:(0,a.__)("Straight Apostrophe","consistency"),description:(0,a.__)("Replace straight apostrophes with curly apostrophes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>'</code> <span style='font-size:20px'>→</span> <code>’</code></span>",mask:/\'/,replace:"’",nbMoved:0,category:"apostrophe"},{slug:"2hyphens",name:(0,a.__)("En Dash","consistency"),description:(0,a.__)("Replace two hyphens with an en dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>--</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">–</code></span>",mask:/(?:\-)\-/,replace:"–",nbMoved:-1,category:"dash"},{slug:"3hyphens",name:(0,a.__)("Em Dash","consistency"),description:(0,a.__)("Replace three hyphens with an em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>---</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">—</code></span>",mask:/(?:–|\-\-)\-/,replace:"—",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"4hyphens",name:(0,a.__)("Two-Em Dash","consistency"),description:(0,a.__)("Replace four hyphens with two-em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>----</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">⸺</code></span>",mask:/(?:—|–\-|\-\-\-)\-/,replace:"⸺",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"ordinalNumberSuffix",name:(0,a.__)("Ordinal Number Suffix","consistency"),description:(0,a.__)("Add the sup HTML tag to ordinal number suffixes","consistency")+"<span aria-hidden='true' style='display:block;'><code>1st</code> <span style='font-size:20px'>→</span> <code>1<sup>st</sup></code></span>",mask:/([10-9]{1,20})(th|nd|rd|e|er|res|d|ds|de|des)( | |\.|\,|\;)/,replace:"$1<sup>$2</sup>$3",nbMoved:0,category:"suffixe"},{slug:"regularToCurlyQuotes",name:(0,a.__)("Curly Quotes","consistency"),description:(0,a.__)("Replace straight quotes with curly quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>“ ”</code></span>",mask:/"/,replace:"“$1”",nbMoved:0,category:"quotation"},{slug:"regularToGermanQuotes",name:(0,a.__)("German Quotes","consistency"),description:(0,a.__)("Replace straight quotes with german quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>„ “</code></span>",mask:/"/,replace:"„$1“",nbMoved:0,category:"quotation"},{slug:"regularToGermanBookStyleQuotes",name:(0,a.__)("German Book-Style Quotes","consistency"),description:(0,a.__)("Replace straight quotes with german book-style quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>» «</code></span>",mask:/"/,replace:"»$1«",nbMoved:0,category:"quotation"},{slug:"regularToFrenchQuotes",name:(0,a.__)("French Quotes with Spaces","consistency"),description:(0,a.__)("Replace straight quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/"/,replace:"« $1 »",nbMoved:1,category:"quotation"},{slug:"regularToFrenchQuotesWithoutSpaces",name:(0,a.__)("French Quotes","consistency"),description:(0,a.__)("Replace straight quotes with french quotes without spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>« »</code></span>",mask:/"/,replace:"«$1»",nbMoved:0,category:"quotation"},{slug:"curlyToFrenchQuotes",name:(0,a.__)("Curly Quotes to French Quotes","consistency"),description:(0,a.__)("Replace curly quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>“ ”</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/“.*?”/,replace:e=>`« ${e.substring(1,e.length-1)} »`,nbMoved:0,category:"quotation"},{slug:"breakingSpace",name:(0,a.__)("Breaking Spaces","consistency"),description:sprintf((0,a.__)("Replace a breaking space followed by a character from this list:%1$s with a non-breaking space","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/ ([\?|\!|\:|\;|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:0,category:"space"},{slug:"noSpaceBefore",name:(0,a.__)("No Space Before","consistency"),description:sprintf((0,a.__)("Add a non-breaking space before a character from this list:%1$s having no space before","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/(?<! | |&nbsp;)([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:1,category:"space"},{slug:"spaceBefore",name:(0,a.__)("Space Before","consistency"),description:(0,a.__)("Remove any space preceding a character from this list:","consistency")+"<span style='display:block;'><code>? ! : ; %</code></span>",mask:/([ | ])(?=[\?|\!|\:|\;|\%])(.)/,replace:"$2",nbMoved:-1,category:"space"},{slug:"noBreakingSpaceAfter",name:(0,a.__)("No Breaking Space After","consistency"),description:sprintf((0,a.__)("Add a breaking space after a character from this list:%1$s when followed with another character","consistency"),"<br /><code>, … ) ]</code><br />"),mask:/([\,|…|\)|\]])(?! | |\.|\,|\d|$)(.)/,replace:"$1 $2",nbMoved:1,category:"space"},{slug:"noNonBreakingSpaceAfter",name:(0,a.__)("No Non-Breaking Space After","consistency"),description:(0,a.__)("Add a non-breaking space after open french quote having no space after","consistency"),mask:/(«)(?! | |&nbsp;)/,replace:"$1 ",nbMoved:0,category:"space"},{slug:"capitalizeFirstSentenceLetter",name:(0,a.__)("First Sentence Letter","consistency"),description:(0,a.__)("Capitalize the first letter of a sentence","consistency"),mask:/(^[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț])|(?<=[\.|\?|\!|…] )[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț]/,replace:e=>e.toUpperCase(),nbMoved:0,category:"case"},{slug:"etcThreeDots",name:(0,a.__)('Three Dots Following "etc"',"consistency"),description:(0,a.__)('Replace 3 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc...</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3})/i,replace:e=>e.substring(0,3)+".",nbMoved:-2,category:"ellipsis"},{slug:"etcTwoDots",name:(0,a.__)('Two Dots Following "etc"',"consistency"),description:(0,a.__)('Replace 2 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc..</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{2})/i,replace:e=>e.substring(0,2)+".",nbMoved:-1,category:"ellipsis"},{slug:"etcEllipsis",name:(0,a.__)('Ellipsis Following "etc"',"consistency"),description:(0,a.__)('Replace the ellipsis placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc…</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3}|…)/i,replace:e=>e.substring(0,3)+".",nbMoved:0,category:"ellipsis"},{slug:"ellipsis",name:(0,a.__)("Ellipsis","consistency"),description:(0,a.__)("Replaces 3 dots with ellipsis:","consistency")+"<span aria-hidden='true' style='display:block;'><code>...</code> <span style='font-size:20px'>→</span> <code>…</code></span>",mask:/\.{3}/,replace:"…",nbMoved:-2,category:"ellipsis"},{slug:"symbolInACircle",name:(0,a.__)("Symbol in a Circle","consistency"),description:(0,a.__)("Replaces 1 character placed in parentheses with a symbol","consistency")+"<span aria-hidden='true' style='display:block;'><code>(c) (p) (r)</code> <span style='font-size:20px'>→</span> <code>© ℗ ®</code></span>",mask:/(\([c|p|r])(\))/,replace:e=>{switch(e[1]){case"c":return"©";case"p":return"℗";case"r":return"®"}return" "},nbMoved:-2,category:"symbol"},{slug:"symbolInSmallCapsAndSuperscriptStyle",name:(0,a.__)("Symbol in Small Caps and Superscript Style","consistency"),description:(0,a.__)("Replaces 2-character abbreviations with a symbol in small caps and superscript style","consistency")+"<span aria-hidden='true' style='display:block;'><code>tm sm md mc</code> <span style='font-size:20px'>→</span> <code>™ ℠ 🅫 🅪</code></span>",mask:/(?<= | |\(|\[|\{|:|^)(tm|sm|md|mc)(?= | |\.|\,|\;|\:|\)|\]|\}|$)/,replace:e=>{switch(e){case"tm":return"™";case"sm":return"℠";case"md":return"🅫";case"mc":return"🅪";default:return" "}},nbMoved:-1,category:"symbol"},{slug:"fractions",name:(0,a.__)("Fractions","consistency"),description:(0,a.__)("Replaces fractions with fraction symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>1/2 3/5 1/9</code> <span style='font-size:20px'>→</span> <code>½ ⅗ ⅑</code></span>",mask:/[1-9]\/[1-9]/,replace:e=>{switch(e){case"1/4":return"¼";case"1/2":return"½";case"3/4":return"¾";case"1/3":return"⅓";case"2/3":return"⅔";case"1/5":return"⅕";case"2/5":return"⅖";case"3/5":return"⅗";case"4/5":return"⅘";case"1/6":return"⅙";case"5/6":return"⅚";case"1/8":return"⅛";case"3/8":return"⅜";case"5/8":return"⅝";case"7/8":return"⅞";case"1/7":return"⅐";case"1/9":return"⅑";default:return" "}},nbMoved:-2,category:"symbol"},{slug:"percentages",name:(0,a.__)("Percentages","consistency"),description:(0,a.__)("Replaces percentages with percentages symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>0/0 0/00 0/000</code> <span style='font-size:20px'>→</span> <code>% ‰ ‱</code></span>",mask:/(0\/0|0\/00|0\/000)(?= | |\.|\,|\;|\:|\)|\]|\})(.)/,replace:e=>{const t=e.substring(0,e.length-1),s=e.substring(e.length-1,e.length);switch(t){case"0/0":return"%"+s;case"0/00":return"‰"+s;case"0/000":return"‱"+s;default:return" "+s}},nbMoved:e=>-(e.substring(0,e.length-1).length-1),category:"symbol"}],B=[{slug:"apostrophe",label:(0,a.__)("Apostrophes","consistency"),description:(0,a.__)("Fixes related to apostrophes.","consistency")},{slug:"quotation",label:(0,a.__)("Quotation marks","consistency"),description:(0,a.__)("Fixes related to quotation marks.","consistency")},{slug:"dash",label:(0,a.__)("Dashes","consistency"),description:(0,a.__)("Fixes related to dashes.","consistency")},{slug:"suffixe",label:(0,a.__)("Suffixes","consistency"),description:(0,a.__)("Fixes related to suffixes.","consistency")},{slug:"space",label:(0,a.__)("Spaces","consistency"),description:(0,a.__)("Fixes related to spaces.","consistency")},{slug:"case",label:(0,a.__)("Case","consistency"),description:(0,a.__)("Fixes related to case.","consistency")},{slug:"ellipsis",label:(0,a.__)("Ellipsis","consistency"),description:(0,a.__)("Fixes related to ellipsis.","consistency")},{slug:"symbol",label:(0,a.__)("Symbols","consistency"),description:(0,a.__)("Fixes related to symbols.","consistency")}],R=()=>(0,c.createElement)(i.Panel,{className:"GlobalSettingPanel"},(0,c.createElement)(i.PanelHeader,null,(0,c.createElement)("strong",null,(0,a.__)("Global correction rules","consistency"),(0,c.createElement)(C,null))),[...B].map(((e,t)=>(0,c.createElement)(i.PanelBody,{key:t,title:(0,a.__)(e.label,"consistency"),initialOpen:!1},[...Q].filter((t=>t.category===e.slug)).map(((e,t)=>(0,c.createElement)(F,{key:t,settingSlug:e.slug,settingName:e.name,settingDescription:{__html:e.description}}))))))),{canUser:P}=(0,s.select)("core"),{getBlock:W,getBlocks:M,getBlockAttributes:$,getSelectionStart:A,isTyping:z}=(0,s.select)("core/block-editor"),{updateBlock:q,selectionChange:I,updateBlockAttributes:L}=(0,s.dispatch)("core/block-editor"),D=t=>{const{currentBlockId:s,isPasting:n,authorizedRuleSettings:o}=t;let c=Q.filter((e=>!0===o?.find((t=>t.slug===e.slug))?.value));const a=W(s);if(!(e=>{const t=f(e);return!!h.includes(t)})(s)||!(e=>{const t=k(e);return!(!t||!t.hasOwnProperty("content")||""===t.content)})(s))return;let r=$(s),i=!1;Object.entries(c).forEach((([t,o])=>{e.g.consistencyLoop++,(t=>{e.g.consistencyLoop>=100&&(t=>{const s=m(t);b(t,{...s,attributes:{...s.attributes,content:s.attributes.content.slice(-2)}}),e.g.consistency_loop=0,console.log("Consistency - a memory leak has occured during the fix of the following block:",s)})(t)})(s);let c,l=o.replace,u="",p="",d=0,g=r.content,h=(e=>e.replace(/<\b(code|pre|kbd)\b>.*?<\/\b(code|pre|kbd)\b>/gi,"").replace(/(<([^>]+)>)/gi,""))(g),_=!1;if(z()||(_=o.mask.test(h)),z()){c=A(a.name),d=c?.offset||0;const e=(e=>{const t=document.querySelector(`#block-${e}`);if(null===t)return;const s=document.getSelection(),n=s?.getRangeAt(0);if(!n.collapsed)return;const o=n.cloneRange(),c=document.createTextNode("\0");o.insertNode(c);let a=t?.innerHTML?.indexOf("\0");c.parentNode.removeChild(c),t.normalize();const r=(t?.innerHTML.match(/&nbsp;/g)||[]).length;return r>0&&(a=a-6*r+r),a})(s)||d,t=h.match(o.mask);if(null===t||0===t.length)return;const n=t[0].length||1;u=g.substring(0,e-n),p=g.substring(e-n,g.length),_=o.mask.test(h)&&o.mask.test(p)}if(!_)return;if((e=>!!y.includes(e.slug))(o)&&(l=((e,t,s)=>{const n=e.replace.charAt(0),o=e.replace.charAt(e.replace.length-1),c=e.replace.substring(1,e.replace.indexOf("$"))||"";let a="";0!==[...e.replace.matchAll(/[0-9]/g)].length&&(a=e.replace.substring([...e.replace.matchAll(/[0-9]/g)].pop().index+1,e.replace.length-1));const r=new RegExp(`${n}`,"g"),i=new RegExp(`${o}`,"g");return(t.match(r)||[]).length===(t.match(i)||[]).length?n+c:a+o})(o,g)),0!==d&&(g=u+p.replace(o.mask,l)),0===d&&(g=g.replace(o.mask,o.replace)),e.g.previousFixCanceled)return void(e.g.previousFixCanceled=!1);if(e.g.previousFixCanceled||(q(s,{...a,attributes:{...a.attributes,content:g}}),i=!0),0===d||n)return;const f="function"==typeof o.nbMoved?o.nbMoved(p):o.nbMoved;f<0&&I(s,c.attributeKey,d+f,d+f),f>0&&I(s,c.attributeKey,d+1+f,d+f),0===f&&I(s,c.attributeKey,d,d)})),e.g.consistencyLoop=0},{getSelectedBlockClientId:N,isTyping:G,getBlockAttributes:O}=(0,s.select)("core/block-editor");(0,t.registerPlugin)("consistency-custom-sidebar",{render:()=>{const e=P("create","users");return(0,c.createElement)(c.Fragment,null,(0,c.createElement)(r.PluginSidebar,{name:"consistency-custom-sidebar",title:(0,a.__)("Consistency","consistency"),icon:l},(0,c.createElement)(g,null),e&&(0,c.createElement)(R,null)),(0,c.createElement)(r.PluginSidebarMoreMenuItem,{target:"consistency-custom-sidebar"},(0,a.__)("Consistency Settings","consistency")))}}),o()((()=>{e.g.consistencyLoop=0,e.g.previousFixCanceledContent="",e.g.previousFixCanceled=!1,e.g.contentPasted=!1,e.g.isPasteEventAttached=!1,e.g.isEditorInIframe=null!==document.querySelector('iframe[name="editor-canvas"]'),document.querySelector("#editor")?.addEventListener("keydown",(t=>{90===t.keyCode&&(t.ctrlKey||t.metaKey)&&(e.g.previousFixCanceled=!0,t.preventDefault())})),(0,s.subscribe)((()=>{(()=>{if((()=>{const t=null!==document.querySelector('iframe[name="editor-canvas"]');e.g.isEditorInIframe!==t&&(e.g.isEditorInIframe=t,e.g.isPasteEventAttached=!1)})(),!e.g.isPasteEventAttached){if(e.g.isEditorInIframe){const t=document.querySelector('iframe[name="editor-canvas"]');t&&(t.onload=()=>{(t.contentDocument||t.contentWindow.document).addEventListener("paste",(t=>{e.g.contentPasted=!0,e.g.isPasteEventAttached=!0}))},"complete"===t.contentWindow.document.readyState&&t.onload())}e.g.isEditorInIframe||document.querySelector("#editor")?.addEventListener("paste",(t=>{e.g.contentPasted=!0,e.g.isPasteEventAttached=!0}))}})();const{onTheFly:t,onPaste:n}=(()=>{const e={onTheFly:!1,onPaste:!1},t=(0,s.select)(u.store).getCurrentUser(),n=v("root","user",t?.id||0,"consistency_plugin_user_settings"),o=n?.meta?.consistency_plugin_user_settings;return e.onTheFly=o?.find((e=>"on_the_fly"===e.slug))?.value||!1,e.onPaste=o?.find((e=>"on_paste"===e.slug))?.value||!1,e})();if(!t&&!n)return;const o=x();if(void 0===o)return;if(e.g.contentPasted&&n)return void(t=>{const{authorizedRuleSettings:s}=t;let n=Q.filter((e=>!0===s?.find((t=>t.slug===e.slug))?.value));const o=M(),c=o.flatMap((({innerBlocks:e,...t})=>e.map((e=>({...t,...e}))))),a=o.reduce(((e,t)=>{let s=t.attributes?.content;return h.includes(t.name)&&void 0!==s?(Object.entries(n).forEach((([e,t])=>{if(y.includes(t.slug)){const e=t.mask.toString().match(/(?<=\/).+?(?=\/)/g)[0],n=new RegExp(`(?<!=)${e}(?!>)([^${e}]*)(?<!=)${e}(?!>)`,"g");s=s.replaceAll(n,t.replace)}if(!y.includes(t.slug)){const e=t.mask.toString(),n=new RegExp(e.substring(1,e.length-1),"g");s=s.replaceAll(n,t.replace)}})),void 0!==s&&(e[t.clientId]={content:s}),e):e}),{});Object.keys(a).length>0&&e.g.contentPasted&&(e.g.contentPasted=!1,L(Object.keys(a),a,!0)),e.g.contentPasted=!1,c.forEach((e=>{if(!h.includes(e.name))return;const t=e.clientId;e?.clientId&&D({currentBlockId:t,theRegs:n,isPasting:!0})}))})({authorizedRuleSettings:o});const c=N();if(null===c||e.g.contentPasted||!t)return;const a=O(c);a.hasOwnProperty("content")&&e.g.previousFixCanceledContent===a.content||(e.g.previousFixCanceledContent=a.content,G()&&D({currentBlockId:c,isPasting:!1,authorizedRuleSettings:o}))}))}))})();
     1(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,s=window.wp.plugins,n=window.wp.domReady;var o=e.n(n);const c=window.wp.element,r=(0,c.createContext)(),a=({children:e})=>{const[s]=(0,c.useState)(null!==document.querySelector('iframe[name="editor-canvas"]')),[n,o]=(0,c.useState)(!1),[a,i]=(0,c.useState)(""),[l,u]=(0,c.useState)([]),p=(0,c.useRef)(!1);return(0,t.createElement)(r.Provider,{value:{isEditorInIframe:s,isPreviousFixCanceled:n,setPreviousFixCanceled:o,previousFixCanceledContent:a,setPreviousFixCanceledContent:i,blocksToBeProcessed:l,setBlocksToBeProcessed:u,isContentPastedRef:p}},e)},i=r,l=window.wp.i18n,u=window.wp.editor,p=window.wp.data,d=window.wp.components,g=()=>(0,t.createElement)(d.Icon,{icon:(0,t.createElement)("svg",{version:"1.1",id:"consistency-plugin",x:"0px",y:"0px",width:"24px",height:"24px",viewBox:"0 0 24 24",enableBackground:"new 0 0 24 24"},(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"4",y1:"20",x2:"7",y2:"20"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"14",y1:"20",x2:"21",y2:"20"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"6.9",y1:"15",x2:"13.8",y2:"15"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"10.2",y1:"6.3",x2:"16",y2:"20"}),(0,t.createElement)("polyline",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",points:"5,20 11,4 13,4 20,20 "}))}),y=window.wp.coreData,m=window.wp.notices,h=e=>{const{settingSlug:s,settingName:n,settingDescription:o}=e,{currentUser:c}=(0,p.useSelect)((e=>({currentUser:e(y.store).getCurrentUser()})),[]),r=c&&c.id,[a,i]=(0,y.useEntityProp)("root","user","meta",r),{saveEditedEntityRecord:u}=(0,p.useDispatch)(y.store),{createNotice:g}=(0,p.useDispatch)(m.store);return(0,t.createElement)(d.ToggleControl,{label:n,help:(0,t.createElement)("span",{dangerouslySetInnerHTML:o}),checked:a?.consistency_plugin_user_settings?.find((e=>e.slug===s))?.value||!1,onChange:e=>{let t=a?.consistency_plugin_user_settings.map((t=>s===t.slug?{...t,value:e}:t));t?.find((e=>e.slug===s))||t.push({slug:s,value:e}),i({...a,consistency_plugin_user_settings:t}),u("root","user",r,{...a,meta:t}),g((0,l.__)("info","consistency"),e?sprintf((0,l.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,l.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}})},b=()=>(0,t.createElement)(d.Panel,{className:"UserSettingPanel"},(0,t.createElement)(d.PanelHeader,null,(0,t.createElement)("strong",null,(0,l.__)("Settings for my account","consistency")),(0,t.createElement)("br",null)),(0,t.createElement)("div",{style:{padding:16}},(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(h,{settingSlug:"on_the_fly",settingName:(0,l.__)("On-the-fly autocorrect","consistency"),settingDescription:{__html:(0,l.__)("Enable/disable on-the-fly autocorrect","consistency")}})),(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(h,{settingSlug:"on_paste",settingName:(0,l.__)("On paste autocorrect","consistency"),settingDescription:{__html:(0,l.__)("Enable/disable autocorrect on paste","consistency")}})))),_=window.wp.richText,f=e=>"string"==typeof e||e instanceof String,k=e=>"object"==typeof e&&e instanceof _.RichTextData,{getEntityRecord:v}=(0,p.select)("core"),{updateBlock:w}=(0,p.dispatch)("core/block-editor"),S=()=>{const e=v("root","site");return e?.consistency_plugin_localization_management||!0},x=()=>{const e=v("root","site");return e?.language||"en_US"},C=()=>{const e=x(),s=S()?(0,l.__)(` (${e} locale)`,"consistency"):(0,l.__)(" (all locales)","consistency");return(0,t.createElement)("span",{style:{fontWeight:"normal",fontStyle:"italic",fontSize:"smaller"}},s)},E=["regularToCurlyQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces","regularToGermanQuotes","regularToGermanBookStyleQuotes"],T=[{slug:"quote",incompatibleWith:[]},{slug:"2hyphens",incompatibleWith:[]},{slug:"3hyphens",incompatibleWith:[]},{slug:"4hyphens",incompatibleWith:[]},{slug:"ordinalNumberSuffix",incompatibleWith:[]},{slug:"regularToCurlyQuotes",incompatibleWith:["regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanBookStyleQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotesWithoutSpaces",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes"]},{slug:"curlyToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes"]},{slug:"breakingSpace",incompatibleWith:["spaceBefore"]},{slug:"noSpaceBefore",incompatibleWith:["spaceBefore"]},{slug:"spaceBefore",incompatibleWith:["breakingSpace","noSpaceBefore"]},{slug:"noBreakingSpaceAfter",incompatibleWith:[]},{slug:"noNonBreakingSpaceAfter",incompatibleWith:[]},{slug:"capitalizeFirstSentenceLetter",incompatibleWith:[]},{slug:"etcThreeDots",incompatibleWith:[]},{slug:"etcTwoDots",incompatibleWith:[]},{slug:"etcEllipsis",incompatibleWith:[]},{slug:"ellipsis",incompatibleWith:[]},{slug:"symbolInACircle",incompatibleWith:[]},{slug:"symbolInSmallCapsAndSuperscriptStyle",incompatibleWith:[]},{slug:"fractions",incompatibleWith:[]},{slug:"percentages",incompatibleWith:[]}],B=[{slug:"quote",name:(0,l.__)("Straight Apostrophe","consistency"),description:(0,l.__)("Replace straight apostrophes with curly apostrophes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>'</code> <span style='font-size:20px'>→</span> <code>’</code></span>",mask:/\'/,replace:"’",nbMoved:0,category:"apostrophe"},{slug:"2hyphens",name:(0,l.__)("En Dash","consistency"),description:(0,l.__)("Replace two hyphens with an en dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>--</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">–</code></span>",mask:/(?:\-)\-/,replace:"–",nbMoved:-1,category:"dash"},{slug:"3hyphens",name:(0,l.__)("Em Dash","consistency"),description:(0,l.__)("Replace three hyphens with an em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>---</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">—</code></span>",mask:/(?:–|\-\-)\-/,replace:"—",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"4hyphens",name:(0,l.__)("Two-Em Dash","consistency"),description:(0,l.__)("Replace four hyphens with two-em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>----</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">⸺</code></span>",mask:/(?:—|–\-|\-\-\-)\-/,replace:"⸺",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"ordinalNumberSuffix",name:(0,l.__)("Ordinal Number Suffix","consistency"),description:(0,l.__)("Add the sup HTML tag to ordinal number suffixes","consistency")+"<span aria-hidden='true' style='display:block;'><code>1st</code> <span style='font-size:20px'>→</span> <code>1<sup>st</sup></code></span>",mask:/([10-9]{1,20})(th|nd|rd|e|er|res|d|ds|de|des)( | |\.|\,|\;)/,replace:"$1<sup>$2</sup>$3",nbMoved:0,category:"suffixe"},{slug:"regularToCurlyQuotes",name:(0,l.__)("Curly Quotes","consistency"),description:(0,l.__)("Replace straight quotes with curly quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>“ ”</code></span>",mask:/"/,replace:"“$1”",nbMoved:0,category:"quotation"},{slug:"regularToGermanQuotes",name:(0,l.__)("German Quotes","consistency"),description:(0,l.__)("Replace straight quotes with german quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>„ “</code></span>",mask:/"/,replace:"„$1“",nbMoved:0,category:"quotation"},{slug:"regularToGermanBookStyleQuotes",name:(0,l.__)("German Book-Style Quotes","consistency"),description:(0,l.__)("Replace straight quotes with german book-style quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>» «</code></span>",mask:/"/,replace:"»$1«",nbMoved:0,category:"quotation"},{slug:"regularToFrenchQuotes",name:(0,l.__)("French Quotes with Spaces","consistency"),description:(0,l.__)("Replace straight quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/"/,replace:"« $1 »",nbMoved:1,category:"quotation"},{slug:"regularToFrenchQuotesWithoutSpaces",name:(0,l.__)("French Quotes","consistency"),description:(0,l.__)("Replace straight quotes with french quotes without spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>« »</code></span>",mask:/"/,replace:"«$1»",nbMoved:0,category:"quotation"},{slug:"curlyToFrenchQuotes",name:(0,l.__)("Curly Quotes to French Quotes","consistency"),description:(0,l.__)("Replace curly quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>“ ”</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/“.*?”/,replace:e=>`« ${e.substring(1,e.length-1)} »`,nbMoved:0,category:"quotation"},{slug:"breakingSpace",name:(0,l.__)("Breaking Spaces","consistency"),description:sprintf((0,l.__)("Replace a breaking space followed by a character from this list:%1$s with a non-breaking space","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/ ([\?|\!|\:|\;|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:0,category:"space"},{slug:"noSpaceBefore",name:(0,l.__)("No Space Before","consistency"),description:sprintf((0,l.__)("Add a non-breaking space before a character from this list:%1$s having no space before","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/(?<! | |&nbsp;)([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:1,category:"space"},{slug:"spaceBefore",name:(0,l.__)("Space Before","consistency"),description:(0,l.__)("Remove any space preceding a character from this list:","consistency")+"<span style='display:block;'><code>? ! : ; %</code></span>",mask:/([ | ])(?=[\?|\!|\:|\;|\%])(.)/,replace:"$2",nbMoved:-1,category:"space"},{slug:"noBreakingSpaceAfter",name:(0,l.__)("No Breaking Space After","consistency"),description:sprintf((0,l.__)("Add a breaking space after a character from this list:%1$s when followed with another character","consistency"),"<br /><code>, … ) ]</code><br />"),mask:/([\,|…|\)|\]])(?! | |\.|\,|\d|$)(.)/,replace:"$1 $2",nbMoved:1,category:"space"},{slug:"noNonBreakingSpaceAfter",name:(0,l.__)("No Non-Breaking Space After","consistency"),description:(0,l.__)("Add a non-breaking space after open french quote having no space after","consistency"),mask:/(«)(?! | |&nbsp;)/,replace:"$1 ",nbMoved:0,category:"space"},{slug:"capitalizeFirstSentenceLetter",name:(0,l.__)("First Sentence Letter","consistency"),description:(0,l.__)("Capitalize the first letter of a sentence","consistency"),mask:/(^[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț])|(?<=[\.|\?|\!|…] )[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț]/,replace:e=>e.toUpperCase(),nbMoved:0,category:"case"},{slug:"etcThreeDots",name:(0,l.__)('Three Dots Following "etc"',"consistency"),description:(0,l.__)('Replace 3 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc...</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3})/i,replace:e=>e.substring(0,3)+".",nbMoved:-2,category:"ellipsis"},{slug:"etcTwoDots",name:(0,l.__)('Two Dots Following "etc"',"consistency"),description:(0,l.__)('Replace 2 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc..</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{2})/i,replace:e=>e.substring(0,2)+".",nbMoved:-1,category:"ellipsis"},{slug:"etcEllipsis",name:(0,l.__)('Ellipsis Following "etc"',"consistency"),description:(0,l.__)('Replace the ellipsis placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc…</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3}|…)/i,replace:e=>e.substring(0,3)+".",nbMoved:0,category:"ellipsis"},{slug:"ellipsis",name:(0,l.__)("Ellipsis","consistency"),description:(0,l.__)("Replaces 3 dots with ellipsis:","consistency")+"<span aria-hidden='true' style='display:block;'><code>...</code> <span style='font-size:20px'>→</span> <code>…</code></span>",mask:/\.{3}/,replace:"…",nbMoved:-2,category:"ellipsis"},{slug:"symbolInACircle",name:(0,l.__)("Symbol in a Circle","consistency"),description:(0,l.__)("Replaces 1 character placed in parentheses with a symbol","consistency")+"<span aria-hidden='true' style='display:block;'><code>(c) (p) (r)</code> <span style='font-size:20px'>→</span> <code>© ℗ ®</code></span>",mask:/(\([c|p|r])(\))/,replace:e=>{switch(e[1]){case"c":return"©";case"p":return"℗";case"r":return"®"}return" "},nbMoved:-2,category:"symbol"},{slug:"symbolInSmallCapsAndSuperscriptStyle",name:(0,l.__)("Symbol in Small Caps and Superscript Style","consistency"),description:(0,l.__)("Replaces 2-character abbreviations with a symbol in small caps and superscript style","consistency")+"<span aria-hidden='true' style='display:block;'><code>tm sm md mc</code> <span style='font-size:20px'>→</span> <code>™ ℠ 🅫 🅪</code></span>",mask:/(?<= | |\(|\[|\{|:|^)(tm|sm|md|mc)(?= | |\.|\,|\;|\:|\)|\]|\}|$)/,replace:e=>{switch(e){case"tm":return"™";case"sm":return"℠";case"md":return"🅫";case"mc":return"🅪";default:return" "}},nbMoved:-1,category:"symbol"},{slug:"fractions",name:(0,l.__)("Fractions","consistency"),description:(0,l.__)("Replaces fractions with fraction symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>1/2 3/5 1/9</code> <span style='font-size:20px'>→</span> <code>½ ⅗ ⅑</code></span>",mask:/[1-9]\/[1-9]/,replace:e=>{switch(e){case"1/4":return"¼";case"1/2":return"½";case"3/4":return"¾";case"1/3":return"⅓";case"2/3":return"⅔";case"1/5":return"⅕";case"2/5":return"⅖";case"3/5":return"⅗";case"4/5":return"⅘";case"1/6":return"⅙";case"5/6":return"⅚";case"1/8":return"⅛";case"3/8":return"⅜";case"5/8":return"⅝";case"7/8":return"⅞";case"1/7":return"⅐";case"1/9":return"⅑";default:return" "}},nbMoved:-2,category:"symbol"},{slug:"percentages",name:(0,l.__)("Percentages","consistency"),description:(0,l.__)("Replaces percentages with percentages symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>0/0 0/00 0/000</code> <span style='font-size:20px'>→</span> <code>% ‰ ‱</code></span>",mask:/(0\/0|0\/00|0\/000)(?= | |\.|\,|\;|\:|\)|\]|\})(.)/,replace:e=>{const t=e.substring(0,e.length-1),s=e.substring(e.length-1,e.length);switch(t){case"0/0":return"%"+s;case"0/00":return"‰"+s;case"0/000":return"‱"+s;default:return" "+s}},nbMoved:e=>-(e.substring(0,e.length-1).length-1),category:"symbol"}],P=()=>{const e=(()=>{const e=v("root","site");return e?.consistency_plugin_settings||[]})();return S()?e.filter((e=>W(e.slug))):e},F=()=>B.filter((e=>!0===P()?.find((t=>t.slug===e.slug))?.value)),{getBlockName:R,getBlockAttributes:Q}=(0,p.select)("core/block-editor"),W=e=>{const t=x();return!(void 0===localesByRules||!localesByRules.hasOwnProperty(e))&&localesByRules[e].includes(t)},M=e=>{const t=T.find((t=>t.slug===e));return!!t&&t.incompatibleWith.some((e=>P()?.find((t=>t.slug===e))?.value))},$=e=>{const{settingSlug:s,settingName:n,settingDescription:o}=e,{createNotice:c}=(0,p.useDispatch)(m.store);if(S()&&!W(s))return"";const[r,a]=(0,y.useEntityProp)("root","site","consistency_plugin_settings",void 0),{saveEditedEntityRecord:i}=(0,p.useDispatch)(y.store);return(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(d.ToggleControl,{label:n,help:(0,t.createElement)("span",{dangerouslySetInnerHTML:o}),checked:r?.find((e=>e.slug===s))?.value||!1,disabled:M(s),onChange:e=>{let t=r.map((t=>s===t.slug?{...t,value:e}:t));a(t),i("root","site",void 0,t),c((0,l.__)("info","consistency"),e?sprintf((0,l.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,l.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}}))},q=[{slug:"apostrophe",label:(0,l.__)("Apostrophes","consistency"),description:(0,l.__)("Fixes related to apostrophes.","consistency")},{slug:"quotation",label:(0,l.__)("Quotation marks","consistency"),description:(0,l.__)("Fixes related to quotation marks.","consistency")},{slug:"dash",label:(0,l.__)("Dashes","consistency"),description:(0,l.__)("Fixes related to dashes.","consistency")},{slug:"suffixe",label:(0,l.__)("Suffixes","consistency"),description:(0,l.__)("Fixes related to suffixes.","consistency")},{slug:"space",label:(0,l.__)("Spaces","consistency"),description:(0,l.__)("Fixes related to spaces.","consistency")},{slug:"case",label:(0,l.__)("Case","consistency"),description:(0,l.__)("Fixes related to case.","consistency")},{slug:"ellipsis",label:(0,l.__)("Ellipsis","consistency"),description:(0,l.__)("Fixes related to ellipsis.","consistency")},{slug:"symbol",label:(0,l.__)("Symbols","consistency"),description:(0,l.__)("Fixes related to symbols.","consistency")}],z=()=>(0,t.createElement)(d.Panel,{className:"GlobalSettingPanel"},(0,t.createElement)(d.PanelHeader,null,(0,t.createElement)("strong",null,(0,l.__)("Global correction rules","consistency"),(0,t.createElement)(C,null))),[...q].map(((e,s)=>(0,t.createElement)(d.PanelBody,{key:s,title:(0,l.__)(e.label,"consistency"),initialOpen:!1},[...B].filter((t=>t.category===e.slug)).map(((e,s)=>(0,t.createElement)($,{key:s,settingSlug:e.slug,name:e.name,settingDescription:{__html:e.description}}))))))),{canUser:A}=(0,p.select)("core"),I=()=>{const e=A("create","users");return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(u.PluginSidebar,{name:"consistency-custom-sidebar",title:(0,l.__)("Consistency","consistency"),icon:g},(0,t.createElement)(b,null),e&&(0,t.createElement)(z,null)),(0,t.createElement)(u.PluginSidebarMoreMenuItem,{target:"consistency-custom-sidebar"},(0,l.__)("Consistency Settings","consistency")))},{getBlock:D,getBlocks:L,getBlockAttributes:N,getSelectionStart:O,isTyping:G,getBlockSelectionStart:j}=(0,p.select)("core/block-editor"),{selectionChange:U,updateBlockAttributes:H}=(0,p.dispatch)("core/block-editor"),K=e=>{const{currentBlockId:t,isPasting:s,isPreviousFixCanceled:n,setPreviousFixCanceled:o,blocksToBeProcessed:c}=e;if(!(e=>{const{currentBlockId:t,blocksToBeProcessed:s}=e,n=R(t);return!!s.includes(n)})({currentBlockId:t,blocksToBeProcessed:c})||!(e=>{const t=Q(e);return!(!t||!t.hasOwnProperty("content")||!f(t.content)&&!k(t.content))})(t))return;let r=F();const a=D(t);let i=N(t),l=!1;Object.entries(r).forEach((([e,c])=>{if(l)return;let r,u=c.replace,p="",d="",g=0,y=0,m="object"==typeof i.content?i.content.text:i.content,h=(e=>e.replace(/<\b(code|pre|kbd)\b>.*?<\/\b(code|pre|kbd)\b>/gi,"").replace(/(<([^>]+)>)/gi,""))(m),b=!1;if(G()||(b=c.mask.test(h)),G()){r=O(),g=r?.offset||document.getSelection()?.anchorOffset||0,y=(e=>{const t=document.querySelector(`#block-${e}`);if(null===t)return;const s=t.querySelector('[contenteditable="true"]')||t,n=document.getSelection(),o=n?.getRangeAt(0);if(!o||!o.collapsed)return;const c=o.cloneRange(),r=document.createTextNode("\0");c.insertNode(r);const a=s.textContent||"";let i=a.indexOf("\0");r.parentNode.removeChild(r),s.normalize();const l=(s?.innerHTML.match(/&nbsp;/g)||[]).length;return l>0&&(i=a.replace(/&nbsp;/g," ").indexOf("\0"),i=i-6*l+l),i})(t)||g;const e=h.match(c.mask);if(null===e||0===e.length)return;const s=e[0].length||1;p=h.substring(0,y-s),d=h.substring(y-s,h.length),b=c.mask.test(h)&&c.mask.test(d)}if(!b)return;if((e=>!!E.includes(e.slug))(c)&&(u=((e,t,s)=>{const n=e.replace.charAt(0),o=e.replace.charAt(e.replace.length-1),c=e.replace.substring(1,e.replace.indexOf("$"))||"";let r="";0!==[...e.replace.matchAll(/[0-9]/g)].length&&(r=e.replace.substring([...e.replace.matchAll(/[0-9]/g)].pop().index+1,e.replace.length-1));const a=new RegExp(`${n}`,"g"),i=new RegExp(`${o}`,"g");return(t.match(a)||[]).length===(t.match(i)||[]).length?n+c:r+o})(c,m)),0!==y&&(m=p+d.replace(c.mask,u)),0===y&&(m=m.replace(c.mask,c.replace)),n)return void o(!1);if(n||(l=(e=>{const{block:t,currentBlockId:s,blockAttributes:n,blockContent:o}=e;let c;if(k(n.content)){const e=(0,_.create)({...n.content,text:o});c=new _.RichTextData(e)}return f(n.content)&&(c=o),void 0!==c&&(w(s,{...t,attributes:{...n,content:c}}),!0)})({block:a,currentBlockId:t,blockAttributes:i,blockContent:m})),0===g||s)return;const v="function"==typeof c.nbMoved?c.nbMoved(d):c.nbMoved||0;let S=r.hasOwnProperty("attributeKey")?r.attributeKey:"content";v<0&&U(t,S,g+v,g+v),v>0&&U(t,S,g+1+v,g+v),0===v&&U(t,S,g,g)}))},{getSelectedBlockClientId:J,isTyping:V,getBlockAttributes:X}=(0,p.select)("core/block-editor"),Y=window.wp.blocks,Z=()=>((()=>{const{isEditorInIframe:e,isContentPastedRef:t}=(0,c.useContext)(i),s=(0,c.useRef)(!1);(0,c.useEffect)((()=>{if(s.current)return;const n=e=>{t.current=!0},o=e=>(e.addEventListener("paste",n),()=>{e.removeEventListener("paste",n)}),c=e?document.querySelector('iframe[name="editor-canvas"]'):document.querySelector("#editor");if(!c)return;const r=o(c);return e&&"complete"===c.contentWindow.document.readyState&&o(c),s.current=!0,r}),[e])})(),(()=>{const{isEditorInIframe:e,setPreviousFixCanceled:t}=(0,c.useContext)(i),s=(0,c.useRef)(!1);(0,c.useEffect)((()=>{if(s.current)return;const n=e=>{90===e.keyCode&&(e.ctrlKey||e.metaKey)&&t(!0)},o=e=>(e.addEventListener("keydown",n),()=>{e.removeEventListener("keydown",n)}),c=e?document.querySelector('iframe[name="editor-canvas"]'):document.querySelector("#editor");if(!c)return;const r=o(c);return e&&"complete"===c.contentWindow.document.readyState&&o(c),s.current=!0,r}),[t])})(),(()=>{const{setBlocksToBeProcessed:e}=(0,c.useContext)(i);(0,c.useEffect)((()=>{let t=(0,Y.getBlockTypes)().filter((e=>e.attributes&&e.attributes.content&&("string"===e.attributes.content.type||"rich-text"===e.attributes.content.type)));t=t.filter((e=>!e.name.startsWith("kadence/")));const s=["core/html","core/freeform"];t=t.filter((e=>!s.includes(e.name))),console.log("Consistency - Processed blocks:",t),e(t.map((e=>e.name)))}),[])})(),(()=>{const{isPreviousFixCanceled:e,setPreviousFixCanceled:t,previousFixCanceledContent:s,setPreviousFixCanceledContent:n,blocksToBeProcessed:o,isContentPastedRef:r}=(0,c.useContext)(i);void 0===r.current&&(r.current=!1),(0,c.useEffect)((()=>{const c=(0,p.subscribe)((()=>{const{onTheFly:c,onPaste:a}=(()=>{const e={onTheFly:!1,onPaste:!1},t=(0,p.select)(y.store).getCurrentUser(),s=v("root","user",t?.id||0,"consistency_plugin_user_settings"),n=s?.meta?.consistency_plugin_user_settings;return e.onTheFly=n?.find((e=>"on_the_fly"===e.slug))?.value||!1,e.onPaste=n?.find((e=>"on_paste"===e.slug))?.value||!1,e})();if(!c&&!a)return;const i=P();if(void 0===i)return;if(a&&!0===r.current)return r.current=!1,void(e=>{const{isPreviousFixCanceled:t,setPreviousFixCanceled:s,localizedRuleSettings:n,blocksToBeProcessed:o}=e;let c=F();const r=L(),a=r.flatMap((({innerBlocks:e,...t})=>e.map((e=>({...t,...e}))))),i=r.reduce(((e,t)=>{let s=t.attributes?.content;return o.includes(t.name)&&void 0!==s?(Object.entries(c).forEach((([e,t])=>{if(E.includes(t.slug)){const e=t.mask.toString().match(/(?<=\/).+?(?=\/)/g)[0],n=new RegExp(`(?<!=)${e}(?!>)([^${e}]*)(?<!=)${e}(?!>)`,"g");s=s.replaceAll(n,t.replace)}if(!E.includes(t.slug)){const e=t.mask.toString(),n=new RegExp(e.substring(1,e.length-1),"g");s=s.replaceAll(n,t.replace)}})),void 0!==s&&(e[t.clientId]={content:s}),e):e}),{});Object.keys(i).length>0&&H(Object.keys(i),i,!0),a.forEach((e=>{if(!o.includes(e.name))return;const n=e.clientId;e?.clientId&&K({currentBlockId:n,isPasting:!0,isPreviousFixCanceled:t,setPreviousFixCanceled:s,blocksToBeProcessed:o})}))})({isPreviousFixCanceled:e,setPreviousFixCanceled:t,localizedRuleSettings:i,blocksToBeProcessed:o});const l=J();if(null===l||!c)return;const u=X(l);u&&(u.hasOwnProperty("content")&&s===u.content||(n(u.content),V()&&K({currentBlockId:l,isPasting:!1,isPreviousFixCanceled:e,setPreviousFixCanceled:t,blocksToBeProcessed:o})))}));return()=>c()}),[e,t,s,n,o,r])})(),(0,t.createElement)(I,null)),ee=()=>(0,t.createElement)(a,null,(0,t.createElement)(Z,null));o()((()=>{(0,s.registerPlugin)("consistency-custom-sidebar",{render:ee})}))})();
  • consistency/tags/1.8.0/consistency.php

    r3135255 r3142172  
    44 * Plugin URI:        https://www.webaxones.com
    55 * Description:       Fixes typographic and punctuation consistency
    6  * Version:           1.7.1
     6 * Version:           1.8.0
    77 * Requires at least: 6.1
    88 * Requires PHP:      7.4
  • consistency/tags/1.8.0/includes/Plugin.php

    r3135255 r3142172  
    3333    protected static function setConstants(): void
    3434    {
    35         defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.7.1' );
     35        defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.8.0' );
    3636        defined( __NAMESPACE__ . '\PLUGIN_URL' ) || define( __NAMESPACE__ . '\PLUGIN_URL', plugin_dir_url( __DIR__ ) );
    3737        defined( __NAMESPACE__ . '\PLUGIN_PATH' ) || define( __NAMESPACE__ . '\PLUGIN_PATH', plugin_dir_path( __DIR__ ) );
  • consistency/tags/1.8.0/package.json

    r3135255 r3142172  
    11{
    22    "name": "consistency",
    3     "version": "1.7.1",
     3    "version": "1.8.0",
    44    "description": "",
    55    "main": "index.js",
  • consistency/tags/1.8.0/readme.txt

    r3135255 r3142172  
    44Requires at least: 6.1
    55Tested up to: 6.6
    6 Stable tag: 1.7.1
     6Stable tag: 1.8.0
    77Requires PHP: 7.4
    88License: GPL-3.0-or-later
     
    7070== Changelog ==
    7171
     72= 1.8.0 =
     73* Update: code refactoring (replace global variables with global context, some functions with custom hooks, and allow to process more blocks)
     74
    7275= 1.7.1 =
    7376* Fix: Ensures in all cases to only use rules authorized by local parameters
  • consistency/tags/1.8.0/src/app/checks.js

    r3135255 r3142172  
    11/**
    2  * Summary: Various checks functions.
     2 * @summary: Various checks functions.
    33 *
    4  * @description This file contains functions for various checks.
     4 * This file contains functions for various checks.
    55 * @author Loïc Antignac.
    66 */
     
    1414 * External dependencies
    1515 */
    16 import { getCurrentLocale } from './data'
    17 import { regsWithPair } from '../config/regsWithPair'
    18 import { processedBlocks } from '../config/processedBlocks'
    19 import { aMemoryLeakHasOccured } from './helpers'
    20 import { ruleIncompatibilities } from '../config/ruleIncompatibilities'
    21 import { getAuthorizedRuleSettings } from './data'
     16import { fetchCurrentLocale } from './data'
     17import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs'
     18import { incompatibilities } from '../config/incompatibilities'
     19import { getLocalizedRuleSettings } from './helpers'
     20import { isString, isRichTextData } from './utils'
    2221
    2322const { getBlockName, getBlockAttributes } = select( 'core/block-editor' )
    24 
    2523
    2624/**
     
    3230export const isUsedByLocale = settingSlug => {
    3331
    34     const currentLocale = getCurrentLocale()
     32    const currentLocale = fetchCurrentLocale()
    3533    if ( localesByRules !== undefined && localesByRules.hasOwnProperty( settingSlug ) ) {
    3634        return localesByRules[settingSlug].includes( currentLocale )
     
    4139
    4240/**
    43  * Checks if the current block is one of those to be checked or not
     41 * Checks if the current block is one of those to be processed or not
    4442 *
    45  * @param {string} currentBlockId currentBlockId current active block ID
    46  * @return {boolean} Should the block be checked?
     43 * @param {Object} props - The props object containing the necessary data.
     44 * @param {string} props.currentBlockId - The ID of the current block.
     45 * @param {Array} props.blocksToBeProcessed - The blocks to be processed.
     46 * @return {boolean} Should the block be processed?
    4747 */
    48 export const blockShouldBeChecked = currentBlockId => {
     48export const shouldProcessBlock = props => {
     49
     50    const { currentBlockId, blocksToBeProcessed } = props
    4951
    5052    const blockName = getBlockName( currentBlockId )
    51     if ( processedBlocks.includes( blockName ) ) {
     53   
     54    if ( blocksToBeProcessed.includes( blockName ) ) {
    5255        return true
    5356    }
     
    5760
    5861/**
    59  * Checks if the current block can technically be verified or not
     62 * Checks if the current block can technically be processed or not
    6063 *
    6164 * @param {string} currentBlockId currentBlockId current active block ID
    62  * @return {boolean} Can the block be checked?
     65 * @return {boolean} Can the block be processed?
    6366 */
    64 export const blockCanTechnicallyBeChecked = currentBlockId => {
     67export const canProcessBlock = currentBlockId => {
    6568
    6669    const blockAttributes = getBlockAttributes( currentBlockId )
    67     if ( blockAttributes && blockAttributes.hasOwnProperty( 'content' ) && '' !== blockAttributes.content ) {
     70
     71    if ( ! blockAttributes || ! blockAttributes.hasOwnProperty( 'content' ) ) return false
     72
     73    if ( isString( blockAttributes.content ) || isRichTextData( blockAttributes.content ) ) {
    6874        return true
    6975    }
     76   
    7077    return false
    7178
     
    8087export const regDealWithPair = reg => {
    8188
    82     if ( regsWithPair.includes( reg.slug ) ) {
     89    if ( pairedCharacterSlugs.includes( reg.slug ) ) {
    8390        return true
    8491    }
    8592    return false
    86 
    87 }
    88 
    89 /**
    90  * Checks if a memory leak has occurred during the fix of one block if the consistency loop count exceeds 150 and stops processing.
    91  * @param {string} currentBlockId - The ID of the current block.
    92  */
    93 export const checkIfAMemoryLeakHasOccuredAndStopProcessing = currentBlockId => {
    94    
    95     if ( global.consistencyLoop >= 100 ) {
    96         aMemoryLeakHasOccured( currentBlockId )
    97     }
    9893
    9994}
     
    107102export const checkRuleCompatibility = currentRule => {
    108103
    109     // Get the current rule from the ruleIncompatibilities array
    110     const rule = ruleIncompatibilities.find( rule => rule.slug === currentRule )
     104    // Get the current rule from the incompatibilities array
     105    const rule = incompatibilities.find( rule => rule.slug === currentRule )
    111106    if ( ! rule ) return false
    112107
     
    114109    return rule.incompatibleWith.some( incompatibleRule => {
    115110        // Return the state of the incompatible rule
    116         return getAuthorizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value
     111        return getLocalizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value
    117112    } )
    118113
  • consistency/tags/1.8.0/src/app/data.js

    r3135255 r3142172  
    11/**
    2  * Summary: Data retrieval.
     2 * @summary: Data retrieval.
    33 *
    4  * @description This file contains functions that retrieve data from database.
     4 * This file contains functions that retrieve data from database.
    55 * @author Loïc Antignac.
    66 */
     
    1010 */
    1111import { store as coreStore } from '@wordpress/core-data'
    12 import { select } from '@wordpress/data'
    13 
    14 const { getEntityRecord } = select( 'core' )
     12import { select, dispatch } from '@wordpress/data'
     13import { RichTextData, create } from '@wordpress/rich-text'
    1514
    1615/**
    1716 * External dependencies
    1817 */
    19 import { isUsedByLocale } from './checks'
     18import { isString, isRichTextData } from './utils'
     19
     20const { getEntityRecord } = select( 'core' )
     21const { updateBlock } = dispatch( 'core/block-editor' )
    2022
    2123/**
     
    3739 * @returns {Object} The rules settings object.
    3840 */
    39 export const getRuleSettings = () => {
     41export const fetchRuleSettings = () => {
    4042   
    4143    const siteEntity = getEntityRecord( 'root', 'site' )
    42     const ruleSetting = siteEntity?.consistency_plugin_settings || []
     44    const ruleSettings = siteEntity?.consistency_plugin_settings || []
    4345
    44     return ruleSetting
    45 
    46 }
    47 
    48 /**
    49  * Retrieves the authorized rules settings.
    50  *
    51  * @returns {Array} The authorized rules settings.
    52  */
    53 export const getAuthorizedRuleSettings = () => {
    54    
    55     const ruleSetting = getRuleSettings()
    56 
    57     const authorizedRuleSettings = isLocalizationEnabled()
    58         ? ruleSetting.filter( setting => isUsedByLocale( setting.slug ) ) : ruleSetting
    59 
    60     return authorizedRuleSettings
     46    return ruleSettings
    6147
    6248}
     
    6753 * @return {object} userSettings Current user settings: userSettings.onTheFly, userSettings.onPaste
    6854 */
    69 export const getCurrentUserSettings = () => {
     55export const fetchCurrentUserSettings = () => {
    7056
    7157    const userSettings = {
     
    8975 * @return {string} currentLocale Current active site locale
    9076 */
    91 export const getCurrentLocale = () => {
     77export const fetchCurrentLocale = () => {
    9278
    9379    const siteEntity = getEntityRecord( 'root', 'site' )
     
    9682
    9783}
     84
     85/**
     86 * Updates the text content of a block depending on its type.
     87 * Blocks can have text content stored as a string or as a RichTextData object.
     88 *
     89 * @param {Object} props - The props object.
     90 * @param {Object} props.block - The block object.
     91 * @param {string} props.currentBlockId - The ID of the current block.
     92 * @param {Object} props.blockAttributes - The attributes of the block.
     93 * @param {string|Object} props.blockContent - The content of the block.
     94 * @returns {boolean} - Returns true if the block text content was updated successfully, otherwise false.
     95 */
     96export const updateBlockTextContent = props => {
     97
     98    const { block, currentBlockId, blockAttributes, blockContent } = props
     99   
     100    let newBlockTextContent
     101   
     102    if ( isRichTextData( blockAttributes.content ) ) {
     103       
     104        const newRichTextValue = create( {
     105            ...blockAttributes.content,
     106            text: blockContent
     107        } )
     108        newBlockTextContent = new RichTextData( newRichTextValue )
     109
     110    }
     111   
     112    if ( isString( blockAttributes.content ) ) {
     113        newBlockTextContent = blockContent
     114    }
     115
     116    if ( newBlockTextContent !== undefined ) {
     117           
     118        updateBlock( currentBlockId, {
     119            ...block,
     120            attributes: { ...blockAttributes, content: newBlockTextContent }
     121        } )
     122       
     123        return true
     124    }
     125   
     126    return false
     127
     128}
  • consistency/tags/1.8.0/src/app/fixes.js

    r3135255 r3142172  
    11/**
    2  * Summary: Block processing.
     2 * @summary: Block processing.
    33 *
    4  * @description This file contains the main processing operations operating on the blocks to adapt the texts according to the configured rules.
     4 * This file contains the main processing operations operating on the blocks to adapt the texts according to the configured rules.
    55 * @author Loïc Antignac.
    66 */
     
    1515 */
    1616import { getAllInnersFromParents, getOnlyTextFromBlockContent, getCursorPositionInInnerHTML } from './utils'
    17 import { getReplacementStringForPairs } from './helpers'
    18 import { rules } from '../config/rules'
    19 import { regsWithPair } from '../config/regsWithPair'
    20 import { processedBlocks } from '../config/processedBlocks'
    21 
    22 import { isUsedByLocale, blockShouldBeChecked, blockCanTechnicallyBeChecked, regDealWithPair, checkIfAMemoryLeakHasOccuredAndStopProcessing } from './checks'
    23 
    24 const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping } = select( 'core/block-editor' )
    25 const { updateBlock, selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' )
    26 
    27 /**
    28  * Fixes the content of a block based on regular expressions.
     17import { getReplacementStringForPairs, getLocalizedRules } from './helpers'
     18import { updateBlockTextContent } from './data'
     19import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs'
     20import { shouldProcessBlock, canProcessBlock, regDealWithPair } from './checks'
     21
     22const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping, getBlockSelectionStart } = select( 'core/block-editor' )
     23const { selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' )
     24
     25
     26
     27/**
     28 * Fixes the current block based on the provided rules and attributes.
    2929 *
    30  * @param {Object} props - The props object containing the necessary data.
     30 * @param {Object} props - The props object.
    3131 * @param {string} props.currentBlockId - The ID of the current block.
    32  * @param {Object} props.theRegs - An object containing regular expressions.
    33  * @param {boolean} props.isPasting - Indicates whether the content is being pasted.
     32 * @param {boolean} props.isPasting - Indicates if the content is being pasted.
     33 * @param {boolean} props.isPreviousFixCanceled - Indicates if the previous fix was canceled.
     34 * @param {function} props.setPreviousFixCanceled - The function to set the previous fix canceled state.
     35 * @param {Array} props.blocksToBeProcessed - The blocks to be processed: we pass them as props to avoid using global context since we are not in a component.
    3436 */
    3537export const fixIt = props => {
    36     const { currentBlockId, isPasting, authorizedRuleSettings } = props
    37    
    38     // Get the regex of all rules
    39     let theRegs = rules.filter( reg => true === authorizedRuleSettings?.find( s => s.slug === reg.slug )?.value )
     38
     39    const { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } = props
     40   
     41    // Check if the current block should be processed and can be processed
     42    if ( ! shouldProcessBlock( { currentBlockId, blocksToBeProcessed } ) || ! canProcessBlock( currentBlockId ) ) return
     43   
     44    // Get the relevant rules
     45    let localizedRules = getLocalizedRules()
    4046   
    4147    // Get the current block
    4248    const block = getBlock( currentBlockId )
    43 
    44     // Check if the current block should be checked and can be checked
    45     if ( ! blockShouldBeChecked( currentBlockId ) || ! blockCanTechnicallyBeChecked( currentBlockId ) ) return
    4649   
    4750    // Get the attributes of the current block
    4851    let blockAttributes = getBlockAttributes( currentBlockId )
    49 
    50     // We don't apply several fixes on the same typing event
     52   
     53    // We don't apply several fixes on the same typing event so we need a variable to check if the content has already been updated
    5154    let contentUpdated = false
    5255
    53     // Loop on regular expressions
    54     Object.entries( theRegs ).forEach( ( [ _, reg ] ) => {
    55 
    56         // Stop correction if block content isn't concerned by the current site locale (language)
    57         // if ( ! isUsedByLocale( reg.slug ) || contentUpdated ) return
    58         global.consistencyLoop ++
    59    
    60         // If the loop is too long, we stop it to avoid infinite loop
    61         checkIfAMemoryLeakHasOccuredAndStopProcessing( currentBlockId )
     56    // Loop on localized rules to check if the block content matches one regex
     57    Object.entries( localizedRules ).forEach( ( [ _, reg ] ) => {
     58
     59        // Stop correction if block content has already been updated
     60        if ( contentUpdated ) return
    6261
    6362        let replaceWithThis = reg.replace
     
    6564        let lastPart = ''
    6665        let cursorPosition = 0
    67         let blockContent = blockAttributes.content
     66        let cursorPositionInsideHTML = 0
    6867        let selectionStart
    6968
     69        let blockContent = ( typeof blockAttributes.content === 'object' ) ? blockAttributes.content.text : blockAttributes.content
     70       
    7071        // Remove 'code' 'pre' and 'kbd' and other HTML tags from block content
    7172        let textContent = getOnlyTextFromBlockContent( blockContent )
    72 
    73         // Check if block content is concerned by the regex in the case of a pasted content
    74         // (isTyping is false but subscribe detected a paste event)
     73       
     74        // Check if the block's text content matches the regex in the case of pasted content
     75        // (isTyping is false but the subscription detected a paste event)
    7576        let isConcerned = false
    7677        if ( ! isTyping() ) {
    7778            isConcerned = reg.mask.test( textContent )
    7879        }
    79 
    80         // Content splitting in case of typing on the fly to allow the user to undo a correction
    81         // If isTyping is false, it is the case of a pasted content, so we do not deal with possible undos of the user
     80       
     81        // Splitting content during real-time typing to allow the user to undo a correction
     82        // If isTyping is false, it indicates pasted content, so we don't handle potential undos by the user
    8283        if ( isTyping() ) {
    83 
     84           
    8485            // Get cursor position in textContent (without tags): needed for further cursor repositioning
    85             selectionStart = getSelectionStart( block.name )
    86             cursorPosition = selectionStart?.offset || 0
     86            selectionStart = getSelectionStart()
     87           
     88            cursorPosition = selectionStart?.offset || document.getSelection()?.anchorOffset || 0
    8789
    8890            // Get cursor position in HTML (with tags): needed to cut in 2 parts at the right position
    89             const cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition
    90 
     91            cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition
     92           
    9193            // If the rule depends on previous characters, we need to separate the string taking those characters into account
    9294            const captureGroups = textContent.match( reg.mask )
     95           
    9396            if( null === captureGroups || 0 === captureGroups.length ) return
     97           
    9498            const lengthToGoBack = captureGroups[0].length || 1
    9599
    96100            // Split the string to process only the part from the cursor position to the end
    97             firstPart = blockContent.substring( 0, cursorPositionInsideHTML - lengthToGoBack )
    98             lastPart = blockContent.substring( cursorPositionInsideHTML - lengthToGoBack, blockContent.length )
    99    
     101            firstPart = textContent.substring( 0, cursorPositionInsideHTML - lengthToGoBack )
     102            lastPart = textContent.substring( cursorPositionInsideHTML - lengthToGoBack, textContent.length )
     103                   
    100104            // If first part of the string matches but not the lastPart,
    101105            // it means that a character has been typed uncorrected voluntarily before with CTRL Z/CMD Z
     
    111115            replaceWithThis = getReplacementStringForPairs( reg, blockContent, replaceWithThis )
    112116        }
    113 
     117       
    114118        // Concat strings
    115         if ( 0 !== cursorPosition ) {
     119        if ( 0 !== cursorPositionInsideHTML ) {
    116120            blockContent = firstPart + lastPart.replace( reg.mask, replaceWithThis )
    117121        }
    118122
    119123        // Pasted content innerBlocks case: no selection, no cursor position so the whole block is fixed
    120         if ( 0 === cursorPosition ) {
     124        if ( 0 === cursorPositionInsideHTML ) {
    121125            blockContent = blockContent.replace( reg.mask, reg.replace )
    122126        }
    123 
    124         // If CTRL Z was used just before, then we do not correct this time
    125         if ( global.previousFixCanceled ) {
    126             global.previousFixCanceled = false
     127       
     128        // If CTRL Z was used just before, skip the correction this time
     129        if ( isPreviousFixCanceled ) {
     130            setPreviousFixCanceled( false )
    127131            return
    128132        }
    129        
    130         // Update block if previous fix was not canceled
    131         if ( ! global.previousFixCanceled ) {
    132             updateBlock( currentBlockId, {
    133                 ...block,
    134                 attributes: { ...block.attributes, content: blockContent }
    135             } )
    136             contentUpdated = true
    137         }
    138 
     133           
     134        // Update block text content if previous fix was not canceled
     135        if ( ! isPreviousFixCanceled ) {
     136            contentUpdated = updateBlockTextContent( { block, currentBlockId, blockAttributes, blockContent } )
     137        }
     138   
    139139        // Cursor repositioning:
    140140        if ( 0 === cursorPosition || isPasting ) return
     
    142142        // Get the number of characters moved by the replacement: needed for cursor repositioning.
    143143        // If the number depends on the replaced string length, we use a function to get it
    144         const nbMoved = typeof reg.nbMoved === 'function' ? reg.nbMoved( lastPart ) : reg.nbMoved
    145 
     144        const nbMoved = typeof reg.nbMoved === 'function' ? reg.nbMoved( lastPart ) : reg.nbMoved || 0
     145       
     146        let attributeKey = selectionStart.hasOwnProperty( 'attributeKey' ) ? selectionStart.attributeKey : 'content'
     147   
    146148        // If the replaced string had more characters than the new string, the cursor has moved forward, so it must be moved back
    147149        // Eg: ... replaced with … removes 2 characters
    148150        if ( nbMoved < 0 ) {
    149             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )
     151            selectionChange( currentBlockId, attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )
    150152        }
    151153       
     
    153155        // Eg: "" replaced with «  » adds 2 characters
    154156        if ( nbMoved > 0 ) {
    155             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )
     157            selectionChange( currentBlockId, attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )
    156158        }
    157159
    158160        if ( 0 === nbMoved ) {
    159             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition, cursorPosition )
     161            selectionChange( currentBlockId, attributeKey, cursorPosition, cursorPosition )
    160162        }
    161163
    162164    } )
    163165
    164     global.consistencyLoop = 0
    165 
    166166}
    167167
     
    171171export const fixAll = props => {
    172172
    173     const { authorizedRuleSettings } = props
    174 
    175     // Get the regex of all rules
    176     let theRegs = rules.filter( reg => true === authorizedRuleSettings?.find( s => s.slug === reg.slug )?.value )
    177 
     173    const { isPreviousFixCanceled, setPreviousFixCanceled, localizedRuleSettings, blocksToBeProcessed } = props
     174
     175    // Get the relevant rules
     176    let localizedRules = getLocalizedRules()
     177   
    178178    // Get all blocks generated by pasting (which does not integrate innerBlocks)
    179179    const allBlocks = getBlocks()
    180 
     180   
    181181    // Get all innerBlocks for a later bulk selection process that will generate their fix
    182182    const allInners = getAllInnersFromParents( allBlocks )
    183 
     183   
    184184    // Loop on all parents blocks
    185185    const updates = allBlocks.reduce( ( acc, block ) => {
     
    187187        let newContent = block.attributes?.content
    188188       
    189         // If the block is not one of the blocks authorized to be processed (list in rules.js) or if the content is undefined, we do nothing
    190         if ( ! processedBlocks.includes( block.name )
     189        // If the block is not one of the blocks authorized to be processed (list in global context) or if the content is undefined, we do nothing
     190        if ( ! blocksToBeProcessed.includes( block.name )
    191191            || undefined === newContent ) {
    192192            return acc
    193193        }
    194194   
    195         Object.entries( theRegs ).forEach( ( [ _, reg ] ) => {
    196 
    197             // If the rule is not used by the locale, we do nothing
    198             // if ( ! isUsedByLocale( reg.slug ) ) return
     195        Object.entries( localizedRules ).forEach( ( [ _, reg ] ) => {
    199196
    200197            // If the rule is a pair rule, we use a specific regex
    201             if ( regsWithPair.includes( reg.slug ) ) {
     198            if ( pairedCharacterSlugs.includes( reg.slug ) ) {
    202199                const singleCharacterOfPair = reg.mask.toString().match( /(?<=\/).+?(?=\/)/g )[0]
    203200                const realReg = new RegExp( `(?<!\=)${singleCharacterOfPair}(?!>)([^${singleCharacterOfPair}]*)(?<!\=)${singleCharacterOfPair}(?!>)`, 'g' )
     
    206203
    207204            // If the rule is not a pair rule, we use the regex as it is
    208             if ( ! regsWithPair.includes( reg.slug ) ) {
     205            if ( ! pairedCharacterSlugs.includes( reg.slug ) ) {
    209206                const stringRegex = reg.mask.toString()
    210207                const regWithGlobalFlag = new RegExp( stringRegex.substring( 1, stringRegex.length - 1 ), 'g' )
    211208                newContent = newContent.replaceAll( regWithGlobalFlag, reg.replace )
    212209            }
    213 
     210           
    214211        } )
    215212
     
    220217        return acc
    221218    }, {} )
    222 
     219   
    223220    // Update all parents blocks
    224     if ( Object.keys( updates ).length > 0 && global.contentPasted ) {
    225         global.contentPasted = false
     221    if ( Object.keys( updates ).length > 0 ) {
    226222        updateBlockAttributes( Object.keys( updates ), updates, true );
    227223    }
    228     global.contentPasted = false
    229224
    230225    // Select all innerBlocks to trigger their correction, then deselect all by selecting the first block
    231226    const isPasting = true
    232227    allInners.forEach( block => {
    233         if ( ! processedBlocks.includes( block.name ) ) return
     228
     229        if ( ! blocksToBeProcessed.includes( block.name ) ) return
    234230        const currentBlockId = block.clientId
    235         block?.clientId && fixIt( { currentBlockId, theRegs, isPasting } )
     231        block?.clientId && fixIt( { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } )
     232
    236233    } )
    237234
  • consistency/tags/1.8.0/src/app/helpers.js

    r3122806 r3142172  
    11/**
    2  * Summary: Specific functions varied and correlated to the application.
     2 * @summary: Specific functions varied and correlated to the application.
    33 *
    4  * @description This file contains specific functions that depends on other parts of the application.
     4 * This file contains specific functions that depends on other parts of the application.
    55 * @author Loïc Antignac.
    66 */
    77
    88/**
    9  * WordPress dependencies
     9 * External dependencies
    1010 */
    11 import { select, dispatch } from '@wordpress/data'
    12 
    13 const { getBlock } = select( 'core/block-editor' )
    14 const { updateBlock } = dispatch( 'core/block-editor' )
     11import { isUsedByLocale } from './checks'
     12import { fetchRuleSettings, isLocalizationEnabled } from './data'
     13import { rules } from '../config/rules'
    1514
    1615/**
     
    5453
    5554/**
    56  * Stop the process in the regex loop if a code error generates an infinite loop
    57  * by removing last 2 characters and adding a message in the console
    58  *
    59  * @param {string} currentBlockId currentBlockId current active block ID
     55 * Retrieves the localized rules settings.
     56 *
     57 * @returns {Array} The localized rules settings.
    6058 */
    61 export const aMemoryLeakHasOccured = currentBlockId => {
     59export const getLocalizedRuleSettings = () => {
     60   
     61    const ruleSettings = fetchRuleSettings()
    6262
    63     const block = getBlock( currentBlockId )
     63    const localizedRuleSettings = isLocalizationEnabled()
     64        ? ruleSettings.filter( setting => isUsedByLocale( setting.slug ) )
     65        : ruleSettings
    6466
    65     updateBlock( currentBlockId, {
    66         ...block,
    67         attributes: { ...block.attributes, content: block.attributes.content.slice( -2 ) }
    68     } )
    69 
    70     global.consistency_loop = 0
    71     console.log( 'Consistency - a memory leak has occured during the fix of the following block:', block )
     67    return localizedRuleSettings
    7268
    7369}
    7470
     71
    7572/**
    76  * Checks the editor location and updates the global accordingly.
     73 * Retrieves localized rules based on the current rule settings.
     74 * @returns {Array} An array of localized rules.
    7775 */
    78 const updatePasteEventGlobalDependingOnEditorLocation = () => {
    79    
    80     // Recheck if the editor is in an iframe or not
    81     const isEditorStillInIframe = document.querySelector( 'iframe[name="editor-canvas"]' ) !== null ? true : false
     76export const getLocalizedRules = () => {
    8277
    83     // If we have changed the editor location (in iframe or not), we need to reattach the paste event
    84     if ( global.isEditorInIframe !== isEditorStillInIframe ) {
    85         global.isEditorInIframe = isEditorStillInIframe
    86         global.isPasteEventAttached = false
    87     }
     78    const localizedRules = rules.filter( reg => true === getLocalizedRuleSettings()?.find( s => s.slug === reg.slug )?.value )
     79
     80    return localizedRules
    8881
    8982}
    90 
    91 /**
    92  * Attaches a 'paste' event listener to the editor or iframe document, depending on the editor location.
    93  * Updates the global variables to track if the paste event has been attached and if content has been pasted.
    94  */
    95 export const interceptPasteEventInEditor = () => {
    96    
    97     // Check if the editor location has changed and update the global accordingly
    98     updatePasteEventGlobalDependingOnEditorLocation()
    99 
    100     if ( global.isPasteEventAttached ) return
    101 
    102     if ( global.isEditorInIframe ) {
    103 
    104         // Select the iframe
    105         const iframe = document.querySelector( 'iframe[name="editor-canvas"]' )
    106 
    107         // Ensure the iframe is not null and has loaded
    108         if ( iframe ) {
    109             iframe.onload = () => {
    110                 // Access the iframe's document
    111                 const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
    112 
    113                 // Attach the 'paste' event listener
    114                 iframeDoc.addEventListener('paste', e => {
    115                     global.contentPasted = true
    116                     global.isPasteEventAttached = true
    117                 } )
    118             }
    119    
    120             // If the iframe is already loaded by the time this code runs, manually trigger the onload handler
    121             if ( iframe.contentWindow.document.readyState === 'complete' ) {
    122                 iframe.onload()
    123             }
    124         }
    125 
    126     }
    127     if ( ! global.isEditorInIframe ) {
    128        
    129         // Attach the paste event to the editor
    130         document.querySelector( '#editor' )?.addEventListener( 'paste', e => {
    131             global.contentPasted = true
    132             global.isPasteEventAttached = true
    133         } )
    134 
    135     }
    136 
    137 }
  • consistency/tags/1.8.0/src/app/utils.js

    r3122806 r3142172  
    11/**
    2  * Summary: Generic utility functions.
     2 * @summary: Generic utility functions.
    33 *
    4  * @description This file contains generic utility functions that are not tied to any specific part of the application.
     4 * This file contains generic utility functions that are not tied to any specific part of the application.
    55 * @author Loïc Antignac.
    66 */
     7
     8import { RichTextData } from '@wordpress/rich-text'
    79
    810/**
     
    5153    if ( null === currentActiveBlock ) return undefined
    5254
     55    // Get the inner content editable element (like <p> or <code>)
     56    // Sometimes the contenteditable is not the direct child of the block
     57    const editableContent = currentActiveBlock.querySelector('[contenteditable="true"]') || currentActiveBlock;
     58   
    5359    // Get current selection
    5460    const selection = document.getSelection()
     
    5662
    5763    // Return if user is selecting text instead of typing
    58     if ( ! _range.collapsed ) return
     64    if ( ! _range || ! _range.collapsed ) return
    5965
    6066    // Clone range to work on
     
    6773    range.insertNode( tempNode )
    6874
    69     // Get position of target inside active block HTML
    70     let cursorPositionInsideHTML = currentActiveBlock?.innerHTML?.indexOf( '\0' )
    71 
     75    // Get position of target inside the contenteditable element
     76    const textContent = editableContent.textContent || ''
     77    let cursorPositionInsideText = textContent.indexOf( '\0' )
     78   
    7279    // Remove temporary node and normalize cut node - important!
    7380    tempNode.parentNode.removeChild( tempNode )
    74     currentActiveBlock.normalize()
     81    editableContent.normalize()
    7582
    7683    // Remove non-breaking spaces in &nbsp; format from the count
    77     const nbNbsp = (currentActiveBlock?.innerHTML.match(/&nbsp;/g) || []).length
     84    const nbNbsp = (editableContent?.innerHTML.match(/&nbsp;/g) || []).length
    7885    if ( nbNbsp > 0 ) {
    79         cursorPositionInsideHTML = cursorPositionInsideHTML - ( nbNbsp * 6 ) + nbNbsp
     86        const textContentWithoutNbsp = textContent.replace(/&nbsp;/g, ' ');
     87        cursorPositionInsideText = textContentWithoutNbsp.indexOf('\0');
     88        cursorPositionInsideText = cursorPositionInsideText - ( nbNbsp * 6 ) + nbNbsp;
    8089    }
    8190
    82     return cursorPositionInsideHTML
     91    return cursorPositionInsideText
    8392}
     93
     94/**
     95 * Checks if a value is a string.
     96 *
     97 * @param {*} value - The value to check.
     98 * @returns {boolean} - Returns true if the value is a string, false otherwise.
     99 */
     100export const isString = value => typeof value === 'string' || value instanceof String
     101
     102/**
     103 * Checks if the given value is an instance of RichTextData.
     104 *
     105 * @param {*} value - The value to check.
     106 * @returns {boolean} - Returns true if the value is an instance of RichTextData, otherwise returns false.
     107 */
     108export const isRichTextData = value => typeof value === 'object' && value instanceof RichTextData
  • consistency/tags/1.8.0/src/components/GlobalSettingPanel.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingPanel component.
     2 * @summary: GlobalSettingPanel component.
    33 *
    4  * @description This file contains the GlobalSettingPanel component used to display the plugin's global settings which contains the global correction rules in sidebar for administrators.
     4 * This file contains the GlobalSettingPanel component used to display the plugin's global settings which contains the global correction rules in sidebar for administrators.
    55 * @author Loïc Antignac.
    66 */
     
    4242                                    key={ key }
    4343                                    settingSlug={ rule.slug }
    44                                     settingName={ rule.name }
     44                                    name={ rule.name }
    4545                                    settingDescription={ {
    4646                                        __html: rule.description
     
    5252            } )
    5353        }
     54
    5455    </Panel>
    5556)
     57
    5658export default GlobalSettingPanel
  • consistency/tags/1.8.0/src/components/GlobalSettingToggle.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingToggle component.
     2 * @summary: GlobalSettingToggle component.
    33 *
    4  * @description This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
     4 * This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/tags/1.8.0/src/components/LocaleLabel.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingToggle component.
     2 * @summary: GlobalSettingToggle component.
    33 *
    4  * @description This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
     4 * This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
     
    1414 * External dependencies
    1515 */
    16 import { getCurrentLocale, isLocalizationEnabled } from '../app/data'
     16import { fetchCurrentLocale, isLocalizationEnabled } from '../app/data'
    1717
    1818
    1919export const LocaleLabel = () => {
    2020
    21     const currentLocale = getCurrentLocale()
     21    const currentLocale = fetchCurrentLocale()
    2222
    2323    const areRulesLocalized = isLocalizationEnabled()
  • consistency/tags/1.8.0/src/components/Settings.js

    r3122806 r3142172  
    11/**
    2  * Summary: SidebarSettings component.
     2 * @summary: SidebarSettings component.
    33 *
    4  * @description This file contains the SidebarSettings component used to display the plugin's settings sidebar in editor.
     4 * This file contains the SidebarSettings component used to display the plugin's settings sidebar in editor.
    55 * @author Loïc Antignac.
    66 */
     
    1010 */
    1111import { __ } from '@wordpress/i18n'
    12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/edit-post'
     12import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/editor'
    1313import { select } from '@wordpress/data'
    1414
     
    1616 * External dependencies
    1717 */
    18 import { ConsistencyIcon } from './Icon'
     18import { ConsistencyIcon } from './icon'
    1919import UserSettingPanel from './UserSettingPanel'
    2020import GlobalSettingPanel from './GlobalSettingPanel'
     21
    2122
    2223const { canUser } = select( 'core' )
  • consistency/tags/1.8.0/src/components/UserSettingPanel.js

    r3130518 r3142172  
    11/**
    2  * Summary: UserSettingPanel component.
     2 * @summary: UserSettingPanel component.
    33 *
    4  * @description This file contains the UserSettingPanel component used to display the plugin's user settings in sidebar.
     4 * This file contains the UserSettingPanel component used to display the plugin's user settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/tags/1.8.0/src/components/UserSettingToggle.js

    r3086094 r3142172  
    11/**
    2  * Summary: UserSettingToggle component.
     2 * @summary: UserSettingToggle component.
    33 *
    4  * @description This file contains the UserSettingToggle component used to display the plugin's user settings in sidebar.
     4 * This file contains the UserSettingToggle component used to display the plugin's user settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/tags/1.8.0/src/components/icon.js

    r3083522 r3142172  
    11/**
    2  * Summary: Consistency Logo.
     2 * @summary: Consistency Logo.
    33 *
    4  * @description This file contains the Consistency Logo component used to display the plugin's settings sidebar in editor.
     4 * This file contains the Consistency Logo component used to display the plugin's settings sidebar in editor.
    55 * @author Loïc Antignac.
    66 */
  • consistency/tags/1.8.0/src/config/categories.js

    r3122806 r3142172  
    11/**
    2  * Summary: Rules categories.
     2 * @summary: Rules categories.
    33 *
    4  * @description This file contains an array of all correction rules categories.
     4 * This file contains an array of all correction rules categories.
    55 * @author Loïc Antignac.
    66 */
  • consistency/tags/1.8.0/src/config/rules.js

    r3130518 r3142172  
    11/**
    2  * Summary: Correction rules.
     2 * @summary: Correction rules.
    33 *
    4  * @description This file contains an array of all correction rules with each regular expression used.
     4 * This file contains an array of all correction rules with each regular expression used.
     5 *
    56 * @author Loïc Antignac.
    67 */
  • consistency/tags/1.8.0/src/index.js

    r3135255 r3142172  
    11/**
    2  * Main entry point for the WordPress Consistency plugin.
     2 * @summary: Main entry point for the WordPress Consistency plugin.
    33 *
    4  * This module registers a custom sidebar for settings and sets up event listeners for
    5  * state changes in the Gutenberg editor. It handles on-the-fly and on-paste text
    6  * consistency fixes based on user and global settings. It also manages several global
    7  * variables and states related to the plugin's operation.
     4 * This module registers a custom sidebar for Consistency settings
     5 * and wraps the ConsistencyPlugin component in a GlobalProvider
     6 * to provide global variables and states to the plugin's operation.
    87 *
    98 * @author Loïc Antignac.
     
    1413 */
    1514import { registerPlugin } from '@wordpress/plugins'
    16 import { subscribe, select } from '@wordpress/data'
    1715import domReady from '@wordpress/dom-ready'
    18 import { SidebarSettings } from './components/Settings'
    19 
    2016
    2117/**
    2218 * External dependencies
    2319 */
    24 import { fixIt, fixAll } from './app/fixes'
    25 import { getAuthorizedRuleSettings, getCurrentUserSettings } from './app/data'
    26 import { interceptPasteEventInEditor } from './app/helpers'
     20import { GlobalProvider } from './contexts/GlobalContext'
     21import ConsistencyPlugin from './components/ConsistencyPlugin'
    2722
    28 // Get the current selected block and its attributes
    29 const { getSelectedBlockClientId, isTyping, getBlockAttributes } = select( 'core/block-editor' )
    30 
    31 // Register the plugin to get the settings sidebar
    32 registerPlugin( 'consistency-custom-sidebar', {
    33     render: SidebarSettings,
    34 } )
     23const PluginWrapper = () => (
     24    <GlobalProvider>
     25        <ConsistencyPlugin />
     26    </GlobalProvider>
     27)
    3528
    3629domReady( () => {
    37 
    38     /**
    39      * Global object properties
    40      */
    41    
    42     // This global makes it possible to count the loops on the regex in order to trigger a cut on a possible infinite loop
    43     global.consistencyLoop = 0
    44     // This global is used to store the content of the block to avoid fixing it if it has not changed since we check at every state change
    45     global.previousFixCanceledContent = ''
    46     // This global is used to avoid new fixes when the user is undoing a fix with CTRL/CMD Z
    47     global.previousFixCanceled = false
    48     // This global is used to know if some content has been pasted in the editor
    49     global.contentPasted = false
    50     // Since we attach the paste event in a subscribe, we need to check if it is already attached
    51     global.isPasteEventAttached = false
    52     // Check if custom fields are active because the editor content is within an iframe when custom fields are inactive
    53     global.isEditorInIframe = document.querySelector( 'iframe[name="editor-canvas"]' ) !== null ? true : false
    54 
    55     // Intercept CTRL Z to cancel next fix: when the user is undoing a fix, we don't want to fix it again.
    56     document.querySelector( '#editor' )?.addEventListener( 'keydown', e => {
    57         if ( 90 === e.keyCode && ( e.ctrlKey || e.metaKey ) ) {
    58             global.previousFixCanceled = true
    59            
    60             e.preventDefault()
    61         }
    62     } )
    63    
    64     // Let’s listen for state changes
    65     subscribe( () => {
    66 
    67         // Intercept clipboard paste to fix all new blocks
    68         interceptPasteEventInEditor()
    69 
    70         // Get current user settings to check if we have to fix the content or to stop here
    71         const { onTheFly, onPaste } = getCurrentUserSettings()
    72         if ( ! onTheFly && ! onPaste ) return
    73 
    74         // Get fixing rules from site entity global settings
    75         const authorizedRuleSettings = getAuthorizedRuleSettings()
    76         if ( undefined === authorizedRuleSettings ) return
    77 
    78         // If content has been copied/pasted generating blocks, and if onPaste is enabled, we fix all blocks then stop here
    79         if ( global.contentPasted && onPaste ) {
    80             fixAll( { authorizedRuleSettings } )
    81             return
    82         }
    83 
    84         // Get current selected block
    85         const currentBlockId = getSelectedBlockClientId()
    86 
    87         // Stop here if no block is selected or if fixing on the fly is disabled
    88         if ( null === currentBlockId || global.contentPasted || ! onTheFly ) return
    89 
    90         // Don't try to fix block content if nothing has changed
    91         const blockAttributes = getBlockAttributes( currentBlockId )
    92         if ( blockAttributes.hasOwnProperty( 'content' ) && global.previousFixCanceledContent === blockAttributes.content ) {
    93             return
    94         }
    95 
    96         // Store the block content to avoid fixing it twice at the next state change
    97         global.previousFixCanceledContent = blockAttributes.content
    98 
    99         // Fixes the typography of current selected block
    100         const isPasting = false
    101         isTyping() && fixIt( { currentBlockId, isPasting, authorizedRuleSettings } )
    102    
    103     } )
     30    registerPlugin( 'consistency-custom-sidebar', { render: PluginWrapper } )
    10431} )
  • consistency/trunk/build/index.asset.php

    r3135255 r3142172  
    1 <?php return array('dependencies' => array('react', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-i18n', 'wp-notices', 'wp-plugins'), 'version' => '8a8d2697904a0e5c0985');
     1<?php return array('dependencies' => array('react', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom-ready', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-rich-text'), 'version' => '525eca4ceb38a5bd7f50');
  • consistency/trunk/build/index.js

    r3135255 r3142172  
    1 (()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})}};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t);const t=window.wp.plugins,s=window.wp.data,n=window.wp.domReady;var o=e.n(n);const c=window.React,a=window.wp.i18n,r=window.wp.editPost,i=window.wp.components,l=()=>(0,c.createElement)(i.Icon,{icon:(0,c.createElement)("svg",{version:"1.1",id:"consistency-plugin",x:"0px",y:"0px",width:"24px",height:"24px",viewBox:"0 0 24 24",enableBackground:"new 0 0 24 24"},(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"4",y1:"20",x2:"7",y2:"20"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"14",y1:"20",x2:"21",y2:"20"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"6.9",y1:"15",x2:"13.8",y2:"15"}),(0,c.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"10.2",y1:"6.3",x2:"16",y2:"20"}),(0,c.createElement)("polyline",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",points:"5,20 11,4 13,4 20,20 "}))}),u=window.wp.coreData,p=window.wp.notices,d=e=>{const{settingSlug:t,settingName:n,settingDescription:o}=e,{currentUser:r}=(0,s.useSelect)((e=>({currentUser:e(u.store).getCurrentUser()})),[]),l=r&&r.id,[d,g]=(0,u.useEntityProp)("root","user","meta",l),{saveEditedEntityRecord:y}=(0,s.useDispatch)(u.store),{createNotice:h}=(0,s.useDispatch)(p.store);return(0,c.createElement)(i.ToggleControl,{label:n,help:(0,c.createElement)("span",{dangerouslySetInnerHTML:o}),checked:d?.consistency_plugin_user_settings?.find((e=>e.slug===t))?.value||!1,onChange:e=>{let s=d?.consistency_plugin_user_settings.map((s=>t===s.slug?{...s,value:e}:s));s?.find((e=>e.slug===t))||s.push({slug:t,value:e}),g({...d,consistency_plugin_user_settings:s}),y("root","user",l,{...d,meta:s}),h((0,a.__)("info","consistency"),e?sprintf((0,a.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,a.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}})},g=()=>(0,c.createElement)(i.Panel,{className:"UserSettingPanel"},(0,c.createElement)(i.PanelHeader,null,(0,c.createElement)("strong",null,(0,a.__)("Settings for my account","consistency")),(0,c.createElement)("br",null)),(0,c.createElement)("div",{style:{padding:16}},(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(d,{settingSlug:"on_the_fly",settingName:(0,a.__)("On-the-fly autocorrect","consistency"),settingDescription:{__html:(0,a.__)("Enable/disable on-the-fly autocorrect","consistency")}})),(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(d,{settingSlug:"on_paste",settingName:(0,a.__)("On paste autocorrect","consistency"),settingDescription:{__html:(0,a.__)("Enable/disable autocorrect on paste","consistency")}})))),y=["regularToCurlyQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces","regularToGermanQuotes","regularToGermanBookStyleQuotes"],h=["core/paragraph","core/heading","core/quote","core/list-item","core/read-more"],{getBlock:m}=(0,s.select)("core/block-editor"),{updateBlock:b}=(0,s.dispatch)("core/block-editor"),_=[{slug:"quote",incompatibleWith:[]},{slug:"2hyphens",incompatibleWith:[]},{slug:"3hyphens",incompatibleWith:[]},{slug:"4hyphens",incompatibleWith:[]},{slug:"ordinalNumberSuffix",incompatibleWith:[]},{slug:"regularToCurlyQuotes",incompatibleWith:["regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanBookStyleQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotesWithoutSpaces",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes"]},{slug:"curlyToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes"]},{slug:"breakingSpace",incompatibleWith:["spaceBefore"]},{slug:"noSpaceBefore",incompatibleWith:["spaceBefore"]},{slug:"spaceBefore",incompatibleWith:["breakingSpace","noSpaceBefore"]},{slug:"noBreakingSpaceAfter",incompatibleWith:[]},{slug:"noNonBreakingSpaceAfter",incompatibleWith:[]},{slug:"capitalizeFirstSentenceLetter",incompatibleWith:[]},{slug:"etcThreeDots",incompatibleWith:[]},{slug:"etcTwoDots",incompatibleWith:[]},{slug:"etcEllipsis",incompatibleWith:[]},{slug:"ellipsis",incompatibleWith:[]},{slug:"symbolInACircle",incompatibleWith:[]},{slug:"symbolInSmallCapsAndSuperscriptStyle",incompatibleWith:[]},{slug:"fractions",incompatibleWith:[]},{slug:"percentages",incompatibleWith:[]}],{getBlockName:f,getBlockAttributes:k}=(0,s.select)("core/block-editor"),w=e=>{const t=T();return!(void 0===localesByRules||!localesByRules.hasOwnProperty(e))&&localesByRules[e].includes(t)},S=e=>{const t=_.find((t=>t.slug===e));return!!t&&t.incompatibleWith.some((e=>x()?.find((t=>t.slug===e))?.value))},{getEntityRecord:v}=(0,s.select)("core"),E=()=>{const e=v("root","site");return e?.consistency_plugin_localization_management||!0},x=()=>{const e=(()=>{const e=v("root","site");return e?.consistency_plugin_settings||[]})();return E()?e.filter((e=>w(e.slug))):e},T=()=>{const e=v("root","site");return e?.language||"en_US"},C=()=>{const e=T(),t=E()?(0,a.__)(` (${e} locale)`,"consistency"):(0,a.__)(" (all locales)","consistency");return(0,c.createElement)("span",{style:{fontWeight:"normal",fontStyle:"italic",fontSize:"smaller"}},t)},F=e=>{const{settingSlug:t,settingName:n,settingDescription:o}=e,{createNotice:r}=(0,s.useDispatch)(p.store);if(E()&&!w(t))return"";const[l,d]=(0,u.useEntityProp)("root","site","consistency_plugin_settings",void 0),{saveEditedEntityRecord:g}=(0,s.useDispatch)(u.store);return(0,c.createElement)(i.PanelRow,null,(0,c.createElement)(i.ToggleControl,{label:n,help:(0,c.createElement)("span",{dangerouslySetInnerHTML:o}),checked:l?.find((e=>e.slug===t))?.value||!1,disabled:S(t),onChange:e=>{let s=l.map((s=>t===s.slug?{...s,value:e}:s));d(s),g("root","site",void 0,s),r((0,a.__)("info","consistency"),e?sprintf((0,a.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,a.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}}))},Q=[{slug:"quote",name:(0,a.__)("Straight Apostrophe","consistency"),description:(0,a.__)("Replace straight apostrophes with curly apostrophes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>'</code> <span style='font-size:20px'>→</span> <code>’</code></span>",mask:/\'/,replace:"’",nbMoved:0,category:"apostrophe"},{slug:"2hyphens",name:(0,a.__)("En Dash","consistency"),description:(0,a.__)("Replace two hyphens with an en dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>--</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">–</code></span>",mask:/(?:\-)\-/,replace:"–",nbMoved:-1,category:"dash"},{slug:"3hyphens",name:(0,a.__)("Em Dash","consistency"),description:(0,a.__)("Replace three hyphens with an em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>---</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">—</code></span>",mask:/(?:–|\-\-)\-/,replace:"—",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"4hyphens",name:(0,a.__)("Two-Em Dash","consistency"),description:(0,a.__)("Replace four hyphens with two-em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>----</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">⸺</code></span>",mask:/(?:—|–\-|\-\-\-)\-/,replace:"⸺",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"ordinalNumberSuffix",name:(0,a.__)("Ordinal Number Suffix","consistency"),description:(0,a.__)("Add the sup HTML tag to ordinal number suffixes","consistency")+"<span aria-hidden='true' style='display:block;'><code>1st</code> <span style='font-size:20px'>→</span> <code>1<sup>st</sup></code></span>",mask:/([10-9]{1,20})(th|nd|rd|e|er|res|d|ds|de|des)( | |\.|\,|\;)/,replace:"$1<sup>$2</sup>$3",nbMoved:0,category:"suffixe"},{slug:"regularToCurlyQuotes",name:(0,a.__)("Curly Quotes","consistency"),description:(0,a.__)("Replace straight quotes with curly quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>“ ”</code></span>",mask:/"/,replace:"“$1”",nbMoved:0,category:"quotation"},{slug:"regularToGermanQuotes",name:(0,a.__)("German Quotes","consistency"),description:(0,a.__)("Replace straight quotes with german quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>„ “</code></span>",mask:/"/,replace:"„$1“",nbMoved:0,category:"quotation"},{slug:"regularToGermanBookStyleQuotes",name:(0,a.__)("German Book-Style Quotes","consistency"),description:(0,a.__)("Replace straight quotes with german book-style quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>» «</code></span>",mask:/"/,replace:"»$1«",nbMoved:0,category:"quotation"},{slug:"regularToFrenchQuotes",name:(0,a.__)("French Quotes with Spaces","consistency"),description:(0,a.__)("Replace straight quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/"/,replace:"« $1 »",nbMoved:1,category:"quotation"},{slug:"regularToFrenchQuotesWithoutSpaces",name:(0,a.__)("French Quotes","consistency"),description:(0,a.__)("Replace straight quotes with french quotes without spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>« »</code></span>",mask:/"/,replace:"«$1»",nbMoved:0,category:"quotation"},{slug:"curlyToFrenchQuotes",name:(0,a.__)("Curly Quotes to French Quotes","consistency"),description:(0,a.__)("Replace curly quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>“ ”</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/“.*?”/,replace:e=>`« ${e.substring(1,e.length-1)} »`,nbMoved:0,category:"quotation"},{slug:"breakingSpace",name:(0,a.__)("Breaking Spaces","consistency"),description:sprintf((0,a.__)("Replace a breaking space followed by a character from this list:%1$s with a non-breaking space","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/ ([\?|\!|\:|\;|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:0,category:"space"},{slug:"noSpaceBefore",name:(0,a.__)("No Space Before","consistency"),description:sprintf((0,a.__)("Add a non-breaking space before a character from this list:%1$s having no space before","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/(?<! | |&nbsp;)([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:1,category:"space"},{slug:"spaceBefore",name:(0,a.__)("Space Before","consistency"),description:(0,a.__)("Remove any space preceding a character from this list:","consistency")+"<span style='display:block;'><code>? ! : ; %</code></span>",mask:/([ | ])(?=[\?|\!|\:|\;|\%])(.)/,replace:"$2",nbMoved:-1,category:"space"},{slug:"noBreakingSpaceAfter",name:(0,a.__)("No Breaking Space After","consistency"),description:sprintf((0,a.__)("Add a breaking space after a character from this list:%1$s when followed with another character","consistency"),"<br /><code>, … ) ]</code><br />"),mask:/([\,|…|\)|\]])(?! | |\.|\,|\d|$)(.)/,replace:"$1 $2",nbMoved:1,category:"space"},{slug:"noNonBreakingSpaceAfter",name:(0,a.__)("No Non-Breaking Space After","consistency"),description:(0,a.__)("Add a non-breaking space after open french quote having no space after","consistency"),mask:/(«)(?! | |&nbsp;)/,replace:"$1 ",nbMoved:0,category:"space"},{slug:"capitalizeFirstSentenceLetter",name:(0,a.__)("First Sentence Letter","consistency"),description:(0,a.__)("Capitalize the first letter of a sentence","consistency"),mask:/(^[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț])|(?<=[\.|\?|\!|…] )[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț]/,replace:e=>e.toUpperCase(),nbMoved:0,category:"case"},{slug:"etcThreeDots",name:(0,a.__)('Three Dots Following "etc"',"consistency"),description:(0,a.__)('Replace 3 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc...</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3})/i,replace:e=>e.substring(0,3)+".",nbMoved:-2,category:"ellipsis"},{slug:"etcTwoDots",name:(0,a.__)('Two Dots Following "etc"',"consistency"),description:(0,a.__)('Replace 2 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc..</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{2})/i,replace:e=>e.substring(0,2)+".",nbMoved:-1,category:"ellipsis"},{slug:"etcEllipsis",name:(0,a.__)('Ellipsis Following "etc"',"consistency"),description:(0,a.__)('Replace the ellipsis placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc…</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3}|…)/i,replace:e=>e.substring(0,3)+".",nbMoved:0,category:"ellipsis"},{slug:"ellipsis",name:(0,a.__)("Ellipsis","consistency"),description:(0,a.__)("Replaces 3 dots with ellipsis:","consistency")+"<span aria-hidden='true' style='display:block;'><code>...</code> <span style='font-size:20px'>→</span> <code>…</code></span>",mask:/\.{3}/,replace:"…",nbMoved:-2,category:"ellipsis"},{slug:"symbolInACircle",name:(0,a.__)("Symbol in a Circle","consistency"),description:(0,a.__)("Replaces 1 character placed in parentheses with a symbol","consistency")+"<span aria-hidden='true' style='display:block;'><code>(c) (p) (r)</code> <span style='font-size:20px'>→</span> <code>© ℗ ®</code></span>",mask:/(\([c|p|r])(\))/,replace:e=>{switch(e[1]){case"c":return"©";case"p":return"℗";case"r":return"®"}return" "},nbMoved:-2,category:"symbol"},{slug:"symbolInSmallCapsAndSuperscriptStyle",name:(0,a.__)("Symbol in Small Caps and Superscript Style","consistency"),description:(0,a.__)("Replaces 2-character abbreviations with a symbol in small caps and superscript style","consistency")+"<span aria-hidden='true' style='display:block;'><code>tm sm md mc</code> <span style='font-size:20px'>→</span> <code>™ ℠ 🅫 🅪</code></span>",mask:/(?<= | |\(|\[|\{|:|^)(tm|sm|md|mc)(?= | |\.|\,|\;|\:|\)|\]|\}|$)/,replace:e=>{switch(e){case"tm":return"™";case"sm":return"℠";case"md":return"🅫";case"mc":return"🅪";default:return" "}},nbMoved:-1,category:"symbol"},{slug:"fractions",name:(0,a.__)("Fractions","consistency"),description:(0,a.__)("Replaces fractions with fraction symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>1/2 3/5 1/9</code> <span style='font-size:20px'>→</span> <code>½ ⅗ ⅑</code></span>",mask:/[1-9]\/[1-9]/,replace:e=>{switch(e){case"1/4":return"¼";case"1/2":return"½";case"3/4":return"¾";case"1/3":return"⅓";case"2/3":return"⅔";case"1/5":return"⅕";case"2/5":return"⅖";case"3/5":return"⅗";case"4/5":return"⅘";case"1/6":return"⅙";case"5/6":return"⅚";case"1/8":return"⅛";case"3/8":return"⅜";case"5/8":return"⅝";case"7/8":return"⅞";case"1/7":return"⅐";case"1/9":return"⅑";default:return" "}},nbMoved:-2,category:"symbol"},{slug:"percentages",name:(0,a.__)("Percentages","consistency"),description:(0,a.__)("Replaces percentages with percentages symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>0/0 0/00 0/000</code> <span style='font-size:20px'>→</span> <code>% ‰ ‱</code></span>",mask:/(0\/0|0\/00|0\/000)(?= | |\.|\,|\;|\:|\)|\]|\})(.)/,replace:e=>{const t=e.substring(0,e.length-1),s=e.substring(e.length-1,e.length);switch(t){case"0/0":return"%"+s;case"0/00":return"‰"+s;case"0/000":return"‱"+s;default:return" "+s}},nbMoved:e=>-(e.substring(0,e.length-1).length-1),category:"symbol"}],B=[{slug:"apostrophe",label:(0,a.__)("Apostrophes","consistency"),description:(0,a.__)("Fixes related to apostrophes.","consistency")},{slug:"quotation",label:(0,a.__)("Quotation marks","consistency"),description:(0,a.__)("Fixes related to quotation marks.","consistency")},{slug:"dash",label:(0,a.__)("Dashes","consistency"),description:(0,a.__)("Fixes related to dashes.","consistency")},{slug:"suffixe",label:(0,a.__)("Suffixes","consistency"),description:(0,a.__)("Fixes related to suffixes.","consistency")},{slug:"space",label:(0,a.__)("Spaces","consistency"),description:(0,a.__)("Fixes related to spaces.","consistency")},{slug:"case",label:(0,a.__)("Case","consistency"),description:(0,a.__)("Fixes related to case.","consistency")},{slug:"ellipsis",label:(0,a.__)("Ellipsis","consistency"),description:(0,a.__)("Fixes related to ellipsis.","consistency")},{slug:"symbol",label:(0,a.__)("Symbols","consistency"),description:(0,a.__)("Fixes related to symbols.","consistency")}],R=()=>(0,c.createElement)(i.Panel,{className:"GlobalSettingPanel"},(0,c.createElement)(i.PanelHeader,null,(0,c.createElement)("strong",null,(0,a.__)("Global correction rules","consistency"),(0,c.createElement)(C,null))),[...B].map(((e,t)=>(0,c.createElement)(i.PanelBody,{key:t,title:(0,a.__)(e.label,"consistency"),initialOpen:!1},[...Q].filter((t=>t.category===e.slug)).map(((e,t)=>(0,c.createElement)(F,{key:t,settingSlug:e.slug,settingName:e.name,settingDescription:{__html:e.description}}))))))),{canUser:P}=(0,s.select)("core"),{getBlock:W,getBlocks:M,getBlockAttributes:$,getSelectionStart:A,isTyping:z}=(0,s.select)("core/block-editor"),{updateBlock:q,selectionChange:I,updateBlockAttributes:L}=(0,s.dispatch)("core/block-editor"),D=t=>{const{currentBlockId:s,isPasting:n,authorizedRuleSettings:o}=t;let c=Q.filter((e=>!0===o?.find((t=>t.slug===e.slug))?.value));const a=W(s);if(!(e=>{const t=f(e);return!!h.includes(t)})(s)||!(e=>{const t=k(e);return!(!t||!t.hasOwnProperty("content")||""===t.content)})(s))return;let r=$(s),i=!1;Object.entries(c).forEach((([t,o])=>{e.g.consistencyLoop++,(t=>{e.g.consistencyLoop>=100&&(t=>{const s=m(t);b(t,{...s,attributes:{...s.attributes,content:s.attributes.content.slice(-2)}}),e.g.consistency_loop=0,console.log("Consistency - a memory leak has occured during the fix of the following block:",s)})(t)})(s);let c,l=o.replace,u="",p="",d=0,g=r.content,h=(e=>e.replace(/<\b(code|pre|kbd)\b>.*?<\/\b(code|pre|kbd)\b>/gi,"").replace(/(<([^>]+)>)/gi,""))(g),_=!1;if(z()||(_=o.mask.test(h)),z()){c=A(a.name),d=c?.offset||0;const e=(e=>{const t=document.querySelector(`#block-${e}`);if(null===t)return;const s=document.getSelection(),n=s?.getRangeAt(0);if(!n.collapsed)return;const o=n.cloneRange(),c=document.createTextNode("\0");o.insertNode(c);let a=t?.innerHTML?.indexOf("\0");c.parentNode.removeChild(c),t.normalize();const r=(t?.innerHTML.match(/&nbsp;/g)||[]).length;return r>0&&(a=a-6*r+r),a})(s)||d,t=h.match(o.mask);if(null===t||0===t.length)return;const n=t[0].length||1;u=g.substring(0,e-n),p=g.substring(e-n,g.length),_=o.mask.test(h)&&o.mask.test(p)}if(!_)return;if((e=>!!y.includes(e.slug))(o)&&(l=((e,t,s)=>{const n=e.replace.charAt(0),o=e.replace.charAt(e.replace.length-1),c=e.replace.substring(1,e.replace.indexOf("$"))||"";let a="";0!==[...e.replace.matchAll(/[0-9]/g)].length&&(a=e.replace.substring([...e.replace.matchAll(/[0-9]/g)].pop().index+1,e.replace.length-1));const r=new RegExp(`${n}`,"g"),i=new RegExp(`${o}`,"g");return(t.match(r)||[]).length===(t.match(i)||[]).length?n+c:a+o})(o,g)),0!==d&&(g=u+p.replace(o.mask,l)),0===d&&(g=g.replace(o.mask,o.replace)),e.g.previousFixCanceled)return void(e.g.previousFixCanceled=!1);if(e.g.previousFixCanceled||(q(s,{...a,attributes:{...a.attributes,content:g}}),i=!0),0===d||n)return;const f="function"==typeof o.nbMoved?o.nbMoved(p):o.nbMoved;f<0&&I(s,c.attributeKey,d+f,d+f),f>0&&I(s,c.attributeKey,d+1+f,d+f),0===f&&I(s,c.attributeKey,d,d)})),e.g.consistencyLoop=0},{getSelectedBlockClientId:N,isTyping:G,getBlockAttributes:O}=(0,s.select)("core/block-editor");(0,t.registerPlugin)("consistency-custom-sidebar",{render:()=>{const e=P("create","users");return(0,c.createElement)(c.Fragment,null,(0,c.createElement)(r.PluginSidebar,{name:"consistency-custom-sidebar",title:(0,a.__)("Consistency","consistency"),icon:l},(0,c.createElement)(g,null),e&&(0,c.createElement)(R,null)),(0,c.createElement)(r.PluginSidebarMoreMenuItem,{target:"consistency-custom-sidebar"},(0,a.__)("Consistency Settings","consistency")))}}),o()((()=>{e.g.consistencyLoop=0,e.g.previousFixCanceledContent="",e.g.previousFixCanceled=!1,e.g.contentPasted=!1,e.g.isPasteEventAttached=!1,e.g.isEditorInIframe=null!==document.querySelector('iframe[name="editor-canvas"]'),document.querySelector("#editor")?.addEventListener("keydown",(t=>{90===t.keyCode&&(t.ctrlKey||t.metaKey)&&(e.g.previousFixCanceled=!0,t.preventDefault())})),(0,s.subscribe)((()=>{(()=>{if((()=>{const t=null!==document.querySelector('iframe[name="editor-canvas"]');e.g.isEditorInIframe!==t&&(e.g.isEditorInIframe=t,e.g.isPasteEventAttached=!1)})(),!e.g.isPasteEventAttached){if(e.g.isEditorInIframe){const t=document.querySelector('iframe[name="editor-canvas"]');t&&(t.onload=()=>{(t.contentDocument||t.contentWindow.document).addEventListener("paste",(t=>{e.g.contentPasted=!0,e.g.isPasteEventAttached=!0}))},"complete"===t.contentWindow.document.readyState&&t.onload())}e.g.isEditorInIframe||document.querySelector("#editor")?.addEventListener("paste",(t=>{e.g.contentPasted=!0,e.g.isPasteEventAttached=!0}))}})();const{onTheFly:t,onPaste:n}=(()=>{const e={onTheFly:!1,onPaste:!1},t=(0,s.select)(u.store).getCurrentUser(),n=v("root","user",t?.id||0,"consistency_plugin_user_settings"),o=n?.meta?.consistency_plugin_user_settings;return e.onTheFly=o?.find((e=>"on_the_fly"===e.slug))?.value||!1,e.onPaste=o?.find((e=>"on_paste"===e.slug))?.value||!1,e})();if(!t&&!n)return;const o=x();if(void 0===o)return;if(e.g.contentPasted&&n)return void(t=>{const{authorizedRuleSettings:s}=t;let n=Q.filter((e=>!0===s?.find((t=>t.slug===e.slug))?.value));const o=M(),c=o.flatMap((({innerBlocks:e,...t})=>e.map((e=>({...t,...e}))))),a=o.reduce(((e,t)=>{let s=t.attributes?.content;return h.includes(t.name)&&void 0!==s?(Object.entries(n).forEach((([e,t])=>{if(y.includes(t.slug)){const e=t.mask.toString().match(/(?<=\/).+?(?=\/)/g)[0],n=new RegExp(`(?<!=)${e}(?!>)([^${e}]*)(?<!=)${e}(?!>)`,"g");s=s.replaceAll(n,t.replace)}if(!y.includes(t.slug)){const e=t.mask.toString(),n=new RegExp(e.substring(1,e.length-1),"g");s=s.replaceAll(n,t.replace)}})),void 0!==s&&(e[t.clientId]={content:s}),e):e}),{});Object.keys(a).length>0&&e.g.contentPasted&&(e.g.contentPasted=!1,L(Object.keys(a),a,!0)),e.g.contentPasted=!1,c.forEach((e=>{if(!h.includes(e.name))return;const t=e.clientId;e?.clientId&&D({currentBlockId:t,theRegs:n,isPasting:!0})}))})({authorizedRuleSettings:o});const c=N();if(null===c||e.g.contentPasted||!t)return;const a=O(c);a.hasOwnProperty("content")&&e.g.previousFixCanceledContent===a.content||(e.g.previousFixCanceledContent=a.content,G()&&D({currentBlockId:c,isPasting:!1,authorizedRuleSettings:o}))}))}))})();
     1(()=>{"use strict";var e={n:t=>{var s=t&&t.__esModule?()=>t.default:()=>t;return e.d(s,{a:s}),s},d:(t,s)=>{for(var n in s)e.o(s,n)&&!e.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:s[n]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.React,s=window.wp.plugins,n=window.wp.domReady;var o=e.n(n);const c=window.wp.element,r=(0,c.createContext)(),a=({children:e})=>{const[s]=(0,c.useState)(null!==document.querySelector('iframe[name="editor-canvas"]')),[n,o]=(0,c.useState)(!1),[a,i]=(0,c.useState)(""),[l,u]=(0,c.useState)([]),p=(0,c.useRef)(!1);return(0,t.createElement)(r.Provider,{value:{isEditorInIframe:s,isPreviousFixCanceled:n,setPreviousFixCanceled:o,previousFixCanceledContent:a,setPreviousFixCanceledContent:i,blocksToBeProcessed:l,setBlocksToBeProcessed:u,isContentPastedRef:p}},e)},i=r,l=window.wp.i18n,u=window.wp.editor,p=window.wp.data,d=window.wp.components,g=()=>(0,t.createElement)(d.Icon,{icon:(0,t.createElement)("svg",{version:"1.1",id:"consistency-plugin",x:"0px",y:"0px",width:"24px",height:"24px",viewBox:"0 0 24 24",enableBackground:"new 0 0 24 24"},(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"4",y1:"20",x2:"7",y2:"20"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"14",y1:"20",x2:"21",y2:"20"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"6.9",y1:"15",x2:"13.8",y2:"15"}),(0,t.createElement)("line",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",x1:"10.2",y1:"6.3",x2:"16",y2:"20"}),(0,t.createElement)("polyline",{fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",points:"5,20 11,4 13,4 20,20 "}))}),y=window.wp.coreData,m=window.wp.notices,h=e=>{const{settingSlug:s,settingName:n,settingDescription:o}=e,{currentUser:c}=(0,p.useSelect)((e=>({currentUser:e(y.store).getCurrentUser()})),[]),r=c&&c.id,[a,i]=(0,y.useEntityProp)("root","user","meta",r),{saveEditedEntityRecord:u}=(0,p.useDispatch)(y.store),{createNotice:g}=(0,p.useDispatch)(m.store);return(0,t.createElement)(d.ToggleControl,{label:n,help:(0,t.createElement)("span",{dangerouslySetInnerHTML:o}),checked:a?.consistency_plugin_user_settings?.find((e=>e.slug===s))?.value||!1,onChange:e=>{let t=a?.consistency_plugin_user_settings.map((t=>s===t.slug?{...t,value:e}:t));t?.find((e=>e.slug===s))||t.push({slug:s,value:e}),i({...a,consistency_plugin_user_settings:t}),u("root","user",r,{...a,meta:t}),g((0,l.__)("info","consistency"),e?sprintf((0,l.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,l.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}})},b=()=>(0,t.createElement)(d.Panel,{className:"UserSettingPanel"},(0,t.createElement)(d.PanelHeader,null,(0,t.createElement)("strong",null,(0,l.__)("Settings for my account","consistency")),(0,t.createElement)("br",null)),(0,t.createElement)("div",{style:{padding:16}},(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(h,{settingSlug:"on_the_fly",settingName:(0,l.__)("On-the-fly autocorrect","consistency"),settingDescription:{__html:(0,l.__)("Enable/disable on-the-fly autocorrect","consistency")}})),(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(h,{settingSlug:"on_paste",settingName:(0,l.__)("On paste autocorrect","consistency"),settingDescription:{__html:(0,l.__)("Enable/disable autocorrect on paste","consistency")}})))),_=window.wp.richText,f=e=>"string"==typeof e||e instanceof String,k=e=>"object"==typeof e&&e instanceof _.RichTextData,{getEntityRecord:v}=(0,p.select)("core"),{updateBlock:w}=(0,p.dispatch)("core/block-editor"),S=()=>{const e=v("root","site");return e?.consistency_plugin_localization_management||!0},x=()=>{const e=v("root","site");return e?.language||"en_US"},C=()=>{const e=x(),s=S()?(0,l.__)(` (${e} locale)`,"consistency"):(0,l.__)(" (all locales)","consistency");return(0,t.createElement)("span",{style:{fontWeight:"normal",fontStyle:"italic",fontSize:"smaller"}},s)},E=["regularToCurlyQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces","regularToGermanQuotes","regularToGermanBookStyleQuotes"],T=[{slug:"quote",incompatibleWith:[]},{slug:"2hyphens",incompatibleWith:[]},{slug:"3hyphens",incompatibleWith:[]},{slug:"4hyphens",incompatibleWith:[]},{slug:"ordinalNumberSuffix",incompatibleWith:[]},{slug:"regularToCurlyQuotes",incompatibleWith:["regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToGermanBookStyleQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToFrenchQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotesWithoutSpaces"]},{slug:"regularToFrenchQuotesWithoutSpaces",incompatibleWith:["regularToCurlyQuotes","regularToGermanQuotes","regularToGermanBookStyleQuotes","regularToFrenchQuotes"]},{slug:"curlyToFrenchQuotes",incompatibleWith:["regularToCurlyQuotes"]},{slug:"breakingSpace",incompatibleWith:["spaceBefore"]},{slug:"noSpaceBefore",incompatibleWith:["spaceBefore"]},{slug:"spaceBefore",incompatibleWith:["breakingSpace","noSpaceBefore"]},{slug:"noBreakingSpaceAfter",incompatibleWith:[]},{slug:"noNonBreakingSpaceAfter",incompatibleWith:[]},{slug:"capitalizeFirstSentenceLetter",incompatibleWith:[]},{slug:"etcThreeDots",incompatibleWith:[]},{slug:"etcTwoDots",incompatibleWith:[]},{slug:"etcEllipsis",incompatibleWith:[]},{slug:"ellipsis",incompatibleWith:[]},{slug:"symbolInACircle",incompatibleWith:[]},{slug:"symbolInSmallCapsAndSuperscriptStyle",incompatibleWith:[]},{slug:"fractions",incompatibleWith:[]},{slug:"percentages",incompatibleWith:[]}],B=[{slug:"quote",name:(0,l.__)("Straight Apostrophe","consistency"),description:(0,l.__)("Replace straight apostrophes with curly apostrophes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>'</code> <span style='font-size:20px'>→</span> <code>’</code></span>",mask:/\'/,replace:"’",nbMoved:0,category:"apostrophe"},{slug:"2hyphens",name:(0,l.__)("En Dash","consistency"),description:(0,l.__)("Replace two hyphens with an en dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>--</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">–</code></span>",mask:/(?:\-)\-/,replace:"–",nbMoved:-1,category:"dash"},{slug:"3hyphens",name:(0,l.__)("Em Dash","consistency"),description:(0,l.__)("Replace three hyphens with an em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>---</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">—</code></span>",mask:/(?:–|\-\-)\-/,replace:"—",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"4hyphens",name:(0,l.__)("Two-Em Dash","consistency"),description:(0,l.__)("Replace four hyphens with two-em dash:","consistency")+"<span aria-hidden='true' style='display:block;'><code>----</code> <span style='font-size:20px'>→</span> <code style=\"font-family:sans-serif;\">⸺</code></span>",mask:/(?:—|–\-|\-\-\-)\-/,replace:"⸺",nbMoved:e=>-(e.length-1),category:"dash"},{slug:"ordinalNumberSuffix",name:(0,l.__)("Ordinal Number Suffix","consistency"),description:(0,l.__)("Add the sup HTML tag to ordinal number suffixes","consistency")+"<span aria-hidden='true' style='display:block;'><code>1st</code> <span style='font-size:20px'>→</span> <code>1<sup>st</sup></code></span>",mask:/([10-9]{1,20})(th|nd|rd|e|er|res|d|ds|de|des)( | |\.|\,|\;)/,replace:"$1<sup>$2</sup>$3",nbMoved:0,category:"suffixe"},{slug:"regularToCurlyQuotes",name:(0,l.__)("Curly Quotes","consistency"),description:(0,l.__)("Replace straight quotes with curly quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>“ ”</code></span>",mask:/"/,replace:"“$1”",nbMoved:0,category:"quotation"},{slug:"regularToGermanQuotes",name:(0,l.__)("German Quotes","consistency"),description:(0,l.__)("Replace straight quotes with german quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>„ “</code></span>",mask:/"/,replace:"„$1“",nbMoved:0,category:"quotation"},{slug:"regularToGermanBookStyleQuotes",name:(0,l.__)("German Book-Style Quotes","consistency"),description:(0,l.__)("Replace straight quotes with german book-style quotes:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>» «</code></span>",mask:/"/,replace:"»$1«",nbMoved:0,category:"quotation"},{slug:"regularToFrenchQuotes",name:(0,l.__)("French Quotes with Spaces","consistency"),description:(0,l.__)("Replace straight quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/"/,replace:"« $1 »",nbMoved:1,category:"quotation"},{slug:"regularToFrenchQuotesWithoutSpaces",name:(0,l.__)("French Quotes","consistency"),description:(0,l.__)("Replace straight quotes with french quotes without spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>\" \"</code> <span style='font-size:20px'>→</span> <code>« »</code></span>",mask:/"/,replace:"«$1»",nbMoved:0,category:"quotation"},{slug:"curlyToFrenchQuotes",name:(0,l.__)("Curly Quotes to French Quotes","consistency"),description:(0,l.__)("Replace curly quotes with french quotes with non-breaking spaces:","consistency")+"<span aria-hidden='true' style='display:block;'><code>“ ”</code> <span style='font-size:20px'>→</span> <code>«   »</code></span>",mask:/“.*?”/,replace:e=>`« ${e.substring(1,e.length-1)} »`,nbMoved:0,category:"quotation"},{slug:"breakingSpace",name:(0,l.__)("Breaking Spaces","consistency"),description:sprintf((0,l.__)("Replace a breaking space followed by a character from this list:%1$s with a non-breaking space","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/ ([\?|\!|\:|\;|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:0,category:"space"},{slug:"noSpaceBefore",name:(0,l.__)("No Space Before","consistency"),description:sprintf((0,l.__)("Add a non-breaking space before a character from this list:%1$s having no space before","consistency"),"<br /><code>? ! : ; » € $ £ ¥ ₽ 元 %</code><br />"),mask:/(?<! | |&nbsp;)([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,replace:" $1",nbMoved:1,category:"space"},{slug:"spaceBefore",name:(0,l.__)("Space Before","consistency"),description:(0,l.__)("Remove any space preceding a character from this list:","consistency")+"<span style='display:block;'><code>? ! : ; %</code></span>",mask:/([ | ])(?=[\?|\!|\:|\;|\%])(.)/,replace:"$2",nbMoved:-1,category:"space"},{slug:"noBreakingSpaceAfter",name:(0,l.__)("No Breaking Space After","consistency"),description:sprintf((0,l.__)("Add a breaking space after a character from this list:%1$s when followed with another character","consistency"),"<br /><code>, … ) ]</code><br />"),mask:/([\,|…|\)|\]])(?! | |\.|\,|\d|$)(.)/,replace:"$1 $2",nbMoved:1,category:"space"},{slug:"noNonBreakingSpaceAfter",name:(0,l.__)("No Non-Breaking Space After","consistency"),description:(0,l.__)("Add a non-breaking space after open french quote having no space after","consistency"),mask:/(«)(?! | |&nbsp;)/,replace:"$1 ",nbMoved:0,category:"space"},{slug:"capitalizeFirstSentenceLetter",name:(0,l.__)("First Sentence Letter","consistency"),description:(0,l.__)("Capitalize the first letter of a sentence","consistency"),mask:/(^[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț])|(?<=[\.|\?|\!|…] )[a-záàâäãåăçéèêëíìîïñóòôöõúùûüýÿæœșț]/,replace:e=>e.toUpperCase(),nbMoved:0,category:"case"},{slug:"etcThreeDots",name:(0,l.__)('Three Dots Following "etc"',"consistency"),description:(0,l.__)('Replace 3 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc...</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3})/i,replace:e=>e.substring(0,3)+".",nbMoved:-2,category:"ellipsis"},{slug:"etcTwoDots",name:(0,l.__)('Two Dots Following "etc"',"consistency"),description:(0,l.__)('Replace 2 dots placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc..</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{2})/i,replace:e=>e.substring(0,2)+".",nbMoved:-1,category:"ellipsis"},{slug:"etcEllipsis",name:(0,l.__)('Ellipsis Following "etc"',"consistency"),description:(0,l.__)('Replace the ellipsis placed after the abbreviation "etc" with a period:',"consistency")+"<span aria-hidden='true' style='display:block;'><code>etc…</code> <span style='font-size:20px'>→</span> <code>etc.</code></span>",mask:/etc(\.{3}|…)/i,replace:e=>e.substring(0,3)+".",nbMoved:0,category:"ellipsis"},{slug:"ellipsis",name:(0,l.__)("Ellipsis","consistency"),description:(0,l.__)("Replaces 3 dots with ellipsis:","consistency")+"<span aria-hidden='true' style='display:block;'><code>...</code> <span style='font-size:20px'>→</span> <code>…</code></span>",mask:/\.{3}/,replace:"…",nbMoved:-2,category:"ellipsis"},{slug:"symbolInACircle",name:(0,l.__)("Symbol in a Circle","consistency"),description:(0,l.__)("Replaces 1 character placed in parentheses with a symbol","consistency")+"<span aria-hidden='true' style='display:block;'><code>(c) (p) (r)</code> <span style='font-size:20px'>→</span> <code>© ℗ ®</code></span>",mask:/(\([c|p|r])(\))/,replace:e=>{switch(e[1]){case"c":return"©";case"p":return"℗";case"r":return"®"}return" "},nbMoved:-2,category:"symbol"},{slug:"symbolInSmallCapsAndSuperscriptStyle",name:(0,l.__)("Symbol in Small Caps and Superscript Style","consistency"),description:(0,l.__)("Replaces 2-character abbreviations with a symbol in small caps and superscript style","consistency")+"<span aria-hidden='true' style='display:block;'><code>tm sm md mc</code> <span style='font-size:20px'>→</span> <code>™ ℠ 🅫 🅪</code></span>",mask:/(?<= | |\(|\[|\{|:|^)(tm|sm|md|mc)(?= | |\.|\,|\;|\:|\)|\]|\}|$)/,replace:e=>{switch(e){case"tm":return"™";case"sm":return"℠";case"md":return"🅫";case"mc":return"🅪";default:return" "}},nbMoved:-1,category:"symbol"},{slug:"fractions",name:(0,l.__)("Fractions","consistency"),description:(0,l.__)("Replaces fractions with fraction symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>1/2 3/5 1/9</code> <span style='font-size:20px'>→</span> <code>½ ⅗ ⅑</code></span>",mask:/[1-9]\/[1-9]/,replace:e=>{switch(e){case"1/4":return"¼";case"1/2":return"½";case"3/4":return"¾";case"1/3":return"⅓";case"2/3":return"⅔";case"1/5":return"⅕";case"2/5":return"⅖";case"3/5":return"⅗";case"4/5":return"⅘";case"1/6":return"⅙";case"5/6":return"⅚";case"1/8":return"⅛";case"3/8":return"⅜";case"5/8":return"⅝";case"7/8":return"⅞";case"1/7":return"⅐";case"1/9":return"⅑";default:return" "}},nbMoved:-2,category:"symbol"},{slug:"percentages",name:(0,l.__)("Percentages","consistency"),description:(0,l.__)("Replaces percentages with percentages symbols:","consistency")+"<span aria-hidden='true' style='display:block;'><code>0/0 0/00 0/000</code> <span style='font-size:20px'>→</span> <code>% ‰ ‱</code></span>",mask:/(0\/0|0\/00|0\/000)(?= | |\.|\,|\;|\:|\)|\]|\})(.)/,replace:e=>{const t=e.substring(0,e.length-1),s=e.substring(e.length-1,e.length);switch(t){case"0/0":return"%"+s;case"0/00":return"‰"+s;case"0/000":return"‱"+s;default:return" "+s}},nbMoved:e=>-(e.substring(0,e.length-1).length-1),category:"symbol"}],P=()=>{const e=(()=>{const e=v("root","site");return e?.consistency_plugin_settings||[]})();return S()?e.filter((e=>W(e.slug))):e},F=()=>B.filter((e=>!0===P()?.find((t=>t.slug===e.slug))?.value)),{getBlockName:R,getBlockAttributes:Q}=(0,p.select)("core/block-editor"),W=e=>{const t=x();return!(void 0===localesByRules||!localesByRules.hasOwnProperty(e))&&localesByRules[e].includes(t)},M=e=>{const t=T.find((t=>t.slug===e));return!!t&&t.incompatibleWith.some((e=>P()?.find((t=>t.slug===e))?.value))},$=e=>{const{settingSlug:s,settingName:n,settingDescription:o}=e,{createNotice:c}=(0,p.useDispatch)(m.store);if(S()&&!W(s))return"";const[r,a]=(0,y.useEntityProp)("root","site","consistency_plugin_settings",void 0),{saveEditedEntityRecord:i}=(0,p.useDispatch)(y.store);return(0,t.createElement)(d.PanelRow,null,(0,t.createElement)(d.ToggleControl,{label:n,help:(0,t.createElement)("span",{dangerouslySetInnerHTML:o}),checked:r?.find((e=>e.slug===s))?.value||!1,disabled:M(s),onChange:e=>{let t=r.map((t=>s===t.slug?{...t,value:e}:t));a(t),i("root","site",void 0,t),c((0,l.__)("info","consistency"),e?sprintf((0,l.__)('"%1$s" Correction is enabled',"consistency"),n):sprintf((0,l.__)('"%1$s" Correction is disabled',"consistency"),n),{isDismissible:!0,type:"snackbar",speak:!0})}}))},q=[{slug:"apostrophe",label:(0,l.__)("Apostrophes","consistency"),description:(0,l.__)("Fixes related to apostrophes.","consistency")},{slug:"quotation",label:(0,l.__)("Quotation marks","consistency"),description:(0,l.__)("Fixes related to quotation marks.","consistency")},{slug:"dash",label:(0,l.__)("Dashes","consistency"),description:(0,l.__)("Fixes related to dashes.","consistency")},{slug:"suffixe",label:(0,l.__)("Suffixes","consistency"),description:(0,l.__)("Fixes related to suffixes.","consistency")},{slug:"space",label:(0,l.__)("Spaces","consistency"),description:(0,l.__)("Fixes related to spaces.","consistency")},{slug:"case",label:(0,l.__)("Case","consistency"),description:(0,l.__)("Fixes related to case.","consistency")},{slug:"ellipsis",label:(0,l.__)("Ellipsis","consistency"),description:(0,l.__)("Fixes related to ellipsis.","consistency")},{slug:"symbol",label:(0,l.__)("Symbols","consistency"),description:(0,l.__)("Fixes related to symbols.","consistency")}],z=()=>(0,t.createElement)(d.Panel,{className:"GlobalSettingPanel"},(0,t.createElement)(d.PanelHeader,null,(0,t.createElement)("strong",null,(0,l.__)("Global correction rules","consistency"),(0,t.createElement)(C,null))),[...q].map(((e,s)=>(0,t.createElement)(d.PanelBody,{key:s,title:(0,l.__)(e.label,"consistency"),initialOpen:!1},[...B].filter((t=>t.category===e.slug)).map(((e,s)=>(0,t.createElement)($,{key:s,settingSlug:e.slug,name:e.name,settingDescription:{__html:e.description}}))))))),{canUser:A}=(0,p.select)("core"),I=()=>{const e=A("create","users");return(0,t.createElement)(t.Fragment,null,(0,t.createElement)(u.PluginSidebar,{name:"consistency-custom-sidebar",title:(0,l.__)("Consistency","consistency"),icon:g},(0,t.createElement)(b,null),e&&(0,t.createElement)(z,null)),(0,t.createElement)(u.PluginSidebarMoreMenuItem,{target:"consistency-custom-sidebar"},(0,l.__)("Consistency Settings","consistency")))},{getBlock:D,getBlocks:L,getBlockAttributes:N,getSelectionStart:O,isTyping:G,getBlockSelectionStart:j}=(0,p.select)("core/block-editor"),{selectionChange:U,updateBlockAttributes:H}=(0,p.dispatch)("core/block-editor"),K=e=>{const{currentBlockId:t,isPasting:s,isPreviousFixCanceled:n,setPreviousFixCanceled:o,blocksToBeProcessed:c}=e;if(!(e=>{const{currentBlockId:t,blocksToBeProcessed:s}=e,n=R(t);return!!s.includes(n)})({currentBlockId:t,blocksToBeProcessed:c})||!(e=>{const t=Q(e);return!(!t||!t.hasOwnProperty("content")||!f(t.content)&&!k(t.content))})(t))return;let r=F();const a=D(t);let i=N(t),l=!1;Object.entries(r).forEach((([e,c])=>{if(l)return;let r,u=c.replace,p="",d="",g=0,y=0,m="object"==typeof i.content?i.content.text:i.content,h=(e=>e.replace(/<\b(code|pre|kbd)\b>.*?<\/\b(code|pre|kbd)\b>/gi,"").replace(/(<([^>]+)>)/gi,""))(m),b=!1;if(G()||(b=c.mask.test(h)),G()){r=O(),g=r?.offset||document.getSelection()?.anchorOffset||0,y=(e=>{const t=document.querySelector(`#block-${e}`);if(null===t)return;const s=t.querySelector('[contenteditable="true"]')||t,n=document.getSelection(),o=n?.getRangeAt(0);if(!o||!o.collapsed)return;const c=o.cloneRange(),r=document.createTextNode("\0");c.insertNode(r);const a=s.textContent||"";let i=a.indexOf("\0");r.parentNode.removeChild(r),s.normalize();const l=(s?.innerHTML.match(/&nbsp;/g)||[]).length;return l>0&&(i=a.replace(/&nbsp;/g," ").indexOf("\0"),i=i-6*l+l),i})(t)||g;const e=h.match(c.mask);if(null===e||0===e.length)return;const s=e[0].length||1;p=h.substring(0,y-s),d=h.substring(y-s,h.length),b=c.mask.test(h)&&c.mask.test(d)}if(!b)return;if((e=>!!E.includes(e.slug))(c)&&(u=((e,t,s)=>{const n=e.replace.charAt(0),o=e.replace.charAt(e.replace.length-1),c=e.replace.substring(1,e.replace.indexOf("$"))||"";let r="";0!==[...e.replace.matchAll(/[0-9]/g)].length&&(r=e.replace.substring([...e.replace.matchAll(/[0-9]/g)].pop().index+1,e.replace.length-1));const a=new RegExp(`${n}`,"g"),i=new RegExp(`${o}`,"g");return(t.match(a)||[]).length===(t.match(i)||[]).length?n+c:r+o})(c,m)),0!==y&&(m=p+d.replace(c.mask,u)),0===y&&(m=m.replace(c.mask,c.replace)),n)return void o(!1);if(n||(l=(e=>{const{block:t,currentBlockId:s,blockAttributes:n,blockContent:o}=e;let c;if(k(n.content)){const e=(0,_.create)({...n.content,text:o});c=new _.RichTextData(e)}return f(n.content)&&(c=o),void 0!==c&&(w(s,{...t,attributes:{...n,content:c}}),!0)})({block:a,currentBlockId:t,blockAttributes:i,blockContent:m})),0===g||s)return;const v="function"==typeof c.nbMoved?c.nbMoved(d):c.nbMoved||0;let S=r.hasOwnProperty("attributeKey")?r.attributeKey:"content";v<0&&U(t,S,g+v,g+v),v>0&&U(t,S,g+1+v,g+v),0===v&&U(t,S,g,g)}))},{getSelectedBlockClientId:J,isTyping:V,getBlockAttributes:X}=(0,p.select)("core/block-editor"),Y=window.wp.blocks,Z=()=>((()=>{const{isEditorInIframe:e,isContentPastedRef:t}=(0,c.useContext)(i),s=(0,c.useRef)(!1);(0,c.useEffect)((()=>{if(s.current)return;const n=e=>{t.current=!0},o=e=>(e.addEventListener("paste",n),()=>{e.removeEventListener("paste",n)}),c=e?document.querySelector('iframe[name="editor-canvas"]'):document.querySelector("#editor");if(!c)return;const r=o(c);return e&&"complete"===c.contentWindow.document.readyState&&o(c),s.current=!0,r}),[e])})(),(()=>{const{isEditorInIframe:e,setPreviousFixCanceled:t}=(0,c.useContext)(i),s=(0,c.useRef)(!1);(0,c.useEffect)((()=>{if(s.current)return;const n=e=>{90===e.keyCode&&(e.ctrlKey||e.metaKey)&&t(!0)},o=e=>(e.addEventListener("keydown",n),()=>{e.removeEventListener("keydown",n)}),c=e?document.querySelector('iframe[name="editor-canvas"]'):document.querySelector("#editor");if(!c)return;const r=o(c);return e&&"complete"===c.contentWindow.document.readyState&&o(c),s.current=!0,r}),[t])})(),(()=>{const{setBlocksToBeProcessed:e}=(0,c.useContext)(i);(0,c.useEffect)((()=>{let t=(0,Y.getBlockTypes)().filter((e=>e.attributes&&e.attributes.content&&("string"===e.attributes.content.type||"rich-text"===e.attributes.content.type)));t=t.filter((e=>!e.name.startsWith("kadence/")));const s=["core/html","core/freeform"];t=t.filter((e=>!s.includes(e.name))),console.log("Consistency - Processed blocks:",t),e(t.map((e=>e.name)))}),[])})(),(()=>{const{isPreviousFixCanceled:e,setPreviousFixCanceled:t,previousFixCanceledContent:s,setPreviousFixCanceledContent:n,blocksToBeProcessed:o,isContentPastedRef:r}=(0,c.useContext)(i);void 0===r.current&&(r.current=!1),(0,c.useEffect)((()=>{const c=(0,p.subscribe)((()=>{const{onTheFly:c,onPaste:a}=(()=>{const e={onTheFly:!1,onPaste:!1},t=(0,p.select)(y.store).getCurrentUser(),s=v("root","user",t?.id||0,"consistency_plugin_user_settings"),n=s?.meta?.consistency_plugin_user_settings;return e.onTheFly=n?.find((e=>"on_the_fly"===e.slug))?.value||!1,e.onPaste=n?.find((e=>"on_paste"===e.slug))?.value||!1,e})();if(!c&&!a)return;const i=P();if(void 0===i)return;if(a&&!0===r.current)return r.current=!1,void(e=>{const{isPreviousFixCanceled:t,setPreviousFixCanceled:s,localizedRuleSettings:n,blocksToBeProcessed:o}=e;let c=F();const r=L(),a=r.flatMap((({innerBlocks:e,...t})=>e.map((e=>({...t,...e}))))),i=r.reduce(((e,t)=>{let s=t.attributes?.content;return o.includes(t.name)&&void 0!==s?(Object.entries(c).forEach((([e,t])=>{if(E.includes(t.slug)){const e=t.mask.toString().match(/(?<=\/).+?(?=\/)/g)[0],n=new RegExp(`(?<!=)${e}(?!>)([^${e}]*)(?<!=)${e}(?!>)`,"g");s=s.replaceAll(n,t.replace)}if(!E.includes(t.slug)){const e=t.mask.toString(),n=new RegExp(e.substring(1,e.length-1),"g");s=s.replaceAll(n,t.replace)}})),void 0!==s&&(e[t.clientId]={content:s}),e):e}),{});Object.keys(i).length>0&&H(Object.keys(i),i,!0),a.forEach((e=>{if(!o.includes(e.name))return;const n=e.clientId;e?.clientId&&K({currentBlockId:n,isPasting:!0,isPreviousFixCanceled:t,setPreviousFixCanceled:s,blocksToBeProcessed:o})}))})({isPreviousFixCanceled:e,setPreviousFixCanceled:t,localizedRuleSettings:i,blocksToBeProcessed:o});const l=J();if(null===l||!c)return;const u=X(l);u&&(u.hasOwnProperty("content")&&s===u.content||(n(u.content),V()&&K({currentBlockId:l,isPasting:!1,isPreviousFixCanceled:e,setPreviousFixCanceled:t,blocksToBeProcessed:o})))}));return()=>c()}),[e,t,s,n,o,r])})(),(0,t.createElement)(I,null)),ee=()=>(0,t.createElement)(a,null,(0,t.createElement)(Z,null));o()((()=>{(0,s.registerPlugin)("consistency-custom-sidebar",{render:ee})}))})();
  • consistency/trunk/consistency.php

    r3135255 r3142172  
    44 * Plugin URI:        https://www.webaxones.com
    55 * Description:       Fixes typographic and punctuation consistency
    6  * Version:           1.7.1
     6 * Version:           1.8.0
    77 * Requires at least: 6.1
    88 * Requires PHP:      7.4
  • consistency/trunk/includes/Plugin.php

    r3135255 r3142172  
    3333    protected static function setConstants(): void
    3434    {
    35         defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.7.1' );
     35        defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.8.0' );
    3636        defined( __NAMESPACE__ . '\PLUGIN_URL' ) || define( __NAMESPACE__ . '\PLUGIN_URL', plugin_dir_url( __DIR__ ) );
    3737        defined( __NAMESPACE__ . '\PLUGIN_PATH' ) || define( __NAMESPACE__ . '\PLUGIN_PATH', plugin_dir_path( __DIR__ ) );
  • consistency/trunk/package.json

    r3135255 r3142172  
    11{
    22    "name": "consistency",
    3     "version": "1.7.1",
     3    "version": "1.8.0",
    44    "description": "",
    55    "main": "index.js",
  • consistency/trunk/readme.txt

    r3135255 r3142172  
    44Requires at least: 6.1
    55Tested up to: 6.6
    6 Stable tag: 1.7.1
     6Stable tag: 1.8.0
    77Requires PHP: 7.4
    88License: GPL-3.0-or-later
     
    7070== Changelog ==
    7171
     72= 1.8.0 =
     73* Update: code refactoring (replace global variables with global context, some functions with custom hooks, and allow to process more blocks)
     74
    7275= 1.7.1 =
    7376* Fix: Ensures in all cases to only use rules authorized by local parameters
  • consistency/trunk/src/app/checks.js

    r3135255 r3142172  
    11/**
    2  * Summary: Various checks functions.
     2 * @summary: Various checks functions.
    33 *
    4  * @description This file contains functions for various checks.
     4 * This file contains functions for various checks.
    55 * @author Loïc Antignac.
    66 */
     
    1414 * External dependencies
    1515 */
    16 import { getCurrentLocale } from './data'
    17 import { regsWithPair } from '../config/regsWithPair'
    18 import { processedBlocks } from '../config/processedBlocks'
    19 import { aMemoryLeakHasOccured } from './helpers'
    20 import { ruleIncompatibilities } from '../config/ruleIncompatibilities'
    21 import { getAuthorizedRuleSettings } from './data'
     16import { fetchCurrentLocale } from './data'
     17import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs'
     18import { incompatibilities } from '../config/incompatibilities'
     19import { getLocalizedRuleSettings } from './helpers'
     20import { isString, isRichTextData } from './utils'
    2221
    2322const { getBlockName, getBlockAttributes } = select( 'core/block-editor' )
    24 
    2523
    2624/**
     
    3230export const isUsedByLocale = settingSlug => {
    3331
    34     const currentLocale = getCurrentLocale()
     32    const currentLocale = fetchCurrentLocale()
    3533    if ( localesByRules !== undefined && localesByRules.hasOwnProperty( settingSlug ) ) {
    3634        return localesByRules[settingSlug].includes( currentLocale )
     
    4139
    4240/**
    43  * Checks if the current block is one of those to be checked or not
     41 * Checks if the current block is one of those to be processed or not
    4442 *
    45  * @param {string} currentBlockId currentBlockId current active block ID
    46  * @return {boolean} Should the block be checked?
     43 * @param {Object} props - The props object containing the necessary data.
     44 * @param {string} props.currentBlockId - The ID of the current block.
     45 * @param {Array} props.blocksToBeProcessed - The blocks to be processed.
     46 * @return {boolean} Should the block be processed?
    4747 */
    48 export const blockShouldBeChecked = currentBlockId => {
     48export const shouldProcessBlock = props => {
     49
     50    const { currentBlockId, blocksToBeProcessed } = props
    4951
    5052    const blockName = getBlockName( currentBlockId )
    51     if ( processedBlocks.includes( blockName ) ) {
     53   
     54    if ( blocksToBeProcessed.includes( blockName ) ) {
    5255        return true
    5356    }
     
    5760
    5861/**
    59  * Checks if the current block can technically be verified or not
     62 * Checks if the current block can technically be processed or not
    6063 *
    6164 * @param {string} currentBlockId currentBlockId current active block ID
    62  * @return {boolean} Can the block be checked?
     65 * @return {boolean} Can the block be processed?
    6366 */
    64 export const blockCanTechnicallyBeChecked = currentBlockId => {
     67export const canProcessBlock = currentBlockId => {
    6568
    6669    const blockAttributes = getBlockAttributes( currentBlockId )
    67     if ( blockAttributes && blockAttributes.hasOwnProperty( 'content' ) && '' !== blockAttributes.content ) {
     70
     71    if ( ! blockAttributes || ! blockAttributes.hasOwnProperty( 'content' ) ) return false
     72
     73    if ( isString( blockAttributes.content ) || isRichTextData( blockAttributes.content ) ) {
    6874        return true
    6975    }
     76   
    7077    return false
    7178
     
    8087export const regDealWithPair = reg => {
    8188
    82     if ( regsWithPair.includes( reg.slug ) ) {
     89    if ( pairedCharacterSlugs.includes( reg.slug ) ) {
    8390        return true
    8491    }
    8592    return false
    86 
    87 }
    88 
    89 /**
    90  * Checks if a memory leak has occurred during the fix of one block if the consistency loop count exceeds 150 and stops processing.
    91  * @param {string} currentBlockId - The ID of the current block.
    92  */
    93 export const checkIfAMemoryLeakHasOccuredAndStopProcessing = currentBlockId => {
    94    
    95     if ( global.consistencyLoop >= 100 ) {
    96         aMemoryLeakHasOccured( currentBlockId )
    97     }
    9893
    9994}
     
    107102export const checkRuleCompatibility = currentRule => {
    108103
    109     // Get the current rule from the ruleIncompatibilities array
    110     const rule = ruleIncompatibilities.find( rule => rule.slug === currentRule )
     104    // Get the current rule from the incompatibilities array
     105    const rule = incompatibilities.find( rule => rule.slug === currentRule )
    111106    if ( ! rule ) return false
    112107
     
    114109    return rule.incompatibleWith.some( incompatibleRule => {
    115110        // Return the state of the incompatible rule
    116         return getAuthorizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value
     111        return getLocalizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value
    117112    } )
    118113
  • consistency/trunk/src/app/data.js

    r3135255 r3142172  
    11/**
    2  * Summary: Data retrieval.
     2 * @summary: Data retrieval.
    33 *
    4  * @description This file contains functions that retrieve data from database.
     4 * This file contains functions that retrieve data from database.
    55 * @author Loïc Antignac.
    66 */
     
    1010 */
    1111import { store as coreStore } from '@wordpress/core-data'
    12 import { select } from '@wordpress/data'
    13 
    14 const { getEntityRecord } = select( 'core' )
     12import { select, dispatch } from '@wordpress/data'
     13import { RichTextData, create } from '@wordpress/rich-text'
    1514
    1615/**
    1716 * External dependencies
    1817 */
    19 import { isUsedByLocale } from './checks'
     18import { isString, isRichTextData } from './utils'
     19
     20const { getEntityRecord } = select( 'core' )
     21const { updateBlock } = dispatch( 'core/block-editor' )
    2022
    2123/**
     
    3739 * @returns {Object} The rules settings object.
    3840 */
    39 export const getRuleSettings = () => {
     41export const fetchRuleSettings = () => {
    4042   
    4143    const siteEntity = getEntityRecord( 'root', 'site' )
    42     const ruleSetting = siteEntity?.consistency_plugin_settings || []
     44    const ruleSettings = siteEntity?.consistency_plugin_settings || []
    4345
    44     return ruleSetting
    45 
    46 }
    47 
    48 /**
    49  * Retrieves the authorized rules settings.
    50  *
    51  * @returns {Array} The authorized rules settings.
    52  */
    53 export const getAuthorizedRuleSettings = () => {
    54    
    55     const ruleSetting = getRuleSettings()
    56 
    57     const authorizedRuleSettings = isLocalizationEnabled()
    58         ? ruleSetting.filter( setting => isUsedByLocale( setting.slug ) ) : ruleSetting
    59 
    60     return authorizedRuleSettings
     46    return ruleSettings
    6147
    6248}
     
    6753 * @return {object} userSettings Current user settings: userSettings.onTheFly, userSettings.onPaste
    6854 */
    69 export const getCurrentUserSettings = () => {
     55export const fetchCurrentUserSettings = () => {
    7056
    7157    const userSettings = {
     
    8975 * @return {string} currentLocale Current active site locale
    9076 */
    91 export const getCurrentLocale = () => {
     77export const fetchCurrentLocale = () => {
    9278
    9379    const siteEntity = getEntityRecord( 'root', 'site' )
     
    9682
    9783}
     84
     85/**
     86 * Updates the text content of a block depending on its type.
     87 * Blocks can have text content stored as a string or as a RichTextData object.
     88 *
     89 * @param {Object} props - The props object.
     90 * @param {Object} props.block - The block object.
     91 * @param {string} props.currentBlockId - The ID of the current block.
     92 * @param {Object} props.blockAttributes - The attributes of the block.
     93 * @param {string|Object} props.blockContent - The content of the block.
     94 * @returns {boolean} - Returns true if the block text content was updated successfully, otherwise false.
     95 */
     96export const updateBlockTextContent = props => {
     97
     98    const { block, currentBlockId, blockAttributes, blockContent } = props
     99   
     100    let newBlockTextContent
     101   
     102    if ( isRichTextData( blockAttributes.content ) ) {
     103       
     104        const newRichTextValue = create( {
     105            ...blockAttributes.content,
     106            text: blockContent
     107        } )
     108        newBlockTextContent = new RichTextData( newRichTextValue )
     109
     110    }
     111   
     112    if ( isString( blockAttributes.content ) ) {
     113        newBlockTextContent = blockContent
     114    }
     115
     116    if ( newBlockTextContent !== undefined ) {
     117           
     118        updateBlock( currentBlockId, {
     119            ...block,
     120            attributes: { ...blockAttributes, content: newBlockTextContent }
     121        } )
     122       
     123        return true
     124    }
     125   
     126    return false
     127
     128}
  • consistency/trunk/src/app/fixes.js

    r3135255 r3142172  
    11/**
    2  * Summary: Block processing.
     2 * @summary: Block processing.
    33 *
    4  * @description This file contains the main processing operations operating on the blocks to adapt the texts according to the configured rules.
     4 * This file contains the main processing operations operating on the blocks to adapt the texts according to the configured rules.
    55 * @author Loïc Antignac.
    66 */
     
    1515 */
    1616import { getAllInnersFromParents, getOnlyTextFromBlockContent, getCursorPositionInInnerHTML } from './utils'
    17 import { getReplacementStringForPairs } from './helpers'
    18 import { rules } from '../config/rules'
    19 import { regsWithPair } from '../config/regsWithPair'
    20 import { processedBlocks } from '../config/processedBlocks'
    21 
    22 import { isUsedByLocale, blockShouldBeChecked, blockCanTechnicallyBeChecked, regDealWithPair, checkIfAMemoryLeakHasOccuredAndStopProcessing } from './checks'
    23 
    24 const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping } = select( 'core/block-editor' )
    25 const { updateBlock, selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' )
    26 
    27 /**
    28  * Fixes the content of a block based on regular expressions.
     17import { getReplacementStringForPairs, getLocalizedRules } from './helpers'
     18import { updateBlockTextContent } from './data'
     19import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs'
     20import { shouldProcessBlock, canProcessBlock, regDealWithPair } from './checks'
     21
     22const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping, getBlockSelectionStart } = select( 'core/block-editor' )
     23const { selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' )
     24
     25
     26
     27/**
     28 * Fixes the current block based on the provided rules and attributes.
    2929 *
    30  * @param {Object} props - The props object containing the necessary data.
     30 * @param {Object} props - The props object.
    3131 * @param {string} props.currentBlockId - The ID of the current block.
    32  * @param {Object} props.theRegs - An object containing regular expressions.
    33  * @param {boolean} props.isPasting - Indicates whether the content is being pasted.
     32 * @param {boolean} props.isPasting - Indicates if the content is being pasted.
     33 * @param {boolean} props.isPreviousFixCanceled - Indicates if the previous fix was canceled.
     34 * @param {function} props.setPreviousFixCanceled - The function to set the previous fix canceled state.
     35 * @param {Array} props.blocksToBeProcessed - The blocks to be processed: we pass them as props to avoid using global context since we are not in a component.
    3436 */
    3537export const fixIt = props => {
    36     const { currentBlockId, isPasting, authorizedRuleSettings } = props
    37    
    38     // Get the regex of all rules
    39     let theRegs = rules.filter( reg => true === authorizedRuleSettings?.find( s => s.slug === reg.slug )?.value )
     38
     39    const { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } = props
     40   
     41    // Check if the current block should be processed and can be processed
     42    if ( ! shouldProcessBlock( { currentBlockId, blocksToBeProcessed } ) || ! canProcessBlock( currentBlockId ) ) return
     43   
     44    // Get the relevant rules
     45    let localizedRules = getLocalizedRules()
    4046   
    4147    // Get the current block
    4248    const block = getBlock( currentBlockId )
    43 
    44     // Check if the current block should be checked and can be checked
    45     if ( ! blockShouldBeChecked( currentBlockId ) || ! blockCanTechnicallyBeChecked( currentBlockId ) ) return
    4649   
    4750    // Get the attributes of the current block
    4851    let blockAttributes = getBlockAttributes( currentBlockId )
    49 
    50     // We don't apply several fixes on the same typing event
     52   
     53    // We don't apply several fixes on the same typing event so we need a variable to check if the content has already been updated
    5154    let contentUpdated = false
    5255
    53     // Loop on regular expressions
    54     Object.entries( theRegs ).forEach( ( [ _, reg ] ) => {
    55 
    56         // Stop correction if block content isn't concerned by the current site locale (language)
    57         // if ( ! isUsedByLocale( reg.slug ) || contentUpdated ) return
    58         global.consistencyLoop ++
    59    
    60         // If the loop is too long, we stop it to avoid infinite loop
    61         checkIfAMemoryLeakHasOccuredAndStopProcessing( currentBlockId )
     56    // Loop on localized rules to check if the block content matches one regex
     57    Object.entries( localizedRules ).forEach( ( [ _, reg ] ) => {
     58
     59        // Stop correction if block content has already been updated
     60        if ( contentUpdated ) return
    6261
    6362        let replaceWithThis = reg.replace
     
    6564        let lastPart = ''
    6665        let cursorPosition = 0
    67         let blockContent = blockAttributes.content
     66        let cursorPositionInsideHTML = 0
    6867        let selectionStart
    6968
     69        let blockContent = ( typeof blockAttributes.content === 'object' ) ? blockAttributes.content.text : blockAttributes.content
     70       
    7071        // Remove 'code' 'pre' and 'kbd' and other HTML tags from block content
    7172        let textContent = getOnlyTextFromBlockContent( blockContent )
    72 
    73         // Check if block content is concerned by the regex in the case of a pasted content
    74         // (isTyping is false but subscribe detected a paste event)
     73       
     74        // Check if the block's text content matches the regex in the case of pasted content
     75        // (isTyping is false but the subscription detected a paste event)
    7576        let isConcerned = false
    7677        if ( ! isTyping() ) {
    7778            isConcerned = reg.mask.test( textContent )
    7879        }
    79 
    80         // Content splitting in case of typing on the fly to allow the user to undo a correction
    81         // If isTyping is false, it is the case of a pasted content, so we do not deal with possible undos of the user
     80       
     81        // Splitting content during real-time typing to allow the user to undo a correction
     82        // If isTyping is false, it indicates pasted content, so we don't handle potential undos by the user
    8283        if ( isTyping() ) {
    83 
     84           
    8485            // Get cursor position in textContent (without tags): needed for further cursor repositioning
    85             selectionStart = getSelectionStart( block.name )
    86             cursorPosition = selectionStart?.offset || 0
     86            selectionStart = getSelectionStart()
     87           
     88            cursorPosition = selectionStart?.offset || document.getSelection()?.anchorOffset || 0
    8789
    8890            // Get cursor position in HTML (with tags): needed to cut in 2 parts at the right position
    89             const cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition
    90 
     91            cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition
     92           
    9193            // If the rule depends on previous characters, we need to separate the string taking those characters into account
    9294            const captureGroups = textContent.match( reg.mask )
     95           
    9396            if( null === captureGroups || 0 === captureGroups.length ) return
     97           
    9498            const lengthToGoBack = captureGroups[0].length || 1
    9599
    96100            // Split the string to process only the part from the cursor position to the end
    97             firstPart = blockContent.substring( 0, cursorPositionInsideHTML - lengthToGoBack )
    98             lastPart = blockContent.substring( cursorPositionInsideHTML - lengthToGoBack, blockContent.length )
    99    
     101            firstPart = textContent.substring( 0, cursorPositionInsideHTML - lengthToGoBack )
     102            lastPart = textContent.substring( cursorPositionInsideHTML - lengthToGoBack, textContent.length )
     103                   
    100104            // If first part of the string matches but not the lastPart,
    101105            // it means that a character has been typed uncorrected voluntarily before with CTRL Z/CMD Z
     
    111115            replaceWithThis = getReplacementStringForPairs( reg, blockContent, replaceWithThis )
    112116        }
    113 
     117       
    114118        // Concat strings
    115         if ( 0 !== cursorPosition ) {
     119        if ( 0 !== cursorPositionInsideHTML ) {
    116120            blockContent = firstPart + lastPart.replace( reg.mask, replaceWithThis )
    117121        }
    118122
    119123        // Pasted content innerBlocks case: no selection, no cursor position so the whole block is fixed
    120         if ( 0 === cursorPosition ) {
     124        if ( 0 === cursorPositionInsideHTML ) {
    121125            blockContent = blockContent.replace( reg.mask, reg.replace )
    122126        }
    123 
    124         // If CTRL Z was used just before, then we do not correct this time
    125         if ( global.previousFixCanceled ) {
    126             global.previousFixCanceled = false
     127       
     128        // If CTRL Z was used just before, skip the correction this time
     129        if ( isPreviousFixCanceled ) {
     130            setPreviousFixCanceled( false )
    127131            return
    128132        }
    129        
    130         // Update block if previous fix was not canceled
    131         if ( ! global.previousFixCanceled ) {
    132             updateBlock( currentBlockId, {
    133                 ...block,
    134                 attributes: { ...block.attributes, content: blockContent }
    135             } )
    136             contentUpdated = true
    137         }
    138 
     133           
     134        // Update block text content if previous fix was not canceled
     135        if ( ! isPreviousFixCanceled ) {
     136            contentUpdated = updateBlockTextContent( { block, currentBlockId, blockAttributes, blockContent } )
     137        }
     138   
    139139        // Cursor repositioning:
    140140        if ( 0 === cursorPosition || isPasting ) return
     
    142142        // Get the number of characters moved by the replacement: needed for cursor repositioning.
    143143        // If the number depends on the replaced string length, we use a function to get it
    144         const nbMoved = typeof reg.nbMoved === 'function' ? reg.nbMoved( lastPart ) : reg.nbMoved
    145 
     144        const nbMoved = typeof reg.nbMoved === 'function' ? reg.nbMoved( lastPart ) : reg.nbMoved || 0
     145       
     146        let attributeKey = selectionStart.hasOwnProperty( 'attributeKey' ) ? selectionStart.attributeKey : 'content'
     147   
    146148        // If the replaced string had more characters than the new string, the cursor has moved forward, so it must be moved back
    147149        // Eg: ... replaced with … removes 2 characters
    148150        if ( nbMoved < 0 ) {
    149             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )
     151            selectionChange( currentBlockId, attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )
    150152        }
    151153       
     
    153155        // Eg: "" replaced with «  » adds 2 characters
    154156        if ( nbMoved > 0 ) {
    155             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )
     157            selectionChange( currentBlockId, attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )
    156158        }
    157159
    158160        if ( 0 === nbMoved ) {
    159             selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition, cursorPosition )
     161            selectionChange( currentBlockId, attributeKey, cursorPosition, cursorPosition )
    160162        }
    161163
    162164    } )
    163165
    164     global.consistencyLoop = 0
    165 
    166166}
    167167
     
    171171export const fixAll = props => {
    172172
    173     const { authorizedRuleSettings } = props
    174 
    175     // Get the regex of all rules
    176     let theRegs = rules.filter( reg => true === authorizedRuleSettings?.find( s => s.slug === reg.slug )?.value )
    177 
     173    const { isPreviousFixCanceled, setPreviousFixCanceled, localizedRuleSettings, blocksToBeProcessed } = props
     174
     175    // Get the relevant rules
     176    let localizedRules = getLocalizedRules()
     177   
    178178    // Get all blocks generated by pasting (which does not integrate innerBlocks)
    179179    const allBlocks = getBlocks()
    180 
     180   
    181181    // Get all innerBlocks for a later bulk selection process that will generate their fix
    182182    const allInners = getAllInnersFromParents( allBlocks )
    183 
     183   
    184184    // Loop on all parents blocks
    185185    const updates = allBlocks.reduce( ( acc, block ) => {
     
    187187        let newContent = block.attributes?.content
    188188       
    189         // If the block is not one of the blocks authorized to be processed (list in rules.js) or if the content is undefined, we do nothing
    190         if ( ! processedBlocks.includes( block.name )
     189        // If the block is not one of the blocks authorized to be processed (list in global context) or if the content is undefined, we do nothing
     190        if ( ! blocksToBeProcessed.includes( block.name )
    191191            || undefined === newContent ) {
    192192            return acc
    193193        }
    194194   
    195         Object.entries( theRegs ).forEach( ( [ _, reg ] ) => {
    196 
    197             // If the rule is not used by the locale, we do nothing
    198             // if ( ! isUsedByLocale( reg.slug ) ) return
     195        Object.entries( localizedRules ).forEach( ( [ _, reg ] ) => {
    199196
    200197            // If the rule is a pair rule, we use a specific regex
    201             if ( regsWithPair.includes( reg.slug ) ) {
     198            if ( pairedCharacterSlugs.includes( reg.slug ) ) {
    202199                const singleCharacterOfPair = reg.mask.toString().match( /(?<=\/).+?(?=\/)/g )[0]
    203200                const realReg = new RegExp( `(?<!\=)${singleCharacterOfPair}(?!>)([^${singleCharacterOfPair}]*)(?<!\=)${singleCharacterOfPair}(?!>)`, 'g' )
     
    206203
    207204            // If the rule is not a pair rule, we use the regex as it is
    208             if ( ! regsWithPair.includes( reg.slug ) ) {
     205            if ( ! pairedCharacterSlugs.includes( reg.slug ) ) {
    209206                const stringRegex = reg.mask.toString()
    210207                const regWithGlobalFlag = new RegExp( stringRegex.substring( 1, stringRegex.length - 1 ), 'g' )
    211208                newContent = newContent.replaceAll( regWithGlobalFlag, reg.replace )
    212209            }
    213 
     210           
    214211        } )
    215212
     
    220217        return acc
    221218    }, {} )
    222 
     219   
    223220    // Update all parents blocks
    224     if ( Object.keys( updates ).length > 0 && global.contentPasted ) {
    225         global.contentPasted = false
     221    if ( Object.keys( updates ).length > 0 ) {
    226222        updateBlockAttributes( Object.keys( updates ), updates, true );
    227223    }
    228     global.contentPasted = false
    229224
    230225    // Select all innerBlocks to trigger their correction, then deselect all by selecting the first block
    231226    const isPasting = true
    232227    allInners.forEach( block => {
    233         if ( ! processedBlocks.includes( block.name ) ) return
     228
     229        if ( ! blocksToBeProcessed.includes( block.name ) ) return
    234230        const currentBlockId = block.clientId
    235         block?.clientId && fixIt( { currentBlockId, theRegs, isPasting } )
     231        block?.clientId && fixIt( { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } )
     232
    236233    } )
    237234
  • consistency/trunk/src/app/helpers.js

    r3122806 r3142172  
    11/**
    2  * Summary: Specific functions varied and correlated to the application.
     2 * @summary: Specific functions varied and correlated to the application.
    33 *
    4  * @description This file contains specific functions that depends on other parts of the application.
     4 * This file contains specific functions that depends on other parts of the application.
    55 * @author Loïc Antignac.
    66 */
    77
    88/**
    9  * WordPress dependencies
     9 * External dependencies
    1010 */
    11 import { select, dispatch } from '@wordpress/data'
    12 
    13 const { getBlock } = select( 'core/block-editor' )
    14 const { updateBlock } = dispatch( 'core/block-editor' )
     11import { isUsedByLocale } from './checks'
     12import { fetchRuleSettings, isLocalizationEnabled } from './data'
     13import { rules } from '../config/rules'
    1514
    1615/**
     
    5453
    5554/**
    56  * Stop the process in the regex loop if a code error generates an infinite loop
    57  * by removing last 2 characters and adding a message in the console
    58  *
    59  * @param {string} currentBlockId currentBlockId current active block ID
     55 * Retrieves the localized rules settings.
     56 *
     57 * @returns {Array} The localized rules settings.
    6058 */
    61 export const aMemoryLeakHasOccured = currentBlockId => {
     59export const getLocalizedRuleSettings = () => {
     60   
     61    const ruleSettings = fetchRuleSettings()
    6262
    63     const block = getBlock( currentBlockId )
     63    const localizedRuleSettings = isLocalizationEnabled()
     64        ? ruleSettings.filter( setting => isUsedByLocale( setting.slug ) )
     65        : ruleSettings
    6466
    65     updateBlock( currentBlockId, {
    66         ...block,
    67         attributes: { ...block.attributes, content: block.attributes.content.slice( -2 ) }
    68     } )
    69 
    70     global.consistency_loop = 0
    71     console.log( 'Consistency - a memory leak has occured during the fix of the following block:', block )
     67    return localizedRuleSettings
    7268
    7369}
    7470
     71
    7572/**
    76  * Checks the editor location and updates the global accordingly.
     73 * Retrieves localized rules based on the current rule settings.
     74 * @returns {Array} An array of localized rules.
    7775 */
    78 const updatePasteEventGlobalDependingOnEditorLocation = () => {
    79    
    80     // Recheck if the editor is in an iframe or not
    81     const isEditorStillInIframe = document.querySelector( 'iframe[name="editor-canvas"]' ) !== null ? true : false
     76export const getLocalizedRules = () => {
    8277
    83     // If we have changed the editor location (in iframe or not), we need to reattach the paste event
    84     if ( global.isEditorInIframe !== isEditorStillInIframe ) {
    85         global.isEditorInIframe = isEditorStillInIframe
    86         global.isPasteEventAttached = false
    87     }
     78    const localizedRules = rules.filter( reg => true === getLocalizedRuleSettings()?.find( s => s.slug === reg.slug )?.value )
     79
     80    return localizedRules
    8881
    8982}
    90 
    91 /**
    92  * Attaches a 'paste' event listener to the editor or iframe document, depending on the editor location.
    93  * Updates the global variables to track if the paste event has been attached and if content has been pasted.
    94  */
    95 export const interceptPasteEventInEditor = () => {
    96    
    97     // Check if the editor location has changed and update the global accordingly
    98     updatePasteEventGlobalDependingOnEditorLocation()
    99 
    100     if ( global.isPasteEventAttached ) return
    101 
    102     if ( global.isEditorInIframe ) {
    103 
    104         // Select the iframe
    105         const iframe = document.querySelector( 'iframe[name="editor-canvas"]' )
    106 
    107         // Ensure the iframe is not null and has loaded
    108         if ( iframe ) {
    109             iframe.onload = () => {
    110                 // Access the iframe's document
    111                 const iframeDoc = iframe.contentDocument || iframe.contentWindow.document
    112 
    113                 // Attach the 'paste' event listener
    114                 iframeDoc.addEventListener('paste', e => {
    115                     global.contentPasted = true
    116                     global.isPasteEventAttached = true
    117                 } )
    118             }
    119    
    120             // If the iframe is already loaded by the time this code runs, manually trigger the onload handler
    121             if ( iframe.contentWindow.document.readyState === 'complete' ) {
    122                 iframe.onload()
    123             }
    124         }
    125 
    126     }
    127     if ( ! global.isEditorInIframe ) {
    128        
    129         // Attach the paste event to the editor
    130         document.querySelector( '#editor' )?.addEventListener( 'paste', e => {
    131             global.contentPasted = true
    132             global.isPasteEventAttached = true
    133         } )
    134 
    135     }
    136 
    137 }
  • consistency/trunk/src/app/utils.js

    r3122806 r3142172  
    11/**
    2  * Summary: Generic utility functions.
     2 * @summary: Generic utility functions.
    33 *
    4  * @description This file contains generic utility functions that are not tied to any specific part of the application.
     4 * This file contains generic utility functions that are not tied to any specific part of the application.
    55 * @author Loïc Antignac.
    66 */
     7
     8import { RichTextData } from '@wordpress/rich-text'
    79
    810/**
     
    5153    if ( null === currentActiveBlock ) return undefined
    5254
     55    // Get the inner content editable element (like <p> or <code>)
     56    // Sometimes the contenteditable is not the direct child of the block
     57    const editableContent = currentActiveBlock.querySelector('[contenteditable="true"]') || currentActiveBlock;
     58   
    5359    // Get current selection
    5460    const selection = document.getSelection()
     
    5662
    5763    // Return if user is selecting text instead of typing
    58     if ( ! _range.collapsed ) return
     64    if ( ! _range || ! _range.collapsed ) return
    5965
    6066    // Clone range to work on
     
    6773    range.insertNode( tempNode )
    6874
    69     // Get position of target inside active block HTML
    70     let cursorPositionInsideHTML = currentActiveBlock?.innerHTML?.indexOf( '\0' )
    71 
     75    // Get position of target inside the contenteditable element
     76    const textContent = editableContent.textContent || ''
     77    let cursorPositionInsideText = textContent.indexOf( '\0' )
     78   
    7279    // Remove temporary node and normalize cut node - important!
    7380    tempNode.parentNode.removeChild( tempNode )
    74     currentActiveBlock.normalize()
     81    editableContent.normalize()
    7582
    7683    // Remove non-breaking spaces in &nbsp; format from the count
    77     const nbNbsp = (currentActiveBlock?.innerHTML.match(/&nbsp;/g) || []).length
     84    const nbNbsp = (editableContent?.innerHTML.match(/&nbsp;/g) || []).length
    7885    if ( nbNbsp > 0 ) {
    79         cursorPositionInsideHTML = cursorPositionInsideHTML - ( nbNbsp * 6 ) + nbNbsp
     86        const textContentWithoutNbsp = textContent.replace(/&nbsp;/g, ' ');
     87        cursorPositionInsideText = textContentWithoutNbsp.indexOf('\0');
     88        cursorPositionInsideText = cursorPositionInsideText - ( nbNbsp * 6 ) + nbNbsp;
    8089    }
    8190
    82     return cursorPositionInsideHTML
     91    return cursorPositionInsideText
    8392}
     93
     94/**
     95 * Checks if a value is a string.
     96 *
     97 * @param {*} value - The value to check.
     98 * @returns {boolean} - Returns true if the value is a string, false otherwise.
     99 */
     100export const isString = value => typeof value === 'string' || value instanceof String
     101
     102/**
     103 * Checks if the given value is an instance of RichTextData.
     104 *
     105 * @param {*} value - The value to check.
     106 * @returns {boolean} - Returns true if the value is an instance of RichTextData, otherwise returns false.
     107 */
     108export const isRichTextData = value => typeof value === 'object' && value instanceof RichTextData
  • consistency/trunk/src/components/GlobalSettingPanel.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingPanel component.
     2 * @summary: GlobalSettingPanel component.
    33 *
    4  * @description This file contains the GlobalSettingPanel component used to display the plugin's global settings which contains the global correction rules in sidebar for administrators.
     4 * This file contains the GlobalSettingPanel component used to display the plugin's global settings which contains the global correction rules in sidebar for administrators.
    55 * @author Loïc Antignac.
    66 */
     
    4242                                    key={ key }
    4343                                    settingSlug={ rule.slug }
    44                                     settingName={ rule.name }
     44                                    name={ rule.name }
    4545                                    settingDescription={ {
    4646                                        __html: rule.description
     
    5252            } )
    5353        }
     54
    5455    </Panel>
    5556)
     57
    5658export default GlobalSettingPanel
  • consistency/trunk/src/components/GlobalSettingToggle.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingToggle component.
     2 * @summary: GlobalSettingToggle component.
    33 *
    4  * @description This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
     4 * This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/trunk/src/components/LocaleLabel.js

    r3135255 r3142172  
    11/**
    2  * Summary: GlobalSettingToggle component.
     2 * @summary: GlobalSettingToggle component.
    33 *
    4  * @description This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
     4 * This file contains the GlobalSettingToggle component used to display the plugin's global settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
     
    1414 * External dependencies
    1515 */
    16 import { getCurrentLocale, isLocalizationEnabled } from '../app/data'
     16import { fetchCurrentLocale, isLocalizationEnabled } from '../app/data'
    1717
    1818
    1919export const LocaleLabel = () => {
    2020
    21     const currentLocale = getCurrentLocale()
     21    const currentLocale = fetchCurrentLocale()
    2222
    2323    const areRulesLocalized = isLocalizationEnabled()
  • consistency/trunk/src/components/Settings.js

    r3122806 r3142172  
    11/**
    2  * Summary: SidebarSettings component.
     2 * @summary: SidebarSettings component.
    33 *
    4  * @description This file contains the SidebarSettings component used to display the plugin's settings sidebar in editor.
     4 * This file contains the SidebarSettings component used to display the plugin's settings sidebar in editor.
    55 * @author Loïc Antignac.
    66 */
     
    1010 */
    1111import { __ } from '@wordpress/i18n'
    12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/edit-post'
     12import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/editor'
    1313import { select } from '@wordpress/data'
    1414
     
    1616 * External dependencies
    1717 */
    18 import { ConsistencyIcon } from './Icon'
     18import { ConsistencyIcon } from './icon'
    1919import UserSettingPanel from './UserSettingPanel'
    2020import GlobalSettingPanel from './GlobalSettingPanel'
     21
    2122
    2223const { canUser } = select( 'core' )
  • consistency/trunk/src/components/UserSettingPanel.js

    r3130518 r3142172  
    11/**
    2  * Summary: UserSettingPanel component.
     2 * @summary: UserSettingPanel component.
    33 *
    4  * @description This file contains the UserSettingPanel component used to display the plugin's user settings in sidebar.
     4 * This file contains the UserSettingPanel component used to display the plugin's user settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/trunk/src/components/UserSettingToggle.js

    r3086094 r3142172  
    11/**
    2  * Summary: UserSettingToggle component.
     2 * @summary: UserSettingToggle component.
    33 *
    4  * @description This file contains the UserSettingToggle component used to display the plugin's user settings in sidebar.
     4 * This file contains the UserSettingToggle component used to display the plugin's user settings in sidebar.
    55 * @author Loïc Antignac.
    66 */
  • consistency/trunk/src/components/icon.js

    r3083522 r3142172  
    11/**
    2  * Summary: Consistency Logo.
     2 * @summary: Consistency Logo.
    33 *
    4  * @description This file contains the Consistency Logo component used to display the plugin's settings sidebar in editor.
     4 * This file contains the Consistency Logo component used to display the plugin's settings sidebar in editor.
    55 * @author Loïc Antignac.
    66 */
  • consistency/trunk/src/config/categories.js

    r3122806 r3142172  
    11/**
    2  * Summary: Rules categories.
     2 * @summary: Rules categories.
    33 *
    4  * @description This file contains an array of all correction rules categories.
     4 * This file contains an array of all correction rules categories.
    55 * @author Loïc Antignac.
    66 */
  • consistency/trunk/src/config/rules.js

    r3130518 r3142172  
    11/**
    2  * Summary: Correction rules.
     2 * @summary: Correction rules.
    33 *
    4  * @description This file contains an array of all correction rules with each regular expression used.
     4 * This file contains an array of all correction rules with each regular expression used.
     5 *
    56 * @author Loïc Antignac.
    67 */
  • consistency/trunk/src/index.js

    r3135255 r3142172  
    11/**
    2  * Main entry point for the WordPress Consistency plugin.
     2 * @summary: Main entry point for the WordPress Consistency plugin.
    33 *
    4  * This module registers a custom sidebar for settings and sets up event listeners for
    5  * state changes in the Gutenberg editor. It handles on-the-fly and on-paste text
    6  * consistency fixes based on user and global settings. It also manages several global
    7  * variables and states related to the plugin's operation.
     4 * This module registers a custom sidebar for Consistency settings
     5 * and wraps the ConsistencyPlugin component in a GlobalProvider
     6 * to provide global variables and states to the plugin's operation.
    87 *
    98 * @author Loïc Antignac.
     
    1413 */
    1514import { registerPlugin } from '@wordpress/plugins'
    16 import { subscribe, select } from '@wordpress/data'
    1715import domReady from '@wordpress/dom-ready'
    18 import { SidebarSettings } from './components/Settings'
    19 
    2016
    2117/**
    2218 * External dependencies
    2319 */
    24 import { fixIt, fixAll } from './app/fixes'
    25 import { getAuthorizedRuleSettings, getCurrentUserSettings } from './app/data'
    26 import { interceptPasteEventInEditor } from './app/helpers'
     20import { GlobalProvider } from './contexts/GlobalContext'
     21import ConsistencyPlugin from './components/ConsistencyPlugin'
    2722
    28 // Get the current selected block and its attributes
    29 const { getSelectedBlockClientId, isTyping, getBlockAttributes } = select( 'core/block-editor' )
    30 
    31 // Register the plugin to get the settings sidebar
    32 registerPlugin( 'consistency-custom-sidebar', {
    33     render: SidebarSettings,
    34 } )
     23const PluginWrapper = () => (
     24    <GlobalProvider>
     25        <ConsistencyPlugin />
     26    </GlobalProvider>
     27)
    3528
    3629domReady( () => {
    37 
    38     /**
    39      * Global object properties
    40      */
    41    
    42     // This global makes it possible to count the loops on the regex in order to trigger a cut on a possible infinite loop
    43     global.consistencyLoop = 0
    44     // This global is used to store the content of the block to avoid fixing it if it has not changed since we check at every state change
    45     global.previousFixCanceledContent = ''
    46     // This global is used to avoid new fixes when the user is undoing a fix with CTRL/CMD Z
    47     global.previousFixCanceled = false
    48     // This global is used to know if some content has been pasted in the editor
    49     global.contentPasted = false
    50     // Since we attach the paste event in a subscribe, we need to check if it is already attached
    51     global.isPasteEventAttached = false
    52     // Check if custom fields are active because the editor content is within an iframe when custom fields are inactive
    53     global.isEditorInIframe = document.querySelector( 'iframe[name="editor-canvas"]' ) !== null ? true : false
    54 
    55     // Intercept CTRL Z to cancel next fix: when the user is undoing a fix, we don't want to fix it again.
    56     document.querySelector( '#editor' )?.addEventListener( 'keydown', e => {
    57         if ( 90 === e.keyCode && ( e.ctrlKey || e.metaKey ) ) {
    58             global.previousFixCanceled = true
    59            
    60             e.preventDefault()
    61         }
    62     } )
    63    
    64     // Let’s listen for state changes
    65     subscribe( () => {
    66 
    67         // Intercept clipboard paste to fix all new blocks
    68         interceptPasteEventInEditor()
    69 
    70         // Get current user settings to check if we have to fix the content or to stop here
    71         const { onTheFly, onPaste } = getCurrentUserSettings()
    72         if ( ! onTheFly && ! onPaste ) return
    73 
    74         // Get fixing rules from site entity global settings
    75         const authorizedRuleSettings = getAuthorizedRuleSettings()
    76         if ( undefined === authorizedRuleSettings ) return
    77 
    78         // If content has been copied/pasted generating blocks, and if onPaste is enabled, we fix all blocks then stop here
    79         if ( global.contentPasted && onPaste ) {
    80             fixAll( { authorizedRuleSettings } )
    81             return
    82         }
    83 
    84         // Get current selected block
    85         const currentBlockId = getSelectedBlockClientId()
    86 
    87         // Stop here if no block is selected or if fixing on the fly is disabled
    88         if ( null === currentBlockId || global.contentPasted || ! onTheFly ) return
    89 
    90         // Don't try to fix block content if nothing has changed
    91         const blockAttributes = getBlockAttributes( currentBlockId )
    92         if ( blockAttributes.hasOwnProperty( 'content' ) && global.previousFixCanceledContent === blockAttributes.content ) {
    93             return
    94         }
    95 
    96         // Store the block content to avoid fixing it twice at the next state change
    97         global.previousFixCanceledContent = blockAttributes.content
    98 
    99         // Fixes the typography of current selected block
    100         const isPasting = false
    101         isTyping() && fixIt( { currentBlockId, isPasting, authorizedRuleSettings } )
    102    
    103     } )
     30    registerPlugin( 'consistency-custom-sidebar', { render: PluginWrapper } )
    10431} )
Note: See TracChangeset for help on using the changeset viewer.