Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal html changes #1116

Merged
merged 20 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h2 data-text-color="red" data-level="2">Heading 1</h2><p>Nested Paragraph 1</p>
matthewlipski marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Nested Paragraph 1</p><p>Nested Paragraph 2</p><p>Nested Paragraph 3</p><h2 data-text-color="red" data-level="2">Heading 2</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Nested Paragraph 1</p><p>Nested Paragraph 2</p><p>Nested Paragraph 3</p><h2 data-text-color="red" data-level="2">Heading 2</h2><p>Nested Paragraph 1</p><p>Nested Paragraph 2</p><p>Nested Paragraph 3</p>
1 change: 1 addition & 0 deletions packages/core/src/api/clipboard/__snapshots__/image.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<img src="https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg" alt="BlockNote image" width="512" data-url="https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg"><p>Nested Paragraph</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Nested Paragraph 1</p><p>Nested Paragraph 2</p><p>Nested Paragraph 3</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<strong>Bold</strong><em>Italic</em>Regular
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h2 data-text-color="red" data-level="2"><strong>Bold</strong><em>Italic</em>Regular</h2><img src="https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg" alt="BlockNote image" width="512" data-url="https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg"><p>Nested Paragraph</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h2 data-text-color="red" data-level="2">ding 1</h2><p>Nested </p>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<em>Italic</em>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="bn-block-content" data-content-type="table"><table class="bn-inline-content"><tbody><tr><td colspan="1" rowspan="1"><p>Table Cell</p></td><td colspan="1" rowspan="1"><p>Table Cell</p></td></tr><tr><td colspan="1" rowspan="1"><p>Table Cell</p></td><td colspan="1" rowspan="1"><p>Table Cell</p></td></tr></tbody></table></div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<tr><td colspan="1" rowspan="1"><p>Table Cell</p></td></tr>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Table Cell
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<tr><td colspan="1" rowspan="1"><p>Table Cell</p></td><td colspan="1" rowspan="1"><p>Table Cell</p></td></tr>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Regular
302 changes: 302 additions & 0 deletions packages/core/src/api/clipboard/clipboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import { Node } from "prosemirror-model";
import { NodeSelection, Selection, TextSelection } from "prosemirror-state";
import { CellSelection } from "prosemirror-tables";
import * as pmView from "prosemirror-view";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";

