Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tiptap RTE: Text Indent extension + toolbar items #18672

Merged
merged 2 commits into from
Mar 13, 2025
Merged
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
Original file line number Diff line number Diff line change
@@ -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;
};
}
}
1 change: 1 addition & 0 deletions src/Umbraco.Web.UI.Client/src/external/tiptap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,7 @@ export const data: Array<UmbMockDataTypeModel> = [
'Umb.Tiptap.Table',
'Umb.Tiptap.TextAlign',
'Umb.Tiptap.TextDirection',
'Umb.Tiptap.TextIndent',
'Umb.Tiptap.Underline',
],
},
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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'],
}),
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -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> = [
Expand Down Expand Up @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading