From 405e83f4734128ad2d065babf1ad54c0732257db Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Sat, 21 Sep 2024 23:33:34 +0800 Subject: [PATCH 1/7] fix: `
` and `

` linebreak not correctly rendered --- .../src/api/message/create/messager.ts | 64 ++++++++++--------- .../messager/fixtures/text/index.test.ts | 1 + .../engine-chronocat-api/tests/mocks/index.ts | 1 + 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index d11b864..5b2d5d3 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -61,6 +61,11 @@ export class Messager { } flush = async () => { + if (this.text) { + this.children.push(r.text(this.text)) + this.text = '' + } + if (!this.children.length) return this.normalize() @@ -82,8 +87,9 @@ export class Messager { private normalize = () => { this.children = this.children.reduce[]>( - (acc, cur) => { + (acc, cur, idx) => { const last = acc[acc.length - 1] + const isLastElement = idx === this.children.length - 1 if ( cur.textElement && cur.textElement?.content && @@ -93,48 +99,63 @@ export class Messager { last?.textElement?.atType === AtType.None ) { last.textElement.content += cur.textElement.content + + if (isLastElement) { + last.textElement.content = last.textElement.content.trimEnd() + } } else { acc.push(cur) + + if (isLastElement && cur.textElement?.content) { + cur.textElement.content = cur.textElement.content.trimEnd() + } } return acc }, [], ) - if (this.children[this.children.length - 1]?.textElement?.content === '\n') - this.children.pop() return this.children } - private isEndLine = false + private text = '' + private isHardBreak = false visit = async (element: h) => { const { type, attrs, children } = element + if (!['text', 'br', 'p'].includes(type)) { + this.isHardBreak = false + if (this.text) { + this.children.push(r.text(this.text)) + this.text = '' + } + } + switch (type) { case 'text': { // 文本消息 - this.children.push(r.text(attrs['content'] as string)) - this.isEndLine = false + this.text += attrs['content'] as string return } case 'br': { // 换行 - if (this.isEndLine) { - this.children.push(r.text('\n')) - } - this.isEndLine = false + this.text += '\n' + this.isHardBreak = true return } case 'p': { // 文本段落 - if (this.isEndLine) { - this.children.push(r.text('\n')) + if (this.isHardBreak) { + this.isHardBreak = false + this.text += '\n' + } else if (!this.text.endsWith('\n')) { + this.text += '\n' } await this.render(children) - this.isEndLine = true + this.text += '\n' return } @@ -143,9 +164,8 @@ export class Messager { const url = attrs['href'] as string await this.render(children) if (url) { - this.children.push(r.text(` ( ${url} )`)) + this.text += ` ( ${url} )` } - this.isEndLine = false return } @@ -185,7 +205,6 @@ export class Messager { } this.children.push(r.remoteImage(result, picType)) - this.isEndLine = false return } @@ -226,7 +245,6 @@ export class Messager { 1, ), ) - this.isEndLine = false return } else { @@ -246,7 +264,6 @@ export class Messager { 1, ), ) - this.isEndLine = false return } @@ -290,7 +307,6 @@ export class Messager { encodeResult.waveAmplitudes, ), ) - this.isEndLine = false return } catch (cause) { @@ -327,7 +343,6 @@ export class Messager { 1, ), ) - this.isEndLine = false return } @@ -351,7 +366,6 @@ export class Messager { fileMime: attrs['chronocat:mime'] as string | undefined, }) this.children.push(r.remoteFile(result)) - this.isEndLine = false return } @@ -364,8 +378,6 @@ export class Messager { r.at(this.ctx, attrs['name'] as string, attrs['id'] as string), ) } - - this.isEndLine = false return } @@ -399,8 +411,6 @@ export class Messager { ), ) } - - this.isEndLine = false return } @@ -412,8 +422,6 @@ export class Messager { attrs['key'] as string, ), ) - - this.isEndLine = false return } @@ -439,7 +447,6 @@ export class Messager { (author?.attrs['id'] as string | undefined) || undefined, ), ) - this.isEndLine = false return } @@ -488,7 +495,6 @@ export class Messager { default: { // 兜底 await this.render(children) - this.isEndLine = false return } } diff --git a/packages/engine-chronocat-api/tests/messager/fixtures/text/index.test.ts b/packages/engine-chronocat-api/tests/messager/fixtures/text/index.test.ts index 125a551..ef14588 100644 --- a/packages/engine-chronocat-api/tests/messager/fixtures/text/index.test.ts +++ b/packages/engine-chronocat-api/tests/messager/fixtures/text/index.test.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import type { RedMessage } from '@chronocat/red' import h from '@satorijs/element' import { Messager } from '../../../../src/api/message/create/messager' diff --git a/packages/engine-chronocat-api/tests/mocks/index.ts b/packages/engine-chronocat-api/tests/mocks/index.ts index d0f4ac4..3dba0e9 100644 --- a/packages/engine-chronocat-api/tests/mocks/index.ts +++ b/packages/engine-chronocat-api/tests/mocks/index.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import type { ChronocatContext } from '@chronocat/shell' import h from '@satorijs/element' From 6b5367de58556fd5d9bbec8f05455840c4394e31 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Sat, 21 Sep 2024 23:55:45 +0800 Subject: [PATCH 2/7] refa: remove reduce function --- .../src/api/message/create/messager.ts | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index 5b2d5d3..68d4ebc 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -1,5 +1,5 @@ import type { Peer, Element as RedElement, RedMessage } from '@chronocat/red' -import { AtType, ChatType, FaceType } from '@chronocat/red' +import { ChatType, FaceType } from '@chronocat/red' import type { ChronocatContext, ChronocatSatoriServerConfig, @@ -86,35 +86,10 @@ export class Messager { } private normalize = () => { - this.children = this.children.reduce[]>( - (acc, cur, idx) => { - const last = acc[acc.length - 1] - const isLastElement = idx === this.children.length - 1 - if ( - cur.textElement && - cur.textElement?.content && - cur.textElement?.atType === AtType.None && - last?.textElement && - last?.textElement?.content && - last?.textElement?.atType === AtType.None - ) { - last.textElement.content += cur.textElement.content - - if (isLastElement) { - last.textElement.content = last.textElement.content.trimEnd() - } - } else { - acc.push(cur) - - if (isLastElement && cur.textElement?.content) { - cur.textElement.content = cur.textElement.content.trimEnd() - } - } - return acc - }, - [], - ) - + const last = this.children[this.children.length - 1] + if (last?.textElement?.content) { + last.textElement.content = last.textElement.content.trimEnd() + } return this.children } From eb698a368ddece97c233613ce6366804033e1354 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Sun, 22 Sep 2024 17:26:51 +0800 Subject: [PATCH 3/7] refa: use placeholder for linebreak implementation --- .../src/api/message/create/messager.ts | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index 68d4ebc..701d743 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -13,6 +13,12 @@ import type { Common } from '../../../common' import { r } from './r' import { unlink } from 'node:fs/promises' +const placeholders = { + br: '\u0099BR\u009c', + pStart: '\u0099PSTART\u009c', + pEnd: '\u0099PEND\u009c', +} + class State { constructor(public type: 'message') {} } @@ -86,6 +92,31 @@ export class Messager { } private normalize = () => { + this.children.forEach((x, idx) => { + const isFirst = idx === 0 + const isLast = idx === this.children.length - 1 + if (x.textElement?.content) { + x.textElement.content = x.textElement.content + // 去除首尾空白行 + .replace(new RegExp('^' + placeholders.pStart + placeholders.pEnd + '*', 'g'), '') + .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*' + '$', 'g'), '') + // 合并连续段落起始标记和结束标记 + .replace(new RegExp(placeholders.pEnd + '{2,}', 'g'), placeholders.pEnd) + .replace(new RegExp(placeholders.pStart + '{2,}', 'g'), placeholders.pStart) + // 空段落转换为换行 + .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), '\n') + // 合并连续段落 + .replaceAll(placeholders.pEnd + placeholders.pStart, '\n') + // 硬换行符 + .replaceAll(placeholders.br, '\n') + // 若是最后一个消息元素,段落起始和段落末尾段落标记替换为空 + .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), isLast ? '' : '\n') + .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), isFirst ? '' : '\n') + // 替换剩余的段落标记为换行 + .replace(new RegExp(placeholders.pEnd + '*', 'g'), '\n') + .replace(new RegExp(placeholders.pStart + '*', 'g'), '\n') + } + }) const last = this.children[this.children.length - 1] if (last?.textElement?.content) { last.textElement.content = last.textElement.content.trimEnd() @@ -94,13 +125,11 @@ export class Messager { } private text = '' - private isHardBreak = false visit = async (element: h) => { const { type, attrs, children } = element if (!['text', 'br', 'p'].includes(type)) { - this.isHardBreak = false if (this.text) { this.children.push(r.text(this.text)) this.text = '' @@ -116,21 +145,15 @@ export class Messager { case 'br': { // 换行 - this.text += '\n' - this.isHardBreak = true + this.text += placeholders.br return } case 'p': { // 文本段落 - if (this.isHardBreak) { - this.isHardBreak = false - this.text += '\n' - } else if (!this.text.endsWith('\n')) { - this.text += '\n' - } + this.text += placeholders.pStart await this.render(children) - this.text += '\n' + this.text += placeholders.pEnd return } From bf1d9931941769e05d2d4d52a94d30e7fc1703a4 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Sun, 22 Sep 2024 17:37:57 +0800 Subject: [PATCH 4/7] fix: should handle continuation of p element --- .../engine-chronocat-api/src/api/message/create/messager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index 701d743..8a3a2e1 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -106,7 +106,7 @@ export class Messager { // 空段落转换为换行 .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), '\n') // 合并连续段落 - .replaceAll(placeholders.pEnd + placeholders.pStart, '\n') + .replace(new RegExp(placeholders.pEnd + placeholders.pStart + '*', 'g'), '\n') // 硬换行符 .replaceAll(placeholders.br, '\n') // 若是最后一个消息元素,段落起始和段落末尾段落标记替换为空 From d24a72a7def9e9099173f369281fc074a3ebcf34 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Sun, 22 Sep 2024 18:09:48 +0800 Subject: [PATCH 5/7] fix: dangling paragraph start mark not correctly handled --- .../src/api/message/create/messager.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index 8a3a2e1..ef4c004 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -17,6 +17,7 @@ const placeholders = { br: '\u0099BR\u009c', pStart: '\u0099PSTART\u009c', pEnd: '\u0099PEND\u009c', + pGeneral: '\u0099P\u009c', } class State { @@ -101,20 +102,19 @@ export class Messager { .replace(new RegExp('^' + placeholders.pStart + placeholders.pEnd + '*', 'g'), '') .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*' + '$', 'g'), '') // 合并连续段落起始标记和结束标记 - .replace(new RegExp(placeholders.pEnd + '{2,}', 'g'), placeholders.pEnd) .replace(new RegExp(placeholders.pStart + '{2,}', 'g'), placeholders.pStart) + .replace(new RegExp(placeholders.pEnd + '{2,}', 'g'), placeholders.pEnd) // 空段落转换为换行 - .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), '\n') + .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), placeholders.pGeneral) // 合并连续段落 - .replace(new RegExp(placeholders.pEnd + placeholders.pStart + '*', 'g'), '\n') + .replace(new RegExp(placeholders.pEnd + placeholders.pStart + '*', 'g'), placeholders.pGeneral) // 硬换行符 .replaceAll(placeholders.br, '\n') // 若是最后一个消息元素,段落起始和段落末尾段落标记替换为空 - .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), isLast ? '' : '\n') - .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), isFirst ? '' : '\n') + .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), isLast ? '' : placeholders.pGeneral) + .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), isFirst ? '' : placeholders.pGeneral) // 替换剩余的段落标记为换行 - .replace(new RegExp(placeholders.pEnd + '*', 'g'), '\n') - .replace(new RegExp(placeholders.pStart + '*', 'g'), '\n') + .replace(new RegExp(`(${placeholders.pGeneral}|${placeholders.pStart}|${placeholders.pEnd})+`, 'g'), '\n') } }) const last = this.children[this.children.length - 1] From b45719dc596b2441185005d3f85a74d8524eb1f4 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Mon, 23 Sep 2024 13:36:43 +0800 Subject: [PATCH 6/7] fix: align with browser behaviour for `

` and `
` --- .../src/api/message/create/messager.ts | 28 +++++++++++++------ .../text/__snapshots__/index.test.ts.snap | 10 ------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index ef4c004..1b0298f 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -98,9 +98,6 @@ export class Messager { const isLast = idx === this.children.length - 1 if (x.textElement?.content) { x.textElement.content = x.textElement.content - // 去除首尾空白行 - .replace(new RegExp('^' + placeholders.pStart + placeholders.pEnd + '*', 'g'), '') - .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*' + '$', 'g'), '') // 合并连续段落起始标记和结束标记 .replace(new RegExp(placeholders.pStart + '{2,}', 'g'), placeholders.pStart) .replace(new RegExp(placeholders.pEnd + '{2,}', 'g'), placeholders.pEnd) @@ -108,12 +105,27 @@ export class Messager { .replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), placeholders.pGeneral) // 合并连续段落 .replace(new RegExp(placeholders.pEnd + placeholders.pStart + '*', 'g'), placeholders.pGeneral) - // 硬换行符 - .replaceAll(placeholders.br, '\n') + // 硬换行符,但段落末尾的 br 不渲染 + // 先把 br + pEnd 替换为单独的 pEnd + .replace(new RegExp(`${placeholders.br}${placeholders.pEnd}`, 'g'), placeholders.pEnd) + // 再把 br 替换为换行 + .replace(new RegExp(placeholders.br, 'g'), '\n') // 若是最后一个消息元素,段落起始和段落末尾段落标记替换为空 - .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), isLast ? '' : placeholders.pGeneral) - .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), isFirst ? '' : placeholders.pGeneral) - // 替换剩余的段落标记为换行 + .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), placeholders.pGeneral) + .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), placeholders.pGeneral) + + if (isFirst) { + x.textElement.content = x.textElement.content + // 如果是第一条消息元素,删除最前面的段落标记 + .replace(new RegExp(`^(${placeholders.pGeneral}|${placeholders.pStart}|${placeholders.pEnd})+`, 'g'), '') + } else if (isLast) { + x.textElement.content = x.textElement.content + // 如果是最后一条消息元素,删除最后面的段落标记 + .replace(new RegExp(`(${placeholders.pGeneral}|${placeholders.pStart}|${placeholders.pEnd})+$`, 'g'), '') + } + + // 最后的兜底,替换剩余的段落标记为换行 + x.textElement.content = x.textElement.content .replace(new RegExp(`(${placeholders.pGeneral}|${placeholders.pStart}|${placeholders.pEnd})+`, 'g'), '\n') } }) diff --git a/packages/engine-chronocat-api/tests/messager/fixtures/text/__snapshots__/index.test.ts.snap b/packages/engine-chronocat-api/tests/messager/fixtures/text/__snapshots__/index.test.ts.snap index d16373a..ee03de3 100644 --- a/packages/engine-chronocat-api/tests/messager/fixtures/text/__snapshots__/index.test.ts.snap +++ b/packages/engine-chronocat-api/tests/messager/fixtures/text/__snapshots__/index.test.ts.snap @@ -52,24 +52,18 @@ exports[`Red 编码器应当正确编码 br/p 混搭换行 1`] = ` "content": "aaa bbb uuu - ccc ddd - eee - vvv www fff ggg - hhh iii - xxx yyy - zzz", }, }, @@ -97,15 +91,11 @@ exports[`Red 编码器应当正确编码 p 换行 1`] = ` "atUid": "", "content": "aaa bbb - ccc ddd - eee - fff ggg - hhh iii", }, From 51fdf913dae348f1f09df6b010793df8d55c3a16 Mon Sep 17 00:00:00 2001 From: Maiko Tan Date: Mon, 23 Sep 2024 13:42:16 +0800 Subject: [PATCH 7/7] fix: remove redundant process --- .../engine-chronocat-api/src/api/message/create/messager.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/engine-chronocat-api/src/api/message/create/messager.ts b/packages/engine-chronocat-api/src/api/message/create/messager.ts index 1b0298f..797b28e 100644 --- a/packages/engine-chronocat-api/src/api/message/create/messager.ts +++ b/packages/engine-chronocat-api/src/api/message/create/messager.ts @@ -110,9 +110,6 @@ export class Messager { .replace(new RegExp(`${placeholders.br}${placeholders.pEnd}`, 'g'), placeholders.pEnd) // 再把 br 替换为换行 .replace(new RegExp(placeholders.br, 'g'), '\n') - // 若是最后一个消息元素,段落起始和段落末尾段落标记替换为空 - .replace(new RegExp(placeholders.pEnd + '*' + '$', 'g'), placeholders.pGeneral) - .replace(new RegExp('^' + placeholders.pStart + '*', 'g'), placeholders.pGeneral) if (isFirst) { x.textElement.content = x.textElement.content