feat: update edra to 2.3.0 (#618)
* feat: Add mathematics extension and toolbar commands This commit introduces the mathematics extension for inline and block expressions. It also adds corresponding toolbar commands and updates the drag handle menu to include "Turn Into" and "Add Node" options. The placeholder styling and blockquote styles have also been refined. Co-authored-by: best <[email protected]> * Update Tiptap and KaTeX dependencies Co-authored-by: best <[email protected]> * Downgrade svelte-tiptap to v2.2.0 Co-authored-by: best <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]>
This commit is contained in:
co-authored by
Cursor Agent
parent
50d6ee3111
commit
2779be6494
+21
-21
@@ -37,33 +37,33 @@
|
|||||||
"vaul-svelte": "1.0.0-next.7"
|
"vaul-svelte": "1.0.0-next.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aarkue/tiptap-math-extension": "^1.4.0",
|
|
||||||
"@floating-ui/dom": "^1.7.4",
|
"@floating-ui/dom": "^1.7.4",
|
||||||
"@tiptap/core": "^3.7.1",
|
"@tiptap/core": "^3.10.7",
|
||||||
"@tiptap/extension-bubble-menu": "^3.7.1",
|
"@tiptap/extension-bubble-menu": "^3.10.7",
|
||||||
"@tiptap/extension-code-block-lowlight": "^3.7.1",
|
"@tiptap/extension-code-block-lowlight": "^3.10.7",
|
||||||
"@tiptap/extension-highlight": "^3.7.1",
|
"@tiptap/extension-highlight": "^3.10.7",
|
||||||
"@tiptap/extension-image": "^3.7.1",
|
"@tiptap/extension-image": "^3.10.7",
|
||||||
"@tiptap/extension-list": "^3.7.1",
|
"@tiptap/extension-list": "^3.10.7",
|
||||||
"@tiptap/extension-subscript": "^3.7.1",
|
"@tiptap/extension-mathematics": "^3.10.7",
|
||||||
"@tiptap/extension-superscript": "^3.7.1",
|
"@tiptap/extension-subscript": "^3.10.7",
|
||||||
"@tiptap/extension-table": "^3.7.1",
|
"@tiptap/extension-superscript": "^3.10.7",
|
||||||
"@tiptap/extension-text-align": "^3.7.1",
|
"@tiptap/extension-table": "^3.10.7",
|
||||||
"@tiptap/extension-text-style": "^3.7.1",
|
"@tiptap/extension-text-align": "^3.10.7",
|
||||||
"@tiptap/extension-typography": "^3.7.1",
|
"@tiptap/extension-text-style": "^3.10.7",
|
||||||
"@tiptap/extensions": "^3.7.1",
|
"@tiptap/extension-typography": "^3.10.7",
|
||||||
"@tiptap/pm": "^3.7.1",
|
"@tiptap/extensions": "^3.10.7",
|
||||||
"@tiptap/starter-kit": "^3.7.1",
|
"@tiptap/markdown": "^3.10.7",
|
||||||
"@tiptap/suggestion": "^3.7.1",
|
"@tiptap/pm": "^3.10.7",
|
||||||
|
"@tiptap/starter-kit": "^3.10.7",
|
||||||
|
"@tiptap/suggestion": "^3.10.7",
|
||||||
"d3-array": "^3.2.4",
|
"d3-array": "^3.2.4",
|
||||||
"d3-scale": "^4.0.2",
|
"d3-scale": "^4.0.2",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"katex": "^0.16.22",
|
"katex": "^0.16.25",
|
||||||
"lowlight": "^3.3.0",
|
"lowlight": "^3.3.0",
|
||||||
"svelte-tiptap": "^3.0.0",
|
"svelte-tiptap": "^2.2.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap-extension-auto-joiner": "^0.1.3",
|
"tiptap-extension-auto-joiner": "^0.1.3"
|
||||||
"tiptap-markdown": "^0.9.0"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"bits-ui": "^2.14.4"
|
"bits-ui": "^2.14.4"
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import Table from '@lucide/svelte/icons/table';
|
|||||||
import Underline from '@lucide/svelte/icons/underline';
|
import Underline from '@lucide/svelte/icons/underline';
|
||||||
import Undo from '@lucide/svelte/icons/undo-2';
|
import Undo from '@lucide/svelte/icons/undo-2';
|
||||||
import Video from '@lucide/svelte/icons/video';
|
import Video from '@lucide/svelte/icons/video';
|
||||||
|
import Radical from '@lucide/svelte/icons/radical';
|
||||||
|
import SquareRadical from '@lucide/svelte/icons/square-radical';
|
||||||
|
import { isTextSelection } from '@tiptap/core';
|
||||||
|
import Pilcrow from '@lucide/svelte/icons/pilcrow';
|
||||||
import { isMac } from '../utils';
|
import { isMac } from '../utils';
|
||||||
|
|
||||||
const commands: Record<string, EdraToolBarCommands[]> = {
|
const commands: Record<string, EdraToolBarCommands[]> = {
|
||||||
@@ -64,6 +68,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleHeading({ level: 1 }).run();
|
editor.chain().focus().toggleHeading({ level: 1 }).run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setHeading({ level: 1 }).run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleHeading({ level: 1 });
|
return editor.can().toggleHeading({ level: 1 });
|
||||||
},
|
},
|
||||||
@@ -79,6 +86,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleHeading({ level: 2 }).run();
|
editor.chain().focus().toggleHeading({ level: 2 }).run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setHeading({ level: 2 }).run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleHeading({ level: 2 });
|
return editor.can().toggleHeading({ level: 2 });
|
||||||
},
|
},
|
||||||
@@ -94,6 +104,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleHeading({ level: 3 }).run();
|
editor.chain().focus().toggleHeading({ level: 3 }).run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setHeading({ level: 3 }).run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleHeading({ level: 3 });
|
return editor.can().toggleHeading({ level: 3 });
|
||||||
},
|
},
|
||||||
@@ -109,6 +122,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleHeading({ level: 4 }).run();
|
editor.chain().focus().toggleHeading({ level: 4 }).run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setHeading({ level: 4 }).run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleHeading({ level: 4 });
|
return editor.can().toggleHeading({ level: 4 });
|
||||||
},
|
},
|
||||||
@@ -136,6 +152,24 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
return editor.isActive('link');
|
return editor.isActive('link');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: Pilcrow,
|
||||||
|
name: 'paragraph',
|
||||||
|
tooltip: 'Paragraph',
|
||||||
|
shortCut: `${isMac ? '⌘⇧' : 'Ctrl+Shift+'}0`,
|
||||||
|
onClick: (editor) => {
|
||||||
|
editor.chain().focus().setParagraph().run();
|
||||||
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setParagraph().run();
|
||||||
|
},
|
||||||
|
clickable: (editor) => {
|
||||||
|
return editor.can().setParagraph();
|
||||||
|
},
|
||||||
|
isActive: (editor) => {
|
||||||
|
return editor.isActive('paragraph');
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: Bold,
|
icon: Bold,
|
||||||
name: 'bold',
|
name: 'bold',
|
||||||
@@ -144,6 +178,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleBold().run();
|
editor.chain().focus().toggleBold().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setMark('bold').run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleBold();
|
return editor.can().toggleBold();
|
||||||
},
|
},
|
||||||
@@ -159,6 +196,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleItalic().run();
|
editor.chain().focus().toggleItalic().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setMark('italic').run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleItalic();
|
return editor.can().toggleItalic();
|
||||||
},
|
},
|
||||||
@@ -174,6 +214,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleUnderline().run();
|
editor.chain().focus().toggleUnderline().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setMark('underline').run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleUnderline();
|
return editor.can().toggleUnderline();
|
||||||
},
|
},
|
||||||
@@ -189,6 +232,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleStrike().run();
|
editor.chain().focus().toggleStrike().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).setMark('strike').run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleStrike();
|
return editor.can().toggleStrike();
|
||||||
},
|
},
|
||||||
@@ -204,6 +250,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleBlockquote().run();
|
editor.chain().focus().toggleBlockquote().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).toggleBlockquote().run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleBlockquote();
|
return editor.can().toggleBlockquote();
|
||||||
},
|
},
|
||||||
@@ -219,6 +268,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleCode().run();
|
editor.chain().focus().toggleCode().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).toggleCodeBlock().run();
|
||||||
|
},
|
||||||
clickable: (editor) => {
|
clickable: (editor) => {
|
||||||
return editor.can().toggleCode();
|
return editor.can().toggleCode();
|
||||||
},
|
},
|
||||||
@@ -320,6 +372,9 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleBulletList().run();
|
editor.chain().focus().toggleBulletList().run();
|
||||||
},
|
},
|
||||||
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).toggleBulletList().run();
|
||||||
|
},
|
||||||
isActive: (editor) => editor.isActive('bulletList')
|
isActive: (editor) => editor.isActive('bulletList')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -330,7 +385,15 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleOrderedList().run();
|
editor.chain().focus().toggleOrderedList().run();
|
||||||
},
|
},
|
||||||
isActive: (editor) => editor.isActive('orderedList')
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).toggleOrderedList().run();
|
||||||
|
},
|
||||||
|
clickable: (editor) => {
|
||||||
|
return editor.can().toggleOrderedList();
|
||||||
|
},
|
||||||
|
isActive: (editor) => {
|
||||||
|
return editor.isActive('orderedList');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: ListChecks,
|
icon: ListChecks,
|
||||||
@@ -340,7 +403,15 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
onClick: (editor) => {
|
onClick: (editor) => {
|
||||||
editor.chain().focus().toggleTaskList().run();
|
editor.chain().focus().toggleTaskList().run();
|
||||||
},
|
},
|
||||||
isActive: (editor) => editor.isActive('taskList')
|
turnInto: (editor, pos) => {
|
||||||
|
editor.chain().setNodeSelection(pos).toggleTaskList().run();
|
||||||
|
},
|
||||||
|
clickable: (editor) => {
|
||||||
|
return editor.can().toggleTaskList();
|
||||||
|
},
|
||||||
|
isActive: (editor) => {
|
||||||
|
return editor.isActive('taskList');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
media: [
|
media: [
|
||||||
@@ -398,6 +469,34 @@ const commands: Record<string, EdraToolBarCommands[]> = {
|
|||||||
},
|
},
|
||||||
isActive: (editor) => editor.isActive('table')
|
isActive: (editor) => editor.isActive('table')
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
math: [
|
||||||
|
{
|
||||||
|
icon: Radical,
|
||||||
|
name: 'mathematics',
|
||||||
|
tooltip: 'Inline Expression',
|
||||||
|
onClick: (editor) => {
|
||||||
|
let latex = 'a^2 + b^2 = c^2';
|
||||||
|
const chain = editor.chain().focus();
|
||||||
|
if (isTextSelection(editor.view.state.selection)) {
|
||||||
|
const { from, to } = editor.view.state.selection;
|
||||||
|
latex = editor.view.state.doc.textBetween(from, to);
|
||||||
|
chain.deleteRange({ from, to });
|
||||||
|
}
|
||||||
|
chain.insertInlineMath({ latex }).run();
|
||||||
|
},
|
||||||
|
isActive: (editor) => editor.isActive('inlineMath')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: SquareRadical,
|
||||||
|
name: 'mathematics',
|
||||||
|
tooltip: 'Block Expression',
|
||||||
|
onClick: (editor) => {
|
||||||
|
const latex = 'a^2 + b^2 = c^2';
|
||||||
|
editor.chain().focus().insertBlockMath({ latex }).run();
|
||||||
|
},
|
||||||
|
isActive: (editor) => editor.isActive('blockMath')
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface EdraToolBarCommands {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
shortCut?: string;
|
shortCut?: string;
|
||||||
onClick?: (editor: Editor) => void;
|
onClick?: (editor: Editor) => void;
|
||||||
|
turnInto?: (editor: Editor, pos: number) => void;
|
||||||
isActive?: (editor: Editor) => boolean;
|
isActive?: (editor: Editor) => boolean;
|
||||||
clickable?: (editor: Editor) => boolean;
|
clickable?: (editor: Editor) => boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* For Placeholder */
|
/* For Placeholder */
|
||||||
.tiptap .is-empty::before {
|
|
||||||
|
.tiptap .is-empty:not(blockquote.is-empty)::before {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
float: left;
|
float: left;
|
||||||
height: 0;
|
height: 0;
|
||||||
color: var(--border-color-hover);
|
color: var(--color-muted-foreground);
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 0.25rem;
|
width: 0.4rem;
|
||||||
background-color: var(--blockquote-border);
|
background-color: var(--blockquote-border);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -208,8 +209,8 @@ input[type='checkbox'] {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
opacity: 100;
|
opacity: 100;
|
||||||
transition-property: opacity;
|
transition-property: all;
|
||||||
transition-duration: 200ms;
|
transition-duration: 300ms;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
|
||||||
color: var(--border-color-hover);
|
color: var(--border-color-hover);
|
||||||
}
|
}
|
||||||
@@ -452,3 +453,41 @@ input[type='checkbox'] {
|
|||||||
.tiptap iframe {
|
.tiptap iframe {
|
||||||
aspect-ratio: 16 / 9;
|
aspect-ratio: 16 / 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mathematics extension styles */
|
||||||
|
.tiptap .tiptap-mathematics-render {
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editable math block */
|
||||||
|
.tiptap .tiptap-mathematics-render--editable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap .tiptap-mathematics-render--editable:hover {
|
||||||
|
background: var(--code-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline math */
|
||||||
|
.tiptap .tiptap-mathematics-render[data-type='inline-math'] {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Block math */
|
||||||
|
.tiptap .tiptap-mathematics-render[data-type='block-math'] {
|
||||||
|
display: block;
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error styles */
|
||||||
|
.tiptap .tiptap-mathematics-render.inline-math-error,
|
||||||
|
.tiptap .tiptap-mathematics-render.block-math-error {
|
||||||
|
background: var(--code-bg);
|
||||||
|
color: var(--color-destructive);
|
||||||
|
border: 1px solid darkred;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import SearchAndReplace from './extensions/FindAndReplace';
|
|||||||
import { TaskItem, TaskList } from '@tiptap/extension-list';
|
import { TaskItem, TaskList } from '@tiptap/extension-list';
|
||||||
import { Table, TableCell, TableRow, TableHeader } from './extensions/table';
|
import { Table, TableCell, TableRow, TableHeader } from './extensions/table';
|
||||||
import { Placeholder } from '@tiptap/extensions';
|
import { Placeholder } from '@tiptap/extensions';
|
||||||
import { Markdown } from 'tiptap-markdown';
|
import { Markdown } from '@tiptap/markdown';
|
||||||
import MathExtension from '@aarkue/tiptap-math-extension';
|
import MathMatics from '@tiptap/extension-mathematics';
|
||||||
|
|
||||||
import AutoJoiner from 'tiptap-extension-auto-joiner';
|
import AutoJoiner from 'tiptap-extension-auto-joiner';
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
|
import { InlineMathReplacer } from './extensions/InlineMathReplacer';
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
element?: HTMLElement,
|
element?: HTMLElement,
|
||||||
@@ -82,22 +84,41 @@ export default (
|
|||||||
nested: true
|
nested: true
|
||||||
}),
|
}),
|
||||||
SearchAndReplace,
|
SearchAndReplace,
|
||||||
MathExtension.configure({ evaluation: true }),
|
InlineMathReplacer,
|
||||||
|
MathMatics.configure({
|
||||||
|
blockOptions: {
|
||||||
|
onClick: (node, pos) => {
|
||||||
|
const newCalculation = prompt('Enter new calculation:', node.attrs.latex);
|
||||||
|
if (newCalculation) {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.setNodeSelection(pos)
|
||||||
|
.updateBlockMath({ latex: newCalculation })
|
||||||
|
.focus()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inlineOptions: {
|
||||||
|
onClick: (node, pos) => {
|
||||||
|
const newCalculation = prompt('Enter new calculation:', node.attrs.latex);
|
||||||
|
if (newCalculation) {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.setNodeSelection(pos)
|
||||||
|
.updateInlineMath({ latex: newCalculation })
|
||||||
|
.focus()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
AutoJoiner,
|
AutoJoiner,
|
||||||
Table,
|
Table,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableCell,
|
TableCell,
|
||||||
Markdown.configure({
|
Markdown,
|
||||||
html: true,
|
|
||||||
tightLists: true,
|
|
||||||
tightListClass: 'tight',
|
|
||||||
bulletListMarker: '-',
|
|
||||||
linkify: true,
|
|
||||||
breaks: true,
|
|
||||||
transformPastedText: true,
|
|
||||||
transformCopiedText: false
|
|
||||||
}),
|
|
||||||
|
|
||||||
...(extensions ?? [])
|
...(extensions ?? [])
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { textInputRule } from '@tiptap/core';
|
||||||
|
import { InlineMath } from '@tiptap/extension-mathematics';
|
||||||
|
|
||||||
|
export const InlineMathReplacer = InlineMath.extend({
|
||||||
|
name: 'inlineMathReplacer',
|
||||||
|
addInputRules() {
|
||||||
|
return [
|
||||||
|
textInputRule({
|
||||||
|
find: /\$\$([^$]+)\$\$/,
|
||||||
|
replace: ({ match, commands }) => {
|
||||||
|
const latex = match[1];
|
||||||
|
// Insert the inline math node with the LaTeX content
|
||||||
|
commands.insertInlineMath({
|
||||||
|
latex
|
||||||
|
});
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -42,6 +42,7 @@ export const GROUPS: Group[] = [
|
|||||||
actions: [
|
actions: [
|
||||||
...commands.media,
|
...commands.media,
|
||||||
...commands.table,
|
...commands.table,
|
||||||
|
...commands.math,
|
||||||
{
|
{
|
||||||
icon: Minus,
|
icon: Minus,
|
||||||
name: 'horizontalRule',
|
name: 'horizontalRule',
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
import Duplicate from '@lucide/svelte/icons/copy';
|
import Duplicate from '@lucide/svelte/icons/copy';
|
||||||
import Clipboard from '@lucide/svelte/icons/clipboard';
|
import Clipboard from '@lucide/svelte/icons/clipboard';
|
||||||
import Delete from '@lucide/svelte/icons/trash-2';
|
import Delete from '@lucide/svelte/icons/trash-2';
|
||||||
|
import Repeat2 from '@lucide/svelte/icons/repeat-2';
|
||||||
|
import Plus from '@lucide/svelte/icons/plus';
|
||||||
import { NodeSelection } from '@tiptap/pm/state';
|
import { NodeSelection } from '@tiptap/pm/state';
|
||||||
|
import commands from '../commands/toolbar-commands';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
@@ -21,6 +24,9 @@
|
|||||||
let currentNode: Node | null = $state(null);
|
let currentNode: Node | null = $state(null);
|
||||||
let currentNodePos: number = $state(-1);
|
let currentNodePos: number = $state(-1);
|
||||||
let open = $state(false);
|
let open = $state(false);
|
||||||
|
const turnIntoCommand = Object.values(commands)
|
||||||
|
.flat()
|
||||||
|
.filter((c) => c.turnInto !== undefined);
|
||||||
|
|
||||||
const pluginKey = 'globalDragHandle';
|
const pluginKey = 'globalDragHandle';
|
||||||
|
|
||||||
@@ -71,10 +77,39 @@
|
|||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
editor.chain().setMeta('hideDragHandle', true).setNodeSelection(currentNodePos).deleteSelection().run();
|
editor.chain().setMeta('hideDragHandle', true).setNodeSelection(currentNodePos).deleteSelection().run();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddNodeNext = () => {
|
||||||
|
if (currentNodePos === -1) return;
|
||||||
|
const currentNodeSize = currentNode?.nodeSize || 0;
|
||||||
|
const insertPos = currentNodePos + currentNodeSize;
|
||||||
|
const currentNodeIsEmptyParagraph =
|
||||||
|
currentNode?.type.name === 'paragraph' && currentNode?.content?.size === 0;
|
||||||
|
const focusPos = currentNodeIsEmptyParagraph ? currentNodePos + 2 : insertPos + 2;
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.command(({ dispatch, tr, state }) => {
|
||||||
|
if (dispatch) {
|
||||||
|
if (currentNodeIsEmptyParagraph) {
|
||||||
|
tr.insertText('/', currentNodePos, currentNodePos + 1);
|
||||||
|
} else {
|
||||||
|
tr.insert(
|
||||||
|
insertPos,
|
||||||
|
state.schema.nodes.paragraph.create(null, [state.schema.text('/')])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dispatch(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.focus(focusPos)
|
||||||
|
.run();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="drag-handle">
|
<div class="drag-handle">
|
||||||
<Button variant="ghost" class="ui:rounded-sm ui:p-0 !size-6" onclick={() => (open = true)}>
|
<Button variant="ghost" class="!ui:size-6 ui:rounded-sm ui:p-0" onclick={() => (open = true)}>
|
||||||
<GripVertical />
|
<GripVertical />
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownMenu.Root bind:open>
|
<DropdownMenu.Root bind:open>
|
||||||
@@ -82,10 +117,43 @@
|
|||||||
<span>Drag Handle</span>
|
<span>Drag Handle</span>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
<DropdownMenu.Content>
|
<DropdownMenu.Content>
|
||||||
|
<DropdownMenu.Group>
|
||||||
|
<DropdownMenu.GroupHeading class="ui:text-muted-foreground ui:capitalize">
|
||||||
|
{currentNode?.type.name}
|
||||||
|
</DropdownMenu.GroupHeading>
|
||||||
|
<DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.SubTrigger openDelay={300}>
|
||||||
|
<Repeat2 />
|
||||||
|
Turn Into
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
|
<DropdownMenu.SubContent class="ui:max-h-96 ui:overflow-auto ui:duration-300">
|
||||||
|
{#each turnIntoCommand as command (command)}
|
||||||
|
{@const Icon = command.icon}
|
||||||
|
<DropdownMenu.Item
|
||||||
|
onclick={() => {
|
||||||
|
if (currentNode && currentNodePos) command.turnInto?.(editor, currentNodePos);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon />
|
||||||
|
<span>{command.tooltip}</span>
|
||||||
|
<DropdownMenu.Shortcut class="ui:bg-background ui:rounded ui:border ui:p-0.5"
|
||||||
|
>{command.shortCut}</DropdownMenu.Shortcut
|
||||||
|
>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
{/each}
|
||||||
|
</DropdownMenu.SubContent>
|
||||||
|
</DropdownMenu.Sub>
|
||||||
|
</DropdownMenu.Group>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
|
<DropdownMenu.Item onclick={handleAddNodeNext}>
|
||||||
|
<Plus />
|
||||||
|
Add Node
|
||||||
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item onclick={handleRemoveFormatting}>
|
<DropdownMenu.Item onclick={handleRemoveFormatting}>
|
||||||
<RemoveFormatting />
|
<RemoveFormatting />
|
||||||
Remove Formatting
|
Remove Formatting
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
<DropdownMenu.Item onclick={handleDuplicate}>
|
<DropdownMenu.Item onclick={handleDuplicate}>
|
||||||
<Duplicate />
|
<Duplicate />
|
||||||
Duplicate Node
|
Duplicate Node
|
||||||
@@ -94,6 +162,7 @@
|
|||||||
<Clipboard />
|
<Clipboard />
|
||||||
Copy to clipboard
|
Copy to clipboard
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Separator />
|
||||||
<DropdownMenu.Item onclick={handleDelete}>
|
<DropdownMenu.Item onclick={handleDelete}>
|
||||||
<Delete class="ui:text-destructive" />
|
<Delete class="ui:text-destructive" />
|
||||||
Delete Node
|
Delete Node
|
||||||
|
|||||||
@@ -106,7 +106,11 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{@const commandGroup = commands[cmd]}
|
{@const commandGroup = commands[cmd]}
|
||||||
{#each commandGroup as command (command)}
|
{#each commandGroup as command (command)}
|
||||||
|
{#if command.name === 'paragraph'}
|
||||||
|
<span></span>
|
||||||
|
{:else}
|
||||||
<ToolBarIcon {editor} {command} />
|
<ToolBarIcon {editor} {command} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
Reference in New Issue
Block a user