mirror of
https://github.com/chrononeko/chronocat.git
synced 2024-11-24 17:19:44 +00:00
fix(api): <br>
and <p>
linebreak not correctly rendered (#10)
* fix: `<br>` and `<p>` linebreak not correctly rendered * refa: remove reduce function * refa: use placeholder for linebreak implementation * fix: should handle continuation of p element * fix: dangling paragraph start mark not correctly handled * fix: align with browser behaviour for `<p>` and `<br>` * fix: remove redundant process
This commit is contained in:
parent
8472929ca5
commit
8540f32c4b
@ -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,
|
||||
@ -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') {}
|
||||
}
|
||||
@ -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()
|
||||
@ -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)
|
||||
}
|
||||
return acc
|
||||
},
|
||||
[],
|
||||
)
|
||||
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 (this.children[this.children.length - 1]?.textElement?.content === '\n')
|
||||
this.children.pop()
|
||||
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')
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -185,7 +212,6 @@ export class Messager {
|
||||
}
|
||||
|
||||
this.children.push(r.remoteImage(result, picType))
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -226,7 +252,6 @@ export class Messager {
|
||||
1,
|
||||
),
|
||||
)
|
||||
this.isEndLine = false
|
||||
|
||||
return
|
||||
} else {
|
||||
@ -246,7 +271,6 @@ export class Messager {
|
||||
1,
|
||||
),
|
||||
)
|
||||
this.isEndLine = false
|
||||
|
||||
return
|
||||
}
|
||||
@ -290,7 +314,6 @@ export class Messager {
|
||||
encodeResult.waveAmplitudes,
|
||||
),
|
||||
)
|
||||
this.isEndLine = false
|
||||
|
||||
return
|
||||
} catch (cause) {
|
||||
@ -327,7 +350,6 @@ export class Messager {
|
||||
1,
|
||||
),
|
||||
)
|
||||
this.isEndLine = false
|
||||
|
||||
return
|
||||
}
|
||||
@ -351,7 +373,6 @@ export class Messager {
|
||||
fileMime: attrs['chronocat:mime'] as string | undefined,
|
||||
})
|
||||
this.children.push(r.remoteFile(result))
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -364,8 +385,6 @@ export class Messager {
|
||||
r.at(this.ctx, attrs['name'] as string, attrs['id'] as string),
|
||||
)
|
||||
}
|
||||
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -399,8 +418,6 @@ export class Messager {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -412,8 +429,6 @@ export class Messager {
|
||||
attrs['key'] as string,
|
||||
),
|
||||
)
|
||||
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -439,7 +454,6 @@ export class Messager {
|
||||
(author?.attrs['id'] as string | undefined) || undefined,
|
||||
),
|
||||
)
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
|
||||
@ -488,7 +502,6 @@ export class Messager {
|
||||
default: {
|
||||
// 兜底
|
||||
await this.render(children)
|
||||
this.isEndLine = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
},
|
||||
|
@ -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'
|
||||
|
@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import type { ChronocatContext } from '@chronocat/shell'
|
||||
import h from '@satorijs/element'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user