feat(web): add markdown round-trip and replace textarea with Tiptap (#501)
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #501.
This commit is contained in:
@@ -46,7 +46,8 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-grid-layout": "^2.2.2",
|
||||
"recharts": "^3.7.0",
|
||||
"socket.io-client": "^4.8.3"
|
||||
"socket.io-client": "^4.8.3",
|
||||
"tiptap-markdown": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mosaic/config": "workspace:*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useRef } from "react";
|
||||
import { LinkAutocomplete } from "./LinkAutocomplete";
|
||||
import React from "react";
|
||||
import { KnowledgeEditor } from "./KnowledgeEditor";
|
||||
|
||||
interface EntryEditorProps {
|
||||
content: string;
|
||||
@@ -9,57 +9,21 @@ interface EntryEditorProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* EntryEditor - Markdown editor with live preview and link autocomplete
|
||||
* EntryEditor - WYSIWYG editor for knowledge entries.
|
||||
* Wraps KnowledgeEditor (Tiptap) with markdown round-trip.
|
||||
* Content is stored as markdown; the editor provides rich text editing.
|
||||
*/
|
||||
export function EntryEditor({ content, onChange }: EntryEditorProps): React.JSX.Element {
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
return (
|
||||
<div className="entry-editor relative">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Content (Markdown)
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowPreview(!showPreview);
|
||||
}}
|
||||
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
{showPreview ? "Edit" : "Preview"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showPreview ? (
|
||||
<div className="prose prose-sm max-w-none dark:prose-invert p-4 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 min-h-[300px]">
|
||||
<div className="whitespace-pre-wrap">{content}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={content}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
className="w-full min-h-[300px] p-4 border border-gray-300 dark:border-gray-700 rounded-md bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 font-mono text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="Write your content here... (Markdown supported)"
|
||||
/>
|
||||
<LinkAutocomplete
|
||||
textareaRef={textareaRef}
|
||||
onInsert={(newContent) => {
|
||||
onChange(newContent);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
Supports Markdown formatting. Type <code className="text-xs">[[</code> to insert links to
|
||||
other entries.
|
||||
</p>
|
||||
<div className="entry-editor">
|
||||
<label className="block text-sm font-medium mb-2" style={{ color: "var(--text-2)" }}>
|
||||
Content
|
||||
</label>
|
||||
<KnowledgeEditor
|
||||
content={content}
|
||||
onChange={onChange}
|
||||
placeholder="Write your content here... Supports markdown formatting."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,20 @@ import { TableCell } from "@tiptap/extension-table-cell";
|
||||
import { TableHeader } from "@tiptap/extension-table-header";
|
||||
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
||||
import Placeholder from "@tiptap/extension-placeholder";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
import { common, createLowlight } from "lowlight";
|
||||
import type { Editor } from "@tiptap/react";
|
||||
import type { MarkdownStorage } from "tiptap-markdown";
|
||||
|
||||
import "./KnowledgeEditor.css";
|
||||
|
||||
const lowlight = createLowlight(common);
|
||||
|
||||
export interface KnowledgeEditorProps {
|
||||
/** HTML content for the editor */
|
||||
/** Markdown content for the editor */
|
||||
content: string;
|
||||
/** Called when editor content changes (provides HTML) */
|
||||
onChange: (html: string) => void;
|
||||
/** Called when editor content changes (provides markdown) */
|
||||
onChange: (markdown: string) => void;
|
||||
/** Placeholder text when editor is empty */
|
||||
placeholder?: string;
|
||||
/** Whether the editor is editable */
|
||||
@@ -366,7 +368,11 @@ export function KnowledgeEditor({
|
||||
}: KnowledgeEditorProps): ReactElement {
|
||||
const handleUpdate = useCallback(
|
||||
({ editor: e }: { editor: Editor }) => {
|
||||
onChange(e.getHTML());
|
||||
const s = e.storage as unknown as Record<string, MarkdownStorage>;
|
||||
const mdStorage = s.markdown;
|
||||
if (mdStorage) {
|
||||
onChange(mdStorage.getMarkdown());
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
@@ -395,6 +401,13 @@ export function KnowledgeEditor({
|
||||
Placeholder.configure({
|
||||
placeholder,
|
||||
}),
|
||||
Markdown.configure({
|
||||
html: true,
|
||||
breaks: false,
|
||||
tightLists: true,
|
||||
transformPastedText: true,
|
||||
transformCopiedText: true,
|
||||
}),
|
||||
],
|
||||
content,
|
||||
editable,
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { EntryEditor } from "../EntryEditor";
|
||||
|
||||
// Mock the LinkAutocomplete component
|
||||
vi.mock("../LinkAutocomplete", () => ({
|
||||
LinkAutocomplete: (): React.JSX.Element => (
|
||||
<div data-testid="link-autocomplete">LinkAutocomplete</div>
|
||||
// Mock KnowledgeEditor since Tiptap requires a full DOM
|
||||
vi.mock("../KnowledgeEditor", () => ({
|
||||
KnowledgeEditor: ({
|
||||
content,
|
||||
onChange,
|
||||
placeholder,
|
||||
}: {
|
||||
content: string;
|
||||
onChange: (md: string) => void;
|
||||
placeholder?: string;
|
||||
}): React.JSX.Element => (
|
||||
<div data-testid="knowledge-editor" data-content={content} data-placeholder={placeholder}>
|
||||
<button
|
||||
data-testid="trigger-change"
|
||||
onClick={(): void => {
|
||||
onChange("updated content");
|
||||
}}
|
||||
>
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -21,133 +37,50 @@ describe("EntryEditor", (): void => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should render textarea in edit mode by default", (): void => {
|
||||
it("should render KnowledgeEditor component", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
const textarea = screen.getByPlaceholderText(/Write your content here/);
|
||||
expect(textarea).toBeInTheDocument();
|
||||
expect(textarea.tagName).toBe("TEXTAREA");
|
||||
expect(screen.getByTestId("knowledge-editor")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display current content in textarea", (): void => {
|
||||
it("should have a content label", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should pass content to KnowledgeEditor", (): void => {
|
||||
const content = "# Test Content\n\nThis is a test.";
|
||||
render(<EntryEditor {...defaultProps} content={content} />);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const textarea = screen.getByPlaceholderText(/Write your content here/) as HTMLTextAreaElement;
|
||||
expect(textarea.value).toBe(content);
|
||||
const editor = screen.getByTestId("knowledge-editor");
|
||||
expect(editor).toHaveAttribute("data-content", content);
|
||||
});
|
||||
|
||||
it("should call onChange when content is modified", async (): Promise<void> => {
|
||||
it("should pass placeholder to KnowledgeEditor", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
const editor = screen.getByTestId("knowledge-editor");
|
||||
expect(editor).toHaveAttribute(
|
||||
"data-placeholder",
|
||||
"Write your content here... Supports markdown formatting."
|
||||
);
|
||||
});
|
||||
|
||||
it("should forward onChange to KnowledgeEditor", async (): Promise<void> => {
|
||||
const { default: userEvent } = await import("@testing-library/user-event");
|
||||
const user = userEvent.setup();
|
||||
const onChangeMock = vi.fn();
|
||||
|
||||
render(<EntryEditor {...defaultProps} onChange={onChangeMock} />);
|
||||
|
||||
const textarea = screen.getByPlaceholderText(/Write your content here/);
|
||||
await user.type(textarea, "Hello");
|
||||
|
||||
expect(onChangeMock).toHaveBeenCalled();
|
||||
await user.click(screen.getByTestId("trigger-change"));
|
||||
expect(onChangeMock).toHaveBeenCalledWith("updated content");
|
||||
});
|
||||
|
||||
it("should toggle between edit and preview modes", async (): Promise<void> => {
|
||||
const user = userEvent.setup();
|
||||
const content = "# Test\n\nPreview this content.";
|
||||
it("should render with entry-editor wrapper class", (): void => {
|
||||
const { container } = render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
render(<EntryEditor {...defaultProps} content={content} />);
|
||||
|
||||
// Initially in edit mode
|
||||
expect(screen.getByPlaceholderText(/Write your content here/)).toBeInTheDocument();
|
||||
expect(screen.getByText("Preview")).toBeInTheDocument();
|
||||
|
||||
// Switch to preview mode
|
||||
const previewButton = screen.getByText("Preview");
|
||||
await user.click(previewButton);
|
||||
|
||||
// Should show preview
|
||||
expect(screen.queryByPlaceholderText(/Write your content here/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Edit")).toBeInTheDocument();
|
||||
// Check for partial content (newlines may split text across elements)
|
||||
expect(screen.getByText(/Test/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Preview this content/)).toBeInTheDocument();
|
||||
|
||||
// Switch back to edit mode
|
||||
const editButton = screen.getByText("Edit");
|
||||
await user.click(editButton);
|
||||
|
||||
// Should show textarea again
|
||||
expect(screen.getByPlaceholderText(/Write your content here/)).toBeInTheDocument();
|
||||
expect(screen.getByText("Preview")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render LinkAutocomplete component in edit mode", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
expect(screen.getByTestId("link-autocomplete")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render LinkAutocomplete in preview mode", async (): Promise<void> => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
// LinkAutocomplete should be present in edit mode
|
||||
expect(screen.getByTestId("link-autocomplete")).toBeInTheDocument();
|
||||
|
||||
// Switch to preview mode
|
||||
const previewButton = screen.getByText("Preview");
|
||||
await user.click(previewButton);
|
||||
|
||||
// LinkAutocomplete should not be in preview mode
|
||||
expect(screen.queryByTestId("link-autocomplete")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show help text about wiki-link syntax", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText(/Type/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/\[\[/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/to insert links/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should maintain content when toggling between modes", async (): Promise<void> => {
|
||||
const user = userEvent.setup();
|
||||
const content = "# My Content\n\nThis should persist.";
|
||||
|
||||
render(<EntryEditor {...defaultProps} content={content} />);
|
||||
|
||||
// Verify content in edit mode
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const textarea = screen.getByPlaceholderText(/Write your content here/) as HTMLTextAreaElement;
|
||||
expect(textarea.value).toBe(content);
|
||||
|
||||
// Toggle to preview
|
||||
await user.click(screen.getByText("Preview"));
|
||||
// Check for partial content (newlines may split text across elements)
|
||||
expect(screen.getByText(/My Content/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/This should persist/)).toBeInTheDocument();
|
||||
|
||||
// Toggle back to edit
|
||||
await user.click(screen.getByText("Edit"));
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const textareaAfter = screen.getByPlaceholderText(
|
||||
/Write your content here/
|
||||
) as HTMLTextAreaElement;
|
||||
expect(textareaAfter.value).toBe(content);
|
||||
});
|
||||
|
||||
it("should apply correct styling classes", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
const textarea = screen.getByPlaceholderText(/Write your content here/);
|
||||
expect(textarea).toHaveClass("font-mono");
|
||||
expect(textarea).toHaveClass("text-sm");
|
||||
expect(textarea).toHaveClass("min-h-[300px]");
|
||||
});
|
||||
|
||||
it("should have label for content field", (): void => {
|
||||
render(<EntryEditor {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Content (Markdown)")).toBeInTheDocument();
|
||||
expect(container.querySelector(".entry-editor")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
| TW-WDG-003 | done | Widget picker UI — Drawer/dialog to browse available widgets from registry, preview size/description, add to dashboard | #488 | web | feat/ms18-widget-picker | TW-WDG-002 | TW-VER-001 | worker | 2026-02-23 | 2026-02-23 | 25K | ~12K | PR #498 merged |
|
||||
| TW-WDG-004 | done | Widget configuration UI — Per-widget settings dialog using configSchema, configure data source/filters/colors/title | #488 | web | feat/ms18-layout-management | TW-WDG-002 | TW-VER-001 | worker | 2026-02-23 | 2026-02-23 | 30K | ~8K | PR #499 merged (bundled with WDG-005) |
|
||||
| TW-WDG-005 | done | Layout management UI — Save/rename/switch/delete layouts, reset to default. UI controls in dashboard header area | #488 | web | feat/ms18-layout-management | TW-WDG-002 | TW-VER-001 | worker | 2026-02-23 | 2026-02-23 | 20K | ~8K | PR #499 merged (bundled with WDG-004) |
|
||||
| TW-EDT-001 | not-started | Tiptap integration — Install @tiptap/react + extensions, build KnowledgeEditor component with toolbar (headings, bold, italic, lists, code, links, tables) | #489 | web | TBD | TW-PLAN-001 | TW-EDT-002 | worker | — | — | 35K | — | |
|
||||
| TW-EDT-002 | not-started | Markdown round-trip + File Manager integration — Import markdown to Tiptap, export to markdown + HTML. Replace textarea in knowledge create/edit | #489 | web | TBD | TW-EDT-001 | TW-VER-001 | worker | — | — | 30K | — | |
|
||||
| TW-EDT-001 | done | Tiptap integration — Install @tiptap/react + extensions, build KnowledgeEditor component with toolbar (headings, bold, italic, lists, code, links, tables) | #489 | web | feat/ms18-tiptap-editor | TW-PLAN-001 | TW-EDT-002 | worker | 2026-02-23 | 2026-02-23 | 35K | ~12K | PR #500 merged |
|
||||
| TW-EDT-002 | done | Markdown round-trip + File Manager integration — Import markdown to Tiptap, export to markdown + HTML. Replace textarea in knowledge create/edit | #489 | web | feat/ms18-markdown-roundtrip | TW-EDT-001 | TW-VER-001 | worker | 2026-02-23 | 2026-02-23 | 30K | ~10K | PR #501 (pending) |
|
||||
| TW-KBN-001 | not-started | Kanban filtering — Add filter bar (project, assignee, priority, search). Support project-level and user-level views. URL param persistence | #490 | web | TBD | TW-PLAN-001 | TW-VER-001 | worker | — | — | 30K | — | |
|
||||
| TW-VER-001 | not-started | Tests — Unit tests for new components, update existing tests, fix any regressions | #491 | web | TBD | TW-WDG-003,TW-WDG-004,TW-WDG-005,TW-EDT-002,TW-KBN-001 | TW-VER-002,TW-DOC-001 | worker | — | — | 25K | — | |
|
||||
| TW-VER-002 | not-started | Theme verification — Verify all 5 themes render correctly on all pages, no broken colors/contrast issues | #491 | web | TBD | TW-THM-003,TW-VER-001 | TW-DOC-001 | worker | — | — | 15K | — | |
|
||||
@@ -23,12 +23,12 @@
|
||||
|
||||
## Summary
|
||||
|
||||
| Metric | Value |
|
||||
| ------------- | ---------------------------------------- |
|
||||
| Total tasks | 16 |
|
||||
| Completed | 10 (PLAN-001, THM-001–003, WDG-001–005) |
|
||||
| In Progress | 0 |
|
||||
| Remaining | 6 |
|
||||
| PRs merged | #493, #494, #495, #496, #497, #498, #499 |
|
||||
| Issues closed | — |
|
||||
| Milestone | MS18-ThemeWidgets |
|
||||
| Metric | Value |
|
||||
| ------------- | ---------------------------------------------------- |
|
||||
| Total tasks | 16 |
|
||||
| Completed | 12 (PLAN-001, THM-001–003, WDG-001–005, EDT-001–002) |
|
||||
| In Progress | 0 |
|
||||
| Remaining | 4 |
|
||||
| PRs merged | #493–#500, #501 (pending) |
|
||||
| Issues closed | — |
|
||||
| Milestone | MS18-ThemeWidgets |
|
||||
|
||||
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@@ -473,6 +473,9 @@ importers:
|
||||
socket.io-client:
|
||||
specifier: ^4.8.3
|
||||
version: 4.8.3
|
||||
tiptap-markdown:
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0))
|
||||
devDependencies:
|
||||
'@mosaic/config':
|
||||
specifier: workspace:*
|
||||
@@ -3201,9 +3204,15 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/linkify-it@3.0.5':
|
||||
resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==}
|
||||
|
||||
'@types/linkify-it@5.0.0':
|
||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||
|
||||
'@types/markdown-it@13.0.9':
|
||||
resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
||||
|
||||
@@ -3211,6 +3220,9 @@ packages:
|
||||
resolution: {integrity: sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA==}
|
||||
deprecated: This is a stub types definition. marked provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/mdurl@1.0.5':
|
||||
resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}
|
||||
|
||||
'@types/mdurl@2.0.0':
|
||||
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
|
||||
|
||||
@@ -5622,6 +5634,9 @@ packages:
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
markdown-it-task-lists@2.1.1:
|
||||
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
|
||||
|
||||
markdown-it@14.1.1:
|
||||
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
|
||||
hasBin: true
|
||||
@@ -6989,6 +7004,11 @@ packages:
|
||||
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
tiptap-markdown@0.9.0:
|
||||
resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^3.0.1
|
||||
|
||||
tldts-core@6.1.86:
|
||||
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
|
||||
|
||||
@@ -10521,8 +10541,15 @@ snapshots:
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/linkify-it@3.0.5': {}
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
'@types/markdown-it@13.0.9':
|
||||
dependencies:
|
||||
'@types/linkify-it': 3.0.5
|
||||
'@types/mdurl': 1.0.5
|
||||
|
||||
'@types/markdown-it@14.1.2':
|
||||
dependencies:
|
||||
'@types/linkify-it': 5.0.0
|
||||
@@ -10532,6 +10559,8 @@ snapshots:
|
||||
dependencies:
|
||||
marked: 17.0.1
|
||||
|
||||
'@types/mdurl@1.0.5': {}
|
||||
|
||||
'@types/mdurl@2.0.0': {}
|
||||
|
||||
'@types/memcached@2.2.10':
|
||||
@@ -13188,6 +13217,8 @@ snapshots:
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
markdown-it-task-lists@2.1.1: {}
|
||||
|
||||
markdown-it@14.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
@@ -14763,6 +14794,14 @@ snapshots:
|
||||
|
||||
tinyspy@4.0.4: {}
|
||||
|
||||
tiptap-markdown@0.9.0(@tiptap/core@3.20.0(@tiptap/pm@3.20.0)):
|
||||
dependencies:
|
||||
'@tiptap/core': 3.20.0(@tiptap/pm@3.20.0)
|
||||
'@types/markdown-it': 13.0.9
|
||||
markdown-it: 14.1.1
|
||||
markdown-it-task-lists: 2.1.1
|
||||
prosemirror-markdown: 1.13.4
|
||||
|
||||
tldts-core@6.1.86: {}
|
||||
|
||||
tldts@6.1.86:
|
||||
|
||||
Reference in New Issue
Block a user