Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Core/DOMSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,15 @@ private function closeMarkTags($markTagsToClose, &$markStack, &$markTagsToReopen
while (! empty($markTagsToClose)) {
# close mark tag from the top of the stack
$markTag = array_pop($markStack);

# the mark stack can run empty before every tag was closed when marks
# are nested inconsistently (e.g. a mark duplicated on a node that
# inherits the same mark from a sibling). Stop here instead of calling
# a method on null.
if ($markTag === null) {
break;
}

$markExtension = $markTag[0];
$mark = $markTag[1];
$html[] = $this->renderClosingTag($markExtension->renderHTML($mark));
Expand Down
40 changes: 40 additions & 0 deletions tests/DOMSerializer/WrongFormatTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,43 @@

expect($result)->toEqual('<a target="_blank" rel="noopener noreferrer nofollow" href="https://tiptap.dev">Example Link</a>');
});

test('duplicated mark inherited from a sibling does not crash the serializer', function () {
// The second text node carries the same mark twice while inheriting it from
// the first node. Opening is skipped for both duplicates (the sibling already
// "has" the mark), but closing fires for both, popping the mark stack once more
// than it was filled. This used to call a method on null in closeMarkTags().
$document = [
'type' => 'doc',
'content' => [
[
'type' => 'paragraph',
'content' => [
[
'type' => 'text',
'text' => 'a',
'marks' => [
['type' => 'bold'],
],
],
[
'type' => 'text',
'text' => 'b',
'marks' => [
['type' => 'bold'],
['type' => 'bold'],
],
],
],
],
],
];

$result = (new Editor([
'extensions' => [
new StarterKit,
],
]))->setContent($document)->getHTML();

expect($result)->toEqual('<p><strong>ab</strong></p>');
});