Indent Extension For Tiptap 2 (just want to share)
See original GitHub issueIs your feature request related to a problem? Please describe.
ueberdosis/tiptap#819 has a thorough list of extensions and it’s stated there that there is an indent extension already implemented at https://github.com/Leecason/element-tiptap ( can be seen here in action https://leecason.github.io/element-tiptap/all_extensions ). There, it’s implemented with an HTML attribute, but I wanted to implement it using styles margin-left so I made an extension myself build on top of the TextAlign extension of Tiptap 2 with inspiration from https://github.com/Leecason/element-tiptap
Describe the solution you’d like
(this is my try at indent extension for tiptap 2 )
import { Command, Extension } from '@tiptap/core'
import { Node } from 'prosemirror-model'
import { TextSelection, AllSelection, Transaction } from 'prosemirror-state'
type IndentOptions = {
types: string[],
indentLevels: number[],
defaultIndentLevel: number,
}
declare module '@tiptap/core' {
interface Commands {
indent: {
/**
* Set the indent attribute
*/
indent: () => Command,
/**
* Unset the indent attribute
*/
outdent: () => Command,
}
}
}
export function clamp(val: number, min: number, max: number): number {
if (val < min) {
return min
}
if (val > max) {
return max
}
return val
}
export enum IndentProps {
min = 0,
max = 210,
more = 30,
less = -30
}
export function isBulletListNode(node: Node): boolean {
return node.type.name === 'bullet_list'
}
export function isOrderedListNode(node: Node): boolean {
return node.type.name === 'order_list'
}
export function isTodoListNode(node: Node): boolean {
return node.type.name === 'todo_list'
}
export function isListNode(node: Node): boolean {
return isBulletListNode(node) ||
isOrderedListNode(node) ||
isTodoListNode(node)
}
function setNodeIndentMarkup(tr: Transaction, pos: number, delta: number): Transaction {
if (!tr.doc) return tr
const node = tr.doc.nodeAt(pos)
if (!node) return tr
const minIndent = IndentProps.min
const maxIndent = IndentProps.max
const indent = clamp(
(node.attrs.indent || 0) + delta,
minIndent,
maxIndent,
)
if (indent === node.attrs.indent) return tr
const nodeAttrs = {
...node.attrs,
indent,
}
return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks)
}
function updateIndentLevel(tr: Transaction, delta: number): Transaction {
const { doc, selection } = tr
if (!doc || !selection) return tr
if (!(selection instanceof TextSelection || selection instanceof AllSelection)) {
return tr
}
const { from, to } = selection
doc.nodesBetween(from, to, (node, pos) => {
const nodeType = node.type
if (nodeType.name === 'paragraph' || nodeType.name === 'heading') {
tr = setNodeIndentMarkup(tr, pos, delta)
return false
} if (isListNode(node)) {
return false
}
return true
})
return tr
}
export const Indent = Extension.create<IndentOptions>({
name: 'indent',
defaultOptions: {
types: ['heading', 'paragraph'],
indentLevels: [0, 30, 60, 90, 120, 150, 180, 210],
defaultIndentLevel: 0,
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
indent: {
default: this.options.defaultIndentLevel,
renderHTML: attributes => ({
style: `margin-left: ${attributes.indent}px!important;`
}),
parseHTML: element => ({
indent: parseInt(element.style.marginLeft) || this.options.defaultIndentLevel,
}),
},
},
},
]
},
addCommands() {
return {
indent: () => ({ tr, state, dispatch }) => {
const { selection } = state
tr = tr.setSelection(selection)
tr = updateIndentLevel(tr, IndentProps.more)
if (tr.docChanged) {
// eslint-disable-next-line no-unused-expressions
dispatch && dispatch(tr)
return true
}
return false
},
outdent: () => ({ tr, state, dispatch }) => {
const { selection } = state
tr = tr.setSelection(selection)
tr = updateIndentLevel(tr, IndentProps.less)
if (tr.docChanged) {
// eslint-disable-next-line no-unused-expressions
dispatch && dispatch(tr)
return true
}
return false
},
}
},
addKeyboardShortcuts() {
return {
Tab: () => this.editor.commands.indent(),
'Shift-Tab': () => this.editor.commands.outdent()
}
},
})
Issue Analytics
- State:
- Created 2 years ago
- Reactions:18
- Comments:17 (8 by maintainers)
Top Results From Across the Web
Custom extensions – Tiptap Editor
Extend existing attributes. If you want to add an attribute to an extension and keep existing attributes, you can access them through this.parent()...
Read more >Newest 'tiptap' Questions - Stack Overflow
I 'm currently experimenting with TipTap, an editor framework. My goal is to build a Custom Node extension for TipTap that wraps a...
Read more >Tiptap custom extensions - Hutton
Build a custom class extension – Tiptap Editor Series: Building custom ... Upload to S3 mechanic) Indent Indent Extension For Tiptap 2 (just...
Read more >Tiptap editor - Strapi Market
I don't want to keep toggling between editor mode and preview mode, just to see what ... 1 2 3 4 5 #...
Read more >Switching Rich Text Editors, Part 1: Picking Tiptap
Then, in your code you just need import `slate` when you want the older ... Trix and I've switched from Trix to TipTap...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@sereneinserenade Thank you so much!!
Here is the full code with imports if anyone else needs it. Since I guess I am using the latest version, I also had to return value instead of object within parseHTML according to: https://github.com/ueberdosis/tiptap/issues/1863
Amazing extension! I’ve tried to add an option to it to unindent using backspace, just seems more intuitive to me than using a shift-tab shortcut. Generally, it works, although it seems kinda brute. But I don’t know tiptap enough to do any better.