Tiptap RTE: Text Indent extension + toolbar items (#18672)

* Tiptap: Text Indent extension

* Updates indent manifest icons
This commit is contained in:
Lee Kelleher
2025-03-13 14:52:58 +00:00
committed by GitHub
parent e9c97f8c9b
commit a02db287ce
7 changed files with 184 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
/* This Source Code has been derived from Tiptiz.
* https://github.com/tiptiz/editor/blob/main/packages/tiptiz-extension-indent/src/indent.ts
* SPDX-License-Identifier: MIT
* Copyright © 2024 Owen Kriz.
* Modifications are licensed under the MIT License.
*/
import type { Dispatch } from '@tiptap/core';
import type { EditorState, Transaction } from '@tiptap/pm/state';
import { Extension } from '@tiptap/core';
import { AllSelection, TextSelection } from '@tiptap/pm/state';
export interface TextIndentOptions {
minLevel: number;
maxLevel: number;
types: Array<string>;
}
export const TextIndent = Extension.create<TextIndentOptions>({
name: 'textIndent',
addOptions() {
return {
minLevel: 0,
maxLevel: 5,
types: ['heading', 'paragraph', 'listItem', 'taskItem'],
};
},
addGlobalAttributes() {
return [
{
types: this.options.types,
attributes: {
indent: {
default: null,
parseHTML: (element) => {
const minLevel = this.options.minLevel;
const maxLevel = this.options.maxLevel;
const indent = element.style.textIndent;
return indent ? Math.max(minLevel, Math.min(maxLevel, parseInt(indent, 10))) : null;
},
renderHTML: (attributes) => {
if (!attributes.indent) return {};
return {
style: `text-indent: ${attributes.indent}rem;`,
};
},
},
},
},
];
},
addCommands() {
const updateNodeIndentMarkup = (tr: Transaction, pos: number, delta: number) => {
const node = tr.doc.nodeAt(pos);
if (!node) return tr;
const minLevel = this.options.minLevel;
const maxLevel = this.options.maxLevel;
let level = (node.attrs.indent || 0) + delta;
level = Math.max(minLevel, Math.min(maxLevel, parseInt(level, 10)));
if (level === node.attrs.indent) return tr;
return tr.setNodeMarkup(pos, node.type, { ...node.attrs, indent: level }, node.marks);
};
const updateIndentLevel = (tr: Transaction, delta: number) => {
if (tr.selection instanceof TextSelection || tr.selection instanceof AllSelection) {
const { from, to } = tr.selection;
tr.doc.nodesBetween(from, to, (node, pos) => {
if (this.options.types.includes(node.type.name)) {
tr = updateNodeIndentMarkup(tr, pos, delta);
return false;
}
return true;
});
}
return tr;
};
type CommanderArgs = {
tr: Transaction;
state: EditorState;
dispatch: Dispatch;
};
const commanderFactory = (direction: number) => () =>
function chainHandler({ tr, state, dispatch }: CommanderArgs) {
const { selection } = state;
tr.setSelection(selection);
tr = updateIndentLevel(tr, direction);
if (tr.docChanged) {
if (dispatch instanceof Function) dispatch(tr);
return true;
}
return false;
};
return {
textIndent: commanderFactory(1),
textOutdent: commanderFactory(-1),
};
},
});
declare module '@tiptap/core' {
interface Commands<ReturnType> {
textIndent: {
textIndent: () => ReturnType;
textOutdent: () => ReturnType;
};
}
}

View File

@@ -35,6 +35,7 @@ export * from './extensions/tiptap-figure.extension.js';
export * from './extensions/tiptap-span.extension.js';
export * from './extensions/tiptap-html-global-attributes.extension.js';
export * from './extensions/tiptap-text-direction-extension.js';
export * from './extensions/tiptap-text-indent-extension.js';
export * from './extensions/tiptap-trailing-node.extension.js';
export * from './extensions/tiptap-umb-embedded-media.extension.js';
export * from './extensions/tiptap-umb-image.extension.js';

View File

@@ -1037,6 +1037,7 @@ export const data: Array<UmbMockDataTypeModel> = [
'Umb.Tiptap.Table',
'Umb.Tiptap.TextAlign',
'Umb.Tiptap.TextDirection',
'Umb.Tiptap.TextIndent',
'Umb.Tiptap.Underline',
],
},
@@ -1069,6 +1070,7 @@ export const data: Array<UmbMockDataTypeModel> = [
'Umb.Tiptap.Toolbar.TextDirectionRtl',
'Umb.Tiptap.Toolbar.TextDirectionLtr',
],
['Umb.Tiptap.Toolbar.TextIndent', 'Umb.Tiptap.Toolbar.TextOutdent'],
[
'Umb.Tiptap.Toolbar.BulletList',
'Umb.Tiptap.Toolbar.OrderedList',

View File

@@ -0,0 +1,10 @@
import { UmbTiptapExtensionApiBase } from '../base.js';
import { TextIndent } from '@umbraco-cms/backoffice/external/tiptap';
export default class UmbTiptapTextIndentExtensionApi extends UmbTiptapExtensionApiBase {
getTiptapExtensions = () => [
TextIndent.configure({
types: ['div', 'heading', 'paragraph', 'blockquote', 'listItem', 'orderedList', 'bulletList'],
}),
];
}

View File

@@ -161,6 +161,17 @@ const coreExtensions: Array<ManifestTiptapExtension> = [
group: '#tiptap_extGroup_media',
},
},
{
type: 'tiptapExtension',
alias: 'Umb.Tiptap.TextIndent',
name: 'Text Indent Tiptap Extension',
api: () => import('./core/text-indent.tiptap-api.js'),
meta: {
icon: 'icon-science',
label: 'Text Indent',
group: '#tiptap_extGroup_formatting',
},
},
];
const toolbarExtensions: Array<UmbExtensionManifest> = [
@@ -606,6 +617,32 @@ const toolbarExtensions: Array<UmbExtensionManifest> = [
label: '#tiptap_charmap',
},
},
{
type: 'tiptapToolbarExtension',
kind: 'button',
alias: 'Umb.Tiptap.Toolbar.TextIndent',
name: 'Text Indent Tiptap Extension',
api: () => import('./toolbar/text-indent.tiptap-toolbar-api.js'),
forExtensions: ['Umb.Tiptap.TextIndent'],
meta: {
alias: 'indent',
icon: 'icon-indent',
label: 'Indent',
},
},
{
type: 'tiptapToolbarExtension',
kind: 'button',
alias: 'Umb.Tiptap.Toolbar.TextOutdent',
name: 'Text Outdent Tiptap Extension',
api: () => import('./toolbar/text-outdent.tiptap-toolbar-api.js'),
forExtensions: ['Umb.Tiptap.TextIndent'],
meta: {
alias: 'outdent',
icon: 'icon-outdent',
label: 'Outdent',
},
},
];
const extensions = [

View File

@@ -0,0 +1,8 @@
import { UmbTiptapToolbarElementApiBase } from '../base.js';
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
export default class UmbTiptapToolbarTextIndentExtensionApi extends UmbTiptapToolbarElementApiBase {
override execute(editor?: Editor) {
editor?.chain().focus().textIndent().run();
}
}

View File

@@ -0,0 +1,8 @@
import { UmbTiptapToolbarElementApiBase } from '../base.js';
import type { Editor } from '@umbraco-cms/backoffice/external/tiptap';
export default class UmbTiptapToolbarTextOutdentExtensionApi extends UmbTiptapToolbarElementApiBase {
override execute(editor?: Editor) {
editor?.chain().focus().textOutdent().run();
}
}