@jx3box/jx3box-editor 3.2.2 → 3.2.4

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.
@@ -2,6 +2,7 @@
2
2
  // 旧主题色
3
3
  // @primary:#0366d6;
4
4
  @primary:#4f46e5;
5
+ @v4yellow: #f5bd1e;
5
6
 
6
7
  // 文字颜色(黑底、白底)
7
8
  @color: #3d454d;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-editor",
3
- "version": "3.2.2",
3
+ "version": "3.2.4",
4
4
  "description": "JX3BOX Article & Editor",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -15,11 +15,11 @@
15
15
  "dependencies": {
16
16
  "@element-plus/icons-vue": "^2.0.10",
17
17
  "@imengyu/vue3-context-menu": "^1.5.4",
18
- "@jx3box/jx3box-common": "^9.2.4",
19
- "@jx3box/jx3box-data": "^3.9.5",
20
- "@jx3box/jx3box-emotion": "^1.3.1",
21
- "@jx3box/jx3box-macro": "^1.0.4",
22
- "@jx3box/jx3box-talent": "^1.3.14",
18
+ "@jx3box/jx3box-common": "^9.2.1",
19
+ "@jx3box/jx3box-data": "^3.9.4",
20
+ "@jx3box/jx3box-emotion": "^1.3.0",
21
+ "@jx3box/jx3box-macro": "^1.0.3",
22
+ "@jx3box/jx3box-talent": "^1.3.13",
23
23
  "@tinymce/tinymce-vue": "^5.0.0",
24
24
  "axios": "^0.19.2",
25
25
  "cheerio": "^1.1.2",
@@ -89,6 +89,6 @@
89
89
  },
90
90
  "repository": {
91
91
  "type": "git",
92
- "url": "git+https://github.com/iruxu/iruxu-editor.git"
92
+ "url": "git+https://github.com/JX3BOX/jx3box-editor.git"
93
93
  }
94
94
  }
@@ -32,12 +32,11 @@
32
32
 
33
33
  h4 {
34
34
  font-size: 15px;
35
- padding: 8px 10px;
35
+ padding: 8px 10px 8px 20px;
36
36
 
37
37
  background-color: #fafbfc;
38
38
  border-radius: 6px;
39
- border-left:4px solid @primary;
40
-
39
+ // border-left:4px solid @primary;
41
40
 
42
41
  position: relative;
43
42
  overflow: hidden;
@@ -45,17 +44,39 @@
45
44
  // border: 1px solid @primary;
46
45
  &::before {
47
46
  content: "";
48
- font-size: 20px;
49
47
  display: block;
50
48
  position: absolute;
51
- width: 80px;
52
- height: 3px;
53
- top: 0;
54
- left: 15px;
55
- // background-color: @primary;
49
+ width: 5px;
50
+ height: 60%;
51
+ top: 50%;
52
+ transform: translateY(-50%);
53
+ left: 10px;
54
+ background-color: @primary;
55
+ border-radius: 10px;
56
+ transition: all 0.2s ease;
56
57
  // border-bottom-right-radius: 20px;
57
58
  // border-bottom-left-radius: 20px;
58
59
  }
60
+ // &::after {
61
+ // content: "";
62
+ // display: block;
63
+ // position: absolute;
64
+ // width: 5px;
65
+ // height: 60%;
66
+ // top: 50%;
67
+ // transform: translateY(-50%);
68
+ // left: 10px;
69
+ // background-color: @v4yellow;
70
+ // border-radius: 10px;
71
+ // z-index: 2;
72
+ // display: none;
73
+ // }
74
+
75
+ &:hover {
76
+ &::before {
77
+ background-color: @v4yellow;
78
+ }
79
+ }
59
80
  }
60
81
 
61
82
  h5 {
@@ -74,6 +95,11 @@
74
95
  bottom: -1px;
75
96
  left: 0;
76
97
  }
98
+ &:hover{
99
+ &::after {
100
+ background-color: @v4yellow;
101
+ }
102
+ }
77
103
  }
78
104
 
