Skip to content

Commit 118bd58

Browse files
committed
feat(paste): support filter functions in tag-based paste config
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 #2959
1 parent 530ec56 commit 118bd58

1 file changed

Lines changed: 43 additions & 3 deletions

File tree

src/components/modules/paste.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ interface TagSubstitute {
2828
* But Tool can explicitly specify sanitizer configuration for supported tags
2929
*/
3030
sanitizationConfig?: SanitizerRule;
31+
32+
/**
33+
* Optional filter function to decide whether a specific element matches this tag substitute.
34+
* When provided, an element must pass the filter in addition to matching the tag name.
35+
*/
36+
filter?: (el: Element) => boolean;
3137
}
3238

3339
/**
@@ -375,16 +381,50 @@ export default class Paste extends Module {
375381
*/
376382
const sanitizationConfig = _.isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : null;
377383

384+
/**
385+
* If the sanitization config is a function, it acts as a filter —
386+
* only elements that pass the filter should be treated as substitutable.
387+
* The function returns a TagConfig: false means reject, anything else means accept.
388+
*/
389+
const filter = _.isFunction(sanitizationConfig)
390+
? (el: Element): boolean => {
391+
const result = (sanitizationConfig as (el: Element) => unknown)(el);
392+
393+
return result !== false;
394+
}
395+
: undefined;
396+
378397
this.toolsTags[tag.toUpperCase()] = {
379398
tool,
380399
sanitizationConfig,
400+
filter,
381401
};
382402
});
383403
});
384404

385405
this.tagsByTool[tool.name] = toolTags.map((t) => t.toUpperCase());
386406
}
387407

408+
/**
409+
* Check if an element matches a registered tag substitute, including any filter function.
410+
*
411+
* @param element - the element to check
412+
* @returns true if the element's tag is registered and passes its filter (if any)
413+
*/
414+
private isTagSubstitutable(element: Element): boolean {
415+
const tagSubstitute = this.toolsTags[element.tagName];
416+
417+
if (!tagSubstitute) {
418+
return false;
419+
}
420+
421+
if (tagSubstitute.filter) {
422+
return tagSubstitute.filter(element);
423+
}
424+
425+
return true;
426+
}
427+
388428
/**
389429
* Get files` types and extensions to substitute by Tool
390430
*
@@ -612,7 +652,7 @@ export default class Paste extends Module {
612652
content = node as HTMLElement;
613653
isBlock = true;
614654

615-
if (this.toolsTags[content.tagName]) {
655+
if (this.isTagSubstitutable(content)) {
616656
tool = this.toolsTags[content.tagName].tool;
617657
}
618658
break;
@@ -907,12 +947,12 @@ export default class Paste extends Module {
907947
const { tool } = this.toolsTags[element.tagName] || {};
908948
const toolTags = this.tagsByTool[tool?.name] || [];
909949

910-
const isSubstitutable = tags.includes(element.tagName);
950+
const isSubstitutable = this.isTagSubstitutable(element);
911951
const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
912952
const containsAnotherToolTags = Array
913953
.from(element.children)
914954
.some(
915-
({ tagName }) => tags.includes(tagName) && !toolTags.includes(tagName)
955+
(child) => this.isTagSubstitutable(child) && !toolTags.includes(child.tagName)
916956
);
917957

918958
const containsBlockElements = Array.from(element.children).some(

0 commit comments

Comments
 (0)