Add document node styling + extensions to handle clicking links with ctrl or cmd

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-18 16:16:10 +04:00
parent 6d18c7fce7
commit 21eb2e5197
2 changed files with 127 additions and 5 deletions

View File

@@ -14,23 +14,76 @@ import { Strike } from "@tiptap/extension-strike";
import { Text } from "@tiptap/extension-text";
import { Underline } from "@tiptap/extension-underline";
import { Dropcursor, Gapcursor, UndoRedo } from "@tiptap/extensions";
import { type Content, EditorContent, useEditor, useEditorState } from "@tiptap/react";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { type Content, EditorContent, getAttributes, useEditor, useEditorState } from "@tiptap/react";
import { BubbleMenu, FloatingMenu } from "@tiptap/react/menus";
import { type ComponentProps, useEffect } from "react";
import { tv } from "tailwind-variants";
export const ControlClickLink = Link.extend({
addProseMirrorPlugins: () => {
return [
new Plugin({
key: new PluginKey("handleControlClick"),
props: {
handleKeyDown: (view, event) => {
if (event.key === "Control" || event.key === "Meta") {
view.dom.classList.add("pointer-on-hovered-link");
}
},
handleDOMEvents: {
keyup: (view, event) => {
if (event.key === "Control" || event.key === "Meta") {
view.dom.classList.remove("pointer-on-hovered-link");
}
},
},
handleClick: (view, _, event) => {
const { ctrlKey, metaKey } = event; // Check for Ctrl (Windows) or Cmd (Mac)
const keyPressed = ctrlKey || metaKey;
if (keyPressed) {
// Get attributes of the mark at the clicked position
const attrs = getAttributes(view.state, "link");
const link = (event.target as Element | null)?.closest("a");
if (link && attrs.href) {
window.open(attrs.href as string, "_blank", "noopener,noreferrer"); // Open link in a new tab
return true; // Handle the event
}
}
return false; // Let other handlers run
},
},
}),
];
},
}).configure({
openOnClick: false,
});
const extensions = [
Document,
Paragraph,
Text,
Heading.configure({ levels: [1, 2, 3] }),
Paragraph.configure({
HTMLAttributes: {
class: "text-base py-2",
},
}),
Text.configure({
HTMLAttributes: {
class: "text-base",
},
}),
Heading.configure({
levels: [1, 2, 3],
}),
Bold,
Italic,
Strike,
Underline,
Code,
CodeBlock,
Link.configure({ openOnClick: false }),
ControlClickLink,
Blockquote,
BulletList,
OrderedList,
@@ -190,6 +243,11 @@ export function RichEditor(props: RichEditorProps) {
active={editor.isActive("orderedList")}
onClick={() => editor.chain().focus().toggleOrderedList().run()}
/>
<MenuButton
label="Code"
active={editor.isActive("code")}
onClick={() => editor.chain().focus().toggleCode().run()}
/>
<MenuButton
label="Code Block"
active={editor.isActive("codeBlock")}

View File

@@ -281,4 +281,68 @@ button[data-cell]:focus-visible {
.ProseMirror-focused {
outline-width: 0 !important;
outline-color: transparent !important;
outline-style: none !important;
}
.tiptap p { @apply py-2; }
.tiptap strong { @apply font-semibold; }
.tiptap h1 {
@apply text-4xl font-semibold pt-4;
}
.tiptap h2 {
@apply text-3xl font-semibold pt-3;
}
.tiptap h3 {
@apply text-2xl font-semibold pt-2;
}
.tiptap ul {
@apply list-disc list-inside py-2;
li {
p { @apply inline-block py-0 }
}
li::marker { @apply mx-2!; }
}
.tiptap ol {
@apply list-decimal list-inside py-2;
li {
p { @apply inline-block py-0 }
}
li::marker { @apply mx-2!; }
}
.tiptap a {
@apply text-txt-info underline font-normal;
}
.tiptap.pointer-on-hovered-link {
a:hover { cursor: pointer; }
}
.tiptap blockquote {
@apply py-2 pl-4 border-l-4 border-l-border-solid;
p { @apply py-0; }
}
.tiptap hr {
@apply my-4 border-t-border-solid border-t;
}
.tiptap pre {
@apply py-4 px-6 bg-border-solid rounded-lg text-sm;
}
.tiptap code {
@apply text-inherit font-mono bg-border-solid rounded-md;
}
.tiptap p code {
@apply py-0.5 px-1;
}