tiptap-extension-shiki 1.0.2 → 1.0.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.
- package/dist/ShikiLightPlugin.d.ts +2 -1
- package/dist/index.cjs.css +1 -1
- package/dist/index.cjs.js +57 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +57 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/rollup.config.js +4 -1
- package/src/ShikiLightPlugin.ts +57 -4
- package/src/index.css +37 -16
- package/src/index.ts +17 -7
|
@@ -11,9 +11,10 @@ import type { BundledLanguage, BundledTheme, HighlighterGeneric, SpecialLanguage
|
|
|
11
11
|
* @param defaultLanguage - 默认编程语言
|
|
12
12
|
* @returns ProseMirror插件实例
|
|
13
13
|
*/
|
|
14
|
-
export declare function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, }: {
|
|
14
|
+
export declare function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, showLineNumbers, }: {
|
|
15
15
|
name: string;
|
|
16
16
|
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
|
|
17
17
|
defaultTheme: ThemeRegistrationAny | StringLiteralUnion<string>;
|
|
18
18
|
defaultLanguage: StringLiteralUnion<SpecialLanguage>;
|
|
19
|
+
showLineNumbers?: boolean;
|
|
19
20
|
}): Plugin<any>;
|
package/dist/index.cjs.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.tiptap-shiki--container{border-radius:.4rem;font-size:.875em
|
|
1
|
+
.tiptap-shiki--container{border-radius:.4rem;font-size:.875em}.tiptap-shiki--container pre{max-height:400px;overflow:auto;padding:1.25rem}.tiptap-shiki--container.show-line-numbers pre{padding-left:5em}.tiptap-shiki--container.show-line-numbers span{word-break:break-all}.tiptap-shiki--container.show-line-numbers span.tiptap-shiki--line-number{margin-left:-5em;word-break:normal}.tiptap-shiki--line-number{box-sizing:border-box;display:inline-block;padding-right:2em;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:5em}.tiptap-shiki--toolbar{align-items:center;display:flex;padding:.75rem}
|
package/dist/index.cjs.js
CHANGED
|
@@ -18,13 +18,13 @@ var view = require('@tiptap/pm/view');
|
|
|
18
18
|
* @param defaultLanguage - 默认编程语言
|
|
19
19
|
* @returns 装饰器集合,用于渲染语法高亮
|
|
20
20
|
*/
|
|
21
|
-
function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage, }) {
|
|
21
|
+
function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage, showLineNumbers, }) {
|
|
22
22
|
// 查找文档中所有指定类型的节点(shiki代码块)
|
|
23
23
|
const decorations = core.findChildren(doc, (node) => {
|
|
24
24
|
return node.type.name === name;
|
|
25
25
|
}).reduce((acc, block) => {
|
|
26
26
|
// 为每个代码块生成装饰
|
|
27
|
-
const nodeDecorations = getSingleNodeDecorations(block.node, block.pos, highlighter, defaultTheme, defaultLanguage);
|
|
27
|
+
const nodeDecorations = getSingleNodeDecorations(block.node, block.pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers);
|
|
28
28
|
return acc.concat(nodeDecorations);
|
|
29
29
|
}, []);
|
|
30
30
|
// 创建装饰器集合
|
|
@@ -41,7 +41,7 @@ function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage,
|
|
|
41
41
|
* @param defaultLanguage - 默认语言
|
|
42
42
|
* @returns 该节点的所有装饰器数组
|
|
43
43
|
*/
|
|
44
|
-
function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage) {
|
|
44
|
+
function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers) {
|
|
45
45
|
const decorations = [];
|
|
46
46
|
// 获取代码块的语言和主题属性,如果没有则使用默认值
|
|
47
47
|
const language = node.attrs.language || defaultLanguage;
|
|
@@ -54,7 +54,45 @@ function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultL
|
|
|
54
54
|
theme: theme,
|
|
55
55
|
});
|
|
56
56
|
// 遍历每一行的token,为有颜色的token创建装饰
|
|
57
|
-
lines.forEach((line) => {
|
|
57
|
+
lines.forEach((line, index) => {
|
|
58
|
+
if (showLineNumbers) {
|
|
59
|
+
// === 新增:在每一行开头添加行号 Widget ===
|
|
60
|
+
decorations.push(view.Decoration.widget(startPos, () => {
|
|
61
|
+
// const lineNumber = index + 1;
|
|
62
|
+
const lineNumberElement = document.createElement("span");
|
|
63
|
+
lineNumberElement.className = "tiptap-shiki--line-number";
|
|
64
|
+
lineNumberElement.textContent = (index + 1).toString();
|
|
65
|
+
return lineNumberElement;
|
|
66
|
+
}, {
|
|
67
|
+
// 设置为负数(如 -1)表示该 Widget 倾向于“依附”在左侧。
|
|
68
|
+
// 当位置处于 0 长度的行首时,光标会落在 Widget 的右侧(即可以输入的位置)。
|
|
69
|
+
side: -1,
|
|
70
|
+
// 这能防止光标进入 Widget 内部,并阻止某些事件冒泡
|
|
71
|
+
stopEvent: () => true,
|
|
72
|
+
// 忽略选区
|
|
73
|
+
// 告诉 ProseMirror 在处理点击和选区时跳过这个元素
|
|
74
|
+
ignoreSelection: true,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
// const lineNumber = index + 1;
|
|
78
|
+
// decorations.push(
|
|
79
|
+
// Decoration.widget(
|
|
80
|
+
// startPos,
|
|
81
|
+
// () => {
|
|
82
|
+
// const dom = document.createElement("span");
|
|
83
|
+
// dom.className = "shiki-line-number";
|
|
84
|
+
// dom.innerText = `${lineNumber}`;
|
|
85
|
+
// dom.style.userSelect = "none";
|
|
86
|
+
// // 关键:设置不可选中,防止干扰复制粘贴
|
|
87
|
+
// dom.setAttribute("unselectable", "on");
|
|
88
|
+
// return dom;
|
|
89
|
+
// },
|
|
90
|
+
// {
|
|
91
|
+
// side: -1, // 确保它在当前位置的最左侧
|
|
92
|
+
// ignoreSelection: true,
|
|
93
|
+
// }
|
|
94
|
+
// )
|
|
95
|
+
// );
|
|
58
96
|
line.forEach((token) => {
|
|
59
97
|
const endPos = startPos + token.content.length;
|
|
60
98
|
// 如果token有颜色信息,创建内联装饰器设置文本颜色
|
|
@@ -82,7 +120,7 @@ function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultL
|
|
|
82
120
|
* @param defaultLanguage - 默认编程语言
|
|
83
121
|
* @returns ProseMirror插件实例
|
|
84
122
|
*/
|
|
85
|
-
function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, }) {
|
|
123
|
+
function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, showLineNumbers, }) {
|
|
86
124
|
const shikiLightPlugin = new state.Plugin({
|
|
87
125
|
key: new state.PluginKey("shiki"),
|
|
88
126
|
// 插件状态管理
|
|
@@ -98,6 +136,7 @@ function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, })
|
|
|
98
136
|
highlighter,
|
|
99
137
|
defaultTheme,
|
|
100
138
|
defaultLanguage,
|
|
139
|
+
showLineNumbers,
|
|
101
140
|
});
|
|
102
141
|
},
|
|
103
142
|
/**
|
|
@@ -130,7 +169,7 @@ function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, })
|
|
|
130
169
|
newDecorationSet = newDecorationSet.remove(oldDecos);
|
|
131
170
|
if (node.type.name === name) {
|
|
132
171
|
// 重新计算该节点的语法高亮装饰
|
|
133
|
-
const newSpecs = getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage);
|
|
172
|
+
const newSpecs = getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers);
|
|
134
173
|
newDecorationSet = newDecorationSet.add(newState.doc, newSpecs);
|
|
135
174
|
}
|
|
136
175
|
});
|
|
@@ -275,6 +314,9 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
275
314
|
container.innerHTML = highlightedCode;
|
|
276
315
|
if (container.firstElementChild) {
|
|
277
316
|
const rsDOM = container.firstElementChild;
|
|
317
|
+
Object.keys(HTMLAttributes).forEach((key) => {
|
|
318
|
+
rsDOM.setAttribute(key, HTMLAttributes[key]);
|
|
319
|
+
});
|
|
278
320
|
if (extraRenderHTMLAttributes) {
|
|
279
321
|
// 处理 classList
|
|
280
322
|
if (extraRenderHTMLAttributes.classList)
|
|
@@ -313,13 +355,20 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
313
355
|
if (!this.options.highlighter)
|
|
314
356
|
throw new Error("highlighter is required");
|
|
315
357
|
return (props) => {
|
|
358
|
+
var _a;
|
|
316
359
|
// 获取视图相关参数
|
|
317
360
|
const { view, getPos, node } = props;
|
|
318
361
|
// 获取当前主题配置
|
|
319
362
|
const theme = this.options.highlighter.getTheme(node.attrs.theme || this.options.defaultTheme);
|
|
320
363
|
// 创建主容器 DOM 元素
|
|
321
364
|
const dom = document.createElement("div");
|
|
322
|
-
dom.classList.add("tiptap-shiki--container", "
|
|
365
|
+
dom.classList.add("tiptap-shiki--container", "shiki");
|
|
366
|
+
if (this.options.showLineNumbers) {
|
|
367
|
+
dom.classList.add("show-line-numbers");
|
|
368
|
+
}
|
|
369
|
+
if ((_a = this.options.extraRenderHTMLAttributes) === null || _a === void 0 ? void 0 : _a.classList) {
|
|
370
|
+
dom.classList.add(...this.options.extraRenderHTMLAttributes.classList);
|
|
371
|
+
}
|
|
323
372
|
// 应用主题颜色
|
|
324
373
|
dom.style.backgroundColor = theme.bg;
|
|
325
374
|
dom.style.color = theme.fg;
|
|
@@ -423,6 +472,7 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
423
472
|
highlighter: this.options.highlighter,
|
|
424
473
|
defaultLanguage: this.options.defaultLanguage,
|
|
425
474
|
defaultTheme: this.options.defaultTheme,
|
|
475
|
+
showLineNumbers: this.options.showLineNumbers,
|
|
426
476
|
}),
|
|
427
477
|
];
|
|
428
478
|
},
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.tiptap-shiki--container{border-radius:.4rem;font-size:.875em
|
|
1
|
+
.tiptap-shiki--container{border-radius:.4rem;font-size:.875em}.tiptap-shiki--container pre{max-height:400px;overflow:auto;padding:1.25rem}.tiptap-shiki--container.show-line-numbers pre{padding-left:5em}.tiptap-shiki--container.show-line-numbers span{word-break:break-all}.tiptap-shiki--container.show-line-numbers span.tiptap-shiki--line-number{margin-left:-5em;word-break:normal}.tiptap-shiki--line-number{box-sizing:border-box;display:inline-block;padding-right:2em;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:5em}.tiptap-shiki--toolbar{align-items:center;display:flex;padding:.75rem}
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ declare const TiptapShiki: import("@tiptap/core").Node<{
|
|
|
30
30
|
styles?: Record<string, string>;
|
|
31
31
|
attrs?: Record<string, string>;
|
|
32
32
|
};
|
|
33
|
+
showLineNumbers?: boolean;
|
|
33
34
|
renderToolbar?: (props: {
|
|
34
35
|
toolbarDOM: HTMLElement;
|
|
35
36
|
language: StringLiteralUnion<SpecialLanguage>;
|
package/dist/index.js
CHANGED
|
@@ -14,13 +14,13 @@ import { DecorationSet, Decoration } from '@tiptap/pm/view';
|
|
|
14
14
|
* @param defaultLanguage - 默认编程语言
|
|
15
15
|
* @returns 装饰器集合,用于渲染语法高亮
|
|
16
16
|
*/
|
|
17
|
-
function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage, }) {
|
|
17
|
+
function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage, showLineNumbers, }) {
|
|
18
18
|
// 查找文档中所有指定类型的节点(shiki代码块)
|
|
19
19
|
const decorations = findChildren(doc, (node) => {
|
|
20
20
|
return node.type.name === name;
|
|
21
21
|
}).reduce((acc, block) => {
|
|
22
22
|
// 为每个代码块生成装饰
|
|
23
|
-
const nodeDecorations = getSingleNodeDecorations(block.node, block.pos, highlighter, defaultTheme, defaultLanguage);
|
|
23
|
+
const nodeDecorations = getSingleNodeDecorations(block.node, block.pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers);
|
|
24
24
|
return acc.concat(nodeDecorations);
|
|
25
25
|
}, []);
|
|
26
26
|
// 创建装饰器集合
|
|
@@ -37,7 +37,7 @@ function getDecorations({ doc, name, highlighter, defaultTheme, defaultLanguage,
|
|
|
37
37
|
* @param defaultLanguage - 默认语言
|
|
38
38
|
* @returns 该节点的所有装饰器数组
|
|
39
39
|
*/
|
|
40
|
-
function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage) {
|
|
40
|
+
function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers) {
|
|
41
41
|
const decorations = [];
|
|
42
42
|
// 获取代码块的语言和主题属性,如果没有则使用默认值
|
|
43
43
|
const language = node.attrs.language || defaultLanguage;
|
|
@@ -50,7 +50,45 @@ function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultL
|
|
|
50
50
|
theme: theme,
|
|
51
51
|
});
|
|
52
52
|
// 遍历每一行的token,为有颜色的token创建装饰
|
|
53
|
-
lines.forEach((line) => {
|
|
53
|
+
lines.forEach((line, index) => {
|
|
54
|
+
if (showLineNumbers) {
|
|
55
|
+
// === 新增:在每一行开头添加行号 Widget ===
|
|
56
|
+
decorations.push(Decoration.widget(startPos, () => {
|
|
57
|
+
// const lineNumber = index + 1;
|
|
58
|
+
const lineNumberElement = document.createElement("span");
|
|
59
|
+
lineNumberElement.className = "tiptap-shiki--line-number";
|
|
60
|
+
lineNumberElement.textContent = (index + 1).toString();
|
|
61
|
+
return lineNumberElement;
|
|
62
|
+
}, {
|
|
63
|
+
// 设置为负数(如 -1)表示该 Widget 倾向于“依附”在左侧。
|
|
64
|
+
// 当位置处于 0 长度的行首时,光标会落在 Widget 的右侧(即可以输入的位置)。
|
|
65
|
+
side: -1,
|
|
66
|
+
// 这能防止光标进入 Widget 内部,并阻止某些事件冒泡
|
|
67
|
+
stopEvent: () => true,
|
|
68
|
+
// 忽略选区
|
|
69
|
+
// 告诉 ProseMirror 在处理点击和选区时跳过这个元素
|
|
70
|
+
ignoreSelection: true,
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
// const lineNumber = index + 1;
|
|
74
|
+
// decorations.push(
|
|
75
|
+
// Decoration.widget(
|
|
76
|
+
// startPos,
|
|
77
|
+
// () => {
|
|
78
|
+
// const dom = document.createElement("span");
|
|
79
|
+
// dom.className = "shiki-line-number";
|
|
80
|
+
// dom.innerText = `${lineNumber}`;
|
|
81
|
+
// dom.style.userSelect = "none";
|
|
82
|
+
// // 关键:设置不可选中,防止干扰复制粘贴
|
|
83
|
+
// dom.setAttribute("unselectable", "on");
|
|
84
|
+
// return dom;
|
|
85
|
+
// },
|
|
86
|
+
// {
|
|
87
|
+
// side: -1, // 确保它在当前位置的最左侧
|
|
88
|
+
// ignoreSelection: true,
|
|
89
|
+
// }
|
|
90
|
+
// )
|
|
91
|
+
// );
|
|
54
92
|
line.forEach((token) => {
|
|
55
93
|
const endPos = startPos + token.content.length;
|
|
56
94
|
// 如果token有颜色信息,创建内联装饰器设置文本颜色
|
|
@@ -78,7 +116,7 @@ function getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultL
|
|
|
78
116
|
* @param defaultLanguage - 默认编程语言
|
|
79
117
|
* @returns ProseMirror插件实例
|
|
80
118
|
*/
|
|
81
|
-
function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, }) {
|
|
119
|
+
function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, showLineNumbers, }) {
|
|
82
120
|
const shikiLightPlugin = new Plugin({
|
|
83
121
|
key: new PluginKey("shiki"),
|
|
84
122
|
// 插件状态管理
|
|
@@ -94,6 +132,7 @@ function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, })
|
|
|
94
132
|
highlighter,
|
|
95
133
|
defaultTheme,
|
|
96
134
|
defaultLanguage,
|
|
135
|
+
showLineNumbers,
|
|
97
136
|
});
|
|
98
137
|
},
|
|
99
138
|
/**
|
|
@@ -126,7 +165,7 @@ function ShikiLightPlugin({ name, highlighter, defaultTheme, defaultLanguage, })
|
|
|
126
165
|
newDecorationSet = newDecorationSet.remove(oldDecos);
|
|
127
166
|
if (node.type.name === name) {
|
|
128
167
|
// 重新计算该节点的语法高亮装饰
|
|
129
|
-
const newSpecs = getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage);
|
|
168
|
+
const newSpecs = getSingleNodeDecorations(node, pos, highlighter, defaultTheme, defaultLanguage, showLineNumbers);
|
|
130
169
|
newDecorationSet = newDecorationSet.add(newState.doc, newSpecs);
|
|
131
170
|
}
|
|
132
171
|
});
|
|
@@ -271,6 +310,9 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
271
310
|
container.innerHTML = highlightedCode;
|
|
272
311
|
if (container.firstElementChild) {
|
|
273
312
|
const rsDOM = container.firstElementChild;
|
|
313
|
+
Object.keys(HTMLAttributes).forEach((key) => {
|
|
314
|
+
rsDOM.setAttribute(key, HTMLAttributes[key]);
|
|
315
|
+
});
|
|
274
316
|
if (extraRenderHTMLAttributes) {
|
|
275
317
|
// 处理 classList
|
|
276
318
|
if (extraRenderHTMLAttributes.classList)
|
|
@@ -309,13 +351,20 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
309
351
|
if (!this.options.highlighter)
|
|
310
352
|
throw new Error("highlighter is required");
|
|
311
353
|
return (props) => {
|
|
354
|
+
var _a;
|
|
312
355
|
// 获取视图相关参数
|
|
313
356
|
const { view, getPos, node } = props;
|
|
314
357
|
// 获取当前主题配置
|
|
315
358
|
const theme = this.options.highlighter.getTheme(node.attrs.theme || this.options.defaultTheme);
|
|
316
359
|
// 创建主容器 DOM 元素
|
|
317
360
|
const dom = document.createElement("div");
|
|
318
|
-
dom.classList.add("tiptap-shiki--container", "
|
|
361
|
+
dom.classList.add("tiptap-shiki--container", "shiki");
|
|
362
|
+
if (this.options.showLineNumbers) {
|
|
363
|
+
dom.classList.add("show-line-numbers");
|
|
364
|
+
}
|
|
365
|
+
if ((_a = this.options.extraRenderHTMLAttributes) === null || _a === void 0 ? void 0 : _a.classList) {
|
|
366
|
+
dom.classList.add(...this.options.extraRenderHTMLAttributes.classList);
|
|
367
|
+
}
|
|
319
368
|
// 应用主题颜色
|
|
320
369
|
dom.style.backgroundColor = theme.bg;
|
|
321
370
|
dom.style.color = theme.fg;
|
|
@@ -419,6 +468,7 @@ const TiptapShiki = CodeBlock.extend({
|
|
|
419
468
|
highlighter: this.options.highlighter,
|
|
420
469
|
defaultLanguage: this.options.defaultLanguage,
|
|
421
470
|
defaultTheme: this.options.defaultTheme,
|
|
471
|
+
showLineNumbers: this.options.showLineNumbers,
|
|
422
472
|
}),
|
|
423
473
|
];
|
|
424
474
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tiptap-extension-shiki",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@tiptap/core": "^2.3.0 || ^3.0.0",
|
|
42
|
+
"@tiptap/extension-code-block": "^2.3.0 || ^3.14.0",
|
|
42
43
|
"@tiptap/pm": "^2.3.0 || ^3.0.0",
|
|
43
44
|
"shiki": "^3.0.0"
|
|
44
45
|
}
|
package/rollup.config.js
CHANGED
|
@@ -8,6 +8,7 @@ const typescript = require("rollup-plugin-typescript2");
|
|
|
8
8
|
const postcss = require("rollup-plugin-postcss");
|
|
9
9
|
const cssnano = require("cssnano");
|
|
10
10
|
const autoprefixer = require("autoprefixer");
|
|
11
|
+
const pkg = require("./package.json");
|
|
11
12
|
|
|
12
13
|
const config = {
|
|
13
14
|
input: "src/index.ts",
|
|
@@ -26,7 +27,7 @@ const config = {
|
|
|
26
27
|
},
|
|
27
28
|
],
|
|
28
29
|
plugins: [
|
|
29
|
-
autoExternal({
|
|
30
|
+
autoExternal({
|
|
30
31
|
packagePath: "./package.json",
|
|
31
32
|
}),
|
|
32
33
|
postcss({
|
|
@@ -65,6 +66,8 @@ const config = {
|
|
|
65
66
|
"@tiptap/extension-code-block",
|
|
66
67
|
// Shiki依赖
|
|
67
68
|
"shiki",
|
|
69
|
+
...Object.keys(pkg.devDependencies || {}),
|
|
70
|
+
...Object.keys(pkg.dependencies || {}),
|
|
68
71
|
],
|
|
69
72
|
};
|
|
70
73
|
|
package/src/ShikiLightPlugin.ts
CHANGED
|
@@ -28,12 +28,14 @@ function getDecorations({
|
|
|
28
28
|
highlighter,
|
|
29
29
|
defaultTheme,
|
|
30
30
|
defaultLanguage,
|
|
31
|
+
showLineNumbers,
|
|
31
32
|
}: {
|
|
32
33
|
doc: ProsemirrorNode;
|
|
33
34
|
name: string;
|
|
34
35
|
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
|
|
35
36
|
defaultTheme: ThemeRegistrationAny | StringLiteralUnion<string>;
|
|
36
37
|
defaultLanguage: StringLiteralUnion<SpecialLanguage>;
|
|
38
|
+
showLineNumbers?: boolean;
|
|
37
39
|
}) {
|
|
38
40
|
// 查找文档中所有指定类型的节点(shiki代码块)
|
|
39
41
|
const decorations = findChildren(doc, (node) => {
|
|
@@ -45,7 +47,8 @@ function getDecorations({
|
|
|
45
47
|
block.pos,
|
|
46
48
|
highlighter,
|
|
47
49
|
defaultTheme,
|
|
48
|
-
defaultLanguage
|
|
50
|
+
defaultLanguage,
|
|
51
|
+
showLineNumbers
|
|
49
52
|
);
|
|
50
53
|
return acc.concat(nodeDecorations);
|
|
51
54
|
}, [] as Decoration[]);
|
|
@@ -70,7 +73,8 @@ function getSingleNodeDecorations(
|
|
|
70
73
|
pos: number,
|
|
71
74
|
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>,
|
|
72
75
|
defaultTheme: ThemeRegistrationAny | StringLiteralUnion<string>,
|
|
73
|
-
defaultLanguage: StringLiteralUnion<SpecialLanguage
|
|
76
|
+
defaultLanguage: StringLiteralUnion<SpecialLanguage>,
|
|
77
|
+
showLineNumbers?: boolean
|
|
74
78
|
) {
|
|
75
79
|
const decorations: Decoration[] = [];
|
|
76
80
|
|
|
@@ -88,7 +92,52 @@ function getSingleNodeDecorations(
|
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
// 遍历每一行的token,为有颜色的token创建装饰
|
|
91
|
-
lines.forEach((line) => {
|
|
95
|
+
lines.forEach((line, index) => {
|
|
96
|
+
if (showLineNumbers) {
|
|
97
|
+
// === 新增:在每一行开头添加行号 Widget ===
|
|
98
|
+
decorations.push(
|
|
99
|
+
Decoration.widget(
|
|
100
|
+
startPos,
|
|
101
|
+
() => {
|
|
102
|
+
// const lineNumber = index + 1;
|
|
103
|
+
const lineNumberElement = document.createElement("span");
|
|
104
|
+
lineNumberElement.className = "tiptap-shiki--line-number";
|
|
105
|
+
lineNumberElement.textContent = (index + 1).toString();
|
|
106
|
+
return lineNumberElement;
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
// 设置为负数(如 -1)表示该 Widget 倾向于“依附”在左侧。
|
|
110
|
+
// 当位置处于 0 长度的行首时,光标会落在 Widget 的右侧(即可以输入的位置)。
|
|
111
|
+
side: -1,
|
|
112
|
+
// 这能防止光标进入 Widget 内部,并阻止某些事件冒泡
|
|
113
|
+
stopEvent: () => true,
|
|
114
|
+
// 忽略选区
|
|
115
|
+
// 告诉 ProseMirror 在处理点击和选区时跳过这个元素
|
|
116
|
+
ignoreSelection: true,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// const lineNumber = index + 1;
|
|
123
|
+
// decorations.push(
|
|
124
|
+
// Decoration.widget(
|
|
125
|
+
// startPos,
|
|
126
|
+
// () => {
|
|
127
|
+
// const dom = document.createElement("span");
|
|
128
|
+
// dom.className = "shiki-line-number";
|
|
129
|
+
// dom.innerText = `${lineNumber}`;
|
|
130
|
+
// dom.style.userSelect = "none";
|
|
131
|
+
// // 关键:设置不可选中,防止干扰复制粘贴
|
|
132
|
+
// dom.setAttribute("unselectable", "on");
|
|
133
|
+
// return dom;
|
|
134
|
+
// },
|
|
135
|
+
// {
|
|
136
|
+
// side: -1, // 确保它在当前位置的最左侧
|
|
137
|
+
// ignoreSelection: true,
|
|
138
|
+
// }
|
|
139
|
+
// )
|
|
140
|
+
// );
|
|
92
141
|
line.forEach((token) => {
|
|
93
142
|
const endPos = startPos + token.content.length;
|
|
94
143
|
|
|
@@ -128,11 +177,13 @@ export function ShikiLightPlugin({
|
|
|
128
177
|
highlighter,
|
|
129
178
|
defaultTheme,
|
|
130
179
|
defaultLanguage,
|
|
180
|
+
showLineNumbers,
|
|
131
181
|
}: {
|
|
132
182
|
name: string;
|
|
133
183
|
highlighter: HighlighterGeneric<BundledLanguage, BundledTheme>;
|
|
134
184
|
defaultTheme: ThemeRegistrationAny | StringLiteralUnion<string>;
|
|
135
185
|
defaultLanguage: StringLiteralUnion<SpecialLanguage>;
|
|
186
|
+
showLineNumbers?: boolean;
|
|
136
187
|
}) {
|
|
137
188
|
const shikiLightPlugin: Plugin = new Plugin({
|
|
138
189
|
key: new PluginKey("shiki"),
|
|
@@ -150,6 +201,7 @@ export function ShikiLightPlugin({
|
|
|
150
201
|
highlighter,
|
|
151
202
|
defaultTheme,
|
|
152
203
|
defaultLanguage,
|
|
204
|
+
showLineNumbers,
|
|
153
205
|
});
|
|
154
206
|
},
|
|
155
207
|
|
|
@@ -194,7 +246,8 @@ export function ShikiLightPlugin({
|
|
|
194
246
|
pos,
|
|
195
247
|
highlighter,
|
|
196
248
|
defaultTheme,
|
|
197
|
-
defaultLanguage
|
|
249
|
+
defaultLanguage,
|
|
250
|
+
showLineNumbers
|
|
198
251
|
);
|
|
199
252
|
newDecorationSet = newDecorationSet.add(newState.doc, newSpecs);
|
|
200
253
|
}
|
package/src/index.css
CHANGED
|
@@ -11,31 +11,52 @@
|
|
|
11
11
|
.tiptap-shiki--container {
|
|
12
12
|
/* 设置圆角边框 */
|
|
13
13
|
border-radius: 0.4rem;
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
/* 设置相对较小的字体大小 */
|
|
16
16
|
font-size: 0.875em;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tiptap-shiki--container pre {
|
|
20
|
+
/* 设置内边距,提供舒适的代码阅读空间 */
|
|
21
|
+
padding: 1.25rem;
|
|
22
|
+
|
|
23
|
+
/* 设置最大高度,超出时显示滚动条 */
|
|
24
|
+
max-height: 400px;
|
|
25
|
+
|
|
26
|
+
/* 设置溢出内容为自动滚动 */
|
|
27
|
+
overflow: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.tiptap-shiki--container.show-line-numbers pre {
|
|
31
|
+
/* padding-left: 0; */
|
|
32
|
+
padding-left: 5em;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tiptap-shiki--container.show-line-numbers span {
|
|
36
|
+
word-break: break-all;
|
|
37
|
+
}
|
|
38
|
+
.tiptap-shiki--container.show-line-numbers span.tiptap-shiki--line-number {
|
|
39
|
+
margin-left: -5em;
|
|
40
|
+
word-break: normal;
|
|
41
|
+
}
|
|
17
42
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/* 设置溢出内容为自动滚动 */
|
|
27
|
-
overflow: auto;
|
|
28
|
-
}
|
|
43
|
+
.tiptap-shiki--line-number {
|
|
44
|
+
display: inline-block;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
width: 5em;
|
|
47
|
+
padding-right: 2em;
|
|
48
|
+
text-align: right;
|
|
49
|
+
user-select: none;
|
|
29
50
|
}
|
|
30
51
|
|
|
31
52
|
/* 工具栏样式 */
|
|
32
53
|
.tiptap-shiki--toolbar {
|
|
33
54
|
/* 使用弹性布局排列工具栏元素 */
|
|
34
55
|
display: flex;
|
|
35
|
-
|
|
56
|
+
|
|
36
57
|
/* 垂直居中对齐工具栏内容 */
|
|
37
58
|
align-items: center;
|
|
38
|
-
|
|
59
|
+
|
|
39
60
|
/* 设置工具栏内边距 */
|
|
40
|
-
padding:
|
|
41
|
-
}
|
|
61
|
+
padding: 0.75rem;
|
|
62
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import type {
|
|
|
27
27
|
|
|
28
28
|
// 导入 Shiki 轻量级插件
|
|
29
29
|
import { ShikiLightPlugin } from "./ShikiLightPlugin.js";
|
|
30
|
-
import { mergeAttributes } from "@tiptap/core";
|
|
31
30
|
|
|
32
31
|
/**
|
|
33
32
|
* TiptapShiki 扩展类定义
|
|
@@ -54,6 +53,8 @@ const TiptapShiki = CodeBlock.extend<
|
|
|
54
53
|
styles?: Record<string, string>;
|
|
55
54
|
attrs?: Record<string, string>;
|
|
56
55
|
};
|
|
56
|
+
// 显示行号
|
|
57
|
+
showLineNumbers?: boolean;
|
|
57
58
|
// 自定义工具栏渲染函数
|
|
58
59
|
renderToolbar?: (props: {
|
|
59
60
|
toolbarDOM: HTMLElement;
|
|
@@ -187,6 +188,10 @@ const TiptapShiki = CodeBlock.extend<
|
|
|
187
188
|
container.innerHTML = highlightedCode;
|
|
188
189
|
if (container.firstElementChild) {
|
|
189
190
|
const rsDOM = container.firstElementChild as HTMLElement;
|
|
191
|
+
Object.keys(HTMLAttributes).forEach((key) => {
|
|
192
|
+
rsDOM.setAttribute(key, HTMLAttributes[key]);
|
|
193
|
+
});
|
|
194
|
+
|
|
190
195
|
if (extraRenderHTMLAttributes) {
|
|
191
196
|
// 处理 classList
|
|
192
197
|
if (extraRenderHTMLAttributes.classList)
|
|
@@ -239,12 +244,16 @@ const TiptapShiki = CodeBlock.extend<
|
|
|
239
244
|
|
|
240
245
|
// 创建主容器 DOM 元素
|
|
241
246
|
const dom = document.createElement("div");
|
|
242
|
-
dom.classList.add(
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
"
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
dom.classList.add("tiptap-shiki--container", "shiki");
|
|
248
|
+
|
|
249
|
+
if (this.options.showLineNumbers) {
|
|
250
|
+
dom.classList.add("show-line-numbers");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (this.options.extraRenderHTMLAttributes?.classList) {
|
|
254
|
+
dom.classList.add(...this.options.extraRenderHTMLAttributes.classList);
|
|
255
|
+
}
|
|
256
|
+
|
|
248
257
|
// 应用主题颜色
|
|
249
258
|
dom.style.backgroundColor = theme.bg;
|
|
250
259
|
dom.style.color = theme.fg;
|
|
@@ -360,6 +369,7 @@ const TiptapShiki = CodeBlock.extend<
|
|
|
360
369
|
highlighter: this.options.highlighter,
|
|
361
370
|
defaultLanguage: this.options.defaultLanguage,
|
|
362
371
|
defaultTheme: this.options.defaultTheme,
|
|
372
|
+
showLineNumbers: this.options.showLineNumbers,
|
|
363
373
|
}),
|
|
364
374
|
];
|
|
365
375
|
},
|