@tiptap/extension-mathematics 2.22.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 ADDED
@@ -0,0 +1,14 @@
1
+ # @tiptap/extension-mathematics
2
+ [![Version](https://img.shields.io/npm/v/@tiptap/extension-mathematics.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-mathematics)
3
+ [![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-mathematics.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
4
+ [![License](https://img.shields.io/npm/l/@tiptap/extension-mathematics.svg)](https://www.npmjs.com/package/@tiptap/extension-mathematics)
5
+ [![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
6
+
7
+ ## Introduction
8
+ Tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
9
+
10
+ ## Official Documentation
11
+ Documentation can be found on the [Tiptap website](https://tiptap.dev).
12
+
13
+ ## License
14
+ Tiptap is open sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap/blob/main/LICENSE.md).
@@ -0,0 +1,13 @@
1
+ import { Plugin } from '@tiptap/pm/state';
2
+ import { DecorationSet } from '@tiptap/pm/view';
3
+ import { MathematicsOptionsWithEditor } from './types.js';
4
+ type PluginState = {
5
+ decorations: DecorationSet;
6
+ isEditable: boolean;
7
+ } | {
8
+ decorations: undefined;
9
+ isEditable: undefined;
10
+ };
11
+ export declare const MathematicsPlugin: (options: MathematicsOptionsWithEditor) => Plugin<PluginState>;
12
+ export {};
13
+ //# sourceMappingURL=MathematicsPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MathematicsPlugin.d.ts","sourceRoot":"","sources":["../src/MathematicsPlugin.ts"],"names":[],"mappings":"AACA,OAAO,EACQ,MAAM,EACpB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAc,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAG3D,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAA;AAWzD,KAAK,WAAW,GACd;IAAE,WAAW,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GACnD;IAAE,WAAW,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,SAAS,CAAA;CAAE,CAAA;AA8CnD,eAAO,MAAM,iBAAiB,YAAa,4BAA4B,wBA6ItE,CAAA"}
package/dist/index.cjs ADDED
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var core = require('@tiptap/core');
6
+ var state = require('@tiptap/pm/state');
7
+ var view = require('@tiptap/pm/view');
8
+ var katex = require('katex');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var katex__default = /*#__PURE__*/_interopDefaultCompat(katex);
13
+
14
+ /**
15
+ * Get the range of positions that have been affected by a transaction
16
+ */
17
+ function getAffectedRange(newState, previousPluginState, isEditable, tr, state) {
18
+ const docSize = newState.doc.nodeSize - 2;
19
+ let minFrom = 0;
20
+ let maxTo = docSize;
21
+ if (previousPluginState.isEditable !== isEditable) {
22
+ // When the editable state changes, run on all nodes just to be safe
23
+ minFrom = 0;
24
+ maxTo = docSize;
25
+ }
26
+ else if (tr.docChanged) {
27
+ // When the document changes, only run on the nodes that have changed
28
+ minFrom = docSize;
29
+ maxTo = 0;
30
+ core.getChangedRanges(tr).forEach(range => {
31
+ // Purposefully over scan the range to ensure we catch all decorations
32
+ minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1);
33
+ maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1);
34
+ });
35
+ }
36
+ else if (tr.selectionSet) {
37
+ const { $from, $to } = state.selection;
38
+ const { $from: $newFrom, $to: $newTo } = newState.selection;
39
+ // When the selection changes, run on all the nodes between the old and new selection
40
+ minFrom = Math.min(
41
+ // Purposefully over scan the range to ensure we catch all decorations
42
+ $from.depth === 0 ? 0 : $from.before(), $newFrom.depth === 0 ? 0 : $newFrom.before());
43
+ maxTo = Math.max($to.depth === 0 ? maxTo : $to.after(), $newTo.depth === 0 ? maxTo : $newTo.after());
44
+ }
45
+ return {
46
+ minFrom: Math.max(minFrom, 0),
47
+ maxTo: Math.min(maxTo, docSize),
48
+ };
49
+ }
50
+ const MathematicsPlugin = (options) => {
51
+ const { regex, katexOptions = {}, editor, shouldRender, } = options;
52
+ return new state.Plugin({
53
+ key: new state.PluginKey('mathematics'),
54
+ state: {
55
+ init() {
56
+ return { decorations: undefined, isEditable: undefined };
57
+ },
58
+ apply(tr, previousPluginState, state, newState) {
59
+ if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {
60
+ // Just reuse the existing decorations, since nothing should have changed
61
+ return previousPluginState;
62
+ }
63
+ const nextDecorationSet = (previousPluginState.decorations || view.DecorationSet.empty).map(tr.mapping, tr.doc);
64
+ const { selection } = newState;
65
+ const isEditable = editor.isEditable;
66
+ const decorationsToAdd = [];
67
+ const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state);
68
+ newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {
69
+ const enabled = shouldRender(newState, pos, node);
70
+ if (node.isText && node.text && enabled) {
71
+ let match;
72
+ // eslint-disable-next-line no-cond-assign
73
+ while ((match = regex.exec(node.text))) {
74
+ const from = pos + match.index;
75
+ const to = from + match[0].length;
76
+ const content = match.slice(1).find(Boolean);
77
+ if (content) {
78
+ const selectionSize = selection.from - selection.to;
79
+ const anchorIsInside = selection.anchor >= from && selection.anchor <= to;
80
+ const rangeIsInside = selection.from >= from && selection.to <= to;
81
+ const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside;
82
+ if (
83
+ // Are the decorations already present?
84
+ nextDecorationSet.find(from, to, (deco) => isEditing === deco.isEditing
85
+ && content === deco.content
86
+ && isEditable === deco.isEditable
87
+ && katexOptions === deco.katexOptions).length) {
88
+ // Decoration exists in set, no need to add it again
89
+ continue;
90
+ }
91
+ // Use an inline decoration to either hide original (preview is showing) or show it (editing "mode")
92
+ decorationsToAdd.push(view.Decoration.inline(from, to, {
93
+ class: isEditing && isEditable
94
+ ? 'Tiptap-mathematics-editor'
95
+ : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',
96
+ style: !isEditing || !isEditable
97
+ ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'
98
+ : undefined,
99
+ }, {
100
+ content,
101
+ isEditable,
102
+ isEditing,
103
+ katexOptions,
104
+ }));
105
+ if (!isEditable || !isEditing) {
106
+ // Create decoration widget and add KaTeX preview if selection is not within the math-editor
107
+ decorationsToAdd.push(view.Decoration.widget(from, () => {
108
+ const element = document.createElement('span');
109
+ // TODO: changeable class names
110
+ element.classList.add('Tiptap-mathematics-render');
111
+ if (isEditable) {
112
+ element.classList.add('Tiptap-mathematics-render--editable');
113
+ }
114
+ try {
115
+ katex__default.default.render(content, element, katexOptions);
116
+ }
117
+ catch {
118
+ element.innerHTML = content;
119
+ }
120
+ return element;
121
+ }, {
122
+ content,
123
+ isEditable,
124
+ isEditing,
125
+ katexOptions,
126
+ }));
127
+ }
128
+ }
129
+ }
130
+ }
131
+ });
132
+ // Remove any decorations that exist at the same position, they will be replaced by the new decorations
133
+ const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to));
134
+ return {
135
+ decorations: nextDecorationSet
136
+ // Remove existing decorations that are going to be replaced
137
+ .remove(decorationsToRemove)
138
+ // Add any new decorations
139
+ .add(tr.doc, decorationsToAdd),
140
+ isEditable,
141
+ };
142
+ },
143
+ },
144
+ props: {
145
+ decorations(state) {
146
+ var _a, _b;
147
+ return (_b = (_a = this.getState(state)) === null || _a === void 0 ? void 0 : _a.decorations) !== null && _b !== void 0 ? _b : view.DecorationSet.empty;
148
+ },
149
+ },
150
+ });
151
+ };
152
+
153
+ const defaultShouldRender = (state, pos) => {
154
+ const $pos = state.doc.resolve(pos);
155
+ const isInCodeBlock = $pos.parent.type.name === 'codeBlock';
156
+ return !isInCodeBlock;
157
+ };
158
+ const Mathematics = core.Extension.create({
159
+ name: 'Mathematics',
160
+ addOptions() {
161
+ return {
162
+ // eslint-disable-next-line no-useless-escape
163
+ regex: /\$([^\$]*)\$/gi,
164
+ katexOptions: undefined,
165
+ shouldRender: defaultShouldRender,
166
+ };
167
+ },
168
+ addProseMirrorPlugins() {
169
+ return [MathematicsPlugin({ ...this.options, editor: this.editor })];
170
+ },
171
+ });
172
+
173
+ exports.Mathematics = Mathematics;
174
+ exports.MathematicsPlugin = MathematicsPlugin;
175
+ exports.default = Mathematics;
176
+ exports.defaultShouldRender = defaultShouldRender;
177
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/MathematicsPlugin.ts","../src/mathematics.ts"],"sourcesContent":["import { getChangedRanges } from '@tiptap/core'\nimport {\n EditorState, Plugin, PluginKey, Transaction,\n} from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport katex from 'katex'\n\nimport { MathematicsOptionsWithEditor } from './types.js'\n\ntype DecoSpec = {\n isEditable: boolean;\n isEditing: boolean;\n katexOptions: MathematicsOptionsWithEditor['katexOptions'];\n content: string;\n};\n\ntype Deco = Omit<Decoration, 'spec'> & { spec: DecoSpec };\n\ntype PluginState =\n| { decorations: DecorationSet; isEditable: boolean }\n| { decorations: undefined; isEditable: undefined }\n\n/**\n * Get the range of positions that have been affected by a transaction\n */\nfunction getAffectedRange(newState: EditorState, previousPluginState: PluginState, isEditable: boolean, tr: Transaction, state: EditorState) {\n const docSize = newState.doc.nodeSize - 2\n let minFrom = 0\n let maxTo = docSize\n\n if (previousPluginState.isEditable !== isEditable) {\n // When the editable state changes, run on all nodes just to be safe\n minFrom = 0\n maxTo = docSize\n } else if (tr.docChanged) {\n // When the document changes, only run on the nodes that have changed\n minFrom = docSize\n maxTo = 0\n\n getChangedRanges(tr).forEach(range => {\n // Purposefully over scan the range to ensure we catch all decorations\n minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1)\n maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1)\n })\n } else if (tr.selectionSet) {\n const { $from, $to } = state.selection\n const { $from: $newFrom, $to: $newTo } = newState.selection\n\n // When the selection changes, run on all the nodes between the old and new selection\n minFrom = Math.min(\n // Purposefully over scan the range to ensure we catch all decorations\n $from.depth === 0 ? 0 : $from.before(),\n $newFrom.depth === 0 ? 0 : $newFrom.before(),\n )\n maxTo = Math.max(\n $to.depth === 0 ? maxTo : $to.after(),\n $newTo.depth === 0 ? maxTo : $newTo.after(),\n )\n }\n\n return {\n minFrom: Math.max(minFrom, 0),\n maxTo: Math.min(maxTo, docSize),\n }\n}\n\nexport const MathematicsPlugin = (options: MathematicsOptionsWithEditor) => {\n const {\n regex, katexOptions = {}, editor, shouldRender,\n } = options\n\n return new Plugin<PluginState>({\n key: new PluginKey('mathematics'),\n\n state: {\n init() {\n return { decorations: undefined, isEditable: undefined }\n },\n apply(tr, previousPluginState, state, newState) {\n\n if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {\n // Just reuse the existing decorations, since nothing should have changed\n return previousPluginState\n }\n\n const nextDecorationSet = (previousPluginState.decorations || DecorationSet.empty).map(\n tr.mapping,\n tr.doc,\n )\n const { selection } = newState\n const isEditable = editor.isEditable\n const decorationsToAdd = [] as Deco[]\n const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state)\n\n newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {\n const enabled = shouldRender(newState, pos, node)\n\n if (node.isText && node.text && enabled) {\n let match: RegExpExecArray | null\n\n // eslint-disable-next-line no-cond-assign\n while ((match = regex.exec(node.text))) {\n const from = pos + match.index\n const to = from + match[0].length\n const content = match.slice(1).find(Boolean)\n\n if (content) {\n const selectionSize = selection.from - selection.to\n const anchorIsInside = selection.anchor >= from && selection.anchor <= to\n const rangeIsInside = selection.from >= from && selection.to <= to\n const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside\n\n if (\n // Are the decorations already present?\n nextDecorationSet.find(\n from,\n to,\n (deco: DecoSpec) => isEditing === deco.isEditing\n && content === deco.content\n && isEditable === deco.isEditable\n && katexOptions === deco.katexOptions,\n ).length\n ) {\n // Decoration exists in set, no need to add it again\n continue\n }\n // Use an inline decoration to either hide original (preview is showing) or show it (editing \"mode\")\n decorationsToAdd.push(\n Decoration.inline(\n from,\n to,\n {\n class:\n isEditing && isEditable\n ? 'Tiptap-mathematics-editor'\n : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',\n style:\n !isEditing || !isEditable\n ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'\n : undefined,\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n\n if (!isEditable || !isEditing) {\n // Create decoration widget and add KaTeX preview if selection is not within the math-editor\n decorationsToAdd.push(\n Decoration.widget(\n from,\n () => {\n const element = document.createElement('span')\n\n // TODO: changeable class names\n element.classList.add('Tiptap-mathematics-render')\n\n if (isEditable) {\n element.classList.add('Tiptap-mathematics-render--editable')\n }\n\n try {\n katex.render(content!, element, katexOptions)\n } catch {\n element.innerHTML = content!\n }\n\n return element\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n }\n }\n }\n }\n })\n\n // Remove any decorations that exist at the same position, they will be replaced by the new decorations\n const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to))\n\n return {\n decorations: nextDecorationSet\n // Remove existing decorations that are going to be replaced\n .remove(decorationsToRemove)\n // Add any new decorations\n .add(tr.doc, decorationsToAdd),\n isEditable,\n }\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport { MathematicsPlugin } from './MathematicsPlugin.js'\nimport { MathematicsOptions } from './types.js'\n\nexport const defaultShouldRender = (state: EditorState, pos: number) => {\n const $pos = state.doc.resolve(pos)\n const isInCodeBlock = $pos.parent.type.name === 'codeBlock'\n\n return !isInCodeBlock\n}\n\nexport const Mathematics = Extension.create<MathematicsOptions>({\n name: 'Mathematics',\n\n addOptions() {\n return {\n // eslint-disable-next-line no-useless-escape\n regex: /\\$([^\\$]*)\\$/gi,\n katexOptions: undefined,\n shouldRender: defaultShouldRender,\n }\n },\n\n addProseMirrorPlugins() {\n return [MathematicsPlugin({ ...this.options, editor: this.editor })]\n },\n})\n\nexport default Mathematics\n"],"names":["getChangedRanges","Plugin","PluginKey","DecorationSet","Decoration","katex","Extension"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Mathematics } from './mathematics.js';
2
+ export * from './mathematics.js';
3
+ export * from './MathematicsPlugin.js';
4
+ export * from './types.js';
5
+ export default Mathematics;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,cAAc,kBAAkB,CAAA;AAChC,cAAc,wBAAwB,CAAA;AACtC,cAAc,YAAY,CAAA;AAE1B,eAAe,WAAW,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ import { getChangedRanges, Extension } from '@tiptap/core';
2
+ import { Plugin, PluginKey } from '@tiptap/pm/state';
3
+ import { DecorationSet, Decoration } from '@tiptap/pm/view';
4
+ import katex from 'katex';
5
+
6
+ /**
7
+ * Get the range of positions that have been affected by a transaction
8
+ */
9
+ function getAffectedRange(newState, previousPluginState, isEditable, tr, state) {
10
+ const docSize = newState.doc.nodeSize - 2;
11
+ let minFrom = 0;
12
+ let maxTo = docSize;
13
+ if (previousPluginState.isEditable !== isEditable) {
14
+ // When the editable state changes, run on all nodes just to be safe
15
+ minFrom = 0;
16
+ maxTo = docSize;
17
+ }
18
+ else if (tr.docChanged) {
19
+ // When the document changes, only run on the nodes that have changed
20
+ minFrom = docSize;
21
+ maxTo = 0;
22
+ getChangedRanges(tr).forEach(range => {
23
+ // Purposefully over scan the range to ensure we catch all decorations
24
+ minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1);
25
+ maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1);
26
+ });
27
+ }
28
+ else if (tr.selectionSet) {
29
+ const { $from, $to } = state.selection;
30
+ const { $from: $newFrom, $to: $newTo } = newState.selection;
31
+ // When the selection changes, run on all the nodes between the old and new selection
32
+ minFrom = Math.min(
33
+ // Purposefully over scan the range to ensure we catch all decorations
34
+ $from.depth === 0 ? 0 : $from.before(), $newFrom.depth === 0 ? 0 : $newFrom.before());
35
+ maxTo = Math.max($to.depth === 0 ? maxTo : $to.after(), $newTo.depth === 0 ? maxTo : $newTo.after());
36
+ }
37
+ return {
38
+ minFrom: Math.max(minFrom, 0),
39
+ maxTo: Math.min(maxTo, docSize),
40
+ };
41
+ }
42
+ const MathematicsPlugin = (options) => {
43
+ const { regex, katexOptions = {}, editor, shouldRender, } = options;
44
+ return new Plugin({
45
+ key: new PluginKey('mathematics'),
46
+ state: {
47
+ init() {
48
+ return { decorations: undefined, isEditable: undefined };
49
+ },
50
+ apply(tr, previousPluginState, state, newState) {
51
+ if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {
52
+ // Just reuse the existing decorations, since nothing should have changed
53
+ return previousPluginState;
54
+ }
55
+ const nextDecorationSet = (previousPluginState.decorations || DecorationSet.empty).map(tr.mapping, tr.doc);
56
+ const { selection } = newState;
57
+ const isEditable = editor.isEditable;
58
+ const decorationsToAdd = [];
59
+ const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state);
60
+ newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {
61
+ const enabled = shouldRender(newState, pos, node);
62
+ if (node.isText && node.text && enabled) {
63
+ let match;
64
+ // eslint-disable-next-line no-cond-assign
65
+ while ((match = regex.exec(node.text))) {
66
+ const from = pos + match.index;
67
+ const to = from + match[0].length;
68
+ const content = match.slice(1).find(Boolean);
69
+ if (content) {
70
+ const selectionSize = selection.from - selection.to;
71
+ const anchorIsInside = selection.anchor >= from && selection.anchor <= to;
72
+ const rangeIsInside = selection.from >= from && selection.to <= to;
73
+ const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside;
74
+ if (
75
+ // Are the decorations already present?
76
+ nextDecorationSet.find(from, to, (deco) => isEditing === deco.isEditing
77
+ && content === deco.content
78
+ && isEditable === deco.isEditable
79
+ && katexOptions === deco.katexOptions).length) {
80
+ // Decoration exists in set, no need to add it again
81
+ continue;
82
+ }
83
+ // Use an inline decoration to either hide original (preview is showing) or show it (editing "mode")
84
+ decorationsToAdd.push(Decoration.inline(from, to, {
85
+ class: isEditing && isEditable
86
+ ? 'Tiptap-mathematics-editor'
87
+ : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',
88
+ style: !isEditing || !isEditable
89
+ ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'
90
+ : undefined,
91
+ }, {
92
+ content,
93
+ isEditable,
94
+ isEditing,
95
+ katexOptions,
96
+ }));
97
+ if (!isEditable || !isEditing) {
98
+ // Create decoration widget and add KaTeX preview if selection is not within the math-editor
99
+ decorationsToAdd.push(Decoration.widget(from, () => {
100
+ const element = document.createElement('span');
101
+ // TODO: changeable class names
102
+ element.classList.add('Tiptap-mathematics-render');
103
+ if (isEditable) {
104
+ element.classList.add('Tiptap-mathematics-render--editable');
105
+ }
106
+ try {
107
+ katex.render(content, element, katexOptions);
108
+ }
109
+ catch {
110
+ element.innerHTML = content;
111
+ }
112
+ return element;
113
+ }, {
114
+ content,
115
+ isEditable,
116
+ isEditing,
117
+ katexOptions,
118
+ }));
119
+ }
120
+ }
121
+ }
122
+ }
123
+ });
124
+ // Remove any decorations that exist at the same position, they will be replaced by the new decorations
125
+ const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to));
126
+ return {
127
+ decorations: nextDecorationSet
128
+ // Remove existing decorations that are going to be replaced
129
+ .remove(decorationsToRemove)
130
+ // Add any new decorations
131
+ .add(tr.doc, decorationsToAdd),
132
+ isEditable,
133
+ };
134
+ },
135
+ },
136
+ props: {
137
+ decorations(state) {
138
+ var _a, _b;
139
+ return (_b = (_a = this.getState(state)) === null || _a === void 0 ? void 0 : _a.decorations) !== null && _b !== void 0 ? _b : DecorationSet.empty;
140
+ },
141
+ },
142
+ });
143
+ };
144
+
145
+ const defaultShouldRender = (state, pos) => {
146
+ const $pos = state.doc.resolve(pos);
147
+ const isInCodeBlock = $pos.parent.type.name === 'codeBlock';
148
+ return !isInCodeBlock;
149
+ };
150
+ const Mathematics = Extension.create({
151
+ name: 'Mathematics',
152
+ addOptions() {
153
+ return {
154
+ // eslint-disable-next-line no-useless-escape
155
+ regex: /\$([^\$]*)\$/gi,
156
+ katexOptions: undefined,
157
+ shouldRender: defaultShouldRender,
158
+ };
159
+ },
160
+ addProseMirrorPlugins() {
161
+ return [MathematicsPlugin({ ...this.options, editor: this.editor })];
162
+ },
163
+ });
164
+
165
+ export { Mathematics, MathematicsPlugin, Mathematics as default, defaultShouldRender };
166
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/MathematicsPlugin.ts","../src/mathematics.ts"],"sourcesContent":["import { getChangedRanges } from '@tiptap/core'\nimport {\n EditorState, Plugin, PluginKey, Transaction,\n} from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport katex from 'katex'\n\nimport { MathematicsOptionsWithEditor } from './types.js'\n\ntype DecoSpec = {\n isEditable: boolean;\n isEditing: boolean;\n katexOptions: MathematicsOptionsWithEditor['katexOptions'];\n content: string;\n};\n\ntype Deco = Omit<Decoration, 'spec'> & { spec: DecoSpec };\n\ntype PluginState =\n| { decorations: DecorationSet; isEditable: boolean }\n| { decorations: undefined; isEditable: undefined }\n\n/**\n * Get the range of positions that have been affected by a transaction\n */\nfunction getAffectedRange(newState: EditorState, previousPluginState: PluginState, isEditable: boolean, tr: Transaction, state: EditorState) {\n const docSize = newState.doc.nodeSize - 2\n let minFrom = 0\n let maxTo = docSize\n\n if (previousPluginState.isEditable !== isEditable) {\n // When the editable state changes, run on all nodes just to be safe\n minFrom = 0\n maxTo = docSize\n } else if (tr.docChanged) {\n // When the document changes, only run on the nodes that have changed\n minFrom = docSize\n maxTo = 0\n\n getChangedRanges(tr).forEach(range => {\n // Purposefully over scan the range to ensure we catch all decorations\n minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1)\n maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1)\n })\n } else if (tr.selectionSet) {\n const { $from, $to } = state.selection\n const { $from: $newFrom, $to: $newTo } = newState.selection\n\n // When the selection changes, run on all the nodes between the old and new selection\n minFrom = Math.min(\n // Purposefully over scan the range to ensure we catch all decorations\n $from.depth === 0 ? 0 : $from.before(),\n $newFrom.depth === 0 ? 0 : $newFrom.before(),\n )\n maxTo = Math.max(\n $to.depth === 0 ? maxTo : $to.after(),\n $newTo.depth === 0 ? maxTo : $newTo.after(),\n )\n }\n\n return {\n minFrom: Math.max(minFrom, 0),\n maxTo: Math.min(maxTo, docSize),\n }\n}\n\nexport const MathematicsPlugin = (options: MathematicsOptionsWithEditor) => {\n const {\n regex, katexOptions = {}, editor, shouldRender,\n } = options\n\n return new Plugin<PluginState>({\n key: new PluginKey('mathematics'),\n\n state: {\n init() {\n return { decorations: undefined, isEditable: undefined }\n },\n apply(tr, previousPluginState, state, newState) {\n\n if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {\n // Just reuse the existing decorations, since nothing should have changed\n return previousPluginState\n }\n\n const nextDecorationSet = (previousPluginState.decorations || DecorationSet.empty).map(\n tr.mapping,\n tr.doc,\n )\n const { selection } = newState\n const isEditable = editor.isEditable\n const decorationsToAdd = [] as Deco[]\n const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state)\n\n newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {\n const enabled = shouldRender(newState, pos, node)\n\n if (node.isText && node.text && enabled) {\n let match: RegExpExecArray | null\n\n // eslint-disable-next-line no-cond-assign\n while ((match = regex.exec(node.text))) {\n const from = pos + match.index\n const to = from + match[0].length\n const content = match.slice(1).find(Boolean)\n\n if (content) {\n const selectionSize = selection.from - selection.to\n const anchorIsInside = selection.anchor >= from && selection.anchor <= to\n const rangeIsInside = selection.from >= from && selection.to <= to\n const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside\n\n if (\n // Are the decorations already present?\n nextDecorationSet.find(\n from,\n to,\n (deco: DecoSpec) => isEditing === deco.isEditing\n && content === deco.content\n && isEditable === deco.isEditable\n && katexOptions === deco.katexOptions,\n ).length\n ) {\n // Decoration exists in set, no need to add it again\n continue\n }\n // Use an inline decoration to either hide original (preview is showing) or show it (editing \"mode\")\n decorationsToAdd.push(\n Decoration.inline(\n from,\n to,\n {\n class:\n isEditing && isEditable\n ? 'Tiptap-mathematics-editor'\n : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',\n style:\n !isEditing || !isEditable\n ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'\n : undefined,\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n\n if (!isEditable || !isEditing) {\n // Create decoration widget and add KaTeX preview if selection is not within the math-editor\n decorationsToAdd.push(\n Decoration.widget(\n from,\n () => {\n const element = document.createElement('span')\n\n // TODO: changeable class names\n element.classList.add('Tiptap-mathematics-render')\n\n if (isEditable) {\n element.classList.add('Tiptap-mathematics-render--editable')\n }\n\n try {\n katex.render(content!, element, katexOptions)\n } catch {\n element.innerHTML = content!\n }\n\n return element\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n }\n }\n }\n }\n })\n\n // Remove any decorations that exist at the same position, they will be replaced by the new decorations\n const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to))\n\n return {\n decorations: nextDecorationSet\n // Remove existing decorations that are going to be replaced\n .remove(decorationsToRemove)\n // Add any new decorations\n .add(tr.doc, decorationsToAdd),\n isEditable,\n }\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport { MathematicsPlugin } from './MathematicsPlugin.js'\nimport { MathematicsOptions } from './types.js'\n\nexport const defaultShouldRender = (state: EditorState, pos: number) => {\n const $pos = state.doc.resolve(pos)\n const isInCodeBlock = $pos.parent.type.name === 'codeBlock'\n\n return !isInCodeBlock\n}\n\nexport const Mathematics = Extension.create<MathematicsOptions>({\n name: 'Mathematics',\n\n addOptions() {\n return {\n // eslint-disable-next-line no-useless-escape\n regex: /\\$([^\\$]*)\\$/gi,\n katexOptions: undefined,\n shouldRender: defaultShouldRender,\n }\n },\n\n addProseMirrorPlugins() {\n return [MathematicsPlugin({ ...this.options, editor: this.editor })]\n },\n})\n\nexport default Mathematics\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tiptap/core'), require('@tiptap/pm/state'), require('@tiptap/pm/view'), require('katex')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', '@tiptap/core', '@tiptap/pm/state', '@tiptap/pm/view', 'katex'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@tiptap/extension-mathematics"] = {}, global.core, global.state, global.view, global.katex));
5
+ })(this, (function (exports, core, state, view, katex) { 'use strict';
6
+
7
+ /**
8
+ * Get the range of positions that have been affected by a transaction
9
+ */
10
+ function getAffectedRange(newState, previousPluginState, isEditable, tr, state) {
11
+ const docSize = newState.doc.nodeSize - 2;
12
+ let minFrom = 0;
13
+ let maxTo = docSize;
14
+ if (previousPluginState.isEditable !== isEditable) {
15
+ // When the editable state changes, run on all nodes just to be safe
16
+ minFrom = 0;
17
+ maxTo = docSize;
18
+ }
19
+ else if (tr.docChanged) {
20
+ // When the document changes, only run on the nodes that have changed
21
+ minFrom = docSize;
22
+ maxTo = 0;
23
+ core.getChangedRanges(tr).forEach(range => {
24
+ // Purposefully over scan the range to ensure we catch all decorations
25
+ minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1);
26
+ maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1);
27
+ });
28
+ }
29
+ else if (tr.selectionSet) {
30
+ const { $from, $to } = state.selection;
31
+ const { $from: $newFrom, $to: $newTo } = newState.selection;
32
+ // When the selection changes, run on all the nodes between the old and new selection
33
+ minFrom = Math.min(
34
+ // Purposefully over scan the range to ensure we catch all decorations
35
+ $from.depth === 0 ? 0 : $from.before(), $newFrom.depth === 0 ? 0 : $newFrom.before());
36
+ maxTo = Math.max($to.depth === 0 ? maxTo : $to.after(), $newTo.depth === 0 ? maxTo : $newTo.after());
37
+ }
38
+ return {
39
+ minFrom: Math.max(minFrom, 0),
40
+ maxTo: Math.min(maxTo, docSize),
41
+ };
42
+ }
43
+ const MathematicsPlugin = (options) => {
44
+ const { regex, katexOptions = {}, editor, shouldRender, } = options;
45
+ return new state.Plugin({
46
+ key: new state.PluginKey('mathematics'),
47
+ state: {
48
+ init() {
49
+ return { decorations: undefined, isEditable: undefined };
50
+ },
51
+ apply(tr, previousPluginState, state, newState) {
52
+ if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {
53
+ // Just reuse the existing decorations, since nothing should have changed
54
+ return previousPluginState;
55
+ }
56
+ const nextDecorationSet = (previousPluginState.decorations || view.DecorationSet.empty).map(tr.mapping, tr.doc);
57
+ const { selection } = newState;
58
+ const isEditable = editor.isEditable;
59
+ const decorationsToAdd = [];
60
+ const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state);
61
+ newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {
62
+ const enabled = shouldRender(newState, pos, node);
63
+ if (node.isText && node.text && enabled) {
64
+ let match;
65
+ // eslint-disable-next-line no-cond-assign
66
+ while ((match = regex.exec(node.text))) {
67
+ const from = pos + match.index;
68
+ const to = from + match[0].length;
69
+ const content = match.slice(1).find(Boolean);
70
+ if (content) {
71
+ const selectionSize = selection.from - selection.to;
72
+ const anchorIsInside = selection.anchor >= from && selection.anchor <= to;
73
+ const rangeIsInside = selection.from >= from && selection.to <= to;
74
+ const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside;
75
+ if (
76
+ // Are the decorations already present?
77
+ nextDecorationSet.find(from, to, (deco) => isEditing === deco.isEditing
78
+ && content === deco.content
79
+ && isEditable === deco.isEditable
80
+ && katexOptions === deco.katexOptions).length) {
81
+ // Decoration exists in set, no need to add it again
82
+ continue;
83
+ }
84
+ // Use an inline decoration to either hide original (preview is showing) or show it (editing "mode")
85
+ decorationsToAdd.push(view.Decoration.inline(from, to, {
86
+ class: isEditing && isEditable
87
+ ? 'Tiptap-mathematics-editor'
88
+ : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',
89
+ style: !isEditing || !isEditable
90
+ ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'
91
+ : undefined,
92
+ }, {
93
+ content,
94
+ isEditable,
95
+ isEditing,
96
+ katexOptions,
97
+ }));
98
+ if (!isEditable || !isEditing) {
99
+ // Create decoration widget and add KaTeX preview if selection is not within the math-editor
100
+ decorationsToAdd.push(view.Decoration.widget(from, () => {
101
+ const element = document.createElement('span');
102
+ // TODO: changeable class names
103
+ element.classList.add('Tiptap-mathematics-render');
104
+ if (isEditable) {
105
+ element.classList.add('Tiptap-mathematics-render--editable');
106
+ }
107
+ try {
108
+ katex.render(content, element, katexOptions);
109
+ }
110
+ catch {
111
+ element.innerHTML = content;
112
+ }
113
+ return element;
114
+ }, {
115
+ content,
116
+ isEditable,
117
+ isEditing,
118
+ katexOptions,
119
+ }));
120
+ }
121
+ }
122
+ }
123
+ }
124
+ });
125
+ // Remove any decorations that exist at the same position, they will be replaced by the new decorations
126
+ const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to));
127
+ return {
128
+ decorations: nextDecorationSet
129
+ // Remove existing decorations that are going to be replaced
130
+ .remove(decorationsToRemove)
131
+ // Add any new decorations
132
+ .add(tr.doc, decorationsToAdd),
133
+ isEditable,
134
+ };
135
+ },
136
+ },
137
+ props: {
138
+ decorations(state) {
139
+ var _a, _b;
140
+ return (_b = (_a = this.getState(state)) === null || _a === void 0 ? void 0 : _a.decorations) !== null && _b !== void 0 ? _b : view.DecorationSet.empty;
141
+ },
142
+ },
143
+ });
144
+ };
145
+
146
+ const defaultShouldRender = (state, pos) => {
147
+ const $pos = state.doc.resolve(pos);
148
+ const isInCodeBlock = $pos.parent.type.name === 'codeBlock';
149
+ return !isInCodeBlock;
150
+ };
151
+ const Mathematics = core.Extension.create({
152
+ name: 'Mathematics',
153
+ addOptions() {
154
+ return {
155
+ // eslint-disable-next-line no-useless-escape
156
+ regex: /\$([^\$]*)\$/gi,
157
+ katexOptions: undefined,
158
+ shouldRender: defaultShouldRender,
159
+ };
160
+ },
161
+ addProseMirrorPlugins() {
162
+ return [MathematicsPlugin({ ...this.options, editor: this.editor })];
163
+ },
164
+ });
165
+
166
+ exports.Mathematics = Mathematics;
167
+ exports.MathematicsPlugin = MathematicsPlugin;
168
+ exports.default = Mathematics;
169
+ exports.defaultShouldRender = defaultShouldRender;
170
+
171
+ Object.defineProperty(exports, '__esModule', { value: true });
172
+
173
+ }));
174
+ //# sourceMappingURL=index.umd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.umd.js","sources":["../src/MathematicsPlugin.ts","../src/mathematics.ts"],"sourcesContent":["import { getChangedRanges } from '@tiptap/core'\nimport {\n EditorState, Plugin, PluginKey, Transaction,\n} from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport katex from 'katex'\n\nimport { MathematicsOptionsWithEditor } from './types.js'\n\ntype DecoSpec = {\n isEditable: boolean;\n isEditing: boolean;\n katexOptions: MathematicsOptionsWithEditor['katexOptions'];\n content: string;\n};\n\ntype Deco = Omit<Decoration, 'spec'> & { spec: DecoSpec };\n\ntype PluginState =\n| { decorations: DecorationSet; isEditable: boolean }\n| { decorations: undefined; isEditable: undefined }\n\n/**\n * Get the range of positions that have been affected by a transaction\n */\nfunction getAffectedRange(newState: EditorState, previousPluginState: PluginState, isEditable: boolean, tr: Transaction, state: EditorState) {\n const docSize = newState.doc.nodeSize - 2\n let minFrom = 0\n let maxTo = docSize\n\n if (previousPluginState.isEditable !== isEditable) {\n // When the editable state changes, run on all nodes just to be safe\n minFrom = 0\n maxTo = docSize\n } else if (tr.docChanged) {\n // When the document changes, only run on the nodes that have changed\n minFrom = docSize\n maxTo = 0\n\n getChangedRanges(tr).forEach(range => {\n // Purposefully over scan the range to ensure we catch all decorations\n minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1)\n maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1)\n })\n } else if (tr.selectionSet) {\n const { $from, $to } = state.selection\n const { $from: $newFrom, $to: $newTo } = newState.selection\n\n // When the selection changes, run on all the nodes between the old and new selection\n minFrom = Math.min(\n // Purposefully over scan the range to ensure we catch all decorations\n $from.depth === 0 ? 0 : $from.before(),\n $newFrom.depth === 0 ? 0 : $newFrom.before(),\n )\n maxTo = Math.max(\n $to.depth === 0 ? maxTo : $to.after(),\n $newTo.depth === 0 ? maxTo : $newTo.after(),\n )\n }\n\n return {\n minFrom: Math.max(minFrom, 0),\n maxTo: Math.min(maxTo, docSize),\n }\n}\n\nexport const MathematicsPlugin = (options: MathematicsOptionsWithEditor) => {\n const {\n regex, katexOptions = {}, editor, shouldRender,\n } = options\n\n return new Plugin<PluginState>({\n key: new PluginKey('mathematics'),\n\n state: {\n init() {\n return { decorations: undefined, isEditable: undefined }\n },\n apply(tr, previousPluginState, state, newState) {\n\n if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {\n // Just reuse the existing decorations, since nothing should have changed\n return previousPluginState\n }\n\n const nextDecorationSet = (previousPluginState.decorations || DecorationSet.empty).map(\n tr.mapping,\n tr.doc,\n )\n const { selection } = newState\n const isEditable = editor.isEditable\n const decorationsToAdd = [] as Deco[]\n const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state)\n\n newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {\n const enabled = shouldRender(newState, pos, node)\n\n if (node.isText && node.text && enabled) {\n let match: RegExpExecArray | null\n\n // eslint-disable-next-line no-cond-assign\n while ((match = regex.exec(node.text))) {\n const from = pos + match.index\n const to = from + match[0].length\n const content = match.slice(1).find(Boolean)\n\n if (content) {\n const selectionSize = selection.from - selection.to\n const anchorIsInside = selection.anchor >= from && selection.anchor <= to\n const rangeIsInside = selection.from >= from && selection.to <= to\n const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside\n\n if (\n // Are the decorations already present?\n nextDecorationSet.find(\n from,\n to,\n (deco: DecoSpec) => isEditing === deco.isEditing\n && content === deco.content\n && isEditable === deco.isEditable\n && katexOptions === deco.katexOptions,\n ).length\n ) {\n // Decoration exists in set, no need to add it again\n continue\n }\n // Use an inline decoration to either hide original (preview is showing) or show it (editing \"mode\")\n decorationsToAdd.push(\n Decoration.inline(\n from,\n to,\n {\n class:\n isEditing && isEditable\n ? 'Tiptap-mathematics-editor'\n : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',\n style:\n !isEditing || !isEditable\n ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'\n : undefined,\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n\n if (!isEditable || !isEditing) {\n // Create decoration widget and add KaTeX preview if selection is not within the math-editor\n decorationsToAdd.push(\n Decoration.widget(\n from,\n () => {\n const element = document.createElement('span')\n\n // TODO: changeable class names\n element.classList.add('Tiptap-mathematics-render')\n\n if (isEditable) {\n element.classList.add('Tiptap-mathematics-render--editable')\n }\n\n try {\n katex.render(content!, element, katexOptions)\n } catch {\n element.innerHTML = content!\n }\n\n return element\n },\n {\n content,\n isEditable,\n isEditing,\n katexOptions,\n } satisfies DecoSpec,\n ),\n )\n }\n }\n }\n }\n })\n\n // Remove any decorations that exist at the same position, they will be replaced by the new decorations\n const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to))\n\n return {\n decorations: nextDecorationSet\n // Remove existing decorations that are going to be replaced\n .remove(decorationsToRemove)\n // Add any new decorations\n .add(tr.doc, decorationsToAdd),\n isEditable,\n }\n },\n },\n\n props: {\n decorations(state) {\n return this.getState(state)?.decorations ?? DecorationSet.empty\n },\n },\n })\n}\n","import { Extension } from '@tiptap/core'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport { MathematicsPlugin } from './MathematicsPlugin.js'\nimport { MathematicsOptions } from './types.js'\n\nexport const defaultShouldRender = (state: EditorState, pos: number) => {\n const $pos = state.doc.resolve(pos)\n const isInCodeBlock = $pos.parent.type.name === 'codeBlock'\n\n return !isInCodeBlock\n}\n\nexport const Mathematics = Extension.create<MathematicsOptions>({\n name: 'Mathematics',\n\n addOptions() {\n return {\n // eslint-disable-next-line no-useless-escape\n regex: /\\$([^\\$]*)\\$/gi,\n katexOptions: undefined,\n shouldRender: defaultShouldRender,\n }\n },\n\n addProseMirrorPlugins() {\n return [MathematicsPlugin({ ...this.options, editor: this.editor })]\n },\n})\n\nexport default Mathematics\n"],"names":["getChangedRanges","Plugin","PluginKey","DecorationSet","Decoration","Extension"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Extension } from '@tiptap/core';
2
+ import { EditorState } from '@tiptap/pm/state';
3
+ import { MathematicsOptions } from './types.js';
4
+ export declare const defaultShouldRender: (state: EditorState, pos: number) => boolean;
5
+ export declare const Mathematics: Extension<MathematicsOptions, any>;
6
+ export default Mathematics;
7
+ //# sourceMappingURL=mathematics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mathematics.d.ts","sourceRoot":"","sources":["../src/mathematics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,eAAO,MAAM,mBAAmB,UAAW,WAAW,OAAO,MAAM,YAKlE,CAAA;AAED,eAAO,MAAM,WAAW,oCAetB,CAAA;AAEF,eAAe,WAAW,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { Editor } from '@tiptap/core';
2
+ import { Node } from '@tiptap/pm/model';
3
+ import { EditorState } from '@tiptap/pm/state';
4
+ import { KatexOptions } from 'katex';
5
+ export type MathematicsOptions = {
6
+ regex: RegExp;
7
+ katexOptions?: KatexOptions;
8
+ shouldRender: (state: EditorState, pos: number, node: Node) => boolean;
9
+ };
10
+ export type MathematicsOptionsWithEditor = MathematicsOptions & {
11
+ editor: Editor;
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAEpC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;CACxE,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,kBAAkB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@tiptap/extension-mathematics",
3
+ "description": "latex math extension for tiptap",
4
+ "version": "2.22.0",
5
+ "homepage": "https://tiptap.dev/api/extensions/mathematics",
6
+ "keywords": [
7
+ "tiptap",
8
+ "tiptap extension"
9
+ ],
10
+ "license": "MIT",
11
+ "funding": {
12
+ "type": "github",
13
+ "url": "https://github.com/sponsors/ueberdosis"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/ueberdosis/tiptap",
18
+ "directory": "packages/extension-mathematics"
19
+ },
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "main": "dist/index.cjs",
29
+ "module": "dist/index.js",
30
+ "umd": "dist/index.umd.js",
31
+ "types": "dist/index.d.ts",
32
+ "files": [
33
+ "src",
34
+ "dist"
35
+ ],
36
+ "devDependencies": {
37
+ "@tiptap/core": "^2.22.0",
38
+ "@tiptap/pm": "^2.22.0",
39
+ "@types/katex": "^0.16.7"
40
+ },
41
+ "peerDependencies": {
42
+ "@tiptap/core": "^2.7.0",
43
+ "@tiptap/pm": "^2.7.0",
44
+ "katex": "^0.16.4"
45
+ },
46
+ "scripts": {
47
+ "clean": "rm -rf dist",
48
+ "build": "npm run clean && rollup -c"
49
+ }
50
+ }
@@ -0,0 +1,208 @@
1
+ import { getChangedRanges } from '@tiptap/core'
2
+ import {
3
+ EditorState, Plugin, PluginKey, Transaction,
4
+ } from '@tiptap/pm/state'
5
+ import { Decoration, DecorationSet } from '@tiptap/pm/view'
6
+ import katex from 'katex'
7
+
8
+ import { MathematicsOptionsWithEditor } from './types.js'
9
+
10
+ type DecoSpec = {
11
+ isEditable: boolean;
12
+ isEditing: boolean;
13
+ katexOptions: MathematicsOptionsWithEditor['katexOptions'];
14
+ content: string;
15
+ };
16
+
17
+ type Deco = Omit<Decoration, 'spec'> & { spec: DecoSpec };
18
+
19
+ type PluginState =
20
+ | { decorations: DecorationSet; isEditable: boolean }
21
+ | { decorations: undefined; isEditable: undefined }
22
+
23
+ /**
24
+ * Get the range of positions that have been affected by a transaction
25
+ */
26
+ function getAffectedRange(newState: EditorState, previousPluginState: PluginState, isEditable: boolean, tr: Transaction, state: EditorState) {
27
+ const docSize = newState.doc.nodeSize - 2
28
+ let minFrom = 0
29
+ let maxTo = docSize
30
+
31
+ if (previousPluginState.isEditable !== isEditable) {
32
+ // When the editable state changes, run on all nodes just to be safe
33
+ minFrom = 0
34
+ maxTo = docSize
35
+ } else if (tr.docChanged) {
36
+ // When the document changes, only run on the nodes that have changed
37
+ minFrom = docSize
38
+ maxTo = 0
39
+
40
+ getChangedRanges(tr).forEach(range => {
41
+ // Purposefully over scan the range to ensure we catch all decorations
42
+ minFrom = Math.min(minFrom, range.newRange.from - 1, range.oldRange.from - 1)
43
+ maxTo = Math.max(maxTo, range.newRange.to + 1, range.oldRange.to + 1)
44
+ })
45
+ } else if (tr.selectionSet) {
46
+ const { $from, $to } = state.selection
47
+ const { $from: $newFrom, $to: $newTo } = newState.selection
48
+
49
+ // When the selection changes, run on all the nodes between the old and new selection
50
+ minFrom = Math.min(
51
+ // Purposefully over scan the range to ensure we catch all decorations
52
+ $from.depth === 0 ? 0 : $from.before(),
53
+ $newFrom.depth === 0 ? 0 : $newFrom.before(),
54
+ )
55
+ maxTo = Math.max(
56
+ $to.depth === 0 ? maxTo : $to.after(),
57
+ $newTo.depth === 0 ? maxTo : $newTo.after(),
58
+ )
59
+ }
60
+
61
+ return {
62
+ minFrom: Math.max(minFrom, 0),
63
+ maxTo: Math.min(maxTo, docSize),
64
+ }
65
+ }
66
+
67
+ export const MathematicsPlugin = (options: MathematicsOptionsWithEditor) => {
68
+ const {
69
+ regex, katexOptions = {}, editor, shouldRender,
70
+ } = options
71
+
72
+ return new Plugin<PluginState>({
73
+ key: new PluginKey('mathematics'),
74
+
75
+ state: {
76
+ init() {
77
+ return { decorations: undefined, isEditable: undefined }
78
+ },
79
+ apply(tr, previousPluginState, state, newState) {
80
+
81
+ if (!tr.docChanged && !tr.selectionSet && previousPluginState.decorations) {
82
+ // Just reuse the existing decorations, since nothing should have changed
83
+ return previousPluginState
84
+ }
85
+
86
+ const nextDecorationSet = (previousPluginState.decorations || DecorationSet.empty).map(
87
+ tr.mapping,
88
+ tr.doc,
89
+ )
90
+ const { selection } = newState
91
+ const isEditable = editor.isEditable
92
+ const decorationsToAdd = [] as Deco[]
93
+ const { minFrom, maxTo } = getAffectedRange(newState, previousPluginState, isEditable, tr, state)
94
+
95
+ newState.doc.nodesBetween(minFrom, maxTo, (node, pos) => {
96
+ const enabled = shouldRender(newState, pos, node)
97
+
98
+ if (node.isText && node.text && enabled) {
99
+ let match: RegExpExecArray | null
100
+
101
+ // eslint-disable-next-line no-cond-assign
102
+ while ((match = regex.exec(node.text))) {
103
+ const from = pos + match.index
104
+ const to = from + match[0].length
105
+ const content = match.slice(1).find(Boolean)
106
+
107
+ if (content) {
108
+ const selectionSize = selection.from - selection.to
109
+ const anchorIsInside = selection.anchor >= from && selection.anchor <= to
110
+ const rangeIsInside = selection.from >= from && selection.to <= to
111
+ const isEditing = (selectionSize === 0 && anchorIsInside) || rangeIsInside
112
+
113
+ if (
114
+ // Are the decorations already present?
115
+ nextDecorationSet.find(
116
+ from,
117
+ to,
118
+ (deco: DecoSpec) => isEditing === deco.isEditing
119
+ && content === deco.content
120
+ && isEditable === deco.isEditable
121
+ && katexOptions === deco.katexOptions,
122
+ ).length
123
+ ) {
124
+ // Decoration exists in set, no need to add it again
125
+ continue
126
+ }
127
+ // Use an inline decoration to either hide original (preview is showing) or show it (editing "mode")
128
+ decorationsToAdd.push(
129
+ Decoration.inline(
130
+ from,
131
+ to,
132
+ {
133
+ class:
134
+ isEditing && isEditable
135
+ ? 'Tiptap-mathematics-editor'
136
+ : 'Tiptap-mathematics-editor Tiptap-mathematics-editor--hidden',
137
+ style:
138
+ !isEditing || !isEditable
139
+ ? 'display: inline-block; height: 0; opacity: 0; overflow: hidden; position: absolute; width: 0;'
140
+ : undefined,
141
+ },
142
+ {
143
+ content,
144
+ isEditable,
145
+ isEditing,
146
+ katexOptions,
147
+ } satisfies DecoSpec,
148
+ ),
149
+ )
150
+
151
+ if (!isEditable || !isEditing) {
152
+ // Create decoration widget and add KaTeX preview if selection is not within the math-editor
153
+ decorationsToAdd.push(
154
+ Decoration.widget(
155
+ from,
156
+ () => {
157
+ const element = document.createElement('span')
158
+
159
+ // TODO: changeable class names
160
+ element.classList.add('Tiptap-mathematics-render')
161
+
162
+ if (isEditable) {
163
+ element.classList.add('Tiptap-mathematics-render--editable')
164
+ }
165
+
166
+ try {
167
+ katex.render(content!, element, katexOptions)
168
+ } catch {
169
+ element.innerHTML = content!
170
+ }
171
+
172
+ return element
173
+ },
174
+ {
175
+ content,
176
+ isEditable,
177
+ isEditing,
178
+ katexOptions,
179
+ } satisfies DecoSpec,
180
+ ),
181
+ )
182
+ }
183
+ }
184
+ }
185
+ }
186
+ })
187
+
188
+ // Remove any decorations that exist at the same position, they will be replaced by the new decorations
189
+ const decorationsToRemove = decorationsToAdd.flatMap(deco => nextDecorationSet.find(deco.from, deco.to))
190
+
191
+ return {
192
+ decorations: nextDecorationSet
193
+ // Remove existing decorations that are going to be replaced
194
+ .remove(decorationsToRemove)
195
+ // Add any new decorations
196
+ .add(tr.doc, decorationsToAdd),
197
+ isEditable,
198
+ }
199
+ },
200
+ },
201
+
202
+ props: {
203
+ decorations(state) {
204
+ return this.getState(state)?.decorations ?? DecorationSet.empty
205
+ },
206
+ },
207
+ })
208
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Mathematics } from './mathematics.js'
2
+
3
+ export * from './mathematics.js'
4
+ export * from './MathematicsPlugin.js'
5
+ export * from './types.js'
6
+
7
+ export default Mathematics
@@ -0,0 +1,31 @@
1
+ import { Extension } from '@tiptap/core'
2
+ import { EditorState } from '@tiptap/pm/state'
3
+
4
+ import { MathematicsPlugin } from './MathematicsPlugin.js'
5
+ import { MathematicsOptions } from './types.js'
6
+
7
+ export const defaultShouldRender = (state: EditorState, pos: number) => {
8
+ const $pos = state.doc.resolve(pos)
9
+ const isInCodeBlock = $pos.parent.type.name === 'codeBlock'
10
+
11
+ return !isInCodeBlock
12
+ }
13
+
14
+ export const Mathematics = Extension.create<MathematicsOptions>({
15
+ name: 'Mathematics',
16
+
17
+ addOptions() {
18
+ return {
19
+ // eslint-disable-next-line no-useless-escape
20
+ regex: /\$([^\$]*)\$/gi,
21
+ katexOptions: undefined,
22
+ shouldRender: defaultShouldRender,
23
+ }
24
+ },
25
+
26
+ addProseMirrorPlugins() {
27
+ return [MathematicsPlugin({ ...this.options, editor: this.editor })]
28
+ },
29
+ })
30
+
31
+ export default Mathematics
package/src/types.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { Editor } from '@tiptap/core'
2
+ import { Node } from '@tiptap/pm/model'
3
+ import { EditorState } from '@tiptap/pm/state'
4
+ import { KatexOptions } from 'katex'
5
+
6
+ export type MathematicsOptions = {
7
+ regex: RegExp,
8
+ katexOptions?: KatexOptions,
9
+ shouldRender: (state: EditorState, pos: number, node: Node) => boolean,
10
+ }
11
+
12
+ export type MathematicsOptionsWithEditor = MathematicsOptions & { editor: Editor }