import { PartialBlock } from "../../blocks/defaultBlocks";
import { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { initializeESMDependencies } from "../../util/esmDependencies";
import { selectedFragmentToHTML } from "./toClipboard/copyExtension";

type SelectionTestCase = {
testName: string;
createSelection: (doc: Node) => Selection;
};

// These tests are meant to test the copying of user selections in the editor.
// The test cases used for the other HTML conversion tests are not suitable here
// as they are represented in the BlockNote API, whereas here we want to test
// ProseMirror/TipTap selections directly.
describe("Test ProseMirror selection clipboard HTML", () => {
const initialContent: PartialBlock[] = [
{
type: "heading",
props: {
level: 2,
textColor: "red",
},
content: "Heading 1",
children: [
{
type: "paragraph",
content: "Nested Paragraph 1",
},
{
type: "paragraph",
content: "Nested Paragraph 2",
},
{
type: "paragraph",
content: "Nested Paragraph 3",
},
],
},
{
type: "heading",
props: {
level: 2,
textColor: "red",
},
content: "Heading 2",
children: [
{
type: "paragraph",
content: "Nested Paragraph 1",
},
{
type: "paragraph",
content: "Nested Paragraph 2",
},
{
type: "paragraph",
content: "Nested Paragraph 3",
},
],
},
{
type: "heading",
props: {
level: 2,
textColor: "red",
},
content: [
{
type: "text",
text: "Bold",
styles: {
bold: true,
},
},
{
type: "text",
text: "Italic",
styles: {
italic: true,
},
},
{
type: "text",
text: "Regular",
styles: {},
},
],
children: [
{
type: "image",
props: {
url: "https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg",
},
children: [
{
type: "paragraph",
content: "Nested Paragraph",
},
],
},
],
},
{
type: "table",
content: {
type: "tableContent",
rows: [
{
cells: ["Table Cell", "Table Cell"],
},
{
cells: ["Table Cell", "Table Cell"],
},
],
},
// Not needed as selections starting in table cells will get snapped to
// the table boundaries.
// children: [
// {
// type: "table",
// content: {
// type: "tableContent",
// rows: [
// {
// cells: ["Table Cell", "Table Cell"],
// },
// {
// cells: ["Table Cell", "Table Cell"],
// },
// ],
// },
// },
// ],
},
];

let editor: BlockNoteEditor;
const div = document.createElement("div");

beforeEach(() => {
editor.replaceBlocks(editor.document, initialContent);
});

beforeAll(async () => {
(window as any).__TEST_OPTIONS = (window as any).__TEST_OPTIONS || {};

editor = BlockNoteEditor.create();
editor.mount(div);

await initializeESMDependencies();
});

afterAll(() => {
editor.mount(undefined);
editor._tiptapEditor.destroy();
editor = undefined as any;

delete (window as Window & { __TEST_OPTIONS?: any }).__TEST_OPTIONS;
});

// Sets the editor selection to the given start and end positions, then
// exports the selected content to HTML and compares it to a snapshot.
async function testSelection(testCase: SelectionTestCase) {
editor.dispatch(
editor._tiptapEditor.state.tr.setSelection(
testCase.createSelection(editor._tiptapEditor.view.state.doc)
)
);

if (
"node" in editor._tiptapEditor.view.state.selection &&
(editor._tiptapEditor.view.state.selection.node as Node).type.spec
.group === "blockContent"
) {
editor.dispatch(
editor._tiptapEditor.state.tr.setSelection(
new NodeSelection(
editor._tiptapEditor.view.state.doc.resolve(
editor._tiptapEditor.view.state.selection.from - 1
)
)
)
);
}

const { clipboardHTML, externalHTML } = await selectedFragmentToHTML(
editor._tiptapEditor.view,
editor
);

expect(externalHTML).toMatchFileSnapshot(
`./__snapshots__/${testCase.testName}.html`
);

const originalDocument = editor.document;
editor._tiptapEditor.state.tr.replaceSelection(
(pmView as any).__parseFromClipboard(
editor._tiptapEditor.view,
"text",
clipboardHTML,
false,
editor._tiptapEditor.view.state.selection.$from
)
);
const newDocument = editor.document;

expect(newDocument).toStrictEqual(originalDocument);
}

const testCases: SelectionTestCase[] = [
// TODO: Consider adding test cases for nested blocks & double nested blocks.
// Selection spans all of first heading's children.
{
testName: "multipleChildren",
createSelection: (doc) => TextSelection.create(doc, 16, 78),
},
// Selection spans from start of first heading to end of its first child.
{
testName: "childToParent",
createSelection: (doc) => TextSelection.create(doc, 3, 34),
},
// Selection spans from middle of first heading to the middle of its first
// child.
{
testName: "partialChildToParent",
createSelection: (doc) => TextSelection.create(doc, 6, 23),
},
// Selection spans from start of first heading's first child to end of
// second heading's content (does not include second heading's children).
{
testName: "childrenToNextParent",
createSelection: (doc) => TextSelection.create(doc, 16, 93),
},
// Selection spans from start of first heading's first child to end of
// second heading's last child.
{
testName: "childrenToNextParentsChildren",
createSelection: (doc) => TextSelection.create(doc, 16, 159),
},
// Selection spans "Regular" text inside third heading.
{
testName: "unstyledText",
createSelection: (doc) => TextSelection.create(doc, 175, 182),
},
// Selection spans "Italic" text inside third heading.
{
testName: "styledText",
createSelection: (doc) => TextSelection.create(doc, 169, 175),
},
// Selection spans third heading's content (does not include third heading's
// children).
{
testName: "multipleStyledText",
createSelection: (doc) => TextSelection.create(doc, 165, 182),
},
// Selection spans the image block content.
{
testName: "image",
createSelection: (doc) => NodeSelection.create(doc, 185),
},
// Selection spans from start of third heading to end of it's last
// descendant.
{
testName: "nestedImage",
createSelection: (doc) => TextSelection.create(doc, 165, 205),
},
// Selection spans text in first cell of the table.
{
testName: "tableCellText",
createSelection: (doc) => TextSelection.create(doc, 216, 226),
},
// Selection spans first cell of the table.
// TODO: External HTML is wrapped in unnecessary `tr` element.
{
testName: "tableCell",
createSelection: (doc) => CellSelection.create(doc, 214),
},
// Selection spans first row of the table.
{
testName: "tableRow",
createSelection: (doc) => CellSelection.create(doc, 214, 228),
},
// Selection spans all cells of the table.
// TODO: External HTML is wrapped in unnecessary `blockContent` element.
{
testName: "tableAllCells",
createSelection: (doc) => CellSelection.create(doc, 214, 258),
},
];

for (const testCase of testCases) {
it(`${testCase.testName}`, async () => {
await testSelection(testCase);
});
}
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Extension } from "@tiptap/core";
import { Plugin } from "prosemirror-state";

import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { handleFileInsertion } from "./handleFileInsertion";
import { acceptedMIMETypes } from "./acceptedMIMETypes";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { PartialBlock } from "../../blocks/defaultBlocks";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import { PartialBlock } from "../../../blocks/defaultBlocks";
import {
BlockSchema,
FileBlockConfig,
InlineContentSchema,
StyleSchema,
} from "../../schema";
import { getBlockInfoFromPos } from "../getBlockInfoFromPos";
} from "../../../schema";
import { getBlockInfoFromPos } from "../../getBlockInfoFromPos";
import { acceptedMIMETypes } from "./acceptedMIMETypes";

function checkFileExtensionsMatch(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Extension } from "@tiptap/core";
import { Plugin } from "prosemirror-state";

import type { BlockNoteEditor } from "../../editor/BlockNoteEditor";
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../schema";
import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor";
import { BlockSchema, InlineContentSchema, StyleSchema } from "../../../schema";
import { handleFileInsertion } from "./handleFileInsertion";
import { nestedListsToBlockNoteStructure } from "./html/util/nestedLists";
import { nestedListsToBlockNoteStructure } from "../../parsers/html/util/nestedLists";
import { acceptedMIMETypes } from "./acceptedMIMETypes";

export const createPasteFromClipboardExtension = <
Expand Down
Loading
Loading