tiptap-extension-code-block-shiki 1.0.0 → 1.2.0

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/README.md CHANGED
@@ -82,6 +82,26 @@ html.dark .tiptap .shiki span {
82
82
  }
83
83
  ```
84
84
 
85
+ ### Custom Theme Support
86
+
87
+ You can optionally supply your own custom themes
88
+
89
+ ```ts
90
+ import type { ThemeRegistration } from 'shiki'
91
+
92
+ const myTheme: ThemeRegistration = {
93
+ name: 'my-theme',
94
+ type: 'dark',
95
+ colors: { 'editor.background': '#1e1e2e' },
96
+ tokenColors: [/* ... */]
97
+ }
98
+
99
+ CodeBlockShiki.configure({
100
+ defaultTheme: 'my-theme',
101
+ customThemes: [myTheme],
102
+ })
103
+ ```
104
+
85
105
  ## Demo
86
106
 
87
107
  I posted a small screen recording here: https://mastodon.social/@timomeh/112282962825285237
@@ -102,6 +122,10 @@ Optionally specify themes for light and dark mode. See https://shiki.matsu.io/gu
102
122
 
103
123
  Which language to use, when no language was provided. See https://shiki.style/languages.
104
124
 
125
+ ### `customThemes`
126
+
127
+ Register custom themes. See https://shiki.style/guide/load-theme#load-custom-themes.
128
+
105
129
  ## Notes
106
130
 
107
131
  ### Lazy loading
@@ -2,6 +2,7 @@ import { BundledLanguage } from 'shiki';
2
2
  import { BundledTheme } from 'shiki';
3
3
  import { CodeBlockOptions } from '@tiptap/extension-code-block';
4
4
  import { Node as Node_2 } from '@tiptap/core';
5
+ import { ThemeRegistration } from 'shiki';
5
6
 
6
7
  declare const CodeBlockShiki: Node_2<CodeBlockShikiOptions, any>;
7
8
  export { CodeBlockShiki }
@@ -9,11 +10,12 @@ export default CodeBlockShiki;
9
10
 
10
11
  export declare interface CodeBlockShikiOptions extends CodeBlockOptions {
11
12
  defaultLanguage: BundledLanguage | null | undefined;
12
- defaultTheme: BundledTheme;
13
+ defaultTheme: BundledTheme | (string & {});
13
14
  themes: {
14
15
  light: BundledTheme;
15
16
  dark: BundledTheme;
16
17
  } | null | undefined;
18
+ customThemes: ThemeRegistration[] | null | undefined;
17
19
  }
18
20
 
19
21
  export { }
@@ -1,8 +1,8 @@
1
- import { Node as _, textblockTypeInputRule as N, mergeAttributes as R, findChildren as C } from "@tiptap/core";
2
- import { Plugin as I, PluginKey as $, TextSelection as M, Selection as W } from "@tiptap/pm/state";
3
- import { DecorationSet as F, Decoration as A } from "@tiptap/pm/view";
4
- import { bundledThemes as E, bundledLanguages as H, createHighlighter as j } from "shiki";
5
- var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create({
1
+ import { Node as O, textblockTypeInputRule as M, mergeAttributes as R, findChildren as L } from "@tiptap/core";
2
+ import { Plugin as z, PluginKey as I, TextSelection as $, Selection as j } from "@tiptap/pm/state";
3
+ import { DecorationSet as F, Decoration as P } from "@tiptap/pm/view";
4
+ import { bundledThemes as E, bundledLanguages as W, createHighlighter as K } from "shiki";
5
+ var A = 4, U = /^```([a-z]+)?[\s\n]$/, V = /^~~~([a-z]+)?[\s\n]$/, J = O.create({
6
6
  name: "codeBlock",
7
7
  addOptions() {
8
8
  return {
@@ -11,7 +11,7 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
11
11
  exitOnArrowDown: !0,
12
12
  defaultLanguage: null,
13
13
  enableTabIndentation: !1,
14
- tabSize: B,
14
+ tabSize: A,
15
15
  HTMLAttributes: {}
16
16
  };
17
17
  },
@@ -25,12 +25,12 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
25
25
  language: {
26
26
  default: this.options.defaultLanguage,
27
27
  parseHTML: (e) => {
28
- var r;
28
+ var t;
29
29
  const { languageClassPrefix: i } = this.options;
30
30
  if (!i)
31
31
  return null;
32
- const n = [...((r = e.firstElementChild) == null ? void 0 : r.classList) || []].filter((l) => l.startsWith(i)).map((l) => l.replace(i, ""))[0];
33
- return n || null;
32
+ const c = [...((t = e.firstElementChild) == null ? void 0 : t.classList) || []].filter((r) => r.startsWith(i)).map((r) => r.replace(i, ""))[0];
33
+ return c || null;
34
34
  },
35
35
  rendered: !1
36
36
  }
@@ -44,10 +44,10 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
44
44
  }
45
45
  ];
