Tiptap RTE: Text Indent extension + toolbar items (#18672)
* Tiptap: Text Indent extension * Updates indent manifest icons
This commit is contained in:
118
src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-text-indent-extension.ts
vendored
Normal file
118
src/Umbraco.Web.UI.Client/src/external/tiptap/extensions/tiptap-text-indent-extension.ts
vendored
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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'],
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user