Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(api): <br> and <p> linebreak not correctly rendered #10

Merged
merged 7 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 61 additions & 48 deletions packages/engine-chronocat-api/src/api/message/create/messager.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -13,6 +13,13 @@ 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',
pGeneral: '\u0099P\u009c',
}

class State {
constructor(public type: 'message') {}
}
Expand Down Expand Up @@ -61,6 +68,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()
Expand All @@ -81,60 +93,76 @@ export class Messager {
}

private normalize = () => {
this.children = this.children.reduce<O.Partial<RedElement, 'deep'>[]>(
(acc, cur) => {
const last = acc[acc.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
} else {
acc.push(cur)
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 + '{2,}', 'g'), placeholders.pStart)
.replace(new RegExp(placeholders.pEnd + '{2,}', 'g'), placeholders.pEnd)
// 空段落转换为换行
.replace(new RegExp(placeholders.pStart + placeholders.pEnd + '*', 'g'), placeholders.pGeneral)
// 合并连续段落
.replace(new RegExp(placeholders.pEnd + placeholders.pStart + '*', 'g'), placeholders.pGeneral)
// 硬换行符,但段落末尾的 br 不渲染
// 先把 br + pEnd 替换为单独的 pEnd
.replace(new RegExp(`${placeholders.br}${placeholders.pEnd}`, 'g'), placeholders.pEnd)
// 再把 br 替换为换行
.replace(new RegExp(placeholders.br, 'g'), '\n')

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'), '')
}
return acc
},
[],
)

if (this.children[this.children.length - 1]?.textElement?.content === '\n')
this.children.pop()
// 最后的兜底,替换剩余的段落标记为换行
x.textElement.content = x.textElement.content
.replace(new RegExp(`(${placeholders.pGeneral}|${placeholders.pStart}|${placeholders.pEnd})+`, 'g'), '\n')
}
})
const last = this.children[this.children.length - 1]
if (last?.textElement?.content) {
last.textElement.content = last.textElement.content.trimEnd()
}
return this.children
}

private isEndLine = false
private text = ''

visit = async (element: h) => {
const { type, attrs, children } = element

if (!['text', 'br', 'p'].includes(type)) {
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 += placeholders.br
return
}

case 'p': {
// 文本段落
if (this.isEndLine) {
this.children.push(r.text('\n'))
}
this.text += placeholders.pStart
await this.render(children)
this.isEndLine = true
this.text += placeholders.pEnd
return
}

Expand All @@ -143,9 +171,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
}

Expand Down Expand Up @@ -185,7 +212,6 @@ export class Messager {
}

this.children.push(r.remoteImage(result, picType))
this.isEndLine = false
return
}

Expand Down Expand Up @@ -226,7 +252,6 @@ export class Messager {
1,
),
)
this.isEndLine = false

return
} else {
Expand All @@ -246,7 +271,6 @@ export class Messager {
1,
),
)
this.isEndLine = false

return
}
Expand Down Expand Up @@ -290,7 +314,6 @@ export class Messager {
encodeResult.waveAmplitudes,
),
)
this.isEndLine = false

return
} catch (cause) {
Expand Down Expand Up @@ -327,7 +350,6 @@ export class Messager {
1,
),
)
this.isEndLine = false

return
}
Expand All @@ -351,7 +373,6 @@ export class Messager {
fileMime: attrs['chronocat:mime'] as string | undefined,
})
this.children.push(r.remoteFile(result))
this.isEndLine = false
return
}

Expand All @@ -364,8 +385,6 @@ export class Messager {
r.at(this.ctx, attrs['name'] as string, attrs['id'] as string),
)
}

this.isEndLine = false
return
}

Expand Down Expand Up @@ -399,8 +418,6 @@ export class Messager {
),
)
}

this.isEndLine = false
return
}

Expand All @@ -412,8 +429,6 @@ export class Messager {
attrs['key'] as string,
),
)

this.isEndLine = false
return
}

Expand All @@ -439,7 +454,6 @@ export class Messager {
(author?.attrs['id'] as string | undefined) || undefined,
),
)
this.isEndLine = false
return
}

Expand Down Expand Up @@ -488,7 +502,6 @@ export class Messager {
default: {
// 兜底
await this.render(children)
this.isEndLine = false
return
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
},
Expand Down Expand Up @@ -97,15 +91,11 @@ exports[`Red 编码器应当正确编码 p 换行 1`] = `
"atUid": "",
"content": "aaa
bbb

ccc
ddd

eee

fff
ggg

hhh
iii",
},
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
1 change: 1 addition & 0 deletions packages/engine-chronocat-api/tests/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-nocheck
import type { ChronocatContext } from '@chronocat/shell'
import h from '@satorijs/element'

Expand Down
Loading