提交 8252b254 编写于 作者: D DCloud_LXH

feat(nvue): rich-text

上级 61ebfa6c
export const props = {
import parseHtml from './html-parser'
const props = {
nodes: {
type: [Array, String],
default: function () {
......@@ -6,3 +8,5 @@ export const props = {
},
},
}
export { props, parseHtml }
......@@ -17,6 +17,7 @@ import Form from './form'
import Icon from './icon'
import Swiper from './swiper'
import SwiperItem from './swiper-item'
import RichText from './rich-text'
export default {
Navigator,
Label,
......@@ -37,4 +38,5 @@ export default {
Icon,
Swiper,
SwiperItem,
RichText,
}
import {
defineComponent,
ExtractPropTypes,
getCurrentInstance,
ComponentInternalInstance,
// @ts-ignore
parseClassList,
} from 'vue'
import { props, parseHtml } from '../../components/rich-text'
import { parseStyleText } from '../helpers'
const defaultFontSize = 16
type Props = ExtractPropTypes<typeof props>
export default defineComponent({
name: 'RichText',
props,
setup(props) {
const instance = getCurrentInstance()
return () => {
let nodes = props.nodes
if (typeof nodes === 'string') {
nodes = parseHtml(nodes)
}
return (
<u-rich-text
{...{
value: normalizeNodes(nodes || [], instance, {
defaultFontSize,
}),
}}
></u-rich-text>
)
}
},
})
function normalizeNodes(
nodes: Props['nodes'],
instance: ComponentInternalInstance | null,
options: { defaultFontSize: number }
) {
type NodeType = keyof typeof strategies | 'span' | 'img' | 'image' | 'text'
type NvueNode = {
type?: NodeType
__type?: string
name?: string
attrs?: {
class: string
width: string
height: string
style: string
}
attr?: {
value: string
}
text?: string
__block?: boolean
__break?: boolean
__value?: string
style?: Record<string, any>
children?: NvueNode[]
}
const TAGS = ['span', 'a', 'image', 'img']
const strategies = {
blockquote: block,
br: br,
div: block,
dl: block,
h1: createHeading(2),
h2: createHeading(1.5),
h3: createHeading(1.17),
h4: createHeading(1),
h5: createHeading(0.83),
h6: createHeading(0.67),
hr: block,
ol: block,
p: block,
strong: bold,
table: block,
tbody: block,
tfoot: block,
thead: block,
ul: block,
}
const HTML_RE = /&(amp|gt|lt|nbsp|quot|apos);/g
const CHARS = {
amp: '&',
gt: '>',
lt: '<',
nbsp: ' ',
quot: '"',
apos: "'",
}
// 插入换行
const breakNode: NvueNode = {
type: 'span',
__type: 'break',
attr: {
value: '\n',
},
}
let lastNode: NvueNode = {
__block: true,
__break: true,
children: [],
}
let breakNodes: NvueNode[] | null = null
function parseStyle(node: NvueNode) {
const styles = Object.create(null)
if (node.attrs) {
const classList = (node.attrs.class || '').split(' ')
Object.assign(
styles,
parseClassList(classList, instance),
parseStyleText(node.attrs.style || '')
)
}
if (node.name === 'img' || node.name === 'image') {
const attrs = node.attrs
styles.width = styles.width || attrs!.width
styles.height = styles.height || attrs!.height
}
return styles
}
function block(node: NvueNode) {
node.__block = true
// node.attr.value = (node.attr.value || '') + '\n'
return node
}
function heading(node: NvueNode, em: number) {
if (node.style)
!node.style.fontSize &&
(node.style.fontSize = options.defaultFontSize * em)
return block(bold(node))
}
function createHeading(em: number) {
return function (node: NvueNode) {
return heading(node, em)
}
}
function bold(node: NvueNode) {
if (node.style) !node.style.fontWeight && (node.style.fontWeight = 'bold')
return node
}
function br(node: NvueNode) {
node.__value = ' '
return block(node)
}
function normalizeText(str: string) {
return str.replace(HTML_RE, function (match, entity: keyof typeof CHARS) {
return CHARS[entity]
})
}
function normalizeNode(node: NvueNode) {
let type: NodeType = (node.name || '').toLowerCase() as NodeType
const __type = type
const strategy = strategies[type as keyof typeof strategies]
if (TAGS.indexOf(type) === -1) {
type = 'span'
}
if (type === 'img') {
type = 'image'
}
const nvueNode: NvueNode = {
type,
__type,
attr: Object.create(null),
}
if (node.type === 'text' || node.text) {
nvueNode.__value = nvueNode.attr!.value = normalizeText(
(node.text || '').trim()
)
}
if (node.attrs) {
Object.keys(node.attrs).forEach((name) => {
if (name !== 'class' && name !== 'style') {
;(nvueNode.attr as any)[name] = (node.attrs as any)[name]
}
})
}
nvueNode.style = parseStyle(node)
if (strategy) {
strategy(nvueNode)
}
if (lastNode.__block || nvueNode.__block) {
if (!breakNodes) {
lastNode.children!.push(breakNode)
breakNodes = [lastNode, breakNode]
}
}
// 进入节点
lastNode = nvueNode
if (
lastNode.__value ||
(lastNode.type === 'image' && (lastNode.attr as any).src)
) {
// 文本和图像消费换行
breakNodes = null
}
nvueNode.children = normalizeNodes(node.children)
// 退出节点
lastNode = nvueNode
if (
lastNode.__block &&
(lastNode.style as any).height &&
!/^0(px)?$/.test((lastNode.style as any).height)
) {
// 有高度的块元素消费换行
breakNodes = null
}
return nvueNode
}
function normalizeNodes(nodes?: NvueNode[]) {
if (Array.isArray(nodes)) {
return nodes.map((node) => normalizeNode(node))
}
return []
}
const nvueNodes = normalizeNodes(nodes as NvueNode[])
if (breakNodes) {
// 撤销未消费的换行
const [lastNode, breakNode] = breakNodes as NvueNode[]
const children = lastNode.children!
const index = children.indexOf(breakNode)
children.splice(index, 1)
}
return nvueNodes
}
......@@ -4,9 +4,8 @@ import {
useCustomEvent,
EmitEvent,
} from '@dcloudio/uni-components'
import parseHtml from './html-parser'
import parseNodes from './nodes-parser'
import { props } from '../../components/rich-text'
import { props, parseHtml } from '../../components/rich-text'
export default /*#__PURE__*/ defineBuiltInComponent({
name: 'RichText',
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册