DTD Plugin
Enforces HTML Document Type Definition (DTD) standards by validating block element nesting and removing invalid <br>
elements.
Description
This plugin ensures HTML compliance with DTD standards by intercepting node insertion events. It prevents invalid block element nesting (e.g., <table>
inside <p>
) and removes extra <br>
tags after pasting. The plugin uses two hooks: beforeInsertNode
and afterInsertNode
.
Example violation prevented:
<!-- Invalid HTML -->
<p><table><tr><td>Data</td></tr></table></p>
<!-- Plugin moves cursor after <p> and removes empty <p> -->
<table><tr><td>Data</td></tr></table>
Features
- Block Nesting Validation: Prevents invalid block-in-block nesting
- Extra BR Removal: Cleans up unnecessary
<br>
tags after insertion - Block Limits Check: Uses configurable list of valid container elements
- Auto-cursor Positioning: Moves cursor to valid position when nesting fails
- Empty Block Cleanup: Removes empty parent blocks after repositioning
dtd.removeExtraBr
Remove extra <br>
elements inside block elements after pasting.
- Type:
boolean
- Default:
true
const editor = Jodit.make('#editor', {
dtd: {
removeExtraBr: true
}
});
What it does:
- After inserting a node, checks if there's an extra
<br>
sibling - If found in a block element (not
<table>
,<pre>
,<blockquote>
,<code>
), removes it - Repositions cursor before the removed
<br>
dtd.checkBlockNesting
Check when inserting a block element if it can be inside another block element.
- Type:
boolean
- Default:
true
const editor = Jodit.make('#editor', {
dtd: {
checkBlockNesting: true
}
});
What it does:
- Before inserting a block element, finds the parent block
- Checks if parent is in
blockLimits
list - If not in list, moves cursor after parent
- Removes parent if it's empty
dtd.blockLimits
List of elements that can contain other blocks.
- Type:
IDictionary<1>
- Default: 26 container elements
const editor = Jodit.make('#editor', {
dtd: {
blockLimits: {
article: 1,
aside: 1,
audio: 1,
body: 1,
caption: 1,
details: 1,
dir: 1,
div: 1,
dl: 1,
fieldset: 1,
figcaption: 1,
figure: 1,
footer: 1,
form: 1,
header: 1,
hgroup: 1,
main: 1,
menu: 1,
nav: 1,
ol: 1,
section: 1,
table: 1,
td: 1,
th: 1,
tr: 1,
ul: 1,
video: 1
}
}
});
Default Behavior
const editor = Jodit.make('#editor');
// Try to insert table inside paragraph
editor.value = '<p>Text</p>';
editor.s.setCursorIn(editor.editor.firstChild); // Inside <p>
editor.s.insertNode(editor.createInside.element('table'));
// Result: <table> inserted after <p>, not inside it
// Empty <p> is removed
Disable Block Nesting Check
const editor = Jodit.make('#editor', {
dtd: {
checkBlockNesting: false
}
});
// Now invalid nesting is allowed
// <p><table>...</table></p> can be created
Disable Extra BR Removal
const editor = Jodit.make('#editor', {
dtd: {
removeExtraBr: false
}
});
// Extra <br> tags won't be removed after paste
Custom Block Limits
const editor = Jodit.make('#editor', {
dtd: {
blockLimits: {
div: 1,
section: 1,
article: 1,
body: 1
// Only these elements can contain blocks
// <p>, <table>, etc. cannot
}
}
});
Allow Blocks Inside Paragraphs
const editor = Jodit.make('#editor', {
dtd: {
blockLimits: {
...Jodit.defaultOptions.dtd.blockLimits,
p: 1 // Add <p> to allowed containers
}
}
});
// Now <p><div>content</div></p> is allowed
Disable Plugin Completely
const editor = Jodit.make('#editor', {
disablePlugins: ['dtd']
});
// No DTD validation
beforeInsertNode
)
Before Insert Hook (Watches :beforeInsertNode
event and runs validation cases:
Check Block Nesting (checkBlockNesting
):
- If
dtd.checkBlockNesting
is false, skip - If node is not a block element, skip
- Find furthest parent block element
- Check if parent is in
blockLimits
dictionary - If not in dictionary:
- Move cursor after parent
- If parent is empty, remove it
// Example: Inserting <table> inside <p>
// <p>|</p> (cursor inside)
// Plugin checks: is 'p' in blockLimits? No.
// Action: Move cursor after <p>
// <p></p>| (cursor after)
// Remove empty <p>
// | (cursor in editor root)
// Insert <table>
// <table>...</table>
afterInsertNode
)
After Insert Hook (Watches :afterInsertNode
event and runs cleanup cases:
Remove Extra BR (removeExtraBr
):
- If
dtd.removeExtraBr
is false, skip - If node itself is
<br>
, skip - Find furthest parent block element
- If parent is
<table>
,<pre>
,<blockquote>
, or<code>
, skip - Find next non-empty sibling
- If sibling is
<br>
:- Move cursor before
<br>
- Remove
<br>
- Move cursor before
BR boxes (excluded from cleanup):
<table>
- BR needed for empty cells<pre>
- BR represents intentional line breaks<blockquote>
- BR may be intentional formatting<code>
- BR represents line breaks in code
Events
This plugin watches two internal events:
:beforeInsertNode
Fired before a node is inserted into the editor.
editor.e.fire(':beforeInsertNode', node);
:afterInsertNode
Fired after a node is inserted into the editor.
editor.e.fire(':afterInsertNode', node);
1. Invalid Table Nesting
<!-- User tries to insert -->
<p>|cursor<table>...</table></p>
<!-- Plugin result -->
<table>...</table>
2. Extra BR After Paste
<!-- After paste -->
<div>Content<br></div>
<!-- Plugin removes BR -->
<div>Content</div>
3. Preserved BR in PRE
<!-- After paste inside <pre> -->
<pre>Line 1<br>Line 2<br></pre>
<!-- BR preserved (brBoxes exclusion) -->
<pre>Line 1<br>Line 2<br></pre>
4. Empty Parent Cleanup
<!-- Before: cursor inside empty <p> -->
<p>|</p>
<!-- Insert <table> -->
<!-- Result: empty <p> removed -->
<table>...</table>
5. Nested DIVs (Valid)
<!-- DIV is in blockLimits -->
<div>
<div>
<p>Nested blocks allowed</p>
</div>
</div>
Configuration
Full configuration with all options:
const editor = Jodit.make('#editor', {
dtd: {
removeExtraBr: true,
checkBlockNesting: true,
blockLimits: {
article: 1,
aside: 1,
audio: 1,
body: 1,
caption: 1,
details: 1,
dir: 1,
div: 1,
dl: 1,
fieldset: 1,
figcaption: 1,
figure: 1,
footer: 1,
form: 1,
header: 1,
hgroup: 1,
main: 1,
menu: 1,
nav: 1,
ol: 1,
section: 1,
table: 1,
td: 1,
th: 1,
tr: 1,
ul: 1,
video: 1
}
}
});
Notes
- Uses
@watch
decorator for event hooks - Runs before/after insert validation cases
- Tag names in
blockLimits
are case-insensitive (converted to lowercase) - Only affects block elements (inline elements pass through)
- Handles document fragments by checking first child
- BR removal excludes:
<table>
,<pre>
,<blockquote>
,<code>
- Empty block removal only happens when not a table cell
- Plugin has empty
afterInit()
andbeforeDestruct()
methods - Extensible via
before-insert/
andafter-insert/
case modules