feat: WordPress tarzı rich editor (TipTap + slash menu + MediaPicker)
WordPress Gutenberg + Notion karışımı blok editor. 4 admin formunda markdown textarea yerine gerçek WYSIWYG editor. RichEditor component (components/admin/rich-editor.tsx): - TipTap v3 (@tiptap/react + starter-kit + link + image + placeholder + underline) - Üst toolbar (her zaman görünür): - B / I / U (bold, italic, underline) - H1 / H2 / H3 - Bullet list / Ordered list / Quote / Code block - Link (URL prompt) - Görsel ekle (MediaPicker modal) - Undo / Redo - Slash menu: '/' yazınca blok seçim menüsü açılır - Notion tarzı keyboard navigation (↓↑ Enter Esc) - 8 blok tipi: H1/H2/H3/ul/ol/quote/code/hr - Image picker modal (toolbar görsel butonundan) - Mevcut MediaPicker'ı kullanır - 'Yeni görsel yükle' (progress bar ile) + 'Kütüphaneden seç' grid - HTML çıktı (hidden input ile form'a) - Mevcut content alanlarıyla backward compat Formlarda değişiklik (4 dosya): - app/admin/(protected)/blog/form.tsx → content - app/admin/(protected)/hizmetler/form.tsx → content - app/admin/(protected)/projeler/form.tsx → content - app/admin/(protected)/sektorler/form.tsx → content Public render (lib/content-render.ts): - renderContent() yardımcısı: - İçerik '<' ile başlıyorsa → HTML (direkt döner) - Aksi halde → markdown (marked.parse) - 4 detay sayfası bu helper'ı kullanıyor (blog/[slug], projeler/[slug], hizmetler/[slug], sektor/[slug]) - Eski markdown içerikler hala çalışıyor, yeni içerikler HTML olarak gelir 37 route, build temiz.
This commit is contained in:
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, Calendar } from "lucide-react";
|
||||
import { marked } from "marked";
|
||||
import { renderContent } from "@/lib/content-render";
|
||||
import { getPostBySlug } from "@/lib/data";
|
||||
import { buildMetadata } from "@/lib/seo";
|
||||
|
||||
@@ -36,7 +36,7 @@ export default async function BlogPostPage({
|
||||
const post = await getPostBySlug(slug);
|
||||
if (!post || post.status !== "published") notFound();
|
||||
|
||||
const html = post.content ? marked.parse(post.content, { async: false }) as string : "";
|
||||
const html = renderContent(post.content);
|
||||
|
||||
return (
|
||||
<article className="mx-auto max-w-3xl px-6 py-20">
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, ArrowRight, CheckCircle2 } from "lucide-react";
|
||||
import { marked } from "marked";
|
||||
import { renderContent } from "@/lib/content-render";
|
||||
import { getServiceBySlug, listProjects } from "@/lib/data";
|
||||
import { buildMetadata } from "@/lib/seo";
|
||||
import { Icon } from "@/components/icon";
|
||||
@@ -55,9 +55,7 @@ export default async function ServiceDetailPage({
|
||||
]);
|
||||
|
||||
const faqItems = parseFaq(service.faq);
|
||||
const html = service.content
|
||||
? (marked.parse(service.content, { async: false }) as string)
|
||||
: "";
|
||||
const html = renderContent(service.content);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowLeft, Building2, Calendar, Clock, ExternalLink, Tag } from "lucide-react";
|
||||
import { marked } from "marked";
|
||||
import { renderContent } from "@/lib/content-render";
|
||||
import { getProjectBySlug, listProjects } from "@/lib/data";
|
||||
import { buildMetadata } from "@/lib/seo";
|
||||
import { Gallery } from "@/components/gallery";
|
||||
@@ -54,9 +54,7 @@ export default async function ProjectDetailPage({
|
||||
const project = await getProjectBySlug(slug);
|
||||
if (!project) notFound();
|
||||
|
||||
const html = project.content
|
||||
? (marked.parse(project.content, { async: false }) as string)
|
||||
: "";
|
||||
const html = renderContent(project.content);
|
||||
|
||||
const metrics = parseMetrics(project.metrics);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { ArrowRight, ArrowLeft, CheckCircle2 } from "lucide-react";
|
||||
import { marked } from "marked";
|
||||
import { renderContent } from "@/lib/content-render";
|
||||
import {
|
||||
getIndustryBySlug,
|
||||
listProjects,
|
||||
@@ -65,9 +65,7 @@ export default async function IndustryPage({
|
||||
]);
|
||||
|
||||
const faqItems = parseFaq(industry.faq);
|
||||
const html = industry.content
|
||||
? (marked.parse(industry.content, { async: false }) as string)
|
||||
: "";
|
||||
const html = renderContent(industry.content);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Textarea,
|
||||
} from "@/components/admin/form";
|
||||
import { MediaPicker } from "@/components/admin/media-picker";
|
||||
import { RichEditor } from "@/components/admin/rich-editor";
|
||||
import { saveBlogPost } from "@/lib/admin-actions";
|
||||
import type { BlogPostRow } from "@/lib/types";
|
||||
import { Save } from "lucide-react";
|
||||
@@ -75,13 +76,19 @@ export function BlogForm({ post }: { post?: BlogPostRow }) {
|
||||
rows={3}
|
||||
placeholder="Liste/kart görünümünde gösterilecek kısa özet"
|
||||
/>
|
||||
<Textarea
|
||||
label="İçerik (Markdown)"
|
||||
name="content"
|
||||
defaultValue={post?.content}
|
||||
rows={14}
|
||||
placeholder={"# Başlık\n\nMarkdown desteklenir…"}
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-[var(--navy)]">
|
||||
İçerik
|
||||
</span>
|
||||
<div className="mt-1.5">
|
||||
<RichEditor
|
||||
name="content"
|
||||
defaultValue={post?.content}
|
||||
placeholder="Yazıya başlayın… `/` ile başlık, görsel, liste ekleyin"
|
||||
minHeight={500}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MediaPicker
|
||||
label="Kapak görseli"
|
||||
name="cover_image"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Textarea,
|
||||
} from "@/components/admin/form";
|
||||
import { MediaPicker } from "@/components/admin/media-picker";
|
||||
import { RichEditor } from "@/components/admin/rich-editor";
|
||||
import { saveService } from "@/lib/admin-actions";
|
||||
import type { FaqItem, ServiceRow } from "@/lib/types";
|
||||
|
||||
@@ -90,14 +91,21 @@ export function ServiceForm({ service }: { service?: ServiceRow }) {
|
||||
help="Listede ve anasayfa kartında gösterilir."
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Detay içerik (Markdown)"
|
||||
name="content"
|
||||
defaultValue={service?.content}
|
||||
rows={10}
|
||||
placeholder={"## Yaklaşım\n\nMarkdown desteklenir…"}
|
||||
help="Hizmet detay sayfasında ana içerik olarak gösterilir."
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-[var(--navy)]">
|
||||
Detay içerik
|
||||
</span>
|
||||
<div className="mt-1.5">
|
||||
<RichEditor
|
||||
name="content"
|
||||
defaultValue={service?.content}
|
||||
placeholder="Hizmetin detaylarını anlatın… `/` ile blok ekleyin"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-[var(--muted)]">
|
||||
Hizmet detay sayfasında ana içerik olarak gösterilir.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
label="Özellikler"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Textarea,
|
||||
} from "@/components/admin/form";
|
||||
import { MediaPicker } from "@/components/admin/media-picker";
|
||||
import { RichEditor } from "@/components/admin/rich-editor";
|
||||
import { saveProject } from "@/lib/admin-actions";
|
||||
import { listServices } from "@/lib/data";
|
||||
import type { ProjectRow } from "@/lib/types";
|
||||
@@ -95,16 +96,21 @@ export async function ProjectForm({ project }: { project?: ProjectRow }) {
|
||||
rows={3}
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Vaka çalışması içeriği (Markdown)"
|
||||
name="content"
|
||||
defaultValue={project?.content}
|
||||
rows={12}
|
||||
placeholder={
|
||||
"## Müşteri\n\n## Problem\n\n## Çözüm\n\n## Sonuç"
|
||||
}
|
||||
help="Proje detay sayfasında uzun anlatım olarak gösterilir."
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-[var(--navy)]">
|
||||
Vaka çalışması içeriği
|
||||
</span>
|
||||
<div className="mt-1.5">
|
||||
<RichEditor
|
||||
name="content"
|
||||
defaultValue={project?.content}
|
||||
placeholder="Müşteri / Problem / Çözüm / Sonuç…"
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-[var(--muted)]">
|
||||
Proje detay sayfasında uzun anlatım olarak gösterilir.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<MediaPicker
|
||||
label="Galeri görselleri"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Textarea,
|
||||
} from "@/components/admin/form";
|
||||
import { MediaPicker } from "@/components/admin/media-picker";
|
||||
import { RichEditor } from "@/components/admin/rich-editor";
|
||||
import { saveIndustry } from "@/lib/admin-actions";
|
||||
import type { FaqItem, IndustryRow } from "@/lib/types";
|
||||
|
||||
@@ -75,13 +76,18 @@ export function IndustryForm({ row }: { row?: IndustryRow }) {
|
||||
placeholder="Avukatlar için KVKK uyumlu, randevu sistemli, SEO odaklı modern web siteleri."
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="İçerik (Markdown)"
|
||||
name="content"
|
||||
rows={10}
|
||||
defaultValue={row?.content}
|
||||
placeholder="## Sektör özellikleri\n\nAvukatlar için..."
|
||||
/>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-[var(--navy)]">
|
||||
İçerik
|
||||
</span>
|
||||
<div className="mt-1.5">
|
||||
<RichEditor
|
||||
name="content"
|
||||
defaultValue={row?.content}
|
||||
placeholder="Sektörünüz için içerik yazın…"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
label="Özellikler"
|
||||
|
||||
@@ -0,0 +1,574 @@
|
||||
"use client";
|
||||
|
||||
import { useEditor, EditorContent } from "@tiptap/react";
|
||||
import StarterKit from "@tiptap/starter-kit";
|
||||
import Link from "@tiptap/extension-link";
|
||||
import Image from "@tiptap/extension-image";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import Underline from "@tiptap/extension-underline";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Bold,
|
||||
Italic,
|
||||
Underline as UnderlineIcon,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
List,
|
||||
ListOrdered,
|
||||
Quote,
|
||||
Code,
|
||||
Link as LinkIcon,
|
||||
Image as ImageIcon,
|
||||
Minus,
|
||||
Undo,
|
||||
Redo,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
interface BlockCommand {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
command: (e: ReturnType<typeof useEditor> & object) => void;
|
||||
}
|
||||
|
||||
const SLASH_BLOCKS: BlockCommand[] = [
|
||||
{
|
||||
id: "h1",
|
||||
title: "Başlık 1",
|
||||
description: "Büyük başlık",
|
||||
icon: Heading1,
|
||||
command: (e) => e.chain().focus().deleteRange({ from: 0, to: 0 }).toggleHeading({ level: 1 }).run(),
|
||||
},
|
||||
{
|
||||
id: "h2",
|
||||
title: "Başlık 2",
|
||||
description: "Orta başlık",
|
||||
icon: Heading2,
|
||||
command: (e) => e.chain().focus().toggleHeading({ level: 2 }).run(),
|
||||
},
|
||||
{
|
||||
id: "h3",
|
||||
title: "Başlık 3",
|
||||
description: "Küçük başlık",
|
||||
icon: Heading3,
|
||||
command: (e) => e.chain().focus().toggleHeading({ level: 3 }).run(),
|
||||
},
|
||||
{
|
||||
id: "ul",
|
||||
title: "Madde listesi",
|
||||
description: "Bullet list",
|
||||
icon: List,
|
||||
command: (e) => e.chain().focus().toggleBulletList().run(),
|
||||
},
|
||||
{
|
||||
id: "ol",
|
||||
title: "Numaralı liste",
|
||||
description: "Ordered list",
|
||||
icon: ListOrdered,
|
||||
command: (e) => e.chain().focus().toggleOrderedList().run(),
|
||||
},
|
||||
{
|
||||
id: "quote",
|
||||
title: "Alıntı",
|
||||
description: "Blockquote",
|
||||
icon: Quote,
|
||||
command: (e) => e.chain().focus().toggleBlockquote().run(),
|
||||
},
|
||||
{
|
||||
id: "code",
|
||||
title: "Kod bloğu",
|
||||
description: "Code block",
|
||||
icon: Code,
|
||||
command: (e) => e.chain().focus().toggleCodeBlock().run(),
|
||||
},
|
||||
{
|
||||
id: "hr",
|
||||
title: "Ayırıcı",
|
||||
description: "Yatay çizgi",
|
||||
icon: Minus,
|
||||
command: (e) => e.chain().focus().setHorizontalRule().run(),
|
||||
},
|
||||
];
|
||||
|
||||
export function RichEditor({
|
||||
name,
|
||||
defaultValue,
|
||||
placeholder = "İçeriği yazmaya başlayın… `/` ile blok seç",
|
||||
minHeight = 400,
|
||||
}: {
|
||||
name: string;
|
||||
defaultValue?: string | null;
|
||||
placeholder?: string;
|
||||
minHeight?: number;
|
||||
}) {
|
||||
const [html, setHtml] = useState<string>(defaultValue ?? "");
|
||||
const [showSlash, setShowSlash] = useState(false);
|
||||
const [slashQuery, setSlashQuery] = useState("");
|
||||
const [slashIndex, setSlashIndex] = useState(0);
|
||||
const [imageModal, setImageModal] = useState(false);
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit.configure({
|
||||
heading: { levels: [1, 2, 3] },
|
||||
}),
|
||||
Underline,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
HTMLAttributes: { class: "text-[var(--sky-600)] underline" },
|
||||
}),
|
||||
Image.configure({
|
||||
HTMLAttributes: { class: "rounded-xl my-4" },
|
||||
}),
|
||||
Placeholder.configure({ placeholder }),
|
||||
],
|
||||
content: defaultValue || "",
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class:
|
||||
"prose prose-base max-w-none focus:outline-none px-6 py-6 min-h-[400px]",
|
||||
},
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
setHtml(editor.getHTML());
|
||||
},
|
||||
immediatelyRender: false,
|
||||
});
|
||||
|
||||
// Slash menu detection
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
const onUpdate = () => {
|
||||
const { $from } = editor.state.selection;
|
||||
const lineText = $from.parent.textContent;
|
||||
const beforeCursor = lineText.slice(0, $from.parentOffset);
|
||||
const match = beforeCursor.match(/\/(\w*)$/);
|
||||
if (match) {
|
||||
setShowSlash(true);
|
||||
setSlashQuery(match[1].toLowerCase());
|
||||
setSlashIndex(0);
|
||||
} else {
|
||||
setShowSlash(false);
|
||||
}
|
||||
};
|
||||
editor.on("update", onUpdate);
|
||||
editor.on("selectionUpdate", onUpdate);
|
||||
return () => {
|
||||
editor.off("update", onUpdate);
|
||||
editor.off("selectionUpdate", onUpdate);
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
const filteredBlocks = SLASH_BLOCKS.filter(
|
||||
(b) =>
|
||||
!slashQuery ||
|
||||
b.title.toLowerCase().includes(slashQuery) ||
|
||||
b.id.includes(slashQuery),
|
||||
);
|
||||
|
||||
// Slash menu keyboard
|
||||
useEffect(() => {
|
||||
if (!showSlash || !editor) return;
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
setShowSlash(false);
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
setSlashIndex((i) => Math.min(i + 1, filteredBlocks.length - 1));
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
setSlashIndex((i) => Math.max(i - 1, 0));
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
const block = filteredBlocks[slashIndex];
|
||||
if (block) {
|
||||
e.preventDefault();
|
||||
// Delete the slash + query first
|
||||
const { $from } = editor.state.selection;
|
||||
const from = $from.pos - (slashQuery.length + 1);
|
||||
editor.chain().focus().deleteRange({ from, to: $from.pos }).run();
|
||||
block.command(
|
||||
editor as unknown as ReturnType<typeof useEditor> & object,
|
||||
);
|
||||
setShowSlash(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handler, true);
|
||||
return () => window.removeEventListener("keydown", handler, true);
|
||||
}, [showSlash, slashIndex, filteredBlocks, editor, slashQuery]);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input type="hidden" name={name} value={html} />
|
||||
|
||||
<div className="overflow-hidden rounded-2xl border border-[var(--border)] bg-white">
|
||||
{/* Top toolbar — always visible, fixed */}
|
||||
<div className="flex flex-wrap items-center gap-0.5 border-b border-[var(--border)] bg-[var(--navy-50)]/40 p-2">
|
||||
<ToolbarButton
|
||||
active={editor.isActive("bold")}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
icon={Bold}
|
||||
label="Kalın (⌘B)"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("italic")}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
icon={Italic}
|
||||
label="İtalik (⌘I)"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("underline")}
|
||||
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||
icon={UnderlineIcon}
|
||||
label="Altı çizili"
|
||||
/>
|
||||
<Divider />
|
||||
<ToolbarButton
|
||||
active={editor.isActive("heading", { level: 1 })}
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||
}
|
||||
icon={Heading1}
|
||||
label="Başlık 1"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("heading", { level: 2 })}
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||
}
|
||||
icon={Heading2}
|
||||
label="Başlık 2"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("heading", { level: 3 })}
|
||||
onClick={() =>
|
||||
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||
}
|
||||
icon={Heading3}
|
||||
label="Başlık 3"
|
||||
/>
|
||||
<Divider />
|
||||
<ToolbarButton
|
||||
active={editor.isActive("bulletList")}
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
icon={List}
|
||||
label="Madde listesi"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("orderedList")}
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
icon={ListOrdered}
|
||||
label="Numaralı liste"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("blockquote")}
|
||||
onClick={() => editor.chain().focus().toggleBlockquote().run()}
|
||||
icon={Quote}
|
||||
label="Alıntı"
|
||||
/>
|
||||
<ToolbarButton
|
||||
active={editor.isActive("codeBlock")}
|
||||
onClick={() => editor.chain().focus().toggleCodeBlock().run()}
|
||||
icon={Code}
|
||||
label="Kod"
|
||||
/>
|
||||
<Divider />
|
||||
<ToolbarButton
|
||||
onClick={() => {
|
||||
const previousUrl = editor.getAttributes("link").href;
|
||||
const url = window.prompt("URL", previousUrl ?? "https://");
|
||||
if (url === null) return;
|
||||
if (url === "") {
|
||||
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
||||
return;
|
||||
}
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.extendMarkRange("link")
|
||||
.setLink({ href: url })
|
||||
.run();
|
||||
}}
|
||||
active={editor.isActive("link")}
|
||||
icon={LinkIcon}
|
||||
label="Bağlantı"
|
||||
/>
|
||||
<ToolbarButton
|
||||
onClick={() => setImageModal(true)}
|
||||
icon={ImageIcon}
|
||||
label="Görsel ekle"
|
||||
/>
|
||||
<Divider />
|
||||
<ToolbarButton
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
icon={Undo}
|
||||
label="Geri al"
|
||||
/>
|
||||
<ToolbarButton
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
icon={Redo}
|
||||
label="İleri al"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<EditorContent editor={editor} style={{ minHeight }} />
|
||||
|
||||
{showSlash && filteredBlocks.length > 0 && (
|
||||
<div className="absolute left-6 top-12 z-10 w-72 rounded-xl border border-[var(--border)] bg-white p-1 shadow-xl">
|
||||
<p className="px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-[var(--muted)]">
|
||||
Blok seç
|
||||
</p>
|
||||
{filteredBlocks.map((b, i) => {
|
||||
const Icon = b.icon;
|
||||
return (
|
||||
<button
|
||||
key={b.id}
|
||||
type="button"
|
||||
onMouseEnter={() => setSlashIndex(i)}
|
||||
onClick={() => {
|
||||
const { $from } = editor.state.selection;
|
||||
const from = $from.pos - (slashQuery.length + 1);
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.deleteRange({ from, to: $from.pos })
|
||||
.run();
|
||||
b.command(
|
||||
editor as unknown as ReturnType<typeof useEditor> &
|
||||
object,
|
||||
);
|
||||
setShowSlash(false);
|
||||
}}
|
||||
className={`flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-sm ${
|
||||
i === slashIndex
|
||||
? "bg-[var(--navy-50)]"
|
||||
: "hover:bg-[var(--navy-50)]/60"
|
||||
}`}
|
||||
>
|
||||
<div className="flex size-8 items-center justify-center rounded-md bg-white text-[var(--navy)]">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-[var(--navy)]">{b.title}</p>
|
||||
<p className="text-[11px] text-[var(--muted)]">
|
||||
{b.description}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Footer info */}
|
||||
<p className="mt-2 text-xs text-[var(--muted)]">
|
||||
‹/› ile blok ekle • Seçili metin için araç çubuğu otomatik açılır
|
||||
</p>
|
||||
|
||||
{imageModal && (
|
||||
<ImagePickerModal
|
||||
onClose={() => setImageModal(false)}
|
||||
onPick={(url) => {
|
||||
editor.chain().focus().setImage({ src: url }).run();
|
||||
setImageModal(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolbarButton({
|
||||
icon: Icon,
|
||||
label,
|
||||
active,
|
||||
onClick,
|
||||
}: {
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
active?: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
title={label}
|
||||
aria-label={label}
|
||||
className={`flex size-8 items-center justify-center rounded-md transition ${
|
||||
active
|
||||
? "bg-[var(--navy)] text-white"
|
||||
: "text-[var(--muted)] hover:bg-white hover:text-[var(--navy)]"
|
||||
}`}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Divider() {
|
||||
return <div className="mx-1 h-5 w-px bg-[var(--border)]" />;
|
||||
}
|
||||
|
||||
// ─── Image picker — uses media library API ───────────────
|
||||
|
||||
interface MediaFile {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
function ImagePickerModal({
|
||||
onClose,
|
||||
onPick,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onPick: (url: string) => void;
|
||||
}) {
|
||||
const [files, setFiles] = useState<MediaFile[] | null>(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/admin/media/list")
|
||||
.then((r) => r.json())
|
||||
.then((d) => setFiles(d.files || []))
|
||||
.catch(() => setFiles([]));
|
||||
}, []);
|
||||
|
||||
function handleFile(file: File) {
|
||||
setUploading(true);
|
||||
setProgress(0);
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/api/admin/media/upload");
|
||||
xhr.upload.addEventListener("progress", (e) => {
|
||||
if (e.lengthComputable) setProgress(Math.round((e.loaded / e.total) * 100));
|
||||
});
|
||||
xhr.onload = () => {
|
||||
try {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
if (data.url) {
|
||||
onPick(data.url);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
setUploading(false);
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
setUploading(false);
|
||||
};
|
||||
xhr.send(fd);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="w-full max-w-3xl rounded-2xl bg-white p-5 shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<h3 className="text-base font-semibold text-[var(--navy)]">
|
||||
Görsel ekle
|
||||
</h3>
|
||||
|
||||
<div className="mt-4 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
className="rounded-full bg-[var(--navy)] px-4 py-2 text-xs font-medium text-white hover:bg-[var(--navy-700)] disabled:opacity-60"
|
||||
>
|
||||
{uploading ? `Yükleniyor… ${progress}%` : "Yeni görsel yükle"}
|
||||
</button>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={(e) => {
|
||||
const f = e.target.files?.[0];
|
||||
if (f) handleFile(f);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{uploading && (
|
||||
<div className="mt-3 h-1.5 overflow-hidden rounded-full bg-[var(--navy-50)]">
|
||||
<div
|
||||
className="h-full bg-[var(--sky)] transition-all"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-5 max-h-[60vh] overflow-y-auto">
|
||||
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-[var(--muted)]">
|
||||
Kütüphane
|
||||
</p>
|
||||
{!files ? (
|
||||
<p className="py-8 text-center text-sm text-[var(--muted)]">
|
||||
Yükleniyor…
|
||||
</p>
|
||||
) : files.length === 0 ? (
|
||||
<p className="py-8 text-center text-sm text-[var(--muted)]">
|
||||
Kütüphane boş. Yeni görsel yükleyin.
|
||||
</p>
|
||||
) : (
|
||||
<div className="grid grid-cols-3 gap-2 sm:grid-cols-4 lg:grid-cols-5">
|
||||
{files.map((f) => (
|
||||
<button
|
||||
key={f.id}
|
||||
type="button"
|
||||
onClick={() => onPick(f.url)}
|
||||
className="aspect-square overflow-hidden rounded-lg border border-[var(--border)] transition hover:border-[var(--sky)]"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={f.url}
|
||||
alt={f.name}
|
||||
className="size-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-right">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded-full border border-[var(--border)] bg-white px-4 py-2 text-xs font-medium text-[var(--muted)] hover:text-[var(--navy)]"
|
||||
>
|
||||
Kapat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { marked } from "marked";
|
||||
|
||||
/**
|
||||
* Akıllı içerik render — RichEditor HTML üretir, eski içerikler markdown olabilir.
|
||||
* - HTML işareti (`<p>`, `<h1>` vs ile başlıyor) varsa direkt döner
|
||||
* - Aksi halde markdown olarak parse eder
|
||||
*/
|
||||
export function renderContent(content?: string | null): string {
|
||||
if (!content) return "";
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) return "";
|
||||
// HTML: ilk karakter '<' ise ve içinde HTML tag varsa
|
||||
if (trimmed.startsWith("<") && /<\w+[^>]*>/.test(trimmed)) {
|
||||
return trimmed;
|
||||
}
|
||||
return marked.parse(trimmed, { async: false }) as string;
|
||||
}
|
||||
Generated
+679
-3
@@ -8,6 +8,13 @@
|
||||
"name": "kovak-yazilim",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@tiptap/extension-image": "^3.23.5",
|
||||
"@tiptap/extension-link": "^3.23.5",
|
||||
"@tiptap/extension-placeholder": "^3.23.5",
|
||||
"@tiptap/extension-text-align": "^3.23.5",
|
||||
"@tiptap/extension-underline": "^3.23.5",
|
||||
"@tiptap/react": "^3.23.5",
|
||||
"@tiptap/starter-kit": "^3.23.5",
|
||||
"lucide-react": "^1.16.0",
|
||||
"marked": "^18.0.4",
|
||||
"next": "16.2.6",
|
||||
@@ -46,6 +53,34 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
|
||||
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.6",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
|
||||
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.5",
|
||||
"@floating-ui/utils": "^0.2.11"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
|
||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@img/colour": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
|
||||
@@ -1048,6 +1083,473 @@
|
||||
"tailwindcss": "4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.23.5.tgz",
|
||||
"integrity": "sha512-657Xqcgf1IYWLkAmRDJKNSGdoS1AHJEgK6zHWHFJERQGIqHnwC7Fz7nvWs/NQhQVBkclQd0ERRdTCZ3XwRc1+g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-blockquote": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.23.5.tgz",
|
||||
"integrity": "sha512-PBQRoGfSWfIY7HmGbS5PTHEBQl5nKbild5J5phPLFF+O3aOBQ0d49AC9cxbaou/6FRCtq6g4Uqse9rRTKJRM0w==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bold": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.23.5.tgz",
|
||||
"integrity": "sha512-DZsDCCf53fA9HmsFzfUHl5jLOwDYf+XzfP+QJjJ4cK23SsxDirameTjgnwi4l1EgEPLWunMZQjU+wHmh7vvX6Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bubble-menu": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.23.5.tgz",
|
||||
"integrity": "sha512-otcGwyVO6OfxdDPnbooZxYGrb+6q5WYmS+g2V+XGGNRn5oJgyY5pW0dqELIUJ66dosIIXXPyw2XqBDpMMY2kyQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-bullet-list": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.23.5.tgz",
|
||||
"integrity": "sha512-o0bzZbFvOPhPX6+RAhIFPKMIN3jIenY6Ib3FJ6ZqxTdVcjuV2mIXUmJU0uV2BwKtz73GmKSRKRKia6KJ0ml8qA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extension-list": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.23.5.tgz",
|
||||
"integrity": "sha512-NOJUD2Z0hrtBWnovXiiH1XtOjEQePOfIG3bNJgXSs1bWxPVhqp6KjVd8mUJNra974hxbml3tC97sL9QqjpAWFg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-code-block": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.23.5.tgz",
|
||||
"integrity": "sha512-P2XH8WPM4UahavcWoQgAwNAKQCbF/JWi6ZqgsQmVBfAqQ3mf8gMxB7HnciMq1DlyI9EfjXoJH11yUqldF/6AaQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-document": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.23.5.tgz",
|
||||
"integrity": "sha512-Y7uPjEM1xIK4Spcdk/kp/vZ/Az3cEaglTCk6uHrWvNFVglEoGehNb6IQbQFZW0wjE19YoMIiLBLtG6V9dqrpBw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-dropcursor": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.23.5.tgz",
|
||||
"integrity": "sha512-l72R798Q69D6f89Vp9xreoRnPcpK0LHPKLZIc6pvqBC2iOjx5wLKtW0uP1uqVWdQtvF5AUYBRNIGAQ5Gel9XEg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extensions": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-floating-menu": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.23.5.tgz",
|
||||
"integrity": "sha512-kP0bZKH/lxNogfvoIy/YJZ5gkty0OwqFVtQUwoc85vXYUfvy5Jh1VdO053tCE1iDzmvOITUpcb+MdWryP8dBxA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@floating-ui/dom": "^1.0.0",
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-gapcursor": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.23.5.tgz",
|
||||
"integrity": "sha512-x9XlYG26TowX0Ly1w0ZV2D8qliyQy9fTmMY4suI6B/6o6m/sXHGTAJMmJqwP66sZKF6cMLU3HECumhtyQxPT2g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extensions": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-hard-break": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.23.5.tgz",
|
||||
"integrity": "sha512-j/BDBMOA1mA+RhCx622SRPBhpp2XWNFYz9asbg8T3yk8v9WI3Vjo6IDlfTp6fwsR2LGE7Pek3R0xDAjW6yVG3g==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-heading": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.23.5.tgz",
|
||||
"integrity": "sha512-tFI+iYk34geacVOGqYgyoC8siQjdGn605XaYSZcGRFF8NY+HrGlLkQi2QRRCeLaUhxoctONmWc8USn3H5U7wLQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-horizontal-rule": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.23.5.tgz",
|
||||
"integrity": "sha512-9XkRYc4XE0stERZB3y8bsJd32Jw9UZfMwZXo1GLNYRHFr7dmhSGUj0IzgofqOVmLDcOMW6XcCk54TBYw6BCrWA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-image": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-3.23.5.tgz",
|
||||
"integrity": "sha512-v6u9zbJSKLjml6DDn1/1WOOIzVxz3K5Idl1EgUl+IpJH7kR1HLRJ3TaSgF7z2RRQmqyHlmtdCzdaKoe0jCIyqQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-italic": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.23.5.tgz",
|
||||
"integrity": "sha512-XjRSPr6j4mz+8O5j5KNfxVb+1fGNt0wr+js6MLxxGdU7M+PoDPdVY6fARbmBazv4ERlZ5PNS9m35Vo5xDjDfrg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-link": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.23.5.tgz",
|
||||
"integrity": "sha512-FEI58NAPnauBbs4nw1dkgRyEhcWnure0vIlStfQoQGXxj3xSRvxKH2lOkz54fGzuzRJAoudyLU65HW6D7kc+8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"linkifyjs": "^4.3.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.23.5.tgz",
|
||||
"integrity": "sha512-nzZXpVwnyKwTj4TVyPyu1bCUFjJCsaXnhAthmvJDnX3RBtemNG9Ka07xGR2NIspzumSbQSMFtDxjmxv3W5dEtg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-item": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.23.5.tgz",
|
||||
"integrity": "sha512-l7Hb4rfNIkO6JrNJYkdXap6QYXCz4XeeFmI1bfQgEiwPGs+RAn/+0cOdg7q+6MmtZFac5uSXV0PftPk6A0GsEA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extension-list": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-list-keymap": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.23.5.tgz",
|
||||
"integrity": "sha512-Hz8jRA51VSiHezEkwqwaMYbTEYcR/5Aq3UgCgDlNPlE6k1OZrvRtV/4s3AOO0RRgzyVLKv7yv7KuOJN/OLGErw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extension-list": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-ordered-list": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.23.5.tgz",
|
||||
"integrity": "sha512-qQeU71ij0cAAD9bbGqot5T5bpR3dysgQ+W67quRs6VDyusU89EYaJHKn/qWU6a1XOEQ4sL+5GNw52FYQVHUxbA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extension-list": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-paragraph": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.23.5.tgz",
|
||||
"integrity": "sha512-LtgMcR1rvWnZDtphFJ/LBltlC0+6HGA07k7vhy+U7P/zIg/V3Fb4RD6YDuAo0cPfBsLm8p1WYJV92WpAsGgtlg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-placeholder": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.23.5.tgz",
|
||||
"integrity": "sha512-B2snUujc6fb/16p8jSQCN4+mto7RlHKLm8quBTUWXksY8D82u/cxjUdmRQ7ueq7vsbRsA+WoJTrKEjJ8RQOpjw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/extensions": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-strike": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.23.5.tgz",
|
||||
"integrity": "sha512-PMB9lpQGOJGuRTIS9rBw8UZtHQwmsiJbWKjcBr5z20MluaJQ3ZCHFhDYG6ncIDRz+0ny4ZvoJ7cKGpI+NTvXMA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.23.5.tgz",
|
||||
"integrity": "sha512-GLa+AaA2NC5XYRZad/Qq/oH5Pa95s+uA17J7+RCkF8j1RNREUBkYQ5CD5MT8kT+D3DHgU8MRyYdTd28I46HBDQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-text-align": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.23.5.tgz",
|
||||
"integrity": "sha512-eOeXrbpPWc6gfXli2aXYg9t61HhkvEkdxQgpEpZPFhrT4pPQcIqTlihswByC+cPb8B5ynrc/iamiY9cRSU1qvw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extension-underline": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.23.5.tgz",
|
||||
"integrity": "sha512-fyxthzE6CNCi9a9OVAwXs1sSyJ7jlrzT3aP2KhYLQCsJABHaPJgJA7k52/CRuKqCW3WbxU1ULH9LGuGtBbhEyw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/extensions": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.23.5.tgz",
|
||||
"integrity": "sha512-ROcdNPV+buzldEFKvD3o29P7H7zpAf2lnLfndO2LHSToWyHw4hlzVPCeAU8uAvhl/jyfeUoFLrBwxphMX/KG6A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/pm": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.23.5.tgz",
|
||||
"integrity": "sha512-9tgLdpTvNN0/fLP4RcNzbyQ0qjg9J2ahaFbQzgV5uvd+QMy8Xkg2IqKKnOoJJUAV3FDjGq3Yx0WrV2BGro9pfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-changeset": "^2.3.0",
|
||||
"prosemirror-commands": "^1.6.2",
|
||||
"prosemirror-dropcursor": "^1.8.1",
|
||||
"prosemirror-gapcursor": "^1.3.2",
|
||||
"prosemirror-history": "^1.4.1",
|
||||
"prosemirror-keymap": "^1.2.2",
|
||||
"prosemirror-model": "^1.24.1",
|
||||
"prosemirror-schema-list": "^1.5.0",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-tables": "^1.6.4",
|
||||
"prosemirror-transform": "^1.10.2",
|
||||
"prosemirror-view": "^1.38.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/react": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.23.5.tgz",
|
||||
"integrity": "sha512-aEdKfJxoa6tCEV4FrnBqMQoUPwGcTWLaDzmP4fL1gR7E40rYDTiYNKoF1Ob+UimUpguAP6Emv1WlJa5oyI8FSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"fast-equals": "^5.3.3",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tiptap/extension-bubble-menu": "^3.23.5",
|
||||
"@tiptap/extension-floating-menu": "^3.23.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "3.23.5",
|
||||
"@tiptap/pm": "3.23.5",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/starter-kit": {
|
||||
"version": "3.23.5",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.23.5.tgz",
|
||||
"integrity": "sha512-ac0edQ1a1nYkNAzOgdqIBKGdrOlNQpPP9wGAG3Q9EgTq4+C4/EftJZZJmUn3KzaSOUv4cLEDo0z0jurJvZPkaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tiptap/core": "^3.23.5",
|
||||
"@tiptap/extension-blockquote": "^3.23.5",
|
||||
"@tiptap/extension-bold": "^3.23.5",
|
||||
"@tiptap/extension-bullet-list": "^3.23.5",
|
||||
"@tiptap/extension-code": "^3.23.5",
|
||||
"@tiptap/extension-code-block": "^3.23.5",
|
||||
"@tiptap/extension-document": "^3.23.5",
|
||||
"@tiptap/extension-dropcursor": "^3.23.5",
|
||||
"@tiptap/extension-gapcursor": "^3.23.5",
|
||||
"@tiptap/extension-hard-break": "^3.23.5",
|
||||
"@tiptap/extension-heading": "^3.23.5",
|
||||
"@tiptap/extension-horizontal-rule": "^3.23.5",
|
||||
"@tiptap/extension-italic": "^3.23.5",
|
||||
"@tiptap/extension-link": "^3.23.5",
|
||||
"@tiptap/extension-list": "^3.23.5",
|
||||
"@tiptap/extension-list-item": "^3.23.5",
|
||||
"@tiptap/extension-list-keymap": "^3.23.5",
|
||||
"@tiptap/extension-ordered-list": "^3.23.5",
|
||||
"@tiptap/extension-paragraph": "^3.23.5",
|
||||
"@tiptap/extension-strike": "^3.23.5",
|
||||
"@tiptap/extension-text": "^3.23.5",
|
||||
"@tiptap/extension-underline": "^3.23.5",
|
||||
"@tiptap/extensions": "^3.23.5",
|
||||
"@tiptap/pm": "^3.23.5"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.41",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
|
||||
@@ -1062,7 +1564,6 @@
|
||||
"version": "19.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.15.tgz",
|
||||
"integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -1072,12 +1573,17 @@
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.31",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.31.tgz",
|
||||
@@ -1120,7 +1626,6 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
@@ -1147,6 +1652,15 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
|
||||
"integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -1437,6 +1951,12 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/linkifyjs": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz",
|
||||
"integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.16.0.tgz",
|
||||
@@ -1567,6 +2087,12 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/orderedmap": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
|
||||
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1602,6 +2128,135 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-changeset": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz",
|
||||
"integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-transform": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-commands": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
|
||||
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.10.2"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-dropcursor": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
|
||||
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.1.0",
|
||||
"prosemirror-view": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-gapcursor": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
|
||||
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-keymap": "^1.0.0",
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-view": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-history": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
|
||||
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.2.2",
|
||||
"prosemirror-transform": "^1.0.0",
|
||||
"prosemirror-view": "^1.31.0",
|
||||
"rope-sequence": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-keymap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
|
||||
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"w3c-keyname": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-model": {
|
||||
"version": "1.25.7",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.7.tgz",
|
||||
"integrity": "sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"orderedmap": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-schema-list": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
|
||||
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-state": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
|
||||
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.0.0",
|
||||
"prosemirror-transform": "^1.0.0",
|
||||
"prosemirror-view": "^1.27.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-tables": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
|
||||
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-keymap": "^1.2.3",
|
||||
"prosemirror-model": "^1.25.4",
|
||||
"prosemirror-state": "^1.4.4",
|
||||
"prosemirror-transform": "^1.10.5",
|
||||
"prosemirror-view": "^1.41.4"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-transform": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
|
||||
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prosemirror-view": {
|
||||
"version": "1.41.8",
|
||||
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz",
|
||||
"integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prosemirror-model": "^1.20.0",
|
||||
"prosemirror-state": "^1.0.0",
|
||||
"prosemirror-transform": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
@@ -1623,6 +2278,12 @@
|
||||
"react": "^19.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/rope-sequence": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
|
||||
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
@@ -1766,6 +2427,21 @@
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,13 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tiptap/extension-image": "^3.23.5",
|
||||
"@tiptap/extension-link": "^3.23.5",
|
||||
"@tiptap/extension-placeholder": "^3.23.5",
|
||||
"@tiptap/extension-text-align": "^3.23.5",
|
||||
"@tiptap/extension-underline": "^3.23.5",
|
||||
"@tiptap/react": "^3.23.5",
|
||||
"@tiptap/starter-kit": "^3.23.5",
|
||||
"lucide-react": "^1.16.0",
|
||||
"marked": "^18.0.4",
|
||||
"next": "16.2.6",
|
||||
|
||||
Reference in New Issue
Block a user