From 41841c1aa754e1cb335f2af1e13c17ad0542e039 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Fri, 15 Mar 2024 20:01:18 +0700 Subject: [PATCH] UBERF-6037 Enhance codeblock extension Signed-off-by: Alexander Onnikov --- .../src/components/extension/codeblock.ts | 71 +++++++++++++++++++ packages/text-editor/src/kits/default-kit.ts | 34 +++++---- packages/text-editor/src/kits/editor-kit.ts | 5 +- packages/text/src/kits/default-kit.ts | 21 +++--- 4 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 packages/text-editor/src/components/extension/codeblock.ts diff --git a/packages/text-editor/src/components/extension/codeblock.ts b/packages/text-editor/src/components/extension/codeblock.ts new file mode 100644 index 00000000000..2447fb3b8e6 --- /dev/null +++ b/packages/text-editor/src/components/extension/codeblock.ts @@ -0,0 +1,71 @@ +// +// Copyright © 2024 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import { isActive } from '@tiptap/core' +import CodeBlock from '@tiptap/extension-code-block' + +export const CodeBlockExtension = CodeBlock.extend({ + addCommands () { + return { + setCodeBlock: + (attributes) => + ({ commands }) => { + return commands.setNode(this.name, attributes) + }, + toggleCodeBlock: + (attributes) => + ({ chain, commands, state }) => { + const { from, to } = state.selection + + // merge multiple paragraphs into codeblock + if (!isActive(state, this.name) && !state.selection.empty) { + let hasParagraphsOnlySelected = true + const textArr: string[] = [] + state.doc.nodesBetween(from, to, (node, pos) => { + if (node.isInline) { + return false + } + if (node.type.name !== 'paragraph') { + if (pos + 1 <= from && pos + node.nodeSize - 1 >= to) { + // skip nodes outside of the selected range + return false + } else { + // cannot merge non-paragraph nodes inside selection + hasParagraphsOnlySelected = false + return false + } + } else { + const selectedText = (node.textContent ?? '').slice( + pos + 1 > from ? 0 : from - pos - 1, + pos + node.nodeSize - 1 < to ? node.nodeSize - 1 : to - pos - 1 + ) + textArr.push(selectedText ?? '') + } + }) + if (hasParagraphsOnlySelected && textArr.length > 1) { + return chain() + .command(({ state, tr }) => { + tr.replaceRangeWith(from, to, this.type.create(attributes, state.schema.text(textArr.join('\n')))) + return true + }) + .setTextSelection({ from: from + 2, to: from + 2 }) + .run() + } + } + return commands.toggleNode(this.name, 'paragraph', attributes) + } + } + } +}) diff --git a/packages/text-editor/src/kits/default-kit.ts b/packages/text-editor/src/kits/default-kit.ts index 5401dc0e3bc..77e1d94b980 100644 --- a/packages/text-editor/src/kits/default-kit.ts +++ b/packages/text-editor/src/kits/default-kit.ts @@ -14,7 +14,8 @@ // import { Extension } from '@tiptap/core' - +import { type CodeOptions } from '@tiptap/extension-code' +import { type CodeBlockOptions } from '@tiptap/extension-code-block' import type { Level } from '@tiptap/extension-heading' import Highlight from '@tiptap/extension-highlight' import Link from '@tiptap/extension-link' @@ -22,31 +23,36 @@ import Typography from '@tiptap/extension-typography' import StarterKit from '@tiptap/starter-kit' export interface DefaultKitOptions { + codeBlock?: false heading?: { levels?: Level[] } history?: false } +export const codeOptions: CodeOptions = { + HTMLAttributes: { + class: 'proseCode' + } +} + +export const codeBlockOptions: CodeBlockOptions = { + languageClassPrefix: 'language-', + exitOnArrowDown: true, + exitOnTripleEnter: true, + HTMLAttributes: { + class: 'proseCodeBlock' + } +} + export const DefaultKit = Extension.create({ name: 'defaultKit', addExtensions () { return [ StarterKit.configure({ - code: { - HTMLAttributes: { - class: 'proseCode' - } - }, - codeBlock: { - languageClassPrefix: 'language-', - exitOnArrowDown: true, - exitOnTripleEnter: true, - HTMLAttributes: { - class: 'proseCodeBlock' - } - }, + code: codeOptions, + codeBlock: this.options.codeBlock ?? codeBlockOptions, heading: this.options.heading, history: this.options.history }), diff --git a/packages/text-editor/src/kits/editor-kit.ts b/packages/text-editor/src/kits/editor-kit.ts index 858b659579f..6f8c4d1de75 100644 --- a/packages/text-editor/src/kits/editor-kit.ts +++ b/packages/text-editor/src/kits/editor-kit.ts @@ -21,8 +21,9 @@ import TaskItem from '@tiptap/extension-task-item' import TaskList from '@tiptap/extension-task-list' import Underline from '@tiptap/extension-underline' -import { DefaultKit, type DefaultKitOptions } from './default-kit' +import { DefaultKit, type DefaultKitOptions, codeBlockOptions } from './default-kit' +import { CodeBlockExtension } from '../components/extension/codeblock' import { CodemarkExtension } from '../components/extension/codemark' import { NodeUuidExtension } from '../components/extension/nodeUuid' import { Table, TableCell, TableRow } from '../components/extension/table' @@ -62,10 +63,12 @@ export const EditorKit = Extension.create({ return [ DefaultKit.configure({ ...this.options, + codeBlock: false, heading: { levels: headingLevels } }), + CodeBlockExtension.configure(codeBlockOptions), CodemarkExtension, Underline, ListKeymap.configure({ diff --git a/packages/text/src/kits/default-kit.ts b/packages/text/src/kits/default-kit.ts index 871f3dae0d3..60b0e464f0b 100644 --- a/packages/text/src/kits/default-kit.ts +++ b/packages/text/src/kits/default-kit.ts @@ -19,9 +19,10 @@ import { Level } from '@tiptap/extension-heading' import Highlight from '@tiptap/extension-highlight' import Link from '@tiptap/extension-link' import Typography from '@tiptap/extension-typography' -import StarterKit from '@tiptap/starter-kit' +import StarterKit, { StarterKitOptions } from '@tiptap/starter-kit' export interface DefaultKitOptions { + codeBlock?: false heading?: { levels?: Level[] } @@ -32,6 +33,15 @@ export const DefaultKit = Extension.create({ name: 'defaultKit', addExtensions () { + const codeBlock: StarterKitOptions['codeBlock'] = this.options.codeBlock ?? { + languageClassPrefix: 'language-', + exitOnArrowDown: true, + exitOnTripleEnter: true, + HTMLAttributes: { + class: 'proseCodeBlock' + } + } + return [ StarterKit.configure({ code: { @@ -39,14 +49,7 @@ export const DefaultKit = Extension.create({ class: 'proseCode' } }, - codeBlock: { - languageClassPrefix: 'language-', - exitOnArrowDown: true, - exitOnTripleEnter: true, - HTMLAttributes: { - class: 'proseCodeBlock' - } - }, + codeBlock, heading: this.options.heading, history: this.options.history }),