From 60f661694ef2bcfd0860d738f0458531fdfb53d7 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:20:00 -0600 Subject: [PATCH 1/5] Add subscript and superscript buttons to the Trix editor toolbar --- composer.lock | 4 ++-- public/js/app.js | 15 +++++++++++++++ public/mix-manifest.json | 4 ++-- resources/assets/js/app.js | 1 - resources/assets/js/bootstrap.js | 15 ++++++++++++++- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/composer.lock b/composer.lock index 4656b979..a690b868 100644 --- a/composer.lock +++ b/composer.lock @@ -12527,12 +12527,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3.0" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/public/js/app.js b/public/js/app.js index 6940d828..132c693c 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -787,6 +787,21 @@ window.Bloodhound = __webpack_require__(/*! corejs-typeahead */ "./node_modules/ // Trix editor __webpack_require__(/*! trix */ "./node_modules/trix/dist/trix.esm.min.js"); +Trix.config.textAttributes.sup = { + tagName: "sup", + inheritable: true +}; +Trix.config.textAttributes.sub = { + tagName: "sub", + inheritable: true +}; +addEventListener("trix-initialize", function (event) { + var buttonHTML, buttonGroup; + buttonHTML = ''; + buttonHTML += ''; + buttonGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools"); + buttonGroup.insertAdjacentHTML("beforeend", buttonHTML); +}); /***/ }), diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 9c83cf3b..878d668f 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,6 +1,6 @@ { - "/js/app.js": "/js/app.js?id=bb647bfe8f8b8ae096323cb6ea42da03", - "/js/app.js.map": "/js/app.js.map?id=9f321a4814e4648916c37b2e980f55e7", + "/js/app.js": "/js/app.js?id=48b30a8272fd64c6b0bc09d2a749970a", + "/js/app.js.map": "/js/app.js.map?id=4dc1dc1b24d493458d19c4c5637410bd", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", "/js/manifest.js.map": "/js/manifest.js.map?id=389e00e7d7680b68d4e1d128ce27ff48", "/css/app.css": "/css/app.css?id=bc1752c1cc35e38e55b5e618b7b4f544", diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index d09f9a79..ba7072f2 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -674,4 +674,3 @@ if (typeof Livewire === 'object') { }); } - diff --git a/resources/assets/js/bootstrap.js b/resources/assets/js/bootstrap.js index 1cf5c1cf..406f7786 100644 --- a/resources/assets/js/bootstrap.js +++ b/resources/assets/js/bootstrap.js @@ -36,4 +36,17 @@ window.Sortable = require('sortablejs/Sortable'); window.Bloodhound = require('corejs-typeahead'); // Trix editor -require('trix'); \ No newline at end of file +require('trix'); + +Trix.config.textAttributes.sup = { tagName: "sup", inheritable: true } +Trix.config.textAttributes.sub = { tagName: "sub", inheritable: true } + +addEventListener("trix-initialize", function(event) { + var buttonHTML, buttonGroup + + buttonHTML = '' + buttonHTML += '' + + buttonGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools") + buttonGroup.insertAdjacentHTML("beforeend", buttonHTML) +}) \ No newline at end of file From 4c36bfc357b2401c9ff740e06dd8e4854795c12e Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:21:05 -0600 Subject: [PATCH 2/5] Update purify config file to proper Html5Definition --- config/purify.php | 193 ++++++++++++++++------------------------------ 1 file changed, 66 insertions(+), 127 deletions(-) diff --git a/config/purify.php b/config/purify.php index de6f0a9e..7cbcee32 100644 --- a/config/purify.php +++ b/config/purify.php @@ -1,148 +1,87 @@ 'default', + + /* + |-------------------------------------------------------------------------- + | Config sets + |-------------------------------------------------------------------------- | - | Feel free to add / remove / customize these attributes as you wish. + | Here you may configure various sets of configuration for differentiated use of HTMLPurifier. + | A specific set of configuration can be applied by calling the "config($name)" method on + | a Purify instance. Feel free to add/remove/customize these attributes as you wish. | | Documentation: http://htmlpurifier.org/live/configdoc/plain.html | + | Core.Encoding The encoding to convert input to. + | HTML.Doctype Doctype to use during filtering. + | HTML.Allowed The allowed HTML Elements with their allowed attributes. + | HTML.ForbiddenElements The forbidden HTML elements. Elements that are listed in this + | string will be removed, however their content will remain. + | CSS.AllowedProperties The Allowed CSS properties. + | AutoFormat.AutoParagraph Newlines are converted in to paragraphs whenever possible. + | AutoFormat.RemoveEmpty Remove empty elements that contribute no semantic information to the document. + | */ - 'settings' => [ - - /* - |-------------------------------------------------------------------------- - | Core.Encoding - |-------------------------------------------------------------------------- - | - | The encoding to convert input to. - | - | http://htmlpurifier.org/live/configdoc/plain.html#Core.Encoding - | - */ - - 'Core.Encoding' => 'utf-8', - - /* - |-------------------------------------------------------------------------- - | Cache.SerializerPath - |-------------------------------------------------------------------------- - | - | The HTML purifier serializer cache path. - | - | http://htmlpurifier.org/live/configdoc/plain.html#Cache.SerializerPath - | - */ - - 'Cache.SerializerPath' => storage_path('app/purify'), - - /* - |-------------------------------------------------------------------------- - | Cache.SerializerPermissions - |-------------------------------------------------------------------------- - | - | The HTML purifier serializer cache path. - | - | http://htmlpurifier.org/live/configdoc/plain.html#Cache.SerializerPermissions - | - */ - - 'Cache.SerializerPermissions' => env('PURIFY_CACHE_PERMISSIONS', null), - - /* - |-------------------------------------------------------------------------- - | HTML.Doctype - |-------------------------------------------------------------------------- - | - | Doctype to use during filtering. - | - | http://htmlpurifier.org/live/configdoc/plain.html#HTML.Doctype - | - */ - - 'HTML.Doctype' => 'XHTML 1.0 Strict', - - /* - |-------------------------------------------------------------------------- - | HTML.Allowed - |-------------------------------------------------------------------------- - | - | The allowed HTML Elements with their allowed attributes. - | - | http://htmlpurifier.org/live/configdoc/plain.html#HTML.Allowed - | - */ - - 'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,b,strong,i,em,a[href|title],ul,ol,li,p[style],br,span,sup,sub,img[width|height|alt|src]', - - /* - |-------------------------------------------------------------------------- - | HTML.ForbiddenElements - |-------------------------------------------------------------------------- - | - | The forbidden HTML elements. Elements that are listed in - | this string will be removed, however their content will remain. - | - | For example if 'p' is inside the string, the string: '
Test
', - | - | Will be cleaned to: 'Test' - | - | http://htmlpurifier.org/live/configdoc/plain.html#HTML.ForbiddenElements - | - */ + 'configs' => [ - 'HTML.ForbiddenElements' => '', + 'default' => [ + 'Core.Encoding' => 'utf-8', + 'HTML.Doctype' => 'HTML 4.01 Transitional', + 'Cache.SerializerPermissions' => env('PURIFY_CACHE_PERMISSIONS', null), + 'HTML.Allowed' => 'h1,h2,h3,h4,h5,h6,b,strong,i,em,a[href|title],ul,ol,li,p[style],br,span,sup,sub,img[width|height|alt|src]', + 'HTML.ForbiddenElements' => '', + 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', + 'AutoFormat.AutoParagraph' => false, + 'AutoFormat.RemoveEmpty' => false, + ], - /* - |-------------------------------------------------------------------------- - | CSS.AllowedProperties - |-------------------------------------------------------------------------- - | - | The Allowed CSS properties. - | - | http://htmlpurifier.org/live/configdoc/plain.html#CSS.AllowedProperties - | - */ - - 'CSS.AllowedProperties' => 'font,font-size,font-weight,font-style,font-family,text-decoration,padding-left,color,background-color,text-align', - - /* - |-------------------------------------------------------------------------- - | AutoFormat.AutoParagraph - |-------------------------------------------------------------------------- - | - | The Allowed CSS properties. - | - | This directive turns on auto-paragraphing, where double - | newlines are converted in to paragraphs whenever possible. - | - | http://htmlpurifier.org/live/configdoc/plain.html#AutoFormat.AutoParagraph - | - */ + ], - 'AutoFormat.AutoParagraph' => false, + /* + |-------------------------------------------------------------------------- + | HTMLPurifier definitions + |-------------------------------------------------------------------------- + | + | Here you may specify a class that augments the HTML definitions used by + | HTMLPurifier. Additional HTML5 definitions are provided out of the box. + | When specifying a custom class, make sure it implements the interface: + | + | \Stevebauman\Purify\Definitions\Definition + | + | Note that these definitions are applied to every Purifier instance. + | + | Documentation: http://htmlpurifier.org/docs/enduser-customize.html + | + */ - /* - |-------------------------------------------------------------------------- - | AutoFormat.RemoveEmpty - |-------------------------------------------------------------------------- - | - | When enabled, HTML Purifier will attempt to remove empty - | elements that contribute no semantic information to the document. - | - | http://htmlpurifier.org/live/configdoc/plain.html#AutoFormat.RemoveEmpty - | - */ + 'definitions' => Html5Definition::class, - 'AutoFormat.RemoveEmpty' => false, + /* + |-------------------------------------------------------------------------- + | Serializer location + |-------------------------------------------------------------------------- + | + | The location where HTMLPurifier can store its temporary serializer files. + | The filepath should be accessible and writable by the web server. + | A good place for this is in the framework's own storage path. + | + */ - ], + 'serializer' => storage_path('app/purify'), -]; +]; \ No newline at end of file From d9a96536ea600c8775b04535a65c05b65a71c8ad Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:38:10 -0600 Subject: [PATCH 3/5] Validate existence of sub and sup buttons before adding them to Trix toolbar --- public/js/app.js | 31 ++++++++++++----- public/mix-manifest.json | 4 +-- resources/assets/js/bootstrap.js | 60 +++++++++++++++++++++++++++----- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 132c693c..58dd389a 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -787,20 +787,33 @@ window.Bloodhound = __webpack_require__(/*! corejs-typeahead */ "./node_modules/ // Trix editor __webpack_require__(/*! trix */ "./node_modules/trix/dist/trix.esm.min.js"); -Trix.config.textAttributes.sup = { - tagName: "sup", - inheritable: true -}; + +// Register text attributes Trix.config.textAttributes.sub = { tagName: "sub", inheritable: true }; +Trix.config.textAttributes.sup = { + tagName: "sup", + inheritable: true +}; addEventListener("trix-initialize", function (event) { - var buttonHTML, buttonGroup; - buttonHTML = ''; - buttonHTML += ''; - buttonGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools"); - buttonGroup.insertAdjacentHTML("beforeend", buttonHTML); + var toolbar = event.target.toolbarElement; + var buttonGroup = toolbar.querySelector(".trix-button-group--block-tools"); + if (!buttonGroup) return; + var hasButton = function hasButton(attribute) { + return buttonGroup.querySelector(".trix-button[data-trix-attribute=\"".concat(attribute, "\"]")); + }; + var buttonsToAdd = []; + if (!hasButton("sub")) { + buttonsToAdd.push("\n \n "); + } + if (!hasButton("sup")) { + buttonsToAdd.push("\n \n "); + } + if (buttonsToAdd.length) { + buttonGroup.insertAdjacentHTML("beforeend", buttonsToAdd.join("")); + } }); /***/ }), diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 878d668f..ea82657d 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,6 +1,6 @@ { - "/js/app.js": "/js/app.js?id=48b30a8272fd64c6b0bc09d2a749970a", - "/js/app.js.map": "/js/app.js.map?id=4dc1dc1b24d493458d19c4c5637410bd", + "/js/app.js": "/js/app.js?id=c2f5081a73e0d40806b382f398744cb0", + "/js/app.js.map": "/js/app.js.map?id=1e5ad24e4fb7df33e0e5b17c4a4b917f", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", "/js/manifest.js.map": "/js/manifest.js.map?id=389e00e7d7680b68d4e1d128ce27ff48", "/css/app.css": "/css/app.css?id=bc1752c1cc35e38e55b5e618b7b4f544", diff --git a/resources/assets/js/bootstrap.js b/resources/assets/js/bootstrap.js index 406f7786..ff5458d8 100644 --- a/resources/assets/js/bootstrap.js +++ b/resources/assets/js/bootstrap.js @@ -38,15 +38,57 @@ window.Bloodhound = require('corejs-typeahead'); // Trix editor require('trix'); -Trix.config.textAttributes.sup = { tagName: "sup", inheritable: true } -Trix.config.textAttributes.sub = { tagName: "sub", inheritable: true } +// Register text attributes +Trix.config.textAttributes.sub = { + tagName: "sub", + inheritable: true, +}; -addEventListener("trix-initialize", function(event) { - var buttonHTML, buttonGroup +Trix.config.textAttributes.sup = { + tagName: "sup", + inheritable: true, +}; - buttonHTML = '' - buttonHTML += '' +addEventListener("trix-initialize", function (event) { + const toolbar = event.target.toolbarElement; + const buttonGroup = toolbar.querySelector(".trix-button-group--block-tools"); - buttonGroup = event.target.toolbarElement.querySelector(".trix-button-group--block-tools") - buttonGroup.insertAdjacentHTML("beforeend", buttonHTML) -}) \ No newline at end of file + if (!buttonGroup) return; + + const hasButton = (attribute) => + buttonGroup.querySelector( + `.trix-button[data-trix-attribute="${attribute}"]` + ); + + const buttonsToAdd = []; + + if (!hasButton("sub")) { + buttonsToAdd.push(` + + `); + } + + if (!hasButton("sup")) { + buttonsToAdd.push(` + + `); + } + + if (buttonsToAdd.length) { + buttonGroup.insertAdjacentHTML("beforeend", buttonsToAdd.join("")); + } +}); From b68ef8297466681600ba69f7cfd62eeabfb7e6f1 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:38:48 -0600 Subject: [PATCH 4/5] Change sub and sup buttons labels --- public/js/app.js | 4 ++-- public/mix-manifest.json | 4 ++-- resources/assets/js/bootstrap.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 58dd389a..1a780c73 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -806,10 +806,10 @@ addEventListener("trix-initialize", function (event) { }; var buttonsToAdd = []; if (!hasButton("sub")) { - buttonsToAdd.push("\n \n "); + buttonsToAdd.push("\n \n "); } if (!hasButton("sup")) { - buttonsToAdd.push("\n \n "); + buttonsToAdd.push("\n \n "); } if (buttonsToAdd.length) { buttonGroup.insertAdjacentHTML("beforeend", buttonsToAdd.join("")); diff --git a/public/mix-manifest.json b/public/mix-manifest.json index ea82657d..50115fa5 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,6 +1,6 @@ { - "/js/app.js": "/js/app.js?id=c2f5081a73e0d40806b382f398744cb0", - "/js/app.js.map": "/js/app.js.map?id=1e5ad24e4fb7df33e0e5b17c4a4b917f", + "/js/app.js": "/js/app.js?id=138a5ff0e25454fe9ec8649d5f1a700f", + "/js/app.js.map": "/js/app.js.map?id=f8db9fca85918860e8f43ad3947ba4b1", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", "/js/manifest.js.map": "/js/manifest.js.map?id=389e00e7d7680b68d4e1d128ce27ff48", "/css/app.css": "/css/app.css?id=bc1752c1cc35e38e55b5e618b7b4f544", diff --git a/resources/assets/js/bootstrap.js b/resources/assets/js/bootstrap.js index ff5458d8..cb7c33ee 100644 --- a/resources/assets/js/bootstrap.js +++ b/resources/assets/js/bootstrap.js @@ -70,7 +70,7 @@ addEventListener("trix-initialize", function (event) { data-trix-attribute="sub" title="Subscript" tabindex="-1"> - SUB + X2 `); } @@ -83,7 +83,7 @@ addEventListener("trix-initialize", function (event) { data-trix-attribute="sup" title="Superscript" tabindex="-1"> - SUP + X2 `); } From a1556988b60f5b605954a4b960d048ac349d08a0 Mon Sep 17 00:00:00 2001 From: Wun Chiou