feat(manuscripts): 正文按后端 contentFormat 渲染
- content-format 增加 normalizeFormat;toPlainText / getRenderPayload 支持显式格式 - 列表行与阅读抽屉透传 contentFormat,字段缺失时仍回退到按内容猜
This commit is contained in:
@@ -39,8 +39,9 @@
|
||||
* 稿件阅读抽屉:列表只放元数据,正文在这里用 mp-html 渲染。
|
||||
*
|
||||
* item 的形状(由列表页整理好,缺内容时传 null 即关闭):
|
||||
* { title, content, cover, statusText, timeLabel, chars }
|
||||
* content 可能是 HTML、markdown 或纯文本,由 getRenderPayload 判断怎么渲染。
|
||||
* { title, content, format, cover, statusText, timeLabel, chars }
|
||||
* content 可能是 HTML、markdown 或纯文本;format 来自后端 contentFormat,
|
||||
* 有值就以它为准,没值由 getRenderPayload 按内容猜。
|
||||
*/
|
||||
import mpHtml from '@/uni_modules/zero-markdown-view/components/mp-html/mp-html.vue'
|
||||
import { getRenderPayload, toPlainText } from '@/utils/content-format'
|
||||
@@ -75,7 +76,8 @@
|
||||
},
|
||||
computed: {
|
||||
payload() {
|
||||
return getRenderPayload(this.item ? this.item.content : '')
|
||||
const item = this.item || {}
|
||||
return getRenderPayload(item.content, item.format)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -108,7 +110,8 @@
|
||||
})
|
||||
},
|
||||
copyContent() {
|
||||
const text = toPlainText(this.item ? this.item.content : '')
|
||||
const item = this.item || {}
|
||||
const text = toPlainText(item.content, item.format)
|
||||
if (!text) {
|
||||
uni.showToast({
|
||||
title: '暂无正文可复制',
|
||||
|
||||
@@ -136,11 +136,13 @@
|
||||
formatDateTime,
|
||||
/**
|
||||
* 稿件 → 列表行视图模型。
|
||||
* 正文可能是 HTML / markdown / 纯文本,这里只做一次纯文本化:
|
||||
* 正文可能是 HTML / markdown / 纯文本,优先信后端的 contentFormat,
|
||||
* 没有这个字段才按内容猜。这里只做一次纯文本化:
|
||||
* 摘要、搜索、字数都基于它,避免每张卡片反复解析富文本。
|
||||
*/
|
||||
toRow(item, index) {
|
||||
const plainText = toPlainText(item.content)
|
||||
const format = item.contentFormat || item.content_format || ''
|
||||
const plainText = toPlainText(item.content, format)
|
||||
const statusMeta = getManuscriptStatusMeta(item)
|
||||
return {
|
||||
key: item.id || `${index}-${item.title || ''}`,
|
||||
@@ -152,7 +154,8 @@
|
||||
timeLabel: `更新 ${formatDateTime(item.updateTime || item.createTime)}`,
|
||||
chars: plainText.replace(/\s/g, '').length,
|
||||
cover: item.cover || '',
|
||||
content: item.content || ''
|
||||
content: item.content || '',
|
||||
format
|
||||
}
|
||||
},
|
||||
applyManuscripts(list, user) {
|
||||
|
||||
+67
-18
@@ -78,19 +78,54 @@ function markdownToText(markdown) {
|
||||
.trim()
|
||||
}
|
||||
|
||||
/** 无论来源是 HTML 还是 markdown,都压成可读纯文本(列表摘要、搜索、复制正文都用它)。 */
|
||||
export function toPlainText(content) {
|
||||
/**
|
||||
* 后端 `contentFormat` 的规范值:`html` / `markdown` / `text`。
|
||||
* 认不出来的(包括老接口没有这个字段)返回空串,交给内容猜测兜底。
|
||||
*/
|
||||
export function normalizeFormat(format) {
|
||||
const value = String(format || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
if (value === 'html' || value === 'text') {
|
||||
return value
|
||||
}
|
||||
if (value === 'markdown' || value === 'md') {
|
||||
return 'markdown'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function plainText(text) {
|
||||
return text.replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
|
||||
}
|
||||
|
||||
function guessFormat(text) {
|
||||
if (isHtmlContent(text)) {
|
||||
return 'html'
|
||||
}
|
||||
if (isMarkdownContent(text)) {
|
||||
return 'markdown'
|
||||
}
|
||||
return 'text'
|
||||
}
|
||||
|
||||
/**
|
||||
* 无论来源是 HTML 还是 markdown,都压成可读纯文本(列表摘要、搜索、复制正文都用它)。
|
||||
* 传了后端格式标记就以标记为准,没传才按内容猜。
|
||||
*/
|
||||
export function toPlainText(content, format) {
|
||||
const text = String(content || '')
|
||||
if (!text.trim()) {
|
||||
return ''
|
||||
}
|
||||
if (isHtmlContent(text)) {
|
||||
return htmlToText(text)
|
||||
const kind = normalizeFormat(format) || guessFormat(text)
|
||||
if (kind === 'html') {
|
||||
return isHtmlContent(text) ? htmlToText(text) : plainText(text)
|
||||
}
|
||||
if (isMarkdownContent(text)) {
|
||||
if (kind === 'markdown') {
|
||||
return markdownToText(text)
|
||||
}
|
||||
return text.replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim()
|
||||
return plainText(text)
|
||||
}
|
||||
|
||||
/** 摘要不能直接 slice 原始内容——会把 HTML 标签切一半;这里输入的已是纯文本。 */
|
||||
@@ -107,8 +142,11 @@ export function truncateText(text, maxLength = 60) {
|
||||
* - HTML 原样渲染;
|
||||
* - markdown 打开 mp-html 的 markdown 插件;
|
||||
* - 纯文本按空行分段、转义后包成 `<p>`,否则 mp-html 会把换行吃掉。
|
||||
*
|
||||
* 后端给了 `contentFormat` 就以它为准(那是写入时定下的真实格式),
|
||||
* 没给或给了不认识的值才按内容猜,兼容尚未升级的后端。
|
||||
*/
|
||||
export function getRenderPayload(content) {
|
||||
export function getRenderPayload(content, format) {
|
||||
const text = String(content || '')
|
||||
if (!text.trim()) {
|
||||
return {
|
||||
@@ -116,23 +154,34 @@ export function getRenderPayload(content) {
|
||||
markdown: false
|
||||
}
|
||||
}
|
||||
if (isHtmlContent(text)) {
|
||||
const kind = normalizeFormat(format) || guessFormat(text)
|
||||
if (kind === 'html') {
|
||||
return {
|
||||
html: text,
|
||||
html: isHtmlContent(text) ? text : textToHtml(text),
|
||||
markdown: false
|
||||
}
|
||||
}
|
||||
if (isMarkdownContent(text)) {
|
||||
return {
|
||||
html: text,
|
||||
markdown: true
|
||||
}
|
||||
if (kind === 'markdown') {
|
||||
// 标成 markdown 但内容其实被 wangEditor 存成过 HTML(历史编辑过的行):按 HTML 渲染
|
||||
return text.trim().startsWith('<')
|
||||
? {
|
||||
html: text,
|
||||
markdown: false
|
||||
}
|
||||
: {
|
||||
html: text,
|
||||
markdown: true
|
||||
}
|
||||
}
|
||||
return {
|
||||
html: escapeHtml(text)
|
||||
.split(/\n{2,}/)
|
||||
.map((paragraph) => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
|
||||
.join(''),
|
||||
html: textToHtml(text),
|
||||
markdown: false
|
||||
}
|
||||
}
|
||||
|
||||
function textToHtml(text) {
|
||||
return escapeHtml(text)
|
||||
.split(/\n{2,}/)
|
||||
.map((paragraph) => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
|
||||
.join('')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user