Skip to content

Instantly share code, notes, and snippets.

@jacekkarczmarczyk
Created November 15, 2022 12:04
Show Gist options
  • Save jacekkarczmarczyk/79e8d741fe965770092091a8cd116a90 to your computer and use it in GitHub Desktop.
Save jacekkarczmarczyk/79e8d741fe965770092091a8cd116a90 to your computer and use it in GitHub Desktop.
TipTap PageBreak extension
import { mergeAttributes, Node } from '@tiptap/core';
import { TextSelection } from 'prosemirror-state';
export interface PageBreakRuleOptions {
HTMLAttributes: Record<string, any>;
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
pageBreak: {
/**
* Add a page break
*/
setPageBreak: () => ReturnType;
/**
* Remove a page break
*/
unsetPageBreak: () => ReturnType;
};
}
}
export const PageBreak = Node.create<PageBreakRuleOptions>({
name: 'pageBreak',
addOptions () {
return {
HTMLAttributes: {
style: 'page-break-after: always',
'data-page-break': 'true',
},
};
},
group: 'block',
parseHTML () {
return [
{
tag: 'div',
getAttrs: node => (node as HTMLElement).style.pageBreakAfter === 'always' && (node as HTMLElement).dataset.pageBreak === 'true' && null,
},
];
},
renderHTML ({ HTMLAttributes }) {
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
},
addCommands () {
return {
setPageBreak: () => ({ chain }) => {
return chain()
.insertContent({ type: this.name })
// set cursor after page break
.command(({ dispatch, tr }) => {
if (dispatch) {
const { $to } = tr.selection;
const posAfter = $to.end();
if ($to.nodeAfter) {
tr.setSelection(TextSelection.create(tr.doc, $to.pos));
} else {
// add node after page break if it’s the end of the document
const node = $to.parent.type.contentMatch.defaultType?.create({
style: {
pageBreakAfter: 'always',
},
'data-page-break': 'true',
});
if (node) {
tr.insert(posAfter, node);
tr.setSelection(TextSelection.create(tr.doc, posAfter));
}
}
tr.scrollIntoView();
}
return true;
})
.run();
},
unsetPageBreak: () => ({ chain }) => {
return chain()
.deleteSelection()
.command(() => true)
.run();
},
};
},
});
@jacekkarczmarczyk
Copy link
Author

jacekkarczmarczyk commented Nov 15, 2022

Possible styles (requires https://github.com/Templarian/MaterialDesign):

div[data-page-break="true"].ProseMirror {
    opacity: 0.3;
    border-width: 2px 0;
    border-style: dashed;
    border-color: currentColor;
}

div[data-page-break="true"].ProseMirror-selectednode {
    opacity: 1;
}

div[data-page-break="true"].ProseMirror::before {
    content: "\F06D7";
    display: block;
    font: normal normal normal 24px/1 "Material Design Icons";
    text-align: center;
}

image

@jacekkarczmarczyk
Copy link
Author

PHP extension:

<?php

namespace Tiptap\Nodes;

use Tiptap\Core\Node;
use Tiptap\Utils\HTML;
use Tiptap\Utils\InlineStyle;

class PageBreak extends Node
{
    public static $name = 'pageBreak';

    public function addOptions()
    {
        return [
            'HTMLAttributes' => [
                'style' => 'page-break-after: always',
                'data-page-break' => 'true',
            ],
        ];
    }

    public function parseHTML()
    {
        return [
            [
                'tag' => 'div',
                'parseHTML' => fn ($DOMNode) => (InlineStyle::getAttribute($DOMNode, 'page-break-after') === 'always' && $DOMNode->getAttribute('data-page-break') === 'true') ?: null,
            ],
        ];
    }

    public function renderHTML($node, $HTMLAttributes = [])
    {
        return ['div', HTML::mergeAttributes($this->options['HTMLAttributes'], $HTMLAttributes)];
    }
}

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