@@ -180,63 +180,111 @@ function buildBlockInputRules() {
180180}
181181
182182function tableInputRulePlugin ( ) {
183- return new Plugin ( {
184- props : {
185- handleKeyDown ( view , event ) {
186- if ( event . key !== "Enter" ) return false ;
187- const { state } = view ;
188- const { $head } = state . selection ;
189- const parentNode = $head . parent ;
190- if ( parentNode . type . name !== "paragraph" ) return false ;
183+ function parseHeaderCells ( text ) {
184+ if ( ! text . match ( / ^ \s * \| .+ \| \s * $ / ) ) return null ;
185+ const cells = text
186+ . replace ( / ^ \s * \| / , "" )
187+ . replace ( / \| \s * $ / , "" )
188+ . split ( "|" )
189+ . map ( ( cell ) => cell . trim ( ) ) ;
190+ return cells . length >= 2 ? cells : null ;
191+ }
191192
192- const currentLine = parentNode . textContent ;
193- if ( ! currentLine . match ( / ^ \s * \| [ \s : ] * - { 3 , } [ \s : ] * (?: \| [ \s : ] * - { 3 , } [ \s : ] * ) + \| ? \s * $ / ) ) return false ;
193+ function parseSeparatorCells ( text ) {
194+ if ( ! text . match ( / ^ \s * \| [ \s : ] * - { 3 , } [ \s : ] * (?: \| [ \s : ] * - { 3 , } [ \s : ] * ) + \| ? \s * $ / ) ) return null ;
195+ const cells = text
196+ . replace ( / ^ \s * \| / , "" )
197+ . replace ( / \| \s * $ / , "" )
198+ . split ( "|" )
199+ . map ( ( cell ) => cell . trim ( ) ) ;
194200
195- const grandParent = $head . node ( $head . depth - 1 ) ;
196- const myIndex = $head . index ( $head . depth - 1 ) ;
197- if ( myIndex === 0 ) return false ;
201+ if ( ! cells . every ( ( cell ) => / ^ : ? - { 3 , } : ? $ / . test ( cell ) ) ) return null ;
202+ return cells ;
203+ }
198204
199- const headerBlock = grandParent . child ( myIndex - 1 ) ;
200- if ( headerBlock . type . name !== "paragraph" ) return false ;
205+ function getAlignments ( separatorCells ) {
206+ return separatorCells . map ( ( cell ) => {
207+ const left = cell . startsWith ( ":" ) ;
208+ const right = cell . endsWith ( ":" ) ;
209+ if ( left && right ) return "center" ;
210+ if ( right ) return "right" ;
211+ return null ;
212+ } ) ;
213+ }
201214
202- const headerText = headerBlock . textContent ;
203- if ( ! headerText . match ( / ^ \s * \| .+ \| / ) ) return false ;
215+ function buildTableNode ( headerCells , aligns ) {
216+ const headerRow = schema . nodes . table_row . create (
217+ null ,
218+ headerCells . map ( ( text , i ) =>
219+ schema . nodes . table_header . create (
220+ { alignment : aligns [ i ] || null } ,
221+ text ? [ schema . node ( "paragraph" , null , [ schema . text ( text ) ] ) ] : [ schema . node ( "paragraph" ) ] ,
222+ ) ,
223+ ) ,
224+ ) ;
225+
226+ const emptyRow = schema . nodes . table_row . create (
227+ null ,
228+ headerCells . map ( ( _ , i ) => schema . nodes . table_cell . create ( { alignment : aligns [ i ] || null } , [ schema . node ( "paragraph" ) ] ) ) ,
229+ ) ;
230+
231+ return schema . nodes . table . create ( null , [ headerRow , emptyRow ] ) ;
232+ }
204233
205- const headerCells = headerText . replace ( / ^ \s * \| / , "" ) . replace ( / \| \s * $ / , "" ) . split ( "|" ) . map ( ( c ) => c . trim ( ) ) ;
206- const sepCells = currentLine . replace ( / ^ \s * \| / , "" ) . replace ( / \| \s * $ / , "" ) . split ( "|" ) . map ( ( c ) => c . trim ( ) ) ;
207- if ( headerCells . length < 2 || sepCells . length !== headerCells . length ) return false ;
234+ function findTableMarkdownRange ( state ) {
235+ const { $head } = state . selection ;
236+ const paragraph = $head . parent ;
237+ if ( paragraph . type . name !== "paragraph" ) return null ;
208238
209- const aligns = sepCells . map ( ( s ) => {
210- const left = s . startsWith ( ":" ) ;
211- const right = s . endsWith ( ":" ) ;
212- if ( left && right ) return "center" ;
213- if ( right ) return "right" ;
214- return null ;
215- } ) ;
239+ const paragraphDepth = $head . depth ;
240+ const paragraphFrom = $head . before ( paragraphDepth ) ;
241+ const paragraphTo = $head . after ( paragraphDepth ) ;
242+
243+ const text = paragraph . textContent ;
244+ const lines = text . split ( "\n" ) ;
245+
246+ if ( lines . length === 2 ) {
247+ const headerCells = parseHeaderCells ( lines [ 0 ] ) ;
248+ const separatorCells = parseSeparatorCells ( lines [ 1 ] ) ;
249+ if ( ! headerCells || ! separatorCells || separatorCells . length !== headerCells . length ) return null ;
250+ return {
251+ from : paragraphFrom ,
252+ to : paragraphTo ,
253+ headerCells,
254+ aligns : getAlignments ( separatorCells ) ,
255+ } ;
256+ }
257+
258+ const containerDepth = paragraphDepth - 1 ;
259+ const container = $head . node ( containerDepth ) ;
260+ const paragraphIndex = $head . index ( containerDepth ) ;
261+ if ( paragraphIndex === 0 ) return null ;
216262
217- const headerRow = schema . nodes . table_row . create (
218- null ,
219- headerCells . map ( ( text , i ) =>
220- schema . nodes . table_header . create (
221- { alignment : aligns [ i ] || null } ,
222- text ? [ schema . node ( "paragraph" , null , [ schema . text ( text ) ] ) ] : [ schema . node ( "paragraph" ) ] ,
223- ) ,
224- ) ,
225- ) ;
226-
227- const emptyRow = schema . nodes . table_row . create (
228- null ,
229- headerCells . map ( ( _ , i ) => schema . nodes . table_cell . create ( { alignment : aligns [ i ] || null } , [ schema . node ( "paragraph" ) ] ) ) ,
230- ) ;
231-
232- const table = schema . nodes . table . create ( null , [ headerRow , emptyRow ] ) ;
233-
234- let pos = $head . before ( $head . depth - 1 ) ;
235- for ( let i = 0 ; i < myIndex - 1 ; i ++ ) pos += grandParent . child ( i ) . nodeSize ;
236- const hStart = pos ;
237- const sepEnd = hStart + headerBlock . nodeSize + parentNode . nodeSize ;
238-
239- const tr = state . tr . replaceWith ( hStart , sepEnd , table ) ;
263+ const headerBlock = container . child ( paragraphIndex - 1 ) ;
264+ if ( headerBlock . type . name !== "paragraph" ) return null ;
265+
266+ const headerCells = parseHeaderCells ( headerBlock . textContent ) ;
267+ const separatorCells = parseSeparatorCells ( text ) ;
268+ if ( ! headerCells || ! separatorCells || separatorCells . length !== headerCells . length ) return null ;
269+
270+ return {
271+ from : paragraphFrom - headerBlock . nodeSize ,
272+ to : paragraphTo ,
273+ headerCells,
274+ aligns : getAlignments ( separatorCells ) ,
275+ } ;
276+ }
277+
278+ return new Plugin ( {
279+ props : {
280+ handleKeyDown ( view , event ) {
281+ if ( event . key !== "Enter" ) return false ;
282+ const { state } = view ;
283+ const tableRange = findTableMarkdownRange ( state ) ;
284+ if ( ! tableRange ) return false ;
285+
286+ const table = buildTableNode ( tableRange . headerCells , tableRange . aligns ) ;
287+ const tr = state . tr . replaceWith ( tableRange . from , tableRange . to , table ) ;
240288 view . dispatch ( tr ) ;
241289 event . preventDefault ( ) ;
242290 return true ;
@@ -245,6 +293,17 @@ function tableInputRulePlugin() {
245293 } ) ;
246294}
247295
296+ function compactLineBreakKeymap ( ) {
297+ return keymap ( {
298+ "Mod-Enter" : ( state , dispatch ) => {
299+ const { $head } = state . selection ;
300+ if ( $head . parent . type . name !== "paragraph" ) return false ;
301+ if ( dispatch ) dispatch ( state . tr . insertText ( "\n" ) ) ;
302+ return true ;
303+ } ,
304+ } ) ;
305+ }
306+
248307// ── Inline Markdown Input Rules ──
249308const inlineMarkdownRules = inputRules ( { rules : [
250309 // **bold**
@@ -524,6 +583,7 @@ function createPlugins() {
524583 keymap ( baseKeymap ) ,
525584 buildBlockInputRules ( ) ,
526585 inlineMarkdownRules ,
586+ compactLineBreakKeymap ( ) ,
527587 codeBlockEscapeKeymap ( ) ,
528588 columnResizing ( ) ,
529589 tableEditing ( ) ,
0 commit comments