Skip to content

Commit 3bd9202

Browse files
feat: add a blank line after Code block in a chat
1 parent 07c364c commit 3bd9202

File tree

1 file changed

+88
-13
lines changed
  • plugins/text-editor-resources/src/components/extension

1 file changed

+88
-13
lines changed

plugins/text-editor-resources/src/components/extension/codeblock.ts

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { Plugin, PluginKey } from '@tiptap/pm/state'
2323
import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view'
2424
import { createLowlight } from 'lowlight'
2525
import { isChangeEditable } from './editable'
26+
import { TextSelection } from '@tiptap/pm/state'
27+
import { Fragment } from '@tiptap/pm/model'
2628

2729
type Lowlight = ReturnType<typeof createLowlight>
2830

@@ -40,11 +42,32 @@ export const CodeBlockHighlighExtension = CodeBlockLowlight.extend<CodeBlockLowl
4042

4143
addCommands () {
4244
return {
43-
setCodeBlock:
44-
(attributes) =>
45-
({ commands }) => {
46-
return commands.setNode(this.name, attributes)
47-
},
45+
setCodeBlock: (attributes) => ({ editor, commands }) => {
46+
return commands.command(({ tr, dispatch }) => {
47+
const { from, to } = tr.selection
48+
const codeBlockType = editor.schema.nodes[this.name]
49+
const paragraphType = editor.schema.nodes.paragraph
50+
51+
if (!codeBlockType || !paragraphType) {
52+
return false
53+
}
54+
55+
const codeBlockNode = codeBlockType.create(attributes)
56+
const paragraphNode = paragraphType.create()
57+
58+
const fragmentToInsert = Fragment.fromArray([codeBlockNode, paragraphNode])
59+
60+
tr.replaceWith(from, to, fragmentToInsert)
61+
62+
const newSelectionPos = from + codeBlockNode.nodeSize + 1
63+
tr.setSelection(TextSelection.create(tr.doc, newSelectionPos))
64+
65+
if (dispatch) {
66+
dispatch(tr.scrollIntoView())
67+
}
68+
return true
69+
})
70+
},
4871
toggleCodeBlock:
4972
(attributes) =>
5073
({ chain, commands, state }) => {
@@ -60,10 +83,10 @@ export const CodeBlockHighlighExtension = CodeBlockLowlight.extend<CodeBlockLowl
6083
}
6184
if (node.type.name !== 'paragraph') {
6285
if (pos + 1 <= from && pos + node.nodeSize - 1 >= to) {
63-
// skip nodes outside of the selected range
86+
// skip nodes outside of the selected range
6487
return false
6588
} else {
66-
// cannot merge non-paragraph nodes inside selection
89+
// cannot merge non-paragraph nodes inside selection
6790
hasParagraphsOnlySelected = false
6891
return false
6992
}
@@ -78,10 +101,17 @@ export const CodeBlockHighlighExtension = CodeBlockLowlight.extend<CodeBlockLowl
78101
if (hasParagraphsOnlySelected && textArr.length > 1) {
79102
return chain()
80103
.command(({ state, tr }) => {
81-
tr.replaceRangeWith(from, to, this.type.create(attributes, state.schema.text(textArr.join('\n'))))
104+
const codeBlockNode = this.type.create(attributes, state.schema.text(textArr.join('\n')))
105+
const paragraphNode = state.schema.nodes.paragraph.create()
106+
const fragmentToInsert = Fragment.fromArray([codeBlockNode, paragraphNode])
107+
108+
tr.replaceWith(from, to, fragmentToInsert)
109+
110+
const newSelectionPos = from + codeBlockNode.nodeSize + 1
111+
tr.setSelection(TextSelection.create(tr.doc, newSelectionPos))
112+
82113
return true
83114
})
84-
.setTextSelection({ from: from + 2, to: from + 2 })
85115
.run()
86116
}
87117
}
@@ -94,20 +124,65 @@ export const CodeBlockHighlighExtension = CodeBlockLowlight.extend<CodeBlockLowl
94124
return [
95125
textblockTypeInputRule({
96126
find: backtickInputRegex,
97-
type: this.type
127+
type: this.type,
128+
getAttributes: (match) => {
129+
return { language: match[3] || null }
130+
}
98131
}),
99132
textblockTypeInputRule({
100133
find: tildeInputRegex,
101-
type: this.type
134+
type: this.type,
135+
getAttributes: (match) => {
136+
return { language: match[3] || null }
137+
}
102138
})
103139
]
104140
},
105141

106142
addProseMirrorPlugins () {
107-
return [...(this.parent?.() ?? []), LanguageSelector(this.options)]
143+
return [...(this.parent?.() ?? []), LanguageSelector(this.options), CodeBlockEnhancer()]
108144
}
109145
})
110146

147+
function CodeBlockEnhancer(): Plugin {
148+
return new Plugin({
149+
key: new PluginKey('codeblock-enhancer'),
150+
appendTransaction(transactions, oldState, newState) {
151+
const tr = newState.tr
152+
let modified = false
153+
154+
transactions.forEach(transaction => {
155+
if (!transaction.docChanged) return
156+
157+
transaction.steps.forEach((step: any) => {
158+
if (step.slice) {
159+
const content = step.slice.content
160+
content.descendants((node: any, pos: number) => {
161+
if (node.type.name === 'codeBlock') {
162+
const nodePos = step.from + pos
163+
const nodeEnd = nodePos + node.nodeSize
164+
165+
const nextNode = newState.doc.nodeAt(nodeEnd)
166+
167+
if (!nextNode || nextNode.type.name !== 'paragraph') {
168+
const paragraphType = newState.schema.nodes.paragraph
169+
if (paragraphType) {
170+
const newParagraph = paragraphType.create()
171+
tr.insert(nodeEnd, newParagraph)
172+
modified = true
173+
}
174+
}
175+
}
176+
})
177+
}
178+
})
179+
})
180+
181+
return modified ? tr : null
182+
}
183+
})
184+
}
185+
111186
export function LanguageSelector (options: CodeBlockLowlightOptions): Plugin {
112187
return new Plugin<DecorationSet>({
113188
key: new PluginKey('codeblock-language-selector'),
@@ -212,4 +287,4 @@ function handleLangButtonClick (
212287
}
213288
}
214289
)
215-
}
290+
}

0 commit comments

Comments
 (0)