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