From 647ac21b8ac9e71eee92cd4c23944faacfb398ee Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Tue, 12 Nov 2024 12:23:11 +1300 Subject: [PATCH 01/10] Add a new text_attribute called `target` using the `a` group tag --- src/trix/config/text_attributes.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/trix/config/text_attributes.js b/src/trix/config/text_attributes.js index 8ac432a73..c727912fa 100644 --- a/src/trix/config/text_attributes.js +++ b/src/trix/config/text_attributes.js @@ -27,6 +27,16 @@ export default { } }, }, + target: { + groupTagName: "a", + parser(element) { + const matchingSelector = `a:not(${attachmentSelector})` + const link = element.closest(matchingSelector) + if (link) { + return link.getAttribute("target") + } + }, + }, strike: { tagName: "del", inheritable: true, From 073f0d7099b56e535c30d70ccf2a6ca954118f4c Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Tue, 12 Nov 2024 12:23:48 +1300 Subject: [PATCH 02/10] Add target as an allowed attribute to the sanitiser --- src/trix/models/html_sanitizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/trix/models/html_sanitizer.js b/src/trix/models/html_sanitizer.js index 3dd0b3e68..19df347c5 100644 --- a/src/trix/models/html_sanitizer.js +++ b/src/trix/models/html_sanitizer.js @@ -2,7 +2,7 @@ import BasicObject from "trix/core/basic_object" import { nodeIsAttachmentElement, removeNode, tagName, walkTree } from "trix/core/helpers" -const DEFAULT_ALLOWED_ATTRIBUTES = "style href src width height language class".split(" ") +const DEFAULT_ALLOWED_ATTRIBUTES = "style href target src width height language class".split(" ") const DEFAULT_FORBIDDEN_PROTOCOLS = "javascript:".split(" ") const DEFAULT_FORBIDDEN_ELEMENTS = "script iframe form noscript".split(" ") From 5098d1d3202b38b375ce52558f19249ff7c590c3 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Tue, 12 Nov 2024 12:26:20 +1300 Subject: [PATCH 03/10] Modify the toolbar controller to allow setting groups of attributes Adjustments to cope with checkboxes --- src/trix/controllers/toolbar_controller.js | 80 +++++++++++++++------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/src/trix/controllers/toolbar_controller.js b/src/trix/controllers/toolbar_controller.js index a1178fea7..b88900666 100644 --- a/src/trix/controllers/toolbar_controller.js +++ b/src/trix/controllers/toolbar_controller.js @@ -9,15 +9,21 @@ const dialogSelector = "[data-trix-dialog]" const activeDialogSelector = `${dialogSelector}[data-trix-active]` const dialogButtonSelector = `${dialogSelector} [data-trix-method]` const dialogInputSelector = `${dialogSelector} [data-trix-input]` -const getInputForDialog = (element, attributeName) => { - if (!attributeName) { attributeName = getAttributeName(element) } - return element.querySelector(`[data-trix-input][name='${attributeName}']`) +const getInputForDialog = (element, dialogName, attributeName) => { + const name = dialogName === attributeName ? dialogName : `${dialogName}[${attributeName}]` + return element.querySelector(`[data-trix-input][name='${name}']`) } const getActionName = (element) => element.getAttribute("data-trix-action") const getAttributeName = (element) => { - return element.getAttribute("data-trix-attribute") || element.getAttribute("data-trix-dialog-attribute") + return element.getAttribute("data-trix-attribute") } const getDialogName = (element) => element.getAttribute("data-trix-dialog") +const getDialogAttributeNames = (element) => { + return Array.from( + element.getAttribute("data-trix-dialog-attribute") || + element.getAttribute("data-trix-dialog-attributes").split(" ") + ) +} export default class ToolbarController extends BasicObject { constructor(element) { @@ -94,7 +100,7 @@ export default class ToolbarController extends BasicObject { event.preventDefault() const attribute = element.getAttribute("name") const dialog = this.getDialog(attribute) - this.setAttribute(dialog) + this.setAttributes(dialog) } if (event.keyCode === 27) { // Escape key @@ -190,37 +196,65 @@ export default class ToolbarController extends BasicObject { disabledInput.removeAttribute("disabled") }) - const attributeName = getAttributeName(element) - if (attributeName) { - const input = getInputForDialog(element, dialogName) + const attributeNames = getDialogAttributeNames(element) + for (let attributeName of attributeNames) { + const input = getInputForDialog(element, dialogName, attributeName) if (input) { - input.value = this.attributes[attributeName] || "" - input.select() + switch (input.type) { + case "checkbox": + input.checked = this.attributes[attributeName] === input.value + break + default: + input.value = this.attributes[attributeName] || "" + input.select() + } } } return this.delegate?.toolbarDidShowDialog(dialogName) } - setAttribute(dialogElement) { - const attributeName = getAttributeName(dialogElement) - const input = getInputForDialog(dialogElement, attributeName) - if (input.willValidate && !input.checkValidity()) { - input.setAttribute("data-trix-validate", "") - input.classList.add("trix-validate") - return input.focus() - } else { - this.delegate?.toolbarDidUpdateAttribute(attributeName, input.value) - return this.hideDialog() + setAttributes(dialogElement) { + const dialogName = getDialogName(dialogElement) + const attributeNames = getDialogAttributeNames(dialogElement) + + for (let attributeName of attributeNames) { + const input = getInputForDialog(dialogElement, dialogName, attributeName) + + if (input.willValidate && !input.checkValidity()) { + input.setAttribute("data-trix-validate", "") + input.classList.add("trix-validate") + input.focus() + } else { + switch (input.type) { + case "checkbox": + if (input.checked) { + this.delegate?.toolbarDidUpdateAttribute(attributeName, input.value) + } + break + default: + this.delegate?.toolbarDidUpdateAttribute(attributeName, input.value) + } + } } + + this.hideDialog() } - removeAttribute(dialogElement) { - const attributeName = getAttributeName(dialogElement) - this.delegate?.toolbarDidRemoveAttribute(attributeName) + setAttribute(dialogElement) { this.setAttributes(dialogElement) } + + removeAttributes(dialogElement) { + const attributeNames = getDialogAttributeNames(dialogElement) + + for (let attributeName of attributeNames) { + this.delegate?.toolbarDidRemoveAttribute(attributeName) + } + return this.hideDialog() } + removeAttribute(dialogElement) { this.removeAttributes(dialogElement) } + hideDialog() { const element = this.element.querySelector(activeDialogSelector) if (element) { From 94151ffc749d8765b112d02c161e6859bbbfe376 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Tue, 12 Nov 2024 19:23:17 +1300 Subject: [PATCH 04/10] Allow createContainerElement to cope with more than one attribute on a groupTag The only flaw would be that it will fixate on the first matching groupTag so the order attributes are listed in the toolbar dialog data attribute is important. --- src/trix/views/piece_view.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/trix/views/piece_view.js b/src/trix/views/piece_view.js index c03d6c266..6d7a70cc4 100644 --- a/src/trix/views/piece_view.js +++ b/src/trix/views/piece_view.js @@ -111,17 +111,24 @@ export default class PieceView extends ObjectView { } createContainerElement() { + const attributes = {} + let groupTagName + for (const key in this.attributes) { const value = this.attributes[key] const config = getTextConfig(key) if (config) { if (config.groupTagName) { - const attributes = {} - attributes[key] = value - return makeElement(config.groupTagName, attributes) + groupTagName = groupTagName || config.groupTagName + + if (config.groupTagName === groupTagName) { + attributes[key] = value + } } } } + + if (groupTagName) { return makeElement(groupTagName, attributes) } } preserveSpaces(string) { From 7cf80bbf28a55dba7e37df04be36590d9e547023 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Tue, 12 Nov 2024 19:23:34 +1300 Subject: [PATCH 05/10] Remove an attribute if the checkbox isn't checked --- src/trix/controllers/toolbar_controller.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/trix/controllers/toolbar_controller.js b/src/trix/controllers/toolbar_controller.js index b88900666..f92fdb535 100644 --- a/src/trix/controllers/toolbar_controller.js +++ b/src/trix/controllers/toolbar_controller.js @@ -230,6 +230,8 @@ export default class ToolbarController extends BasicObject { case "checkbox": if (input.checked) { this.delegate?.toolbarDidUpdateAttribute(attributeName, input.value) + } else { + this.delegate?.toolbarDidRemoveAttribute(attributeName) } break default: From 09cccbf0c05cdfa44b56949da736ebb7109690c5 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Wed, 13 Nov 2024 09:43:23 +1300 Subject: [PATCH 06/10] Use const not let as the variable isn't variable --- src/trix/controllers/toolbar_controller.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/trix/controllers/toolbar_controller.js b/src/trix/controllers/toolbar_controller.js index f92fdb535..c13b46120 100644 --- a/src/trix/controllers/toolbar_controller.js +++ b/src/trix/controllers/toolbar_controller.js @@ -197,7 +197,7 @@ export default class ToolbarController extends BasicObject { }) const attributeNames = getDialogAttributeNames(element) - for (let attributeName of attributeNames) { + for (const attributeName of attributeNames) { const input = getInputForDialog(element, dialogName, attributeName) if (input) { switch (input.type) { @@ -218,7 +218,7 @@ export default class ToolbarController extends BasicObject { const dialogName = getDialogName(dialogElement) const attributeNames = getDialogAttributeNames(dialogElement) - for (let attributeName of attributeNames) { + for (const attributeName of attributeNames) { const input = getInputForDialog(dialogElement, dialogName, attributeName) if (input.willValidate && !input.checkValidity()) { @@ -248,7 +248,7 @@ export default class ToolbarController extends BasicObject { removeAttributes(dialogElement) { const attributeNames = getDialogAttributeNames(dialogElement) - for (let attributeName of attributeNames) { + for (const attributeName of attributeNames) { this.delegate?.toolbarDidRemoveAttribute(attributeName) } From 5c9b9ba2ac295ad47c2b002f2d64198bb7868376 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Wed, 13 Nov 2024 09:53:03 +1300 Subject: [PATCH 07/10] Update ci.yml --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ab86c134..4779c6d8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - target-blank pull_request: jobs: From 0b666a178923eef2365db0a79aa6a89fdcf172c1 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Wed, 13 Nov 2024 10:03:17 +1300 Subject: [PATCH 08/10] Update ci.yml --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4779c6d8b..8ab86c134 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - target-blank pull_request: jobs: From f15055bc253e41afc6cd5834d007901d1792e72a Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Wed, 13 Nov 2024 10:27:29 +1300 Subject: [PATCH 09/10] Fix dialog attribute splitting and wrapping --- src/trix/controllers/toolbar_controller.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/trix/controllers/toolbar_controller.js b/src/trix/controllers/toolbar_controller.js index c13b46120..3d62b8003 100644 --- a/src/trix/controllers/toolbar_controller.js +++ b/src/trix/controllers/toolbar_controller.js @@ -19,10 +19,8 @@ const getAttributeName = (element) => { } const getDialogName = (element) => element.getAttribute("data-trix-dialog") const getDialogAttributeNames = (element) => { - return Array.from( - element.getAttribute("data-trix-dialog-attribute") || - element.getAttribute("data-trix-dialog-attributes").split(" ") - ) + return (element.getAttribute("data-trix-dialog-attribute") || + element.getAttribute("data-trix-dialog-attributes")).split(" ") } export default class ToolbarController extends BasicObject { From 9dc19902cbd0254aa14a270e390e7eda7c348930 Mon Sep 17 00:00:00 2001 From: Brendon Muir Date: Wed, 13 Nov 2024 10:27:45 +1300 Subject: [PATCH 10/10] Prevent chrome page reload errors in testing locally --- src/test/test_helpers/test_helpers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/test_helpers/test_helpers.js b/src/test/test_helpers/test_helpers.js index f51dc3566..cd28afc72 100644 --- a/src/test/test_helpers/test_helpers.js +++ b/src/test/test_helpers/test_helpers.js @@ -21,6 +21,8 @@ export const testGroup = function (name, options, callback) { } const beforeEach = async () => { + window.onbeforeunload = () => {} // Inhibit Chrome page reload errors in testing + // Ensure window is active on CI so focus and blur events are natively dispatched window.focus() @@ -33,6 +35,8 @@ export const testGroup = function (name, options, callback) { } const afterEach = () => { + window.onbeforeunload = null + if (template != null) setFixtureHTML("") return teardown?.() }