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-dom": "^19.0.0",
|
||||||
"react-grid-layout": "^2.2.2",
|
"react-grid-layout": "^2.2.2",
|
||||||
"recharts": "^3.7.0",
|
"recharts": "^3.7.0",
|
||||||
"socket.io-client": "^4.8.3"
|
"socket.io-client": "^4.8.3",
|
||||||
|
"tiptap-markdown": "^0.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@mosaic/config": "workspace:*",
|
"@mosaic/config": "workspace:*",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useRef } from "react";
|
import React from "react";
|
||||||
import { LinkAutocomplete } from "./LinkAutocomplete";
|
import { KnowledgeEditor } from "./KnowledgeEditor";
|
||||||
|
|
||||||
interface EntryEditorProps {
|
interface EntryEditorProps {
|
||||||
content: string;
|
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 {
|
export function EntryEditor({ content, onChange }: EntryEditorProps): React.JSX.Element {
|
||||||
const [showPreview, setShowPreview] = useState(false);
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="entry-editor relative">
|
<div className="entry-editor">
|
||||||
<div className="flex justify-between items-center mb-2">
|
<label className="block text-sm font-medium mb-2" style={{ color: "var(--text-2)" }}>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
Content
|
||||||
Content (Markdown)
|
</label>
|
||||||
</label>
|
<KnowledgeEditor
|
||||||
<button
|
content={content}
|
||||||
type="button"
|
onChange={onChange}
|
||||||
onClick={() => {
|
placeholder="Write your content here... Supports markdown formatting."
|
||||||
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,20 @@ import { TableCell } from "@tiptap/extension-table-cell";
|
|||||||
import { TableHeader } from "@tiptap/extension-table-header";
|
import { TableHeader } from "@tiptap/extension-table-header";
|
||||||
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
||||||
import Placeholder from "@tiptap/extension-placeholder";
|
import Placeholder from "@tiptap/extension-placeholder";
|
||||||
|
import { Markdown } from "tiptap-markdown";
|
||||||
import { common, createLowlight } from "lowlight";
|
import { common, createLowlight } from "lowlight";
|
||||||
import type { Editor } from "@tiptap/react";
|
import type { Editor } from "@tiptap/react";
|
||||||
|
import type { MarkdownStorage } from "tiptap-markdown";
|
||||||
|
|
||||||
import "./KnowledgeEditor.css";
|
import "./KnowledgeEditor.css";
|
||||||
|
|
||||||
const lowlight = createLowlight(common);
|
const lowlight = createLowlight(common);
|
||||||
|
|
||||||
export interface KnowledgeEditorProps {
|
export interface KnowledgeEditorProps {
|
||||||
/** HTML content for the editor */
|
/** Markdown content for the editor */
|
||||||
content: string;
|
content: string;
|
||||||
/** Called when editor content changes (provides HTML) */
|
/** Called when editor content changes (provides markdown) */
|
||||||
onChange: (html: string) => void;
|
onChange: (markdown: string) => void;
|
||||||
/** Placeholder text when editor is empty */
|
/** Placeholder text when editor is empty */
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
/** Whether the editor is editable */
|
/** Whether the editor is editable */
|
||||||
@@ -366,7 +368,11 @@ export function KnowledgeEditor({
|
|||||||
}: KnowledgeEditorProps): ReactElement {
|
}: KnowledgeEditorProps): ReactElement {
|
||||||
const handleUpdate = useCallback(
|
const handleUpdate = useCallback(
|
||||||
({ editor: e }: { editor: Editor }) => {
|
({ 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]
|
[onChange]
|
||||||
);
|
);
|
||||||
@@ -395,6 +401,13 @@ export function KnowledgeEditor({
|
|||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder,
|
placeholder,
|
||||||
}),
|
}),
|
||||||
|
Markdown.configure({
|
||||||
|
html: true,
|
||||||
|
breaks: false,
|
||||||
|
tightLists: true,
|
||||||
|
transformPastedText: true,
|
||||||
|
transformCopiedText: true,
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
content,
|
content,
|
||||||
editable,
|
editable,
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||||
import { EntryEditor } from "../EntryEditor";
|
import { EntryEditor } from "../EntryEditor";
|
||||||
|
|
||||||
// Mock the LinkAutocomplete component
|
// Mock KnowledgeEditor since Tiptap requires a full DOM
|
||||||
vi.mock("../LinkAutocomplete", () => ({
|
vi.mock("../KnowledgeEditor", () => ({
|
||||||
LinkAutocomplete: (): React.JSX.Element => (
|
KnowledgeEditor: ({
|
||||||
<div data-testid="link-autocomplete">LinkAutocomplete</div>
|
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();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render textarea in edit mode by default", (): void => {
|
it("should render KnowledgeEditor component", (): void => {
|
||||||
render(<EntryEditor {...defaultProps} />);
|
render(<EntryEditor {...defaultProps} />);
|
||||||
|
|
||||||
const textarea = screen.getByPlaceholderText(/Write your content here/);
|
expect(screen.getByTestId("knowledge-editor")).toBeInTheDocument();
|
||||||
expect(textarea).toBeInTheDocument();
|
|
||||||
expect(textarea.tagName).toBe("TEXTAREA");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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.";
|
const content = "# Test Content\n\nThis is a test.";
|
||||||
render(<EntryEditor {...defaultProps} content={content} />);
|
render(<EntryEditor {...defaultProps} content={content} />);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
const editor = screen.getByTestId("knowledge-editor");
|
||||||
const textarea = screen.getByPlaceholderText(/Write your content here/) as HTMLTextAreaElement;
|
expect(editor).toHaveAttribute("data-content", content);
|
||||||
expect(textarea.value).toBe(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 user = userEvent.setup();
|
||||||
const onChangeMock = vi.fn();
|
const onChangeMock = vi.fn();
|
||||||
|
|
||||||
render(<EntryEditor {...defaultProps} onChange={onChangeMock} />);
|
render(<EntryEditor {...defaultProps} onChange={onChangeMock} />);
|
||||||
|
|
||||||
const textarea = screen.getByPlaceholderText(/Write your content here/);
|
await user.click(screen.getByTestId("trigger-change"));
|
||||||
await user.type(textarea, "Hello");
|
expect(onChangeMock).toHaveBeenCalledWith("updated content");
|
||||||
|
|
||||||
expect(onChangeMock).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should toggle between edit and preview modes", async (): Promise<void> => {
|
it("should render with entry-editor wrapper class", (): void => {
|
||||||
const user = userEvent.setup();
|
const { container } = render(<EntryEditor {...defaultProps} />);
|
||||||
const content = "# Test\n\nPreview this content.";
|
|
||||||
|
|
||||||
render(<EntryEditor {...defaultProps} content={content} />);
|
expect(container.querySelector(".entry-editor")).toBeInTheDocument();
|
||||||
|
|
||||||
// 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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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-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-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-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-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 | 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-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-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-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 | — | |
|
| 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
|
## Summary
|
||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
| ------------- | ---------------------------------------- |
|
| ------------- | ---------------------------------------------------- |
|
||||||
| Total tasks | 16 |
|
| Total tasks | 16 |
|
||||||
| Completed | 10 (PLAN-001, THM-001–003, WDG-001–005) |
|
| Completed | 12 (PLAN-001, THM-001–003, WDG-001–005, EDT-001–002) |
|
||||||
| In Progress | 0 |
|
| In Progress | 0 |
|
||||||
| Remaining | 6 |
|
| Remaining | 4 |
|
||||||
| PRs merged | #493, #494, #495, #496, #497, #498, #499 |
|
| PRs merged | #493–#500, #501 (pending) |
|
||||||
| Issues closed | — |
|
| Issues closed | — |
|
||||||
| Milestone | MS18-ThemeWidgets |
|
| Milestone | MS18-ThemeWidgets |
|
||||||
|
|||||||
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@@ -473,6 +473,9 @@ importers:
|
|||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.8.3
|
specifier: ^4.8.3
|
||||||
version: 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:
|
devDependencies:
|
||||||
'@mosaic/config':
|
'@mosaic/config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@@ -3201,9 +3204,15 @@ packages:
|
|||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
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':
|
'@types/linkify-it@5.0.0':
|
||||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||||
|
|
||||||
|
'@types/markdown-it@13.0.9':
|
||||||
|
resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==}
|
||||||
|
|
||||||
'@types/markdown-it@14.1.2':
|
'@types/markdown-it@14.1.2':
|
||||||
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
|
||||||
|
|
||||||
@@ -3211,6 +3220,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA==}
|
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.
|
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':
|
'@types/mdurl@2.0.0':
|
||||||
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
|
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
|
||||||
|
|
||||||
@@ -5622,6 +5634,9 @@ packages:
|
|||||||
make-error@1.3.6:
|
make-error@1.3.6:
|
||||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||||
|
|
||||||
|
markdown-it-task-lists@2.1.1:
|
||||||
|
resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==}
|
||||||
|
|
||||||
markdown-it@14.1.1:
|
markdown-it@14.1.1:
|
||||||
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
|
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -6989,6 +7004,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
|
||||||
engines: {node: '>=14.0.0'}
|
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:
|
tldts-core@6.1.86:
|
||||||
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
|
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
|
||||||
|
|
||||||
@@ -10521,8 +10541,15 @@ snapshots:
|
|||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/linkify-it@3.0.5': {}
|
||||||
|
|
||||||
'@types/linkify-it@5.0.0': {}
|
'@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':
|
'@types/markdown-it@14.1.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/linkify-it': 5.0.0
|
'@types/linkify-it': 5.0.0
|
||||||
@@ -10532,6 +10559,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
marked: 17.0.1
|
marked: 17.0.1
|
||||||
|
|
||||||
|
'@types/mdurl@1.0.5': {}
|
||||||
|
|
||||||
'@types/mdurl@2.0.0': {}
|
'@types/mdurl@2.0.0': {}
|
||||||
|
|
||||||
'@types/memcached@2.2.10':
|
'@types/memcached@2.2.10':
|
||||||
@@ -13188,6 +13217,8 @@ snapshots:
|
|||||||
|
|
||||||
make-error@1.3.6: {}
|
make-error@1.3.6: {}
|
||||||
|
|
||||||
|
markdown-it-task-lists@2.1.1: {}
|
||||||
|
|
||||||
markdown-it@14.1.1:
|
markdown-it@14.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse: 2.0.1
|
argparse: 2.0.1
|
||||||
@@ -14763,6 +14794,14 @@ snapshots:
|
|||||||
|
|
||||||
tinyspy@4.0.4: {}
|
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-core@6.1.86: {}
|
||||||
|
|
||||||
tldts@6.1.86:
|
tldts@6.1.86:
|
||||||
|
|||||||
Reference in New Issue
Block a user