tiptap-extension-shiki 1.0.2 → 1.0.3

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.
@@ -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>;
@@ -1 +1 @@
1
- .tiptap-shiki--container{border-radius:.4rem;font-size:.875em;pre{max-height:400px;overflow:auto;padding:20px}}.tiptap-shiki--toolbar{align-items:center;display:flex;padding:10px}
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:0}.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", "not-prose", "shiki", "dracula");
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
  },
@@ -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;pre{max-height:400px;overflow:auto;padding:20px}}.tiptap-shiki--toolbar{align-items:center;display:flex;padding:10px}
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:0}.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", "not-prose", "shiki", "dracula");
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.2",
3
+ "version": "1.0.3",
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
 
@@ -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,43 @@
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
+ }
17
33
 
18
- /* 嵌套样式:代码内容区域 */
19
- pre {
20
- /* 设置内边距,提供舒适的代码阅读空间 */
21
- padding: 20px;
22
-
23
- /* 设置最大高度,超出时显示滚动条 */
24
- max-height: 400px;
25
-
26
- /* 设置溢出内容为自动滚动 */
27
- overflow: auto;
28
- }
34
+ .tiptap-shiki--line-number {
35
+ display: inline-block;
36
+ box-sizing: border-box;
37
+ width: 5em;
38
+ padding-right: 2em;
39
+ text-align: right;
40
+ user-select: none;
29
41
  }
30
42
 
31
43
  /* 工具栏样式 */
32
44
  .tiptap-shiki--toolbar {
33
45
  /* 使用弹性布局排列工具栏元素 */
34
46
  display: flex;
35
-
47
+
36
48
  /* 垂直居中对齐工具栏内容 */
37
49
  align-items: center;
38
-
50
+
39
51
  /* 设置工具栏内边距 */
40
- padding: 10px;
52
+ padding: 0.75rem;
41
53
  }
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
- "tiptap-shiki--container",
244
- "not-prose",
245
- "shiki",
246
- "dracula"
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
  },