79
105
  h6 {
@@ -24,6 +24,7 @@
24
24
  max-width: 100%;
25
25
  overflow-x: auto;
26
26
  text-align: center;
27
+ justify-content: center;
27
28
 
28
29
  .el-pager{
29
30
  margin:0;
@@ -12,17 +12,17 @@
12
12
  line-height:2.2;
13
13
  color:@color;
14
14
 
15
- td,
16
- th {
15
+ td,th {
17
16
  padding: 6px 10px;
18
- }
19
- td {
20
17
  border: 1px solid #eee;
21
18
  }
22
19
  th {
23
20
  background-color: #fafbfc;
24
21
  font-weight: 500;
25
22
  }
23
+ tr{
24
+ background-color:#fff;
25
+ }
26
26
  tr:nth-child(2n + 1) {
27
27
  background-color: #fafbfc;
28
28
  }
@@ -2,6 +2,181 @@ import $ from 'jquery';
2
2
  import katex from 'katex';
3
3
  import 'katex/dist/katex.min.css';
4
4
 
5
+ function isEscaped(text, index) {
6
+ let count = 0;
7
+ for (let i = index - 1; i >= 0 && text[i] === '\\'; i--) {
8
+ count++;
9
+ }
10
+ return count % 2 === 1;
11
+ }
12
+
13
+ function findUnescaped(text, token, start) {
14
+ let index = start;
15
+ while (index < text.length) {
16
+ const found = text.indexOf(token, index);
17
+ if (found === -1) return -1;
18
+ if (!isEscaped(text, found)) return found;
19
+ index = found + token.length;
20
+ }
21
+ return -1;
22
+ }
23
+
24
+ function getTextNodes(container, shouldAccept) {
25
+ const walker = document.createTreeWalker(
26
+ container,
27
+ NodeFilter.SHOW_TEXT,
28
+ {
29
+ acceptNode: function (node) {
30
+ // 跳过已渲染的节点
31
+ if (
32
+ node.parentNode &&
33
+ (node.parentNode.classList?.contains('katex') ||
34
+ node.parentNode.closest("pre, code, .katex"))
35
+ ) {
36
+ return NodeFilter.FILTER_REJECT;
37
+ }
38
+
39
+ return shouldAccept(node.nodeValue || '') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
40
+ },
41
+ }
42
+ );
43
+
44
+ const nodesToReplace = [];
45
+ while (walker.nextNode()) {
46
+ nodesToReplace.push(walker.currentNode);
47
+ }
48
+ return nodesToReplace;
49
+ }
50
+
51
+ function collectInlineMatches(text) {
52
+ const matches = [];
53
+ let index = 0;
54
+
55
+ while (index < text.length) {
56
+ const parenStart = text.indexOf('\\(', index);
57
+ const dollarStart = text.indexOf('$', index);
58
+ const candidates = [parenStart, dollarStart].filter((value) => value !== -1 && !isEscaped(text, value));
59
+ const start = candidates.length ? Math.min(...candidates) : -1;
60
+ if (start === -1) break;
61
+
62
+ if (text.slice(start, start + 2) === '\\(') {
63
+ const end = findUnescaped(text, '\\)', start + 2);
64
+ if (end !== -1) {
65
+ const raw = text.slice(start + 2, end);
66
+ if (raw && !raw.includes('\n')) {
67
+ matches.push({
68
+ start,
69
+ end: end + 2,
70
+ raw,
71
+ full: text.slice(start, end + 2),
72
+ });
73
+ }
74
+ index = end + 2;
75
+ continue;
76
+ }
77
+ } else if (text[start] === '$' && text[start + 1] !== '$') {
78
+ const end = findUnescaped(text, '$', start + 1);
79
+ if (end !== -1 && text[end + 1] !== '$') {
80
+ const raw = text.slice(start + 1, end);
81
+ if (raw && !raw.includes('\n')) {
82
+ matches.push({
83
+ start,
84
+ end: end + 1,
85
+ raw,
86
+ full: text.slice(start, end + 1),
87
+ });
88
+ }
89
+ index = end + 1;
90
+ continue;
91
+ }
92
+ }
93
+
94
+ index = start + 1;
95
+ }
96
+
97
+ return matches;
98
+ }
99
+
100
+ function collectBlockMatches(text) {
101
+ const matches = [];
102
+ let index = 0;
103
+
104
+ while (index < text.length) {
105
+ const dollarStart = text.indexOf('$$', index);
106
+ const bracketStart = text.indexOf('\\[', index);
107
+ const candidates = [dollarStart, bracketStart].filter((value) => value !== -1 && !isEscaped(text, value));
108
+ const start = candidates.length ? Math.min(...candidates) : -1;
109
+ if (start === -1) break;
110
+
111
+ if (text.slice(start, start + 2) === '$$') {
112
+ const end = findUnescaped(text, '$$', start + 2);
113
+ if (end !== -1) {
114
+ const raw = text.slice(start + 2, end).trim();
115
+ if (raw) {
116
+ matches.push({
117
+ start,
118
+ end: end + 2,
119
+ raw,
120
+ full: text.slice(start, end + 2),
121
+ });
122
+ }
123
+ index = end + 2;
124
+ continue;
125
+ }
126
+ } else if (text.slice(start, start + 2) === '\\[') {
127
+ const end = findUnescaped(text, '\\]', start + 2);
128
+ if (end !== -1) {
129
+ const raw = text.slice(start + 2, end).trim();
130
+ if (raw) {
131
+ matches.push({
132
+ start,
133
+ end: end + 2,
134
+ raw,
135
+ full: text.slice(start, end + 2),
136
+ });
137
+ }
138
+ index = end + 2;
139
+ continue;
140
+ }
141
+ }
142
+
143
+ index = start + 1;
144
+ }
145
+
146
+ return matches;
147
+ }
148
+
149
+ function replaceTextNode(node, matches, renderMatch, logPrefix) {
150
+ if (!matches.length) return;
151
+
152
+ const text = node.nodeValue || '';
153
+ const frag = document.createDocumentFragment();
154
+ let lastIndex = 0;
155
+
156
+ matches.forEach((match) => {
157
+ if (match.start > lastIndex) {
158
+ frag.appendChild(document.createTextNode(text.slice(lastIndex, match.start)));
159
+ }
160
+
161
+ try {
162
+ frag.appendChild(renderMatch(match.raw));
163
+ } catch (e) {
164
+ frag.appendChild(document.createTextNode(match.full));
165
+ console.error(logPrefix, match.raw, e.message);
166
+ }
167
+
168
+ lastIndex = match.end;
169
+ });
170
+
171
+ if (lastIndex < text.length) {
172
+ frag.appendChild(document.createTextNode(text.slice(lastIndex)));
173
+ }
174
+
175
+ if (frag.hasChildNodes()) {
176
+ node.parentNode.replaceChild(frag, node);
177
+ }
178
+ }
179
+
5
180
  function renderKatexBlock(selector = ".w-latex") {
6
181
  try {
7
182
  $(selector).each(function() {
@@ -39,60 +214,11 @@ function renderKatexBlock(selector = ".w-latex") {
39
214
  }
40
215
 
41
216
  function renderKatexInline(container = document.body) {
42
- // 改进的正则:不匹配换行符,支持转义
43
- const inlineRegex = /(?<!\\)(\\\((.+?)\\\)|(?<!\\)\$([^\n$]+?)(?<!\\)\$)/g;
44
-
45
- const walker = document.createTreeWalker(
46
- container,
47
- NodeFilter.SHOW_TEXT,
48
- {
49
- acceptNode: function (node) {
50
- // 跳过已渲染的节点
51
- if (
52
- node.parentNode &&
53
- (node.parentNode.classList?.contains('katex') ||
54
- node.parentNode.closest("pre, code, .katex"))
55
- ) {
56
- return NodeFilter.FILTER_REJECT;
57
- }
58
-
59
- const value = node.nodeValue || '';
60
- if (value.includes("\\(") || value.includes("$")) {
61
- return NodeFilter.FILTER_ACCEPT;
62
- }
63
- return NodeFilter.FILTER_REJECT;
64
- },
65
- }
66
- );
67
-
68
- const nodesToReplace = [];
69
- while (walker.nextNode()) {
70
- nodesToReplace.push(walker.currentNode);
71
- }
72
-
73
- nodesToReplace.forEach((node) => {
74
- const text = node.nodeValue;
75
- const frag = document.createDocumentFragment();
76
- let lastIndex = 0;
77
-
78
- // 重置正则状态
79
- inlineRegex.lastIndex = 0;
80
-
81
- const matches = [...text.matchAll(inlineRegex)];
82
-
83
- matches.forEach((match) => {
84
- const fullMatch = match[0];
85
- const parenContent = match[2];
86
- const dollarContent = match[3];
87
- const raw = parenContent || dollarContent;
88
- const matchStart = match.index;
89
-
90
- // 添加匹配前的文本
91
- if (matchStart > lastIndex) {
92
- frag.appendChild(document.createTextNode(text.slice(lastIndex, matchStart)));
93
- }
94
-
95
- try {
217
+ getTextNodes(container, (value) => value.includes("\\(") || value.includes("$")).forEach((node) => {
218
+ replaceTextNode(
219
+ node,
220
+ collectInlineMatches(node.nodeValue || ''),
221
+ (raw) => {
96
222
  const span = document.createElement("span");
97
223
  span.className = "katex-inline";
98
224
  span.innerHTML = katex.renderToString(raw, {
@@ -101,81 +227,19 @@ function renderKatexInline(container = document.body) {
101
227
  strict: false,
102
228
  trust: true
103
229
  });
104
- frag.appendChild(span);
105
- } catch (e) {
106
- frag.appendChild(document.createTextNode(fullMatch));
107
- console.error("Inline render error:", raw, e.message);
108
- }
109
-
110
- lastIndex = matchStart + fullMatch.length;
111
- });
112
-
113
- // 添加剩余文本
114
- if (lastIndex < text.length) {
115
- frag.appendChild(document.createTextNode(text.slice(lastIndex)));
116
- }
117
-
118
- if (frag.hasChildNodes()) {
119
- node.parentNode.replaceChild(frag, node);
120
- }
230
+ return span;
231
+ },
232
+ "Inline render error:"
233
+ );
121
234
  });
122
235
  }
123
236
 
124
237
  function renderKatexDisplayBlock(container = document.body) {
125
- // 使用非贪婪匹配,允许多行但不跨过多段落
126
- const blockRegex = /(?<!\\)(\$\$([\s\S]+?)\$\$|(?<!\\)\\\[([\s\S]+?)\\\])/g;
127
-
128
- const walker = document.createTreeWalker(
129
- container,
130
- NodeFilter.SHOW_TEXT,
131
- {
132
- acceptNode: function (node) {
133
- // 跳过已渲染的节点
134
- if (
135
- node.parentNode &&
136
- (node.parentNode.classList?.contains('katex') ||
137
- node.parentNode.closest("pre, code, .katex"))
138
- ) {
139
- return NodeFilter.FILTER_REJECT;
140
- }
141
-
142
- const value = node.nodeValue || '';
143
- if (value.includes("$$") || value.includes("\\[")) {
144
- return NodeFilter.FILTER_ACCEPT;
145
- }
146
- return NodeFilter.FILTER_REJECT;
147
- },
148
- }
149
- );
150
-
151
- const nodesToReplace = [];
152
- while (walker.nextNode()) {
153
- nodesToReplace.push(walker.currentNode);
154
- }
155
-
156
- nodesToReplace.forEach((node) => {
157
- const text = node.nodeValue;
158
- const frag = document.createDocumentFragment();
159
- let lastIndex = 0;
160
-
161
- // 重置正则状态
162
- blockRegex.lastIndex = 0;
163
-
164
- const matches = [...text.matchAll(blockRegex)];
165
-
166
- matches.forEach((match) => {
167
- const fullMatch = match[0];
168
- const dollarContent = match[2];
169
- const bracketContent = match[3];
170
- const raw = (dollarContent || bracketContent).trim();
171
- const matchStart = match.index;
172
-
173
- // 添加匹配前的文本
174
- if (matchStart > lastIndex) {
175
- frag.appendChild(document.createTextNode(text.slice(lastIndex, matchStart)));
176
- }
177
-
178
- try {
238
+ getTextNodes(container, (value) => value.includes("$$") || value.includes("\\[")).forEach((node) => {
239
+ replaceTextNode(
240
+ node,
241
+ collectBlockMatches(node.nodeValue || ''),
242
+ (raw) => {
179
243
  const div = document.createElement("div");
180
244
  div.className = "katex-block";
181
245
  div.innerHTML = katex.renderToString(raw, {
@@ -184,23 +248,10 @@ function renderKatexDisplayBlock(container = document.body) {
184
248
  strict: false,
185
249
  trust: true
186
250
  });
187
- frag.appendChild(div);
188
- } catch (e) {
189
- frag.appendChild(document.createTextNode(fullMatch));
190
- console.error("Block render error:", raw, e.message);
191
- }
192
-
193
- lastIndex = matchStart + fullMatch.length;
194
- });
195
-
196
- // 添加剩余文本
197
- if (lastIndex < text.length) {
198
- frag.appendChild(document.createTextNode(text.slice(lastIndex)));
199
- }
200
-
201
- if (frag.hasChildNodes()) {
202
- node.parentNode.replaceChild(frag, node);
203
- }
251
+ return div;
252
+ },
253
+ "Block render error:"
254
+ );
204
255
  });
205
256
  }
206
257