Skip to content

feat(paste): support filter functions in tag-based paste config - #2999

Open
costajohnt wants to merge 3 commits into
codex-team:nextfrom
costajohnt:feat/paste-filter-function
Open

costajohnt wants to merge 3 commits into
codex-team:nextfrom
costajohnt:feat/paste-filter-function

Conversation

@costajohnt

Copy link
Copy Markdown
Contributor

Extend the paste processing logic to check filter functions when matching pasted elements to tools.

Previously, filter functions in pasteConfig.tags were only used during sanitization (via HTMLJanitor) but ignored during paste processing, causing all elements with a matching tag name to be treated as substitutable regardless of the filter.

Changes

  • Add a filter field to TagSubstitute interface
  • Store filter in getTagsConfig when the sanitization config is a function
  • Add isTagSubstitutable helper that checks both tag name and filter
  • Update processHTML, processElementNode, and containsAnotherToolTags to use the helper

Example

A tool can now use a filter function in pasteConfig.tags to match only specific elements:

static get pasteConfig() {
  return {
    tags: {
      SPAN: (el) => parseInt(el.style.fontWeight, 10) > 400
    }
  }
}

Previously, this filter was only checked during sanitization. Now it's also checked during paste processing, so only <span> elements that pass the filter will be assigned to the tool.

Closes #2959

@costajohnt
costajohnt marked this pull request as ready for review March 29, 2026 18:37
@costajohnt

Copy link
Copy Markdown
Contributor Author

Bumping this in case it got lost in the queue. The change is opt-in (filter functions are only used when configured), so existing paste configs are unaffected. Happy to address any feedback.

@neSpecc neSpecc left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall seems ok. Please, add a corresponded test case. And write a line in a changelog

@costajohnt
costajohnt force-pushed the feat/paste-filter-function branch from d5fa0e7 to 36ce01b Compare May 10, 2026 04:05
@costajohnt

Copy link
Copy Markdown
Contributor Author

Totally understand if priorities have shifted here. Let me know if this is still something you'd consider, otherwise I'm happy to close it out.

Extend the paste processing logic to check filter functions when
matching pasted elements to tools. Previously, filter functions in
pasteConfig.tags were only used during sanitization (via HTMLJanitor)
but ignored during paste processing, causing all elements with a
matching tag name to be treated as substitutable regardless of the
filter.

Add a filter field to TagSubstitute, store it in getTagsConfig when
the sanitization config is a function, and introduce isTagSubstitutable
that checks both tag name and filter. Update processHTML,
processElementNode, and containsAnotherToolTags to use the helper.

Closes codex-team#2959
Adds Cypress test exercising both branches of the filter function
(accept and reject), and a 2.31.6 changelog entry.
The processElementNode refactor left a dead `const tags = ...` line that
broke `yarn lint`. Remove it.

The new filter-function Cypress test passed pasteConfig parsing but
failed visually because the test's FilteredDivTool only assigned to
`this.data` in onPaste, never updating the rendered element. Editor.js
calls render() once at block creation and dispatches onPaste later via
requestIdleCallback (paragraph tool follows the same pattern). Track
the rendered element and mutate its textContent in onPaste so the
asserted "Accepted" text shows up.
@costajohnt
costajohnt force-pushed the feat/paste-filter-function branch from 04547cf to 47e2e0b Compare August 5, 2026 15:47
@costajohnt

Copy link
Copy Markdown
Contributor Author

Hi @neSpecc, following up on your review. Both asks are in, I just never said so here, sorry about that.

  • Test case: 34864d3 adds a Cypress test in test/cypress/tests/copy-paste.cy.ts covering both branches of the filter function, one where the filter accepts the pasted node and one where it rejects it.
  • Changelog: same commit adds an Improvement line under 2.31.6 in docs/CHANGELOG.md.

47e2e0b after it is just cleanup, dropping an unused tags var and stabilizing the new test.

I've also rebased onto current next, so this is running against the Node version fix from #3013. Anything else you'd like changed here?

@codeCraft-Ritik codeCraft-Ritik left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, HTMLJanitor checked filters during sanitization, and now isTagSubstitutable checks it during paste processing (processHTML, processElementNode).

Does HTMLJanitor strip, normalize, or mutate styles/attributes on the element before paste processing runs?

For instance, if a filter relies on an inline style like el.style.fontWeight, but HTMLJanitor strips or cleans inline styles beforehand, the element might fail the filter during paste processing even if it passed initial sanitization. Confirming the exact ordering between sanitization and isTagSubstitutable evaluation will prevent unexpected rejections.

@costajohnt

Copy link
Copy Markdown
Contributor Author

Good question. Ordering on the clipboard path is: HTMLJanitor first, then isTagSubstitutable.

processDataTransfer passes the filter function straight through to HTMLJanitor as the tag rule (paste.ts sanitizationConfig ?? {}), same as before this PR. Janitor calls the function with the raw element and uses the return value as the attribute whitelist: true keeps every attribute, an object keeps only the listed ones, false unwraps the element. It never rewrites style values, it only drops attributes that are not whitelisted. The cleaned string is then re-parsed in processHTML, and that is where isTagSubstitutable runs the filter again on the sanitized element.

So for a boolean filter like the el.style.fontWeight example, true means all attributes survive and the second check sees the same styles. No rejection.

The case you describe only happens if the filter returns an attribute map that omits the attribute it inspects, e.g. el.style.fontWeight > 400 ? {} : false. Then janitor strips style and the second evaluation fails. That is consistent with what the function declared it wants kept; returning { style: true } (or true) fixes it. I can add a line to the filter docs saying the filter is re-evaluated after sanitization so any attribute it reads must be whitelisted, if the maintainers want that.

renderFromHTML skips janitor entirely, so there the filter only ever sees the raw element.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Paste Config Improvement: add support for a filter function in pasteConfig to allow matching elements by attributes

3 participants