46
46
  },
47
- renderHTML({ node: e, HTMLAttributes: r }) {
47
+ renderHTML({ node: e, HTMLAttributes: t }) {
48
48
  return [
49
49
  "pre",
50
- R(this.options.HTMLAttributes, r),
50
+ R(this.options.HTMLAttributes, t),
51
51
  [
52
52
  "code",
53
53
  {
@@ -57,10 +57,28 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
57
57
  ]
58
58
  ];
59
59
  },
60
+ markdownTokenName: "code",
61
+ parseMarkdown: (e, t) => {
62
+ var i, o;
63
+ return ((i = e.raw) == null ? void 0 : i.startsWith("```")) === !1 && ((o = e.raw) == null ? void 0 : o.startsWith("~~~")) === !1 && e.codeBlockStyle !== "indented" ? [] : t.createNode(
64
+ "codeBlock",
65
+ { language: e.lang || null },
66
+ e.text ? [t.createTextNode(e.text)] : []
67
+ );
68
+ },
69
+ renderMarkdown: (e, t) => {
70
+ var i;
71
+ let o = "";
72
+ const n = ((i = e.attrs) == null ? void 0 : i.language) || "";
73
+ return e.content ? o = [`\`\`\`${n}`, t.renderChildren(e.content), "```"].join(`
74
+ `) : o = `\`\`\`${n}
75
+
76
+ \`\`\``, o;
77
+ },
60
78
  addCommands() {
61
79
  return {
62
- setCodeBlock: (e) => ({ commands: r }) => r.setNode(this.name, e),
63
- toggleCodeBlock: (e) => ({ commands: r }) => r.toggleNode(this.name, "paragraph", e)
80
+ setCodeBlock: (e) => ({ commands: t }) => t.setNode(this.name, e),
81
+ toggleCodeBlock: (e) => ({ commands: t }) => t.toggleNode(this.name, "paragraph", e)
64
82
  };
65
83
  },
66
84
  addKeyboardShortcuts() {
@@ -68,103 +86,103 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
68
86
  "Mod-Alt-c": () => this.editor.commands.toggleCodeBlock(),
69
87
  // remove code block when at start of document or code block is empty
70
88
  Backspace: () => {
71
- const { empty: e, $anchor: r } = this.editor.state.selection, i = r.pos === 1;
72
- return !e || r.parent.type.name !== this.name ? !1 : i || !r.parent.textContent.length ? this.editor.commands.clearNodes() : !1;
89
+ const { empty: e, $anchor: t } = this.editor.state.selection, i = t.pos === 1;
90
+ return !e || t.parent.type.name !== this.name ? !1 : i || !t.parent.textContent.length ? this.editor.commands.clearNodes() : !1;
73
91
  },
74
92
  // handle tab indentation
75
93
  Tab: ({ editor: e }) => {
76
- var r;
94
+ var t;
77
95
  if (!this.options.enableTabIndentation)
78
96
  return !1;
79
- const i = (r = this.options.tabSize) != null ? r : B, { state: t } = e, { selection: a } = t, { $from: n, empty: l } = a;
80
- if (n.parent.type !== this.type)
97
+ const i = (t = this.options.tabSize) != null ? t : A, { state: o } = e, { selection: n } = o, { $from: c, empty: r } = n;
98
+ if (c.parent.type !== this.type)
81
99
  return !1;
82
- const o = " ".repeat(i);
83
- return l ? e.commands.insertContent(o) : e.commands.command(({ tr: s }) => {
84
- const { from: d, to: g } = a, c = t.doc.textBetween(d, g, `
100
+ const s = " ".repeat(i);
101
+ return r ? e.commands.insertContent(s) : e.commands.command(({ tr: d }) => {
102
+ const { from: a, to: g } = n, l = o.doc.textBetween(a, g, `
85
103
  `, `
86
104
  `).split(`
87
- `).map((u) => o + u).join(`
105
+ `).map((h) => s + h).join(`
88
106
  `);
89
- return s.replaceWith(d, g, t.schema.text(c)), !0;
107
+ return d.replaceWith(a, g, o.schema.text(l)), !0;
90
108
  });
91
109
  },
92
110
  // handle shift+tab reverse indentation
93
111
  "Shift-Tab": ({ editor: e }) => {
94
- var r;
112
+ var t;
95
113
  if (!this.options.enableTabIndentation)
96
114
  return !1;
97
- const i = (r = this.options.tabSize) != null ? r : B, { state: t } = e, { selection: a } = t, { $from: n, empty: l } = a;
98
- return n.parent.type !== this.type ? !1 : l ? e.commands.command(({ tr: o }) => {
99
- var s;
100
- const { pos: d } = n, g = n.start(), y = n.end(), c = t.doc.textBetween(g, y, `
115
+ const i = (t = this.options.tabSize) != null ? t : A, { state: o } = e, { selection: n } = o, { $from: c, empty: r } = n;
116
+ return c.parent.type !== this.type ? !1 : r ? e.commands.command(({ tr: s }) => {
117
+ var d;
118
+ const { pos: a } = c, g = c.start(), y = c.end(), l = o.doc.textBetween(g, y, `
101
119
  `, `
102
120
  `).split(`
103
121
  `);
104
- let u = 0, h = 0;
105
- const f = d - g;
106
- for (let T = 0; T < c.length; T += 1) {
107
- if (h + c[T].length >= f) {
108
- u = T;
122
+ let h = 0, u = 0;
123
+ const p = a - g;
124
+ for (let T = 0; T < l.length; T += 1) {
125
+ if (u + l[T].length >= p) {
126
+ h = T;
109
127
  break;
110
128
  }
111
- h += c[T].length + 1;
129
+ u += l[T].length + 1;
112
130
  }
113
- const b = ((s = c[u].match(/^ */)) == null ? void 0 : s[0]) || "", x = Math.min(b.length, i);
131
+ const v = ((d = l[h].match(/^ */)) == null ? void 0 : d[0]) || "", x = Math.min(v.length, i);
114
132
  if (x === 0)
115
133
  return !0;
116
134
  let k = g;
117
- for (let T = 0; T < u; T += 1)
118
- k += c[T].length + 1;
119
- return o.delete(k, k + x), d - k <= x && o.setSelection(M.create(o.doc, k)), !0;
120
- }) : e.commands.command(({ tr: o }) => {
121
- const { from: s, to: d } = a, p = t.doc.textBetween(s, d, `
135
+ for (let T = 0; T < h; T += 1)
136
+ k += l[T].length + 1;
137
+ return s.delete(k, k + x), a - k <= x && s.setSelection($.create(s.doc, k)), !0;
138
+ }) : e.commands.command(({ tr: s }) => {
139
+ const { from: d, to: a } = n, m = o.doc.textBetween(d, a, `
122
140
  `, `
123
141
  `).split(`
124
- `).map((c) => {
125
- var u;
126
- const h = ((u = c.match(/^ */)) == null ? void 0 : u[0]) || "", f = Math.min(h.length, i);
127
- return c.slice(f);
142
+ `).map((l) => {
143
+ var h;
144
+ const u = ((h = l.match(/^ */)) == null ? void 0 : h[0]) || "", p = Math.min(u.length, i);
145
+ return l.slice(p);
128
146
  }).join(`
129
147
  `);
130
- return o.replaceWith(s, d, t.schema.text(p)), !0;
148
+ return s.replaceWith(d, a, o.schema.text(m)), !0;
131
149
  });
132
150
  },
133
151
  // exit node on triple enter
134
152
  Enter: ({ editor: e }) => {
135
153
  if (!this.options.exitOnTripleEnter)
136
154
  return !1;
137
- const { state: r } = e, { selection: i } = r, { $from: t, empty: a } = i;
138
- if (!a || t.parent.type !== this.type)
155
+ const { state: t } = e, { selection: i } = t, { $from: o, empty: n } = i;
156
+ if (!n || o.parent.type !== this.type)
139
157
  return !1;
140
- const n = t.parentOffset === t.parent.nodeSize - 2, l = t.parent.textContent.endsWith(`
158
+ const c = o.parentOffset === o.parent.nodeSize - 2, r = o.parent.textContent.endsWith(`
141
159
 
142
160
  `);
143
- return !n || !l ? !1 : e.chain().command(({ tr: o }) => (o.delete(t.pos - 2, t.pos), !0)).exitCode().run();
161
+ return !c || !r ? !1 : e.chain().command(({ tr: s }) => (s.delete(o.pos - 2, o.pos), !0)).exitCode().run();
144
162
  },
145
163
  // exit node on arrow down
146
164
  ArrowDown: ({ editor: e }) => {
147
165
  if (!this.options.exitOnArrowDown)
148
166
  return !1;
149
- const { state: r } = e, { selection: i, doc: t } = r, { $from: a, empty: n } = i;
150
- if (!n || a.parent.type !== this.type || !(a.parentOffset === a.parent.nodeSize - 2))
167
+ const { state: t } = e, { selection: i, doc: o } = t, { $from: n, empty: c } = i;
168
+ if (!c || n.parent.type !== this.type || !(n.parentOffset === n.parent.nodeSize - 2))
151
169
  return !1;
152
- const o = a.after();
153
- return o === void 0 ? !1 : t.nodeAt(o) ? e.commands.command(({ tr: d }) => (d.setSelection(W.near(t.resolve(o))), !0)) : e.commands.exitCode();
170
+ const s = n.after();
171
+ return s === void 0 ? !1 : o.nodeAt(s) ? e.commands.command(({ tr: a }) => (a.setSelection(j.near(o.resolve(s))), !0)) : e.commands.exitCode();
154
172
  }
155
173
  };
156
174
  },
157
175
  addInputRules() {
158
176
  return [
159
- N({
177
+ M({
160
178
  find: U,
161
179
  type: this.type,
162
180
  getAttributes: (e) => ({
163
181
  language: e[1]
164
182
  })
165
183
  }),
166
- N({
167
- find: K,
184
+ M({
185
+ find: V,
168
186
  type: this.type,
169
187
  getAttributes: (e) => ({
170
188
  language: e[1]
@@ -176,145 +194,159 @@ var B = 4, U = /^```([a-z]+)?[\s\n]$/, K = /^~~~([a-z]+)?[\s\n]$/, V = _.create(
176
194
  return [
177
195
  // this plugin creates a code block for pasted content from VS Code
178
196
  // we can also detect the copied code language
179
- new I({
180
- key: new $("codeBlockVSCodeHandler"),
197
+ new z({
198
+ key: new I("codeBlockVSCodeHandler"),
181
199
  props: {
182
- handlePaste: (e, r) => {
183
- if (!r.clipboardData || this.editor.isActive(this.type.name))
200
+ handlePaste: (e, t) => {
201
+ if (!t.clipboardData || this.editor.isActive(this.type.name))
184
202
  return !1;
185
- const i = r.clipboardData.getData("text/plain"), t = r.clipboardData.getData("vscode-editor-data"), a = t ? JSON.parse(t) : void 0, n = a?.mode;
186
- if (!i || !n)
203
+ const i = t.clipboardData.getData("text/plain"), o = t.clipboardData.getData("vscode-editor-data"), n = o ? JSON.parse(o) : void 0, c = n?.mode;
204
+ if (!i || !c)
187
205
  return !1;
188
- const { tr: l, schema: o } = e.state, s = o.text(i.replace(/\r\n?/g, `
206
+ const { tr: r, schema: s } = e.state, d = s.text(i.replace(/\r\n?/g, `
189
207
  `));
190
- return l.replaceSelectionWith(this.type.create({ language: n }, s)), l.selection.$from.parent.type !== this.type && l.setSelection(M.near(l.doc.resolve(Math.max(0, l.selection.from - 2)))), l.setMeta("paste", !0), e.dispatch(l), !0;
208
+ return r.replaceSelectionWith(this.type.create({ language: c }, d)), r.selection.$from.parent.type !== this.type && r.setSelection($.near(r.doc.resolve(Math.max(0, r.selection.from - 2)))), r.setMeta("paste", !0), e.dispatch(r), !0;
191
209
  }
192
210
  }
193
211
  })
194
212
  ];
195
213
  }
196
- }), J = V;
197
- let m, L;
198
- const w = /* @__PURE__ */ new Set(), D = /* @__PURE__ */ new Set();
199
- function Z() {
200
- return m;
201
- }
214
+ }), Z = J;
215
+ let f, w;
216
+ const N = /* @__PURE__ */ new Set(), D = /* @__PURE__ */ new Set(), B = /* @__PURE__ */ new Map();
202
217
  function q(e) {
203
- if (!m && !L) {
204
- const r = e.themes.filter(
205
- (t) => !!t && t in E
218
+ return e in E || B.has(e);
219
+ }
220
+ function G() {
221
+ return f;
222
+ }
223
+ function Q(e) {
224
+ if (!f && !w) {
225
+ if (e.customThemes)
226
+ for (const n of e.customThemes)
227
+ n.name && B.set(n.name, n);
228
+ const t = e.themes.filter(
229
+ (n) => !!n && n in E
206
230
  ), i = e.languages.filter(
207
- (t) => !!t && t in H
208
- );
209
- return L = j({ themes: r, langs: i }).then((t) => {
210
- m = t;
211
- }), L;
231
+ (n) => !!n && n in W
232
+ ), o = [...t, ...B.values()];
233
+ return w = K({ themes: o, langs: i }).then((n) => {
234
+ f = n;
235
+ }), w;
212
236
  }
213
- if (L)
214
- return L;
237
+ if (w)
238
+ return w;
215
239
  }
216
- async function P(e) {
217
- return m && !m.getLoadedThemes().includes(e) && !D.has(e) && e in E ? (D.add(e), await m.loadTheme(e), D.delete(e), !0) : !1;
240
+ async function b(e) {
241
+ if (f && !f.getLoadedThemes().includes(e) && !D.has(e) && q(e)) {
242
+ D.add(e);
243
+ const t = B.get(e) ?? e;
244
+ return await f.loadTheme(t), D.delete(e), !0;
245
+ }
246
+ return !1;
218
247
  }
219
- async function O(e) {
220
- return m && !m.getLoadedLanguages().includes(e) && !w.has(e) && e in H ? (w.add(e), await m.loadLanguage(e), w.delete(e), !0) : !1;
248
+ async function H(e) {
249
+ return f && !f.getLoadedLanguages().includes(e) && !N.has(e) && e in W ? (N.add(e), await f.loadLanguage(e), N.delete(e), !0) : !1;
221
250
  }
222
- async function G({
251
+ async function X({
223
252
  doc: e,
224
- name: r,
253
+ name: t,
225
254
  defaultTheme: i,
226
- defaultLanguage: t,
227
- themeModes: a
255
+ defaultLanguage: o,
256
+ themeModes: n,
257
+ customThemes: c
228
258
  }) {
229
- const n = C(e, (s) => s.type.name === r), l = [
230
- ...n.map((s) => s.node.attrs.theme),
259
+ const r = L(e, (a) => a.type.name === t), s = [
260
+ ...r.map((a) => a.node.attrs.theme),
231
261
  i
232
- ], o = [
233
- ...n.map((s) => s.node.attrs.language),
234
- t
262
+ ], d = [
263
+ ...r.map((a) => a.node.attrs.language),
264
+ o
235
265
  ];
236
- if (m)
266
+ if (f)
237
267
  await Promise.all([
238
- ...l.flatMap((s) => P(s)),
239
- ...o.flatMap((s) => !!s && O(s))
268
+ ...s.flatMap((a) => b(a)),
269
+ ...d.flatMap((a) => !!a && H(a))
240
270
  ]);
241
271
  else {
242
- const s = [...l];
243
- a && (a.light && !s.includes(a.light) && s.push(a.light), a.dark && !s.includes(a.dark) && s.push(a.dark)), await q({
244
- languages: o,
245
- themes: s
272
+ const a = [...s];
273
+ n && (n.light && !a.includes(n.light) && a.push(n.light), n.dark && !a.includes(n.dark) && a.push(n.dark)), await Q({
274
+ languages: d,
275
+ themes: a,
276
+ customThemes: c
246
277
  });
247
278
  }
248
279
  }
249
- function v(e) {
250
- return Object.entries(e).map(([r, i]) => `${r}:${i}`).join(";");
280
+ function C(e) {
281
+ return Object.entries(e).map(([t, i]) => `${t}:${i}`).join(";");
251
282
  }
252
- function z({
283
+ function _({
253
284
  doc: e,
254
- name: r,
285
+ name: t,
255
286
  defaultTheme: i,
256
- defaultLanguage: t,
257
- themes: a
287
+ defaultLanguage: o,
288
+ themes: n
258
289
  }) {
259
- const n = [];
260
- return C(e, (o) => o.type.name === r).forEach((o) => {
261
- let s = o.pos + 1, d = o.node.attrs.language || t;
262
- const g = o.node.attrs.theme || i, y = o.node.attrs.themes?.light || a?.light, p = o.node.attrs.themes?.dark || a?.dark, c = Z();
263
- if (!c) return;
264
- c.getLoadedLanguages().includes(d) || (d = "plaintext");
265
- const u = (f) => c.getLoadedThemes().includes(f) ? f : c.getLoadedThemes()[0];
266
- let h;
267
- if (a) {
268
- h = c.codeToTokens(o.node.textContent, {
269
- lang: d,
290
+ const c = [];
291
+ return L(e, (s) => s.type.name === t).forEach((s) => {
292
+ let d = s.pos + 1, a = s.node.attrs.language || o;
293
+ const g = s.node.attrs.theme || i, y = s.node.attrs.themes?.light || n?.light, m = s.node.attrs.themes?.dark || n?.dark, l = G();
294
+ if (!l) return;
295
+ l.getLoadedLanguages().includes(a) || (a = "plaintext");
296
+ const h = (p) => l.getLoadedThemes().includes(p) ? p : l.getLoadedThemes()[0];
297
+ let u;
298
+ if (n) {
299
+ u = l.codeToTokens(s.node.textContent, {
300
+ lang: a,
270
301
  themes: {
271
- light: u(y),
272
- dark: u(p)
302
+ light: h(y),
303
+ dark: h(m)
273
304
  }
274
305
  });
275
- const f = {};
276
- h.bg && (f["background-color"] = h.bg), h.fg && (f.color = h.fg), n.push(
277
- A.node(o.pos, o.pos + o.node.nodeSize, {
278
- style: v(f),
306
+ const p = {};
307
+ u.bg && (p["background-color"] = u.bg), u.fg && (p.color = u.fg), c.push(
308
+ P.node(s.pos, s.pos + s.node.nodeSize, {
309
+ style: C(p),
279
310
  class: "shiki"
280
311
  })
281
312
  );
282
313
  } else {
283
- h = c.codeToTokens(o.node.textContent, {
284
- lang: d,
285
- theme: u(g)
314
+ u = l.codeToTokens(s.node.textContent, {
315
+ lang: a,
316
+ theme: h(g)
286
317
  });
287
- const f = c.getLoadedThemes().includes(g) ? g : c.getLoadedThemes()[0], S = c.getTheme(f);
288
- n.push(
289
- A.node(o.pos, o.pos + o.node.nodeSize, {
290
- style: v({ "background-color": S.bg })
318
+ const p = l.getLoadedThemes().includes(g) ? g : l.getLoadedThemes()[0], S = l.getTheme(p);
319
+ c.push(
320
+ P.node(s.pos, s.pos + s.node.nodeSize, {
321
+ style: C({ "background-color": S.bg })
291
322
  })
292
323
  );
293
324
  }
294
- for (const f of h.tokens) {
295
- for (const S of f) {
296
- const b = s + S.content.length;
325
+ for (const p of u.tokens) {
326
+ for (const S of p) {
327
+ const v = d + S.content.length;
297
328
  let x = "";
298
- a ? x = v(S.htmlStyle || {}) : x = v({ color: S.color || "inherit" });
299
- const k = A.inline(s, b, {
329
+ n ? x = C(S.htmlStyle || {}) : x = C({ color: S.color || "inherit" });
330
+ const k = P.inline(d, v, {
300
331
  style: x
301
332
  });
302
- n.push(k), s = b;
333
+ c.push(k), d = v;
303
334
  }
304
- s += 1;
335
+ d += 1;
305
336
  }
306
- }), F.create(e, n);
337
+ }), F.create(e, c);
307
338
  }
308
- function Q({
339
+ function Y({
309
340
  name: e,
310
- defaultLanguage: r,
341
+ defaultLanguage: t,
311
342
  defaultTheme: i,
312
- themes: t
343
+ themes: o,
344
+ customThemes: n
313
345
  }) {
314
- const a = new I({
315
- key: new $("shiki"),
316
- view(n) {
317
- class l {
346
+ const c = new z({
347
+ key: new I("shiki"),
348
+ view(r) {
349
+ class s {
318
350
  constructor() {
319
351
  this.initDecorations();
320
352
  }
@@ -325,110 +357,130 @@ function Q({
325
357
  }
326
358
  // Initialize shiki async, and then highlight initial document
327
359
  async initDecorations() {
328
- const s = n.state.doc;
329
- await G({
330
- doc: s,
360
+ const a = r.state.doc;
361
+ await X({
362
+ doc: a,
331
363
  name: e,
332
- defaultLanguage: r,
364
+ defaultLanguage: t,
333
365
  defaultTheme: i,
334
- themeModes: t
366
+ themeModes: o,
367
+ customThemes: n ?? void 0
335
368
  });
336
- const d = n.state.tr.setMeta("shikiPluginForceDecoration", !0);
337
- n.dispatch(d);
369
+ const g = r.state.tr.setMeta("shikiPluginForceDecoration", !0);
370
+ r.dispatch(g);
338
371
  }
339
372
  // When new codeblocks were added and they have missing themes or
340
373
  // languages, load those and then add code decorations once again.
341
374
  async checkUndecoratedBlocks() {
342
- const s = C(
343
- n.state.doc,
344
- (p) => p.type.name === e
345
- ), d = (p) => {
346
- const c = [O(p.node.attrs.language)];
347
- return t ? (c.push(
348
- P(p.node.attrs.themes?.light || t.light)
349
- ), c.push(P(p.node.attrs.themes?.dark || t.dark))) : c.push(P(p.node.attrs.theme)), c;
375
+ const a = L(
376
+ r.state.doc,
377
+ (l) => l.type.name === e
378
+ ), g = (l) => {
379
+ const h = [H(l.node.attrs.language)];
380
+ return o ? (h.push(
381
+ b(l.node.attrs.themes?.light || o.light)
382
+ ), h.push(b(l.node.attrs.themes?.dark || o.dark))) : h.push(b(l.node.attrs.theme)), h;
350
383
  };
351
384
  if ((await Promise.all(
352
- s.flatMap((p) => d(p))
385
+ a.flatMap((l) => g(l))
353
386
  )).includes(!0)) {
354
- const p = n.state.tr.setMeta("shikiPluginForceDecoration", !0);
355
- n.dispatch(p);
387
+ const l = r.state.tr.setMeta("shikiPluginForceDecoration", !0);
388
+ r.dispatch(l);
356
389
  }
357
390
  }
358
391
  }
359
- return new l();
392
+ return new s();
360
393
  },
361
394
  state: {
362
- init: (n, { doc: l }) => z({
363
- doc: l,
395
+ init: (r, { doc: s }) => _({
396
+ doc: s,
364
397
  name: e,
365
- defaultLanguage: r,
398
+ defaultLanguage: t,
366
399
  defaultTheme: i,
367
- themes: t
400
+ themes: o
368
401
  }),
369
- apply: (n, l, o, s) => {
370
- const d = o.selection.$head.parent.type.name, g = s.selection.$head.parent.type.name, y = C(
371
- o.doc,
402
+ apply: (r, s, d, a) => {
403
+ const g = d.selection.$head.parent.type.name, y = a.selection.$head.parent.type.name, m = L(
404
+ d.doc,
372
405
  (u) => u.type.name === e
373
- ), p = C(
374
- s.doc,
406
+ ), l = L(
407
+ a.doc,
375
408
  (u) => u.type.name === e
376
- ), c = n.docChanged && // Apply decorations if:
409
+ ), h = r.docChanged && // Apply decorations if:
377
410
  // selection includes named node,
378
- ([d, g].includes(e) || // OR transaction adds/removes named node,
379
- p.length !== y.length || // OR transaction has changes that completely encapsulte a node
411
+ ([g, y].includes(e) || // OR transaction adds/removes named node,
412
+ l.length !== m.length || // OR transaction has changes that completely encapsulte a node
380
413
  // (for example, a transaction that affects the entire document).
381
414
  // Such transactions can happen during collab syncing via y-prosemirror, for example.
382
- n.steps.some((u) => (
415
+ r.steps.some((u) => (
383
416
  // @ts-expect-error
384
417
  u.from !== void 0 && // @ts-expect-error
385
- u.to !== void 0 && y.some((h) => (
418
+ u.to !== void 0 && m.some((p) => (
386
419
  // @ts-expect-error
387
- h.pos >= u.from && // @ts-expect-error
388
- h.pos + h.node.nodeSize <= u.to
420
+ p.pos >= u.from && // @ts-expect-error
421
+ p.pos + p.node.nodeSize <= u.to
389
422
  ))
390
423
  )));
391
- return n.getMeta("shikiPluginForceDecoration") || c ? z({
392
- doc: n.doc,
424
+ return r.getMeta("shikiPluginForceDecoration") || h ? _({
425
+ doc: r.doc,
393
426
  name: e,
394
- defaultLanguage: r,
427
+ defaultLanguage: t,
395
428
  defaultTheme: i,
396
- themes: t
397
- }) : l.map(n.mapping, n.doc);
429
+ themes: o
430
+ }) : s.map(r.mapping, r.doc);
398
431
  }
399
432
  },
400
433
  props: {
401
- decorations(n) {
402
- return a.getState(n);
434
+ decorations(r) {
435
+ return c.getState(r);
403
436
  }
404
437
  }
405
438
  });
406
- return a;
439
+ return c;
407
440
  }
408
441
  /* v8 ignore start -- @preserve */
409
442
  /* v8 ignore stop -- @preserve */
410
- const oe = J.extend({
443
+ const re = Z.extend({
411
444
  addOptions() {
412
445
  return {
413
446
  ...this.parent?.(),
414
447
  defaultLanguage: null,
415
448
  defaultTheme: "github-dark",
416
- themes: null
449
+ themes: null,
450
+ customThemes: null
417
451
  };
418
452
  },
453
+ markdownTokenName: "code",
454
+ parseMarkdown(e, t) {
455
+ return e.raw?.startsWith("```") === !1 && e.raw?.startsWith("~~~") === !1 && e.codeBlockStyle !== "indented" ? [] : t.createNode(
456
+ "codeBlock",
457
+ { language: e.lang || null },
458
+ e.text ? [t.createTextNode(e.text)] : []
459
+ );
460
+ },
461
+ renderMarkdown(e, t, i) {
462
+ const o = e.attrs?.language || "";
463
+ return e.content ? [`\`\`\`${o}`, t.renderChildren(e.content), "```"].join(
464
+ `
465
+ `
466
+ ) : `\`\`\`${o}
467
+
468
+ \`\`\``;
469
+ },
419
470
  addProseMirrorPlugins() {
420
471
  return [
421
472
  ...this.parent?.() || [],
422
- Q({
473
+ Y({
423
474
  name: this.name,
424
475
  defaultLanguage: this.options.defaultLanguage,
425
476
  defaultTheme: this.options.defaultTheme,
426
- themes: this.options.themes
477
+ themes: this.options.themes,
478
+ customThemes: this.options.customThemes
427
479
  })
428
480
  ];
429
481
  }
430
482
  });
431
483
  export {
432
- oe as CodeBlockShiki,
433
- oe as default
484
+ re as CodeBlockShiki,
485
+ re as default
434
486
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tiptap-extension-code-block-shiki",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Use Shiki syntax highlighting for codeblocks in TipTap.",
5
5
  "repository": "timomeh/tiptap-extension-code-block-shiki",
6
6
  "author": {
@@ -32,13 +32,14 @@
32
32
  "@biomejs/biome": "2.2.6",
33
33
  "@codecov/vite-plugin": "^1.9.1",
34
34
  "@microsoft/api-extractor": "^7.53.1",
35
- "@tiptap/core": "^3.6.6",
36
- "@tiptap/extension-code-block": "^3.6.6",
37
- "@tiptap/pm": "^3.6.6",
38
- "@tiptap/starter-kit": "^3.6.6",
35
+ "@tiptap/core": "^3.21.0",
36
+ "@tiptap/extension-code-block": "^3.21.0",
37
+ "@tiptap/markdown": "^3.21.0",
38
+ "@tiptap/pm": "^3.21.0",
39
+ "@tiptap/starter-kit": "^3.21.0",
39
40
  "@vitest/coverage-v8": "^3.2.4",
40
41
  "happy-dom": "^20.0.0",
41
- "shiki": "^3.13.0",
42
+ "shiki": "^4.0.0",
42
43
  "typescript": "^5.9.3",
43
44
  "unplugin-dts": "1.0.0-beta.0",
44
45
  "vite": "^7.1.9",
@@ -47,7 +48,7 @@
47
48
  "peerDependencies": {
48
49
  "@tiptap/core": "^2.3.0 || ^3.0.0",
49
50
  "@tiptap/pm": "^2.3.0 || ^3.0.0",
50
- "shiki": "^3.0.0"
51
+ "shiki": "^3.0.0 || ^4.0.0"
51
52
  },
52
53
  "keywords": [
53
54
  "tiptap",