@ant-design/x-markdown-mini 0.1.0-beta.0 → 0.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Markdown/index.acss +65 -7
- package/dist/components/MiniNodeRenderer/index.acss +19 -4
- package/dist/components/MiniNodeRenderer/index.axml +109 -29
- package/dist/components/MiniNodeRenderer/index.js +12 -0
- package/dist/components/MiniNodeRenderer/index.sjs +48 -0
- package/dist/es/Markdown/index.acss +65 -7
- package/dist/es/MiniNodeRenderer/index.acss +19 -4
- package/dist/es/MiniNodeRenderer/index.axml +109 -29
- package/dist/es/MiniNodeRenderer/index.js +12 -0
- package/dist/es/MiniNodeRenderer/index.sjs +48 -0
- package/dist/index.d.mts +39 -1
- package/dist/index.d.ts +39 -1
- package/dist/index.js +134 -56
- package/dist/index.mjs +134 -56
- package/dist/miniprogram_dist/components/Markdown/index.json +1 -1
- package/dist/miniprogram_dist/components/Markdown/index.wxss +65 -7
- package/dist/miniprogram_dist/components/MiniNodeRenderer/index.js +12 -0
- package/dist/miniprogram_dist/components/MiniNodeRenderer/index.json +1 -1
- package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxml +98 -21
- package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxs +41 -0
- package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxss +13 -2
- package/dist/miniprogram_dist/es/Markdown/index.json +1 -1
- package/dist/miniprogram_dist/es/Markdown/index.wxss +65 -7
- package/dist/miniprogram_dist/es/MiniNodeRenderer/index.js +12 -0
- package/dist/miniprogram_dist/es/MiniNodeRenderer/index.json +1 -1
- package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxml +98 -21
- package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxs +41 -0
- package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxss +13 -2
- package/dist/miniprogram_dist/index.js +134 -56
- package/dist/miniprogram_dist/plugins/CodeHighlight/index.js +13 -5
- package/dist/miniprogram_dist/plugins/CodeHighlight/style.wxss +41 -31
- package/dist/miniprogram_dist/plugins/Latex/index.js +44 -23
- package/dist/miniprogram_dist/plugins/Latex/style.wxss +11 -11
- package/dist/miniprogram_dist/shared/flattenInline.js +33 -5
- package/dist/plugins/CodeHighlight/index.d.mts +1 -1
- package/dist/plugins/CodeHighlight/index.d.ts +1 -1
- package/dist/plugins/CodeHighlight/index.js +13 -5
- package/dist/plugins/CodeHighlight/index.mjs +13 -5
- package/dist/plugins/CodeHighlight/style.acss +41 -31
- package/dist/plugins/Latex/index.d.mts +1 -1
- package/dist/plugins/Latex/index.d.ts +1 -1
- package/dist/plugins/Latex/index.js +44 -23
- package/dist/plugins/Latex/index.mjs +47 -23
- package/dist/plugins/Latex/style.acss +11 -6
- package/dist/shared/flattenInline.js +33 -5
- package/dist/{types-CegkonfJ.d.mts → types-BcxGtbQZ.d.mts} +25 -0
- package/dist/{types-CegkonfJ.d.ts → types-BcxGtbQZ.d.ts} +25 -0
- package/package.json +3 -2
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
<wxs src="./index.wxs" module="u" />
|
|
2
2
|
|
|
3
3
|
<block wx:for="{{nodes}}" wx:for-item="node" wx:for-index="i" wx:key="i">
|
|
4
|
-
<!-- text leaf
|
|
4
|
+
<!-- text leaf: 流式时逐字淡入(每字一个 <text>,按下标 key 复用,只有新字触发动画);
|
|
5
|
+
非流式时整段渲染为单个 <text>。 -->
|
|
5
6
|
<text
|
|
6
|
-
wx:if="{{u.isText(node.name)}}"
|
|
7
|
+
wx:if="{{u.isText(node.name) && animation}}"
|
|
8
|
+
class="{{u.classOf(node)}}"
|
|
9
|
+
selectable="{{selectable}}"
|
|
10
|
+
><text
|
|
11
|
+
wx:for="{{u.charsOf(node)}}"
|
|
12
|
+
wx:for-item="ch"
|
|
13
|
+
wx:for-index="ci"
|
|
14
|
+
wx:key="ci"
|
|
15
|
+
class="md-anim-char"
|
|
16
|
+
>{{ch}}</text></text>
|
|
17
|
+
<text
|
|
18
|
+
wx:elif="{{u.isText(node.name)}}"
|
|
7
19
|
class="{{u.classOf(node)}}"
|
|
8
20
|
selectable="{{selectable}}"
|
|
9
21
|
>{{u.valueOf(node)}}</text>
|
|
@@ -25,26 +37,91 @@
|
|
|
25
37
|
catch:tap="_tap"
|
|
26
38
|
/>
|
|
27
39
|
|
|
28
|
-
<!-- pre: scrollable
|
|
29
|
-
<
|
|
40
|
+
<!-- pre: code block with language label + copy toolbar, scrollable body -->
|
|
41
|
+
<view
|
|
30
42
|
wx:elif="{{u.isPre(node.name)}}"
|
|
31
|
-
|
|
32
|
-
class="{{u.classOf(node)}}"
|
|
33
|
-
style="{{u.styleOf(node)}}"
|
|
34
|
-
data-data="{{node}}"
|
|
35
|
-
catch:tap="_tap"
|
|
43
|
+
class="md-codeblock {{node.animate ? 'md-animate-block' : ''}}"
|
|
36
44
|
>
|
|
37
|
-
<
|
|
38
|
-
wx:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
<view wx:if="{{node.header}}" class="md-codeblock-bar">
|
|
46
|
+
<block wx:for="{{node.header}}" wx:for-item="h" wx:for-index="hi" wx:key="hi">
|
|
47
|
+
<text wx:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
|
|
48
|
+
<image
|
|
49
|
+
wx:elif="{{u.isCopy(h.name)}}"
|
|
50
|
+
class="{{u.classOf(h) || 'md-copy-icon'}}"
|
|
51
|
+
src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
|
|
52
|
+
data-copy="{{u.copyOf(h)}}"
|
|
53
|
+
catch:tap="_copy"
|
|
54
|
+
/>
|
|
55
|
+
<view wx:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
|
|
56
|
+
<mini-node-renderer
|
|
57
|
+
wx:if="{{h.children}}"
|
|
58
|
+
generic:custom-slot="custom-slot"
|
|
59
|
+
nodes="{{h.children}}"
|
|
60
|
+
selectable="{{selectable}}"
|
|
61
|
+
animation="{{false}}"
|
|
62
|
+
slotComponents="{{slotComponents}}"
|
|
63
|
+
bind:tap="_tap"
|
|
64
|
+
/>
|
|
65
|
+
</view>
|
|
66
|
+
</block>
|
|
67
|
+
</view>
|
|
68
|
+
<scroll-view scroll-x class="md-code-block">
|
|
69
|
+
<mini-node-renderer
|
|
70
|
+
wx:if="{{node.children}}"
|
|
71
|
+
generic:custom-slot="custom-slot"
|
|
72
|
+
nodes="{{node.children}}"
|
|
73
|
+
selectable="{{selectable}}"
|
|
74
|
+
animation="{{false}}"
|
|
75
|
+
slotComponents="{{slotComponents}}"
|
|
76
|
+
bind:tap="_tap"
|
|
77
|
+
bind:appear="_appear"
|
|
78
|
+
/>
|
|
79
|
+
</scroll-view>
|
|
80
|
+
</view>
|
|
81
|
+
|
|
82
|
+
<!-- table: caption + copy toolbar card, horizontal-scroll CSS table (columns kept aligned) -->
|
|
83
|
+
<view
|
|
84
|
+
wx:elif="{{u.isTable(node.name)}}"
|
|
85
|
+
class="md-tableblock {{node.animate ? 'md-animate-block' : ''}}"
|
|
86
|
+
>
|
|
87
|
+
<view wx:if="{{node.header}}" class="md-tableblock-bar">
|
|
88
|
+
<block wx:for="{{node.header}}" wx:for-item="h" wx:for-index="hi" wx:key="hi">
|
|
89
|
+
<text wx:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
|
|
90
|
+
<image
|
|
91
|
+
wx:elif="{{u.isCopy(h.name)}}"
|
|
92
|
+
class="{{u.classOf(h) || 'md-copy-icon'}}"
|
|
93
|
+
src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
|
|
94
|
+
data-copy="{{u.copyOf(h)}}"
|
|
95
|
+
catch:tap="_copy"
|
|
96
|
+
/>
|
|
97
|
+
<view wx:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
|
|
98
|
+
<mini-node-renderer
|
|
99
|
+
wx:if="{{h.children}}"
|
|
100
|
+
generic:custom-slot="custom-slot"
|
|
101
|
+
nodes="{{h.children}}"
|
|
102
|
+
selectable="{{selectable}}"
|
|
103
|
+
animation="{{false}}"
|
|
104
|
+
slotComponents="{{slotComponents}}"
|
|
105
|
+
bind:tap="_tap"
|
|
106
|
+
/>
|
|
107
|
+
</view>
|
|
108
|
+
</block>
|
|
109
|
+
</view>
|
|
110
|
+
<scroll-view scroll-x class="md-table-scroll">
|
|
111
|
+
<view class="md-table">
|
|
112
|
+
<mini-node-renderer
|
|
113
|
+
wx:if="{{node.children}}"
|
|
114
|
+
generic:custom-slot="custom-slot"
|
|
115
|
+
nodes="{{node.children}}"
|
|
116
|
+
selectable="{{selectable}}"
|
|
117
|
+
animation="{{false}}"
|
|
118
|
+
slotComponents="{{slotComponents}}"
|
|
119
|
+
bind:tap="_tap"
|
|
120
|
+
bind:appear="_appear"
|
|
121
|
+
/>
|
|
122
|
+
</view>
|
|
123
|
+
</scroll-view>
|
|
124
|
+
</view>
|
|
48
125
|
|
|
49
126
|
<!-- anchor: kept interactive, children are already flattened text runs -->
|
|
50
127
|
<text
|
|
@@ -66,7 +143,7 @@
|
|
|
66
143
|
|
|
67
144
|
<!-- inline tag rendered as <text>: children are flat text runs -->
|
|
68
145
|
<text
|
|
69
|
-
wx:elif="{{u.isInline(node.name)}}"
|
|
146
|
+
wx:elif="{{u.isInline(node.name) && !u.isRich(node)}}"
|
|
70
147
|
class="{{u.classOf(node)}}"
|
|
71
148
|
style="{{u.styleOf(node)}}"
|
|
72
149
|
selectable="{{selectable}}"
|
|
@@ -6,6 +6,20 @@ function isBr(name) { return name === 'br'; }
|
|
|
6
6
|
function isImg(name) { return name === 'img'; }
|
|
7
7
|
function isHr(name) { return name === 'hr'; }
|
|
8
8
|
function isPre(name) { return name === 'pre'; }
|
|
9
|
+
function isTable(name) { return name === 'table'; }
|
|
10
|
+
function isCopy(name) { return name === 'copy-button'; }
|
|
11
|
+
function copyOf(node) { return (node.attrs || {})['data-copy'] || ''; }
|
|
12
|
+
// 富内联节点(如 KaTeX 公式容器)含有元素子节点,必须走递归 <view> 路径
|
|
13
|
+
// 渲染,保留嵌套结构与 style;普通已扁平化的内联只有 text/br 子节点。
|
|
14
|
+
function isRich(node) {
|
|
15
|
+
var ch = node.children;
|
|
16
|
+
if (!ch) return false;
|
|
17
|
+
for (var i = 0; i < ch.length; i++) {
|
|
18
|
+
var nm = ch[i].name;
|
|
19
|
+
if (nm !== 'text' && nm !== 'br') return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
9
23
|
function isSlot(name, slotComponents) {
|
|
10
24
|
return !!slotComponents && slotComponents.indexOf(name) > -1;
|
|
11
25
|
}
|
|
@@ -19,8 +33,29 @@ function classOf(node) {
|
|
|
19
33
|
return cls;
|
|
20
34
|
}
|
|
21
35
|
|
|
36
|
+
// 把文本拆成「按 code point 安全」的字符数组(不劈坏 emoji/组合 surrogate pair),
|
|
37
|
+
// 供流式逐字淡入:每个字符渲染成独立 <text>,按下标 key 复用,只有新字符会触发淡入动画。
|
|
38
|
+
function charsOf(node) {
|
|
39
|
+
var v = (node.attrs || {}).value || '';
|
|
40
|
+
var out = [];
|
|
41
|
+
var i = 0;
|
|
42
|
+
var n = v.length;
|
|
43
|
+
while (i < n) {
|
|
44
|
+
var code = v.charCodeAt(i);
|
|
45
|
+
if (code >= 55296 && code <= 56319 && i + 1 < n) {
|
|
46
|
+
out.push(v.charAt(i) + v.charAt(i + 1));
|
|
47
|
+
i += 2;
|
|
48
|
+
} else {
|
|
49
|
+
out.push(v.charAt(i));
|
|
50
|
+
i += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
function styleOf(node) { return (node.attrs || {}).style || ''; }
|
|
23
57
|
function srcOf(node) { return (node.attrs || {}).src || ''; }
|
|
58
|
+
function langOf(node) { return (node.attrs || {}).lang || ''; }
|
|
24
59
|
function altOf(node) { return (node.attrs || {}).alt || ''; }
|
|
25
60
|
function valueOf(node) { return (node.attrs || {}).value || ''; }
|
|
26
61
|
|
|
@@ -31,10 +66,16 @@ module.exports = {
|
|
|
31
66
|
isImg: isImg,
|
|
32
67
|
isHr: isHr,
|
|
33
68
|
isPre: isPre,
|
|
69
|
+
isTable: isTable,
|
|
70
|
+
isCopy: isCopy,
|
|
71
|
+
copyOf: copyOf,
|
|
72
|
+
isRich: isRich,
|
|
34
73
|
isSlot: isSlot,
|
|
35
74
|
classOf: classOf,
|
|
75
|
+
charsOf: charsOf,
|
|
36
76
|
styleOf: styleOf,
|
|
37
77
|
srcOf: srcOf,
|
|
78
|
+
langOf: langOf,
|
|
38
79
|
altOf: altOf,
|
|
39
80
|
valueOf: valueOf,
|
|
40
81
|
};
|
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
animation-duration: var(--md-animation-duration, 300ms);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/* 纯透明度淡入:不做 translateY 位移,避免流式逐字重渲染时块体上下抖动。 */
|
|
8
9
|
@keyframes md-block-appear {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
from { opacity: 0.3; }
|
|
11
|
+
to { opacity: 1; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* 流式逐字淡入:每个新字符从透明渐显,已显示的字符(按下标复用)不会重播。 */
|
|
15
|
+
.md-anim-char {
|
|
16
|
+
opacity: 0;
|
|
17
|
+
animation: md-char-in 360ms ease-out forwards;
|
|
18
|
+
}
|
|
19
|
+
@keyframes md-char-in {
|
|
20
|
+
from { opacity: 0; }
|
|
21
|
+
to { opacity: 1; }
|
|
11
22
|
}
|
|
@@ -45,11 +45,47 @@
|
|
|
45
45
|
|
|
46
46
|
.md-code-block {
|
|
47
47
|
display: block;
|
|
48
|
-
margin: 12rpx 0;
|
|
49
48
|
padding: 16rpx;
|
|
50
49
|
background: #f6f8fa;
|
|
51
|
-
border-radius: 8rpx;
|
|
52
50
|
overflow-x: auto;
|
|
51
|
+
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
52
|
+
font-size: 26rpx;
|
|
53
|
+
line-height: 1.6;
|
|
54
|
+
white-space: pre;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* code & table cards share a captioned header bar with an icon copy button */
|
|
58
|
+
.md-codeblock,
|
|
59
|
+
.md-tableblock {
|
|
60
|
+
margin: 16rpx 0;
|
|
61
|
+
border: 2rpx solid #eceef1;
|
|
62
|
+
border-radius: 16rpx;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
.md-codeblock-bar,
|
|
66
|
+
.md-tableblock-bar {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
justify-content: space-between;
|
|
70
|
+
height: 64rpx;
|
|
71
|
+
padding: 0 24rpx;
|
|
72
|
+
background: #eceef2;
|
|
73
|
+
font-size: 24rpx;
|
|
74
|
+
color: #4e5969;
|
|
75
|
+
}
|
|
76
|
+
.md-codeblock-lang {
|
|
77
|
+
text-transform: uppercase;
|
|
78
|
+
letter-spacing: 1rpx;
|
|
79
|
+
font-weight: 600;
|
|
80
|
+
color: #1d2129;
|
|
81
|
+
}
|
|
82
|
+
.md-tableblock-title {
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
color: #1d2129;
|
|
85
|
+
}
|
|
86
|
+
.md-copy-icon {
|
|
87
|
+
width: 32rpx;
|
|
88
|
+
height: 32rpx;
|
|
53
89
|
}
|
|
54
90
|
|
|
55
91
|
.md-code {
|
|
@@ -78,6 +114,8 @@
|
|
|
78
114
|
.md-img {
|
|
79
115
|
display: block;
|
|
80
116
|
max-width: 100%;
|
|
117
|
+
margin: 16rpx 0;
|
|
118
|
+
border-radius: 12rpx;
|
|
81
119
|
}
|
|
82
120
|
|
|
83
121
|
.md-hr {
|
|
@@ -87,17 +125,37 @@
|
|
|
87
125
|
margin: 16rpx 0;
|
|
88
126
|
}
|
|
89
127
|
|
|
90
|
-
.md-
|
|
128
|
+
/* table body scrolls horizontally inside the .md-tableblock card */
|
|
129
|
+
.md-table-scroll {
|
|
91
130
|
display: block;
|
|
92
131
|
width: 100%;
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
overflow: hidden;
|
|
133
|
+
white-space: nowrap;
|
|
95
134
|
}
|
|
135
|
+
.md-table {
|
|
136
|
+
display: table;
|
|
137
|
+
table-layout: auto;
|
|
138
|
+
width: max-content;
|
|
139
|
+
min-width: 100%;
|
|
140
|
+
border-collapse: separate;
|
|
141
|
+
border-spacing: 0;
|
|
142
|
+
font-size: 26rpx;
|
|
143
|
+
}
|
|
144
|
+
.md-tr { display: table-row; }
|
|
96
145
|
.md-th, .md-td {
|
|
97
146
|
display: table-cell;
|
|
98
|
-
padding:
|
|
99
|
-
|
|
147
|
+
padding: 12rpx 20rpx;
|
|
148
|
+
max-width: 480rpx;
|
|
149
|
+
border-right: 2rpx solid #e5e5e5;
|
|
150
|
+
border-bottom: 2rpx solid #e5e5e5;
|
|
151
|
+
text-align: left;
|
|
152
|
+
vertical-align: top;
|
|
153
|
+
word-break: break-word;
|
|
154
|
+
white-space: normal;
|
|
100
155
|
}
|
|
101
156
|
.md-th { background: #fafafa; font-weight: 600; }
|
|
157
|
+
.md-tr .md-th:last-child,
|
|
158
|
+
.md-tr .md-td:last-child { border-right: none; }
|
|
159
|
+
.md-table .md-tr:last-child .md-td { border-bottom: none; }
|
|
102
160
|
|
|
103
161
|
.md-html { display: block; }
|
|
@@ -16,6 +16,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
16
16
|
// src/components/wechat/MiniNodeRenderer/index.ts
|
|
17
17
|
var MiniNodeRenderer_exports = {};
|
|
18
18
|
module.exports = __toCommonJS(MiniNodeRenderer_exports);
|
|
19
|
+
function copyToClipboard(text) {
|
|
20
|
+
if (!text) return;
|
|
21
|
+
wx.setClipboardData({
|
|
22
|
+
data: text,
|
|
23
|
+
success: () => wx.showToast({ title: "\u5DF2\u590D\u5236", icon: "none", duration: 1200 }),
|
|
24
|
+
fail: () => wx.showToast({ title: "\u590D\u5236\u5931\u8D25", icon: "none" })
|
|
25
|
+
});
|
|
26
|
+
}
|
|
19
27
|
Component({
|
|
20
28
|
options: {
|
|
21
29
|
multipleSlots: true,
|
|
@@ -34,6 +42,10 @@ Component({
|
|
|
34
42
|
},
|
|
35
43
|
_appear(e) {
|
|
36
44
|
this.triggerEvent("appear", e, { bubbles: true, composed: true });
|
|
45
|
+
},
|
|
46
|
+
_copy(e) {
|
|
47
|
+
const ds = e && e.currentTarget && e.currentTarget.dataset;
|
|
48
|
+
copyToClipboard(ds && ds.copy || "");
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
51
|
});
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
<wxs src="./index.wxs" module="u" />
|
|
2
2
|
|
|
3
3
|
<block wx:for="{{nodes}}" wx:for-item="node" wx:for-index="i" wx:key="i">
|
|
4
|
-
<!-- text leaf
|
|
4
|
+
<!-- text leaf: 流式时逐字淡入(每字一个 <text>,按下标 key 复用,只有新字触发动画);
|
|
5
|
+
非流式时整段渲染为单个 <text>。 -->
|
|
5
6
|
<text
|
|
6
|
-
wx:if="{{u.isText(node.name)}}"
|
|
7
|
+
wx:if="{{u.isText(node.name) && animation}}"
|
|
8
|
+
class="{{u.classOf(node)}}"
|
|
9
|
+
selectable="{{selectable}}"
|
|
10
|
+
><text
|
|
11
|
+
wx:for="{{u.charsOf(node)}}"
|
|
12
|
+
wx:for-item="ch"
|
|
13
|
+
wx:for-index="ci"
|
|
14
|
+
wx:key="ci"
|
|
15
|
+
class="md-anim-char"
|
|
16
|
+
>{{ch}}</text></text>
|
|
17
|
+
<text
|
|
18
|
+
wx:elif="{{u.isText(node.name)}}"
|
|
7
19
|
class="{{u.classOf(node)}}"
|
|
8
20
|
selectable="{{selectable}}"
|
|
9
21
|
>{{u.valueOf(node)}}</text>
|
|
@@ -25,26 +37,91 @@
|
|
|
25
37
|
catch:tap="_tap"
|
|
26
38
|
/>
|
|
27
39
|
|
|
28
|
-
<!-- pre: scrollable
|
|
29
|
-
<
|
|
40
|
+
<!-- pre: code block with language label + copy toolbar, scrollable body -->
|
|
41
|
+
<view
|
|
30
42
|
wx:elif="{{u.isPre(node.name)}}"
|
|
31
|
-
|
|
32
|
-
class="{{u.classOf(node)}}"
|
|
33
|
-
style="{{u.styleOf(node)}}"
|
|
34
|
-
data-data="{{node}}"
|
|
35
|
-
catch:tap="_tap"
|
|
43
|
+
class="md-codeblock {{node.animate ? 'md-animate-block' : ''}}"
|
|
36
44
|
>
|
|
37
|
-
<
|
|
38
|
-
wx:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
<view wx:if="{{node.header}}" class="md-codeblock-bar">
|
|
46
|
+
<block wx:for="{{node.header}}" wx:for-item="h" wx:for-index="hi" wx:key="hi">
|
|
47
|
+
<text wx:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
|
|
48
|
+
<image
|
|
49
|
+
wx:elif="{{u.isCopy(h.name)}}"
|
|
50
|
+
class="{{u.classOf(h) || 'md-copy-icon'}}"
|
|
51
|
+
src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
|
|
52
|
+
data-copy="{{u.copyOf(h)}}"
|
|
53
|
+
catch:tap="_copy"
|
|
54
|
+
/>
|
|
55
|
+
<view wx:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
|
|
56
|
+
<mini-node-renderer
|
|
57
|
+
wx:if="{{h.children}}"
|
|
58
|
+
generic:custom-slot="custom-slot"
|
|
59
|
+
nodes="{{h.children}}"
|
|
60
|
+
selectable="{{selectable}}"
|
|
61
|
+
animation="{{false}}"
|
|
62
|
+
slotComponents="{{slotComponents}}"
|
|
63
|
+
bind:tap="_tap"
|
|
64
|
+
/>
|
|
65
|
+
</view>
|
|
66
|
+
</block>
|
|
67
|
+
</view>
|
|
68
|
+
<scroll-view scroll-x class="md-code-block">
|
|
69
|
+
<mini-node-renderer
|
|
70
|
+
wx:if="{{node.children}}"
|
|
71
|
+
generic:custom-slot="custom-slot"
|
|
72
|
+
nodes="{{node.children}}"
|
|
73
|
+
selectable="{{selectable}}"
|
|
74
|
+
animation="{{false}}"
|
|
75
|
+
slotComponents="{{slotComponents}}"
|
|
76
|
+
bind:tap="_tap"
|
|
77
|
+
bind:appear="_appear"
|
|
78
|
+
/>
|
|
79
|
+
</scroll-view>
|
|
80
|
+
</view>
|
|
81
|
+
|
|
82
|
+
<!-- table: caption + copy toolbar card, horizontal-scroll CSS table (columns kept aligned) -->
|
|
83
|
+
<view
|
|
84
|
+
wx:elif="{{u.isTable(node.name)}}"
|
|
85
|
+
class="md-tableblock {{node.animate ? 'md-animate-block' : ''}}"
|
|
86
|
+
>
|
|
87
|
+
<view wx:if="{{node.header}}" class="md-tableblock-bar">
|
|
88
|
+
<block wx:for="{{node.header}}" wx:for-item="h" wx:for-index="hi" wx:key="hi">
|
|
89
|
+
<text wx:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
|
|
90
|
+
<image
|
|
91
|
+
wx:elif="{{u.isCopy(h.name)}}"
|
|
92
|
+
class="{{u.classOf(h) || 'md-copy-icon'}}"
|
|
93
|
+
src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
|
|
94
|
+
data-copy="{{u.copyOf(h)}}"
|
|
95
|
+
catch:tap="_copy"
|
|
96
|
+
/>
|
|
97
|
+
<view wx:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
|
|
98
|
+
<mini-node-renderer
|
|
99
|
+
wx:if="{{h.children}}"
|
|
100
|
+
generic:custom-slot="custom-slot"
|
|
101
|
+
nodes="{{h.children}}"
|
|
102
|
+
selectable="{{selectable}}"
|
|
103
|
+
animation="{{false}}"
|
|
104
|
+
slotComponents="{{slotComponents}}"
|
|
105
|
+
bind:tap="_tap"
|
|
106
|
+
/>
|
|
107
|
+
</view>
|
|
108
|
+
</block>
|
|
109
|
+
</view>
|
|
110
|
+
<scroll-view scroll-x class="md-table-scroll">
|
|
111
|
+
<view class="md-table">
|
|
112
|
+
<mini-node-renderer
|
|
113
|
+
wx:if="{{node.children}}"
|
|
114
|
+
generic:custom-slot="custom-slot"
|
|
115
|
+
nodes="{{node.children}}"
|
|
116
|
+
selectable="{{selectable}}"
|
|
117
|
+
animation="{{false}}"
|
|
118
|
+
slotComponents="{{slotComponents}}"
|
|
119
|
+
bind:tap="_tap"
|
|
120
|
+
bind:appear="_appear"
|
|
121
|
+
/>
|
|
122
|
+
</view>
|
|
123
|
+
</scroll-view>
|
|
124
|
+
</view>
|
|
48
125
|
|
|
49
126
|
<!-- anchor: kept interactive, children are already flattened text runs -->
|
|
50
127
|
<text
|
|
@@ -66,7 +143,7 @@
|
|
|
66
143
|
|
|
67
144
|
<!-- inline tag rendered as <text>: children are flat text runs -->
|
|
68
145
|
<text
|
|
69
|
-
wx:elif="{{u.isInline(node.name)}}"
|
|
146
|
+
wx:elif="{{u.isInline(node.name) && !u.isRich(node)}}"
|
|
70
147
|
class="{{u.classOf(node)}}"
|
|
71
148
|
style="{{u.styleOf(node)}}"
|
|
72
149
|
selectable="{{selectable}}"
|
|
@@ -6,6 +6,20 @@ function isBr(name) { return name === 'br'; }
|
|
|
6
6
|
function isImg(name) { return name === 'img'; }
|
|
7
7
|
function isHr(name) { return name === 'hr'; }
|
|
8
8
|
function isPre(name) { return name === 'pre'; }
|
|
9
|
+
function isTable(name) { return name === 'table'; }
|
|
10
|
+
function isCopy(name) { return name === 'copy-button'; }
|
|
11
|
+
function copyOf(node) { return (node.attrs || {})['data-copy'] || ''; }
|
|
12
|
+
// 富内联节点(如 KaTeX 公式容器)含有元素子节点,必须走递归 <view> 路径
|
|
13
|
+
// 渲染,保留嵌套结构与 style;普通已扁平化的内联只有 text/br 子节点。
|
|
14
|
+
function isRich(node) {
|
|
15
|
+
var ch = node.children;
|
|
16
|
+
if (!ch) return false;
|
|
17
|
+
for (var i = 0; i < ch.length; i++) {
|
|
18
|
+
var nm = ch[i].name;
|
|
19
|
+
if (nm !== 'text' && nm !== 'br') return true;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
9
23
|
function isSlot(name, slotComponents) {
|
|
10
24
|
return !!slotComponents && slotComponents.indexOf(name) > -1;
|
|
11
25
|
}
|
|
@@ -19,8 +33,29 @@ function classOf(node) {
|
|
|
19
33
|
return cls;
|
|
20
34
|
}
|
|
21
35
|
|
|
36
|
+
// 把文本拆成「按 code point 安全」的字符数组(不劈坏 emoji/组合 surrogate pair),
|
|
37
|
+
// 供流式逐字淡入:每个字符渲染成独立 <text>,按下标 key 复用,只有新字符会触发淡入动画。
|
|
38
|
+
function charsOf(node) {
|
|
39
|
+
var v = (node.attrs || {}).value || '';
|
|
40
|
+
var out = [];
|
|
41
|
+
var i = 0;
|
|
42
|
+
var n = v.length;
|
|
43
|
+
while (i < n) {
|
|
44
|
+
var code = v.charCodeAt(i);
|
|
45
|
+
if (code >= 55296 && code <= 56319 && i + 1 < n) {
|
|
46
|
+
out.push(v.charAt(i) + v.charAt(i + 1));
|
|
47
|
+
i += 2;
|
|
48
|
+
} else {
|
|
49
|
+
out.push(v.charAt(i));
|
|
50
|
+
i += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
function styleOf(node) { return (node.attrs || {}).style || ''; }
|
|
23
57
|
function srcOf(node) { return (node.attrs || {}).src || ''; }
|
|
58
|
+
function langOf(node) { return (node.attrs || {}).lang || ''; }
|
|
24
59
|
function altOf(node) { return (node.attrs || {}).alt || ''; }
|
|
25
60
|
function valueOf(node) { return (node.attrs || {}).value || ''; }
|
|
26
61
|
|
|
@@ -31,10 +66,16 @@ module.exports = {
|
|
|
31
66
|
isImg: isImg,
|
|
32
67
|
isHr: isHr,
|
|
33
68
|
isPre: isPre,
|
|
69
|
+
isTable: isTable,
|
|
70
|
+
isCopy: isCopy,
|
|
71
|
+
copyOf: copyOf,
|
|
72
|
+
isRich: isRich,
|
|
34
73
|
isSlot: isSlot,
|
|
35
74
|
classOf: classOf,
|
|
75
|
+
charsOf: charsOf,
|
|
36
76
|
styleOf: styleOf,
|
|
37
77
|
srcOf: srcOf,
|
|
78
|
+
langOf: langOf,
|
|
38
79
|
altOf: altOf,
|
|
39
80
|
valueOf: valueOf,
|
|
40
81
|
};
|
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
animation-duration: var(--md-animation-duration, 300ms);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/* 纯透明度淡入:不做 translateY 位移,避免流式逐字重渲染时块体上下抖动。 */
|
|
8
9
|
@keyframes md-block-appear {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
from { opacity: 0.3; }
|
|
11
|
+
to { opacity: 1; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* 流式逐字淡入:每个新字符从透明渐显,已显示的字符(按下标复用)不会重播。 */
|
|
15
|
+
.md-anim-char {
|
|
16
|
+
opacity: 0;
|
|
17
|
+
animation: md-char-in 360ms ease-out forwards;
|
|
18
|
+
}
|
|
19
|
+
@keyframes md-char-in {
|
|
20
|
+
from { opacity: 0; }
|
|
21
|
+
to { opacity: 1; }
|
|
11
22
|
}
|