Changeset 3142172
- Timestamp:
- 08/27/2024 09:42:05 AM (16 months ago)
- Location:
- consistency
- Files:
-
- 20 added
- 6 deleted
- 42 edited
- 1 copied
-
tags/1.8.0 (copied) (copied from consistency/trunk)
-
tags/1.8.0/build/index.asset.php (modified) (1 diff)
-
tags/1.8.0/build/index.js (modified) (1 diff)
-
tags/1.8.0/consistency.php (modified) (1 diff)
-
tags/1.8.0/includes/Plugin.php (modified) (1 diff)
-
tags/1.8.0/package.json (modified) (1 diff)
-
tags/1.8.0/readme.txt (modified) (2 diffs)
-
tags/1.8.0/src/app/checks.js (modified) (8 diffs)
-
tags/1.8.0/src/app/data.js (modified) (6 diffs)
-
tags/1.8.0/src/app/fixes.js (modified) (10 diffs)
-
tags/1.8.0/src/app/helpers.js (modified) (2 diffs)
-
tags/1.8.0/src/app/utils.js (modified) (4 diffs)
-
tags/1.8.0/src/components/ConsistencyPlugin.js (added)
-
tags/1.8.0/src/components/GlobalSettingPanel.js (modified) (3 diffs)
-
tags/1.8.0/src/components/GlobalSettingToggle.js (modified) (1 diff)
-
tags/1.8.0/src/components/LocaleLabel.js (modified) (2 diffs)
-
tags/1.8.0/src/components/Settings.js (modified) (3 diffs)
-
tags/1.8.0/src/components/UserSettingPanel.js (modified) (1 diff)
-
tags/1.8.0/src/components/UserSettingToggle.js (modified) (1 diff)
-
tags/1.8.0/src/components/icon.js (modified) (1 diff)
-
tags/1.8.0/src/config/categories.js (modified) (1 diff)
-
tags/1.8.0/src/config/incompatibilities.js (added)
-
tags/1.8.0/src/config/pairedCharacterSlugs.js (added)
-
tags/1.8.0/src/config/processedBlocks.js (deleted)
-
tags/1.8.0/src/config/regsWithPair.js (deleted)
-
tags/1.8.0/src/config/ruleIncompatibilities.js (deleted)
-
tags/1.8.0/src/config/rules.js (modified) (1 diff)
-
tags/1.8.0/src/contexts (added)
-
tags/1.8.0/src/contexts/GlobalContext.js (added)
-
tags/1.8.0/src/hooks (added)
-
tags/1.8.0/src/hooks/useCancelEventHandler.js (added)
-
tags/1.8.0/src/hooks/useEditorEffects.js (added)
-
tags/1.8.0/src/hooks/usePasteEventHandler.js (added)
-
tags/1.8.0/src/hooks/useSetAllowedBlocks.js (added)
-
tags/1.8.0/src/index.js (modified) (2 diffs)
-
trunk/build/index.asset.php (modified) (1 diff)
-
trunk/build/index.js (modified) (1 diff)
-
trunk/consistency.php (modified) (1 diff)
-
trunk/includes/Plugin.php (modified) (1 diff)
-
trunk/package.json (modified) (1 diff)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/app/checks.js (modified) (8 diffs)
-
trunk/src/app/data.js (modified) (6 diffs)
-
trunk/src/app/fixes.js (modified) (10 diffs)
-
trunk/src/app/helpers.js (modified) (2 diffs)
-
trunk/src/app/utils.js (modified) (4 diffs)
-
trunk/src/components/ConsistencyPlugin.js (added)
-
trunk/src/components/GlobalSettingPanel.js (modified) (3 diffs)
-
trunk/src/components/GlobalSettingToggle.js (modified) (1 diff)
-
trunk/src/components/LocaleLabel.js (modified) (2 diffs)
-
trunk/src/components/Settings.js (modified) (3 diffs)
-
trunk/src/components/UserSettingPanel.js (modified) (1 diff)
-
trunk/src/components/UserSettingToggle.js (modified) (1 diff)
-
trunk/src/components/icon.js (modified) (1 diff)
-
trunk/src/config/categories.js (modified) (1 diff)
-
trunk/src/config/incompatibilities.js (added)
-
trunk/src/config/pairedCharacterSlugs.js (added)
-
trunk/src/config/processedBlocks.js (deleted)
-
trunk/src/config/regsWithPair.js (deleted)
-
trunk/src/config/ruleIncompatibilities.js (deleted)
-
trunk/src/config/rules.js (modified) (1 diff)
-
trunk/src/contexts (added)
-
trunk/src/contexts/GlobalContext.js (added)
-
trunk/src/hooks (added)
-
trunk/src/hooks/useCancelEventHandler.js (added)
-
trunk/src/hooks/useEditorEffects.js (added)
-
trunk/src/hooks/usePasteEventHandler.js (added)
-
trunk/src/hooks/useSetAllowedBlocks.js (added)
-
trunk/src/index.js (modified) (2 diffs)
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:/(?<! | | )([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,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:/(«)(?! | | )/,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(/ /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:/(?<! | | )([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,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:/(«)(?! | | )/,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(/ /g)||[]).length;return l>0&&(i=a.replace(/ /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 4 4 * Plugin URI: https://www.webaxones.com 5 5 * Description: Fixes typographic and punctuation consistency 6 * Version: 1. 7.16 * Version: 1.8.0 7 7 * Requires at least: 6.1 8 8 * Requires PHP: 7.4 -
consistency/tags/1.8.0/includes/Plugin.php
r3135255 r3142172 33 33 protected static function setConstants(): void 34 34 { 35 defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1. 7.1' );35 defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.8.0' ); 36 36 defined( __NAMESPACE__ . '\PLUGIN_URL' ) || define( __NAMESPACE__ . '\PLUGIN_URL', plugin_dir_url( __DIR__ ) ); 37 37 defined( __NAMESPACE__ . '\PLUGIN_PATH' ) || define( __NAMESPACE__ . '\PLUGIN_PATH', plugin_dir_path( __DIR__ ) ); -
consistency/tags/1.8.0/package.json
r3135255 r3142172 1 1 { 2 2 "name": "consistency", 3 "version": "1. 7.1",3 "version": "1.8.0", 4 4 "description": "", 5 5 "main": "index.js", -
consistency/tags/1.8.0/readme.txt
r3135255 r3142172 4 4 Requires at least: 6.1 5 5 Tested up to: 6.6 6 Stable tag: 1. 7.16 Stable tag: 1.8.0 7 7 Requires PHP: 7.4 8 8 License: GPL-3.0-or-later … … 70 70 == Changelog == 71 71 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 72 75 = 1.7.1 = 73 76 * Fix: Ensures in all cases to only use rules authorized by local parameters -
consistency/tags/1.8.0/src/app/checks.js
r3135255 r3142172 1 1 /** 2 * Summary: Various checks functions.2 * @summary: Various checks functions. 3 3 * 4 * @descriptionThis file contains functions for various checks.4 * This file contains functions for various checks. 5 5 * @author Loïc Antignac. 6 6 */ … … 14 14 * External dependencies 15 15 */ 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' 16 import { fetchCurrentLocale } from './data' 17 import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs' 18 import { incompatibilities } from '../config/incompatibilities' 19 import { getLocalizedRuleSettings } from './helpers' 20 import { isString, isRichTextData } from './utils' 22 21 23 22 const { getBlockName, getBlockAttributes } = select( 'core/block-editor' ) 24 25 23 26 24 /** … … 32 30 export const isUsedByLocale = settingSlug => { 33 31 34 const currentLocale = getCurrentLocale()32 const currentLocale = fetchCurrentLocale() 35 33 if ( localesByRules !== undefined && localesByRules.hasOwnProperty( settingSlug ) ) { 36 34 return localesByRules[settingSlug].includes( currentLocale ) … … 41 39 42 40 /** 43 * Checks if the current block is one of those to be checked or not41 * Checks if the current block is one of those to be processed or not 44 42 * 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? 47 47 */ 48 export const blockShouldBeChecked = currentBlockId => { 48 export const shouldProcessBlock = props => { 49 50 const { currentBlockId, blocksToBeProcessed } = props 49 51 50 52 const blockName = getBlockName( currentBlockId ) 51 if ( processedBlocks.includes( blockName ) ) { 53 54 if ( blocksToBeProcessed.includes( blockName ) ) { 52 55 return true 53 56 } … … 57 60 58 61 /** 59 * Checks if the current block can technically be verified or not62 * Checks if the current block can technically be processed or not 60 63 * 61 64 * @param {string} currentBlockId currentBlockId current active block ID 62 * @return {boolean} Can the block be checked?65 * @return {boolean} Can the block be processed? 63 66 */ 64 export const blockCanTechnicallyBeChecked= currentBlockId => {67 export const canProcessBlock = currentBlockId => { 65 68 66 69 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 ) ) { 68 74 return true 69 75 } 76 70 77 return false 71 78 … … 80 87 export const regDealWithPair = reg => { 81 88 82 if ( regsWithPair.includes( reg.slug ) ) {89 if ( pairedCharacterSlugs.includes( reg.slug ) ) { 83 90 return true 84 91 } 85 92 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 }98 93 99 94 } … … 107 102 export const checkRuleCompatibility = currentRule => { 108 103 109 // Get the current rule from the ruleIncompatibilities array110 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 ) 111 106 if ( ! rule ) return false 112 107 … … 114 109 return rule.incompatibleWith.some( incompatibleRule => { 115 110 // Return the state of the incompatible rule 116 return get AuthorizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value111 return getLocalizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value 117 112 } ) 118 113 -
consistency/tags/1.8.0/src/app/data.js
r3135255 r3142172 1 1 /** 2 * Summary: Data retrieval.2 * @summary: Data retrieval. 3 3 * 4 * @descriptionThis file contains functions that retrieve data from database.4 * This file contains functions that retrieve data from database. 5 5 * @author Loïc Antignac. 6 6 */ … … 10 10 */ 11 11 import { store as coreStore } from '@wordpress/core-data' 12 import { select } from '@wordpress/data' 13 14 const { getEntityRecord } = select( 'core' ) 12 import { select, dispatch } from '@wordpress/data' 13 import { RichTextData, create } from '@wordpress/rich-text' 15 14 16 15 /** 17 16 * External dependencies 18 17 */ 19 import { isUsedByLocale } from './checks' 18 import { isString, isRichTextData } from './utils' 19 20 const { getEntityRecord } = select( 'core' ) 21 const { updateBlock } = dispatch( 'core/block-editor' ) 20 22 21 23 /** … … 37 39 * @returns {Object} The rules settings object. 38 40 */ 39 export const getRuleSettings = () => {41 export const fetchRuleSettings = () => { 40 42 41 43 const siteEntity = getEntityRecord( 'root', 'site' ) 42 const ruleSetting = siteEntity?.consistency_plugin_settings || []44 const ruleSettings = siteEntity?.consistency_plugin_settings || [] 43 45 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 61 47 62 48 } … … 67 53 * @return {object} userSettings Current user settings: userSettings.onTheFly, userSettings.onPaste 68 54 */ 69 export const getCurrentUserSettings = () => {55 export const fetchCurrentUserSettings = () => { 70 56 71 57 const userSettings = { … … 89 75 * @return {string} currentLocale Current active site locale 90 76 */ 91 export const getCurrentLocale = () => {77 export const fetchCurrentLocale = () => { 92 78 93 79 const siteEntity = getEntityRecord( 'root', 'site' ) … … 96 82 97 83 } 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 */ 96 export 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 1 1 /** 2 * Summary: Block processing.2 * @summary: Block processing. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 15 15 */ 16 16 import { 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 c ontent of a block based on regular expressions.17 import { getReplacementStringForPairs, getLocalizedRules } from './helpers' 18 import { updateBlockTextContent } from './data' 19 import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs' 20 import { shouldProcessBlock, canProcessBlock, regDealWithPair } from './checks' 21 22 const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping, getBlockSelectionStart } = select( 'core/block-editor' ) 23 const { selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' ) 24 25 26 27 /** 28 * Fixes the current block based on the provided rules and attributes. 29 29 * 30 * @param {Object} props - The props object containing the necessary data.30 * @param {Object} props - The props object. 31 31 * @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. 34 36 */ 35 37 export 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() 40 46 41 47 // Get the current block 42 48 const block = getBlock( currentBlockId ) 43 44 // Check if the current block should be checked and can be checked45 if ( ! blockShouldBeChecked( currentBlockId ) || ! blockCanTechnicallyBeChecked( currentBlockId ) ) return46 49 47 50 // Get the attributes of the current block 48 51 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 51 54 let contentUpdated = false 52 55 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 62 61 63 62 let replaceWithThis = reg.replace … … 65 64 let lastPart = '' 66 65 let cursorPosition = 0 67 let blockContent = blockAttributes.content66 let cursorPositionInsideHTML = 0 68 67 let selectionStart 69 68 69 let blockContent = ( typeof blockAttributes.content === 'object' ) ? blockAttributes.content.text : blockAttributes.content 70 70 71 // Remove 'code' 'pre' and 'kbd' and other HTML tags from block content 71 72 let textContent = getOnlyTextFromBlockContent( blockContent ) 72 73 // Check if block content is concerned by the regex in the case of apasted content74 // (isTyping is false but subscribedetected 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) 75 76 let isConcerned = false 76 77 if ( ! isTyping() ) { 77 78 isConcerned = reg.mask.test( textContent ) 78 79 } 79 80 // Content splitting in case of typing on the flyto allow the user to undo a correction81 // If isTyping is false, it i s the case of a pasted content, so we do not deal with possible undos ofthe user80 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 82 83 if ( isTyping() ) { 83 84 84 85 // 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 87 89 88 90 // Get cursor position in HTML (with tags): needed to cut in 2 parts at the right position 89 c onst cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition90 91 cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition 92 91 93 // If the rule depends on previous characters, we need to separate the string taking those characters into account 92 94 const captureGroups = textContent.match( reg.mask ) 95 93 96 if( null === captureGroups || 0 === captureGroups.length ) return 97 94 98 const lengthToGoBack = captureGroups[0].length || 1 95 99 96 100 // 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 100 104 // If first part of the string matches but not the lastPart, 101 105 // it means that a character has been typed uncorrected voluntarily before with CTRL Z/CMD Z … … 111 115 replaceWithThis = getReplacementStringForPairs( reg, blockContent, replaceWithThis ) 112 116 } 113 117 114 118 // Concat strings 115 if ( 0 !== cursorPosition ) {119 if ( 0 !== cursorPositionInsideHTML ) { 116 120 blockContent = firstPart + lastPart.replace( reg.mask, replaceWithThis ) 117 121 } 118 122 119 123 // Pasted content innerBlocks case: no selection, no cursor position so the whole block is fixed 120 if ( 0 === cursorPosition ) {124 if ( 0 === cursorPositionInsideHTML ) { 121 125 blockContent = blockContent.replace( reg.mask, reg.replace ) 122 126 } 123 124 // If CTRL Z was used just before, then we do not correctthis time125 if ( global.previousFixCanceled ) {126 global.previousFixCanceled = false127 128 // If CTRL Z was used just before, skip the correction this time 129 if ( isPreviousFixCanceled ) { 130 setPreviousFixCanceled( false ) 127 131 return 128 132 } 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 139 139 // Cursor repositioning: 140 140 if ( 0 === cursorPosition || isPasting ) return … … 142 142 // Get the number of characters moved by the replacement: needed for cursor repositioning. 143 143 // 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 146 148 // If the replaced string had more characters than the new string, the cursor has moved forward, so it must be moved back 147 149 // Eg: ... replaced with … removes 2 characters 148 150 if ( nbMoved < 0 ) { 149 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )151 selectionChange( currentBlockId, attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved ) 150 152 } 151 153 … … 153 155 // Eg: "" replaced with « » adds 2 characters 154 156 if ( nbMoved > 0 ) { 155 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )157 selectionChange( currentBlockId, attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved ) 156 158 } 157 159 158 160 if ( 0 === nbMoved ) { 159 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition, cursorPosition )161 selectionChange( currentBlockId, attributeKey, cursorPosition, cursorPosition ) 160 162 } 161 163 162 164 } ) 163 165 164 global.consistencyLoop = 0165 166 166 } 167 167 … … 171 171 export const fixAll = props => { 172 172 173 const { authorizedRuleSettings} = props174 175 // Get the re gex of allrules176 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 178 178 // Get all blocks generated by pasting (which does not integrate innerBlocks) 179 179 const allBlocks = getBlocks() 180 180 181 181 // Get all innerBlocks for a later bulk selection process that will generate their fix 182 182 const allInners = getAllInnersFromParents( allBlocks ) 183 183 184 184 // Loop on all parents blocks 185 185 const updates = allBlocks.reduce( ( acc, block ) => { … … 187 187 let newContent = block.attributes?.content 188 188 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 nothing190 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 ) 191 191 || undefined === newContent ) { 192 192 return acc 193 193 } 194 194 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 ] ) => { 199 196 200 197 // If the rule is a pair rule, we use a specific regex 201 if ( regsWithPair.includes( reg.slug ) ) {198 if ( pairedCharacterSlugs.includes( reg.slug ) ) { 202 199 const singleCharacterOfPair = reg.mask.toString().match( /(?<=\/).+?(?=\/)/g )[0] 203 200 const realReg = new RegExp( `(?<!\=)${singleCharacterOfPair}(?!>)([^${singleCharacterOfPair}]*)(?<!\=)${singleCharacterOfPair}(?!>)`, 'g' ) … … 206 203 207 204 // 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 ) ) { 209 206 const stringRegex = reg.mask.toString() 210 207 const regWithGlobalFlag = new RegExp( stringRegex.substring( 1, stringRegex.length - 1 ), 'g' ) 211 208 newContent = newContent.replaceAll( regWithGlobalFlag, reg.replace ) 212 209 } 213 210 214 211 } ) 215 212 … … 220 217 return acc 221 218 }, {} ) 222 219 223 220 // Update all parents blocks 224 if ( Object.keys( updates ).length > 0 && global.contentPasted ) { 225 global.contentPasted = false 221 if ( Object.keys( updates ).length > 0 ) { 226 222 updateBlockAttributes( Object.keys( updates ), updates, true ); 227 223 } 228 global.contentPasted = false229 224 230 225 // Select all innerBlocks to trigger their correction, then deselect all by selecting the first block 231 226 const isPasting = true 232 227 allInners.forEach( block => { 233 if ( ! processedBlocks.includes( block.name ) ) return 228 229 if ( ! blocksToBeProcessed.includes( block.name ) ) return 234 230 const currentBlockId = block.clientId 235 block?.clientId && fixIt( { currentBlockId, theRegs, isPasting } ) 231 block?.clientId && fixIt( { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } ) 232 236 233 } ) 237 234 -
consistency/tags/1.8.0/src/app/helpers.js
r3122806 r3142172 1 1 /** 2 * Summary: Specific functions varied and correlated to the application.2 * @summary: Specific functions varied and correlated to the application. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ 7 7 8 8 /** 9 * WordPressdependencies9 * External dependencies 10 10 */ 11 import { select, dispatch } from '@wordpress/data' 12 13 const { getBlock } = select( 'core/block-editor' ) 14 const { updateBlock } = dispatch( 'core/block-editor' ) 11 import { isUsedByLocale } from './checks' 12 import { fetchRuleSettings, isLocalizationEnabled } from './data' 13 import { rules } from '../config/rules' 15 14 16 15 /** … … 54 53 55 54 /** 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. 60 58 */ 61 export const aMemoryLeakHasOccured = currentBlockId => { 59 export const getLocalizedRuleSettings = () => { 60 61 const ruleSettings = fetchRuleSettings() 62 62 63 const block = getBlock( currentBlockId ) 63 const localizedRuleSettings = isLocalizationEnabled() 64 ? ruleSettings.filter( setting => isUsedByLocale( setting.slug ) ) 65 : ruleSettings 64 66 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 72 68 73 69 } 74 70 71 75 72 /** 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. 77 75 */ 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 76 export const getLocalizedRules = () => { 82 77 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 88 81 89 82 } 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 accordingly98 updatePasteEventGlobalDependingOnEditorLocation()99 100 if ( global.isPasteEventAttached ) return101 102 if ( global.isEditorInIframe ) {103 104 // Select the iframe105 const iframe = document.querySelector( 'iframe[name="editor-canvas"]' )106 107 // Ensure the iframe is not null and has loaded108 if ( iframe ) {109 iframe.onload = () => {110 // Access the iframe's document111 const iframeDoc = iframe.contentDocument || iframe.contentWindow.document112 113 // Attach the 'paste' event listener114 iframeDoc.addEventListener('paste', e => {115 global.contentPasted = true116 global.isPasteEventAttached = true117 } )118 }119 120 // If the iframe is already loaded by the time this code runs, manually trigger the onload handler121 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 editor130 document.querySelector( '#editor' )?.addEventListener( 'paste', e => {131 global.contentPasted = true132 global.isPasteEventAttached = true133 } )134 135 }136 137 } -
consistency/tags/1.8.0/src/app/utils.js
r3122806 r3142172 1 1 /** 2 * Summary: Generic utility functions.2 * @summary: Generic utility functions. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ 7 8 import { RichTextData } from '@wordpress/rich-text' 7 9 8 10 /** … … 51 53 if ( null === currentActiveBlock ) return undefined 52 54 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 53 59 // Get current selection 54 60 const selection = document.getSelection() … … 56 62 57 63 // Return if user is selecting text instead of typing 58 if ( ! _range .collapsed ) return64 if ( ! _range || ! _range.collapsed ) return 59 65 60 66 // Clone range to work on … … 67 73 range.insertNode( tempNode ) 68 74 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 72 79 // Remove temporary node and normalize cut node - important! 73 80 tempNode.parentNode.removeChild( tempNode ) 74 currentActiveBlock.normalize()81 editableContent.normalize() 75 82 76 83 // Remove non-breaking spaces in format from the count 77 const nbNbsp = ( currentActiveBlock?.innerHTML.match(/ /g) || []).length84 const nbNbsp = (editableContent?.innerHTML.match(/ /g) || []).length 78 85 if ( nbNbsp > 0 ) { 79 cursorPositionInsideHTML = cursorPositionInsideHTML - ( nbNbsp * 6 ) + nbNbsp 86 const textContentWithoutNbsp = textContent.replace(/ /g, ' '); 87 cursorPositionInsideText = textContentWithoutNbsp.indexOf('\0'); 88 cursorPositionInsideText = cursorPositionInsideText - ( nbNbsp * 6 ) + nbNbsp; 80 89 } 81 90 82 return cursorPositionInside HTML91 return cursorPositionInsideText 83 92 } 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 */ 100 export 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 */ 108 export const isRichTextData = value => typeof value === 'object' && value instanceof RichTextData -
consistency/tags/1.8.0/src/components/GlobalSettingPanel.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingPanel component.2 * @summary: GlobalSettingPanel component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 42 42 key={ key } 43 43 settingSlug={ rule.slug } 44 settingName={ rule.name }44 name={ rule.name } 45 45 settingDescription={ { 46 46 __html: rule.description … … 52 52 } ) 53 53 } 54 54 55 </Panel> 55 56 ) 57 56 58 export default GlobalSettingPanel -
consistency/tags/1.8.0/src/components/GlobalSettingToggle.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingToggle component.2 * @summary: GlobalSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/tags/1.8.0/src/components/LocaleLabel.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingToggle component.2 * @summary: GlobalSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 14 14 * External dependencies 15 15 */ 16 import { getCurrentLocale, isLocalizationEnabled } from '../app/data'16 import { fetchCurrentLocale, isLocalizationEnabled } from '../app/data' 17 17 18 18 19 19 export const LocaleLabel = () => { 20 20 21 const currentLocale = getCurrentLocale()21 const currentLocale = fetchCurrentLocale() 22 22 23 23 const areRulesLocalized = isLocalizationEnabled() -
consistency/tags/1.8.0/src/components/Settings.js
r3122806 r3142172 1 1 /** 2 * Summary: SidebarSettings component.2 * @summary: SidebarSettings component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 10 10 */ 11 11 import { __ } from '@wordpress/i18n' 12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/edit -post'12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/editor' 13 13 import { select } from '@wordpress/data' 14 14 … … 16 16 * External dependencies 17 17 */ 18 import { ConsistencyIcon } from './ Icon'18 import { ConsistencyIcon } from './icon' 19 19 import UserSettingPanel from './UserSettingPanel' 20 20 import GlobalSettingPanel from './GlobalSettingPanel' 21 21 22 22 23 const { canUser } = select( 'core' ) -
consistency/tags/1.8.0/src/components/UserSettingPanel.js
r3130518 r3142172 1 1 /** 2 * Summary: UserSettingPanel component.2 * @summary: UserSettingPanel component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/tags/1.8.0/src/components/UserSettingToggle.js
r3086094 r3142172 1 1 /** 2 * Summary: UserSettingToggle component.2 * @summary: UserSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/tags/1.8.0/src/components/icon.js
r3083522 r3142172 1 1 /** 2 * Summary: Consistency Logo.2 * @summary: Consistency Logo. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/tags/1.8.0/src/config/categories.js
r3122806 r3142172 1 1 /** 2 * Summary: Rules categories.2 * @summary: Rules categories. 3 3 * 4 * @descriptionThis file contains an array of all correction rules categories.4 * This file contains an array of all correction rules categories. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/tags/1.8.0/src/config/rules.js
r3130518 r3142172 1 1 /** 2 * Summary: Correction rules.2 * @summary: Correction rules. 3 3 * 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 * 5 6 * @author Loïc Antignac. 6 7 */ -
consistency/tags/1.8.0/src/index.js
r3135255 r3142172 1 1 /** 2 * Main entry point for the WordPress Consistency plugin.2 * @summary: Main entry point for the WordPress Consistency plugin. 3 3 * 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. 8 7 * 9 8 * @author Loïc Antignac. … … 14 13 */ 15 14 import { registerPlugin } from '@wordpress/plugins' 16 import { subscribe, select } from '@wordpress/data'17 15 import domReady from '@wordpress/dom-ready' 18 import { SidebarSettings } from './components/Settings'19 20 16 21 17 /** 22 18 * External dependencies 23 19 */ 24 import { fixIt, fixAll } from './app/fixes' 25 import { getAuthorizedRuleSettings, getCurrentUserSettings } from './app/data' 26 import { interceptPasteEventInEditor } from './app/helpers' 20 import { GlobalProvider } from './contexts/GlobalContext' 21 import ConsistencyPlugin from './components/ConsistencyPlugin' 27 22 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 } ) 23 const PluginWrapper = () => ( 24 <GlobalProvider> 25 <ConsistencyPlugin /> 26 </GlobalProvider> 27 ) 35 28 36 29 domReady( () => { 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 } ) 104 31 } ) -
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:/(?<! | | )([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,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:/(«)(?! | | )/,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(/ /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:/(?<! | | )([\?|\!|\:|»|€|\$|£|¥|₽|元|\%])/,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:/(«)(?! | | )/,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(/ /g)||[]).length;return l>0&&(i=a.replace(/ /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 4 4 * Plugin URI: https://www.webaxones.com 5 5 * Description: Fixes typographic and punctuation consistency 6 * Version: 1. 7.16 * Version: 1.8.0 7 7 * Requires at least: 6.1 8 8 * Requires PHP: 7.4 -
consistency/trunk/includes/Plugin.php
r3135255 r3142172 33 33 protected static function setConstants(): void 34 34 { 35 defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1. 7.1' );35 defined( __NAMESPACE__ . '\VERSION' ) || define( __NAMESPACE__ . '\VERSION', '1.8.0' ); 36 36 defined( __NAMESPACE__ . '\PLUGIN_URL' ) || define( __NAMESPACE__ . '\PLUGIN_URL', plugin_dir_url( __DIR__ ) ); 37 37 defined( __NAMESPACE__ . '\PLUGIN_PATH' ) || define( __NAMESPACE__ . '\PLUGIN_PATH', plugin_dir_path( __DIR__ ) ); -
consistency/trunk/package.json
r3135255 r3142172 1 1 { 2 2 "name": "consistency", 3 "version": "1. 7.1",3 "version": "1.8.0", 4 4 "description": "", 5 5 "main": "index.js", -
consistency/trunk/readme.txt
r3135255 r3142172 4 4 Requires at least: 6.1 5 5 Tested up to: 6.6 6 Stable tag: 1. 7.16 Stable tag: 1.8.0 7 7 Requires PHP: 7.4 8 8 License: GPL-3.0-or-later … … 70 70 == Changelog == 71 71 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 72 75 = 1.7.1 = 73 76 * Fix: Ensures in all cases to only use rules authorized by local parameters -
consistency/trunk/src/app/checks.js
r3135255 r3142172 1 1 /** 2 * Summary: Various checks functions.2 * @summary: Various checks functions. 3 3 * 4 * @descriptionThis file contains functions for various checks.4 * This file contains functions for various checks. 5 5 * @author Loïc Antignac. 6 6 */ … … 14 14 * External dependencies 15 15 */ 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' 16 import { fetchCurrentLocale } from './data' 17 import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs' 18 import { incompatibilities } from '../config/incompatibilities' 19 import { getLocalizedRuleSettings } from './helpers' 20 import { isString, isRichTextData } from './utils' 22 21 23 22 const { getBlockName, getBlockAttributes } = select( 'core/block-editor' ) 24 25 23 26 24 /** … … 32 30 export const isUsedByLocale = settingSlug => { 33 31 34 const currentLocale = getCurrentLocale()32 const currentLocale = fetchCurrentLocale() 35 33 if ( localesByRules !== undefined && localesByRules.hasOwnProperty( settingSlug ) ) { 36 34 return localesByRules[settingSlug].includes( currentLocale ) … … 41 39 42 40 /** 43 * Checks if the current block is one of those to be checked or not41 * Checks if the current block is one of those to be processed or not 44 42 * 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? 47 47 */ 48 export const blockShouldBeChecked = currentBlockId => { 48 export const shouldProcessBlock = props => { 49 50 const { currentBlockId, blocksToBeProcessed } = props 49 51 50 52 const blockName = getBlockName( currentBlockId ) 51 if ( processedBlocks.includes( blockName ) ) { 53 54 if ( blocksToBeProcessed.includes( blockName ) ) { 52 55 return true 53 56 } … … 57 60 58 61 /** 59 * Checks if the current block can technically be verified or not62 * Checks if the current block can technically be processed or not 60 63 * 61 64 * @param {string} currentBlockId currentBlockId current active block ID 62 * @return {boolean} Can the block be checked?65 * @return {boolean} Can the block be processed? 63 66 */ 64 export const blockCanTechnicallyBeChecked= currentBlockId => {67 export const canProcessBlock = currentBlockId => { 65 68 66 69 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 ) ) { 68 74 return true 69 75 } 76 70 77 return false 71 78 … … 80 87 export const regDealWithPair = reg => { 81 88 82 if ( regsWithPair.includes( reg.slug ) ) {89 if ( pairedCharacterSlugs.includes( reg.slug ) ) { 83 90 return true 84 91 } 85 92 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 }98 93 99 94 } … … 107 102 export const checkRuleCompatibility = currentRule => { 108 103 109 // Get the current rule from the ruleIncompatibilities array110 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 ) 111 106 if ( ! rule ) return false 112 107 … … 114 109 return rule.incompatibleWith.some( incompatibleRule => { 115 110 // Return the state of the incompatible rule 116 return get AuthorizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value111 return getLocalizedRuleSettings()?.find( setting => setting.slug === incompatibleRule )?.value 117 112 } ) 118 113 -
consistency/trunk/src/app/data.js
r3135255 r3142172 1 1 /** 2 * Summary: Data retrieval.2 * @summary: Data retrieval. 3 3 * 4 * @descriptionThis file contains functions that retrieve data from database.4 * This file contains functions that retrieve data from database. 5 5 * @author Loïc Antignac. 6 6 */ … … 10 10 */ 11 11 import { store as coreStore } from '@wordpress/core-data' 12 import { select } from '@wordpress/data' 13 14 const { getEntityRecord } = select( 'core' ) 12 import { select, dispatch } from '@wordpress/data' 13 import { RichTextData, create } from '@wordpress/rich-text' 15 14 16 15 /** 17 16 * External dependencies 18 17 */ 19 import { isUsedByLocale } from './checks' 18 import { isString, isRichTextData } from './utils' 19 20 const { getEntityRecord } = select( 'core' ) 21 const { updateBlock } = dispatch( 'core/block-editor' ) 20 22 21 23 /** … … 37 39 * @returns {Object} The rules settings object. 38 40 */ 39 export const getRuleSettings = () => {41 export const fetchRuleSettings = () => { 40 42 41 43 const siteEntity = getEntityRecord( 'root', 'site' ) 42 const ruleSetting = siteEntity?.consistency_plugin_settings || []44 const ruleSettings = siteEntity?.consistency_plugin_settings || [] 43 45 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 61 47 62 48 } … … 67 53 * @return {object} userSettings Current user settings: userSettings.onTheFly, userSettings.onPaste 68 54 */ 69 export const getCurrentUserSettings = () => {55 export const fetchCurrentUserSettings = () => { 70 56 71 57 const userSettings = { … … 89 75 * @return {string} currentLocale Current active site locale 90 76 */ 91 export const getCurrentLocale = () => {77 export const fetchCurrentLocale = () => { 92 78 93 79 const siteEntity = getEntityRecord( 'root', 'site' ) … … 96 82 97 83 } 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 */ 96 export 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 1 1 /** 2 * Summary: Block processing.2 * @summary: Block processing. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 15 15 */ 16 16 import { 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 c ontent of a block based on regular expressions.17 import { getReplacementStringForPairs, getLocalizedRules } from './helpers' 18 import { updateBlockTextContent } from './data' 19 import { pairedCharacterSlugs } from '../config/pairedCharacterSlugs' 20 import { shouldProcessBlock, canProcessBlock, regDealWithPair } from './checks' 21 22 const { getBlock, getBlocks, getBlockAttributes, getSelectionStart, isTyping, getBlockSelectionStart } = select( 'core/block-editor' ) 23 const { selectionChange, updateBlockAttributes } = dispatch( 'core/block-editor' ) 24 25 26 27 /** 28 * Fixes the current block based on the provided rules and attributes. 29 29 * 30 * @param {Object} props - The props object containing the necessary data.30 * @param {Object} props - The props object. 31 31 * @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. 34 36 */ 35 37 export 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() 40 46 41 47 // Get the current block 42 48 const block = getBlock( currentBlockId ) 43 44 // Check if the current block should be checked and can be checked45 if ( ! blockShouldBeChecked( currentBlockId ) || ! blockCanTechnicallyBeChecked( currentBlockId ) ) return46 49 47 50 // Get the attributes of the current block 48 51 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 51 54 let contentUpdated = false 52 55 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 62 61 63 62 let replaceWithThis = reg.replace … … 65 64 let lastPart = '' 66 65 let cursorPosition = 0 67 let blockContent = blockAttributes.content66 let cursorPositionInsideHTML = 0 68 67 let selectionStart 69 68 69 let blockContent = ( typeof blockAttributes.content === 'object' ) ? blockAttributes.content.text : blockAttributes.content 70 70 71 // Remove 'code' 'pre' and 'kbd' and other HTML tags from block content 71 72 let textContent = getOnlyTextFromBlockContent( blockContent ) 72 73 // Check if block content is concerned by the regex in the case of apasted content74 // (isTyping is false but subscribedetected 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) 75 76 let isConcerned = false 76 77 if ( ! isTyping() ) { 77 78 isConcerned = reg.mask.test( textContent ) 78 79 } 79 80 // Content splitting in case of typing on the flyto allow the user to undo a correction81 // If isTyping is false, it i s the case of a pasted content, so we do not deal with possible undos ofthe user80 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 82 83 if ( isTyping() ) { 83 84 84 85 // 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 87 89 88 90 // Get cursor position in HTML (with tags): needed to cut in 2 parts at the right position 89 c onst cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition90 91 cursorPositionInsideHTML = getCursorPositionInInnerHTML( currentBlockId ) || cursorPosition 92 91 93 // If the rule depends on previous characters, we need to separate the string taking those characters into account 92 94 const captureGroups = textContent.match( reg.mask ) 95 93 96 if( null === captureGroups || 0 === captureGroups.length ) return 97 94 98 const lengthToGoBack = captureGroups[0].length || 1 95 99 96 100 // 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 100 104 // If first part of the string matches but not the lastPart, 101 105 // it means that a character has been typed uncorrected voluntarily before with CTRL Z/CMD Z … … 111 115 replaceWithThis = getReplacementStringForPairs( reg, blockContent, replaceWithThis ) 112 116 } 113 117 114 118 // Concat strings 115 if ( 0 !== cursorPosition ) {119 if ( 0 !== cursorPositionInsideHTML ) { 116 120 blockContent = firstPart + lastPart.replace( reg.mask, replaceWithThis ) 117 121 } 118 122 119 123 // Pasted content innerBlocks case: no selection, no cursor position so the whole block is fixed 120 if ( 0 === cursorPosition ) {124 if ( 0 === cursorPositionInsideHTML ) { 121 125 blockContent = blockContent.replace( reg.mask, reg.replace ) 122 126 } 123 124 // If CTRL Z was used just before, then we do not correctthis time125 if ( global.previousFixCanceled ) {126 global.previousFixCanceled = false127 128 // If CTRL Z was used just before, skip the correction this time 129 if ( isPreviousFixCanceled ) { 130 setPreviousFixCanceled( false ) 127 131 return 128 132 } 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 139 139 // Cursor repositioning: 140 140 if ( 0 === cursorPosition || isPasting ) return … … 142 142 // Get the number of characters moved by the replacement: needed for cursor repositioning. 143 143 // 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 146 148 // If the replaced string had more characters than the new string, the cursor has moved forward, so it must be moved back 147 149 // Eg: ... replaced with … removes 2 characters 148 150 if ( nbMoved < 0 ) { 149 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved )151 selectionChange( currentBlockId, attributeKey, cursorPosition + nbMoved, cursorPosition + nbMoved ) 150 152 } 151 153 … … 153 155 // Eg: "" replaced with « » adds 2 characters 154 156 if ( nbMoved > 0 ) { 155 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved )157 selectionChange( currentBlockId, attributeKey, cursorPosition + 1 + nbMoved, cursorPosition + nbMoved ) 156 158 } 157 159 158 160 if ( 0 === nbMoved ) { 159 selectionChange( currentBlockId, selectionStart.attributeKey, cursorPosition, cursorPosition )161 selectionChange( currentBlockId, attributeKey, cursorPosition, cursorPosition ) 160 162 } 161 163 162 164 } ) 163 165 164 global.consistencyLoop = 0165 166 166 } 167 167 … … 171 171 export const fixAll = props => { 172 172 173 const { authorizedRuleSettings} = props174 175 // Get the re gex of allrules176 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 178 178 // Get all blocks generated by pasting (which does not integrate innerBlocks) 179 179 const allBlocks = getBlocks() 180 180 181 181 // Get all innerBlocks for a later bulk selection process that will generate their fix 182 182 const allInners = getAllInnersFromParents( allBlocks ) 183 183 184 184 // Loop on all parents blocks 185 185 const updates = allBlocks.reduce( ( acc, block ) => { … … 187 187 let newContent = block.attributes?.content 188 188 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 nothing190 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 ) 191 191 || undefined === newContent ) { 192 192 return acc 193 193 } 194 194 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 ] ) => { 199 196 200 197 // If the rule is a pair rule, we use a specific regex 201 if ( regsWithPair.includes( reg.slug ) ) {198 if ( pairedCharacterSlugs.includes( reg.slug ) ) { 202 199 const singleCharacterOfPair = reg.mask.toString().match( /(?<=\/).+?(?=\/)/g )[0] 203 200 const realReg = new RegExp( `(?<!\=)${singleCharacterOfPair}(?!>)([^${singleCharacterOfPair}]*)(?<!\=)${singleCharacterOfPair}(?!>)`, 'g' ) … … 206 203 207 204 // 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 ) ) { 209 206 const stringRegex = reg.mask.toString() 210 207 const regWithGlobalFlag = new RegExp( stringRegex.substring( 1, stringRegex.length - 1 ), 'g' ) 211 208 newContent = newContent.replaceAll( regWithGlobalFlag, reg.replace ) 212 209 } 213 210 214 211 } ) 215 212 … … 220 217 return acc 221 218 }, {} ) 222 219 223 220 // Update all parents blocks 224 if ( Object.keys( updates ).length > 0 && global.contentPasted ) { 225 global.contentPasted = false 221 if ( Object.keys( updates ).length > 0 ) { 226 222 updateBlockAttributes( Object.keys( updates ), updates, true ); 227 223 } 228 global.contentPasted = false229 224 230 225 // Select all innerBlocks to trigger their correction, then deselect all by selecting the first block 231 226 const isPasting = true 232 227 allInners.forEach( block => { 233 if ( ! processedBlocks.includes( block.name ) ) return 228 229 if ( ! blocksToBeProcessed.includes( block.name ) ) return 234 230 const currentBlockId = block.clientId 235 block?.clientId && fixIt( { currentBlockId, theRegs, isPasting } ) 231 block?.clientId && fixIt( { currentBlockId, isPasting, isPreviousFixCanceled, setPreviousFixCanceled, blocksToBeProcessed } ) 232 236 233 } ) 237 234 -
consistency/trunk/src/app/helpers.js
r3122806 r3142172 1 1 /** 2 * Summary: Specific functions varied and correlated to the application.2 * @summary: Specific functions varied and correlated to the application. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ 7 7 8 8 /** 9 * WordPressdependencies9 * External dependencies 10 10 */ 11 import { select, dispatch } from '@wordpress/data' 12 13 const { getBlock } = select( 'core/block-editor' ) 14 const { updateBlock } = dispatch( 'core/block-editor' ) 11 import { isUsedByLocale } from './checks' 12 import { fetchRuleSettings, isLocalizationEnabled } from './data' 13 import { rules } from '../config/rules' 15 14 16 15 /** … … 54 53 55 54 /** 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. 60 58 */ 61 export const aMemoryLeakHasOccured = currentBlockId => { 59 export const getLocalizedRuleSettings = () => { 60 61 const ruleSettings = fetchRuleSettings() 62 62 63 const block = getBlock( currentBlockId ) 63 const localizedRuleSettings = isLocalizationEnabled() 64 ? ruleSettings.filter( setting => isUsedByLocale( setting.slug ) ) 65 : ruleSettings 64 66 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 72 68 73 69 } 74 70 71 75 72 /** 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. 77 75 */ 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 76 export const getLocalizedRules = () => { 82 77 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 88 81 89 82 } 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 accordingly98 updatePasteEventGlobalDependingOnEditorLocation()99 100 if ( global.isPasteEventAttached ) return101 102 if ( global.isEditorInIframe ) {103 104 // Select the iframe105 const iframe = document.querySelector( 'iframe[name="editor-canvas"]' )106 107 // Ensure the iframe is not null and has loaded108 if ( iframe ) {109 iframe.onload = () => {110 // Access the iframe's document111 const iframeDoc = iframe.contentDocument || iframe.contentWindow.document112 113 // Attach the 'paste' event listener114 iframeDoc.addEventListener('paste', e => {115 global.contentPasted = true116 global.isPasteEventAttached = true117 } )118 }119 120 // If the iframe is already loaded by the time this code runs, manually trigger the onload handler121 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 editor130 document.querySelector( '#editor' )?.addEventListener( 'paste', e => {131 global.contentPasted = true132 global.isPasteEventAttached = true133 } )134 135 }136 137 } -
consistency/trunk/src/app/utils.js
r3122806 r3142172 1 1 /** 2 * Summary: Generic utility functions.2 * @summary: Generic utility functions. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ 7 8 import { RichTextData } from '@wordpress/rich-text' 7 9 8 10 /** … … 51 53 if ( null === currentActiveBlock ) return undefined 52 54 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 53 59 // Get current selection 54 60 const selection = document.getSelection() … … 56 62 57 63 // Return if user is selecting text instead of typing 58 if ( ! _range .collapsed ) return64 if ( ! _range || ! _range.collapsed ) return 59 65 60 66 // Clone range to work on … … 67 73 range.insertNode( tempNode ) 68 74 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 72 79 // Remove temporary node and normalize cut node - important! 73 80 tempNode.parentNode.removeChild( tempNode ) 74 currentActiveBlock.normalize()81 editableContent.normalize() 75 82 76 83 // Remove non-breaking spaces in format from the count 77 const nbNbsp = ( currentActiveBlock?.innerHTML.match(/ /g) || []).length84 const nbNbsp = (editableContent?.innerHTML.match(/ /g) || []).length 78 85 if ( nbNbsp > 0 ) { 79 cursorPositionInsideHTML = cursorPositionInsideHTML - ( nbNbsp * 6 ) + nbNbsp 86 const textContentWithoutNbsp = textContent.replace(/ /g, ' '); 87 cursorPositionInsideText = textContentWithoutNbsp.indexOf('\0'); 88 cursorPositionInsideText = cursorPositionInsideText - ( nbNbsp * 6 ) + nbNbsp; 80 89 } 81 90 82 return cursorPositionInside HTML91 return cursorPositionInsideText 83 92 } 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 */ 100 export 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 */ 108 export const isRichTextData = value => typeof value === 'object' && value instanceof RichTextData -
consistency/trunk/src/components/GlobalSettingPanel.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingPanel component.2 * @summary: GlobalSettingPanel component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 42 42 key={ key } 43 43 settingSlug={ rule.slug } 44 settingName={ rule.name }44 name={ rule.name } 45 45 settingDescription={ { 46 46 __html: rule.description … … 52 52 } ) 53 53 } 54 54 55 </Panel> 55 56 ) 57 56 58 export default GlobalSettingPanel -
consistency/trunk/src/components/GlobalSettingToggle.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingToggle component.2 * @summary: GlobalSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/trunk/src/components/LocaleLabel.js
r3135255 r3142172 1 1 /** 2 * Summary: GlobalSettingToggle component.2 * @summary: GlobalSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 14 14 * External dependencies 15 15 */ 16 import { getCurrentLocale, isLocalizationEnabled } from '../app/data'16 import { fetchCurrentLocale, isLocalizationEnabled } from '../app/data' 17 17 18 18 19 19 export const LocaleLabel = () => { 20 20 21 const currentLocale = getCurrentLocale()21 const currentLocale = fetchCurrentLocale() 22 22 23 23 const areRulesLocalized = isLocalizationEnabled() -
consistency/trunk/src/components/Settings.js
r3122806 r3142172 1 1 /** 2 * Summary: SidebarSettings component.2 * @summary: SidebarSettings component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ … … 10 10 */ 11 11 import { __ } from '@wordpress/i18n' 12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/edit -post'12 import { PluginSidebarMoreMenuItem, PluginSidebar } from '@wordpress/editor' 13 13 import { select } from '@wordpress/data' 14 14 … … 16 16 * External dependencies 17 17 */ 18 import { ConsistencyIcon } from './ Icon'18 import { ConsistencyIcon } from './icon' 19 19 import UserSettingPanel from './UserSettingPanel' 20 20 import GlobalSettingPanel from './GlobalSettingPanel' 21 21 22 22 23 const { canUser } = select( 'core' ) -
consistency/trunk/src/components/UserSettingPanel.js
r3130518 r3142172 1 1 /** 2 * Summary: UserSettingPanel component.2 * @summary: UserSettingPanel component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/trunk/src/components/UserSettingToggle.js
r3086094 r3142172 1 1 /** 2 * Summary: UserSettingToggle component.2 * @summary: UserSettingToggle component. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/trunk/src/components/icon.js
r3083522 r3142172 1 1 /** 2 * Summary: Consistency Logo.2 * @summary: Consistency Logo. 3 3 * 4 * @descriptionThis 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. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/trunk/src/config/categories.js
r3122806 r3142172 1 1 /** 2 * Summary: Rules categories.2 * @summary: Rules categories. 3 3 * 4 * @descriptionThis file contains an array of all correction rules categories.4 * This file contains an array of all correction rules categories. 5 5 * @author Loïc Antignac. 6 6 */ -
consistency/trunk/src/config/rules.js
r3130518 r3142172 1 1 /** 2 * Summary: Correction rules.2 * @summary: Correction rules. 3 3 * 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 * 5 6 * @author Loïc Antignac. 6 7 */ -
consistency/trunk/src/index.js
r3135255 r3142172 1 1 /** 2 * Main entry point for the WordPress Consistency plugin.2 * @summary: Main entry point for the WordPress Consistency plugin. 3 3 * 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. 8 7 * 9 8 * @author Loïc Antignac. … … 14 13 */ 15 14 import { registerPlugin } from '@wordpress/plugins' 16 import { subscribe, select } from '@wordpress/data'17 15 import domReady from '@wordpress/dom-ready' 18 import { SidebarSettings } from './components/Settings'19 20 16 21 17 /** 22 18 * External dependencies 23 19 */ 24 import { fixIt, fixAll } from './app/fixes' 25 import { getAuthorizedRuleSettings, getCurrentUserSettings } from './app/data' 26 import { interceptPasteEventInEditor } from './app/helpers' 20 import { GlobalProvider } from './contexts/GlobalContext' 21 import ConsistencyPlugin from './components/ConsistencyPlugin' 27 22 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 } ) 23 const PluginWrapper = () => ( 24 <GlobalProvider> 25 <ConsistencyPlugin /> 26 </GlobalProvider> 27 ) 35 28 36 29 domReady( () => { 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 } ) 104 31 } )
Note: See TracChangeset
for help on using the changeset viewer.