mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-25 16:09:42 +01:00
Merge pull request #8772 from 01zulfi/editor/fix-images-in-outline-lists
editor: fix inserting images in outline lists
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`migration > inline image in outline list 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "item 1",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "hello",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"align": undefined,
|
||||
"aspectRatio": 1,
|
||||
"filename": undefined,
|
||||
"hash": undefined,
|
||||
"height": null,
|
||||
"mime": undefined,
|
||||
"progress": 0,
|
||||
"size": undefined,
|
||||
"src": "image.png",
|
||||
"type": "image",
|
||||
"width": null,
|
||||
},
|
||||
"type": "image",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "world",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "sub item 2",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "sub item 3",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
],
|
||||
"type": "outlineList",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "item 4",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
],
|
||||
"type": "outlineList",
|
||||
},
|
||||
],
|
||||
"type": "doc",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`migration > inline image in paragraph 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "hello",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"align": undefined,
|
||||
"aspectRatio": 1,
|
||||
"filename": undefined,
|
||||
"hash": undefined,
|
||||
"height": null,
|
||||
"mime": undefined,
|
||||
"progress": 0,
|
||||
"size": undefined,
|
||||
"src": "image.png",
|
||||
"type": "image",
|
||||
"width": null,
|
||||
},
|
||||
"type": "image",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "world",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "doc",
|
||||
}
|
||||
`;
|
||||
75
packages/editor/src/extensions/image/__tests__/image.test.ts
Normal file
75
packages/editor/src/extensions/image/__tests__/image.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
createEditor,
|
||||
h,
|
||||
p,
|
||||
outlineList,
|
||||
outlineListItem
|
||||
} from "../../../../test-utils/index.js";
|
||||
import { test, expect, describe } from "vitest";
|
||||
import { ImageNode } from "../index.js";
|
||||
import { OutlineList } from "../../outline-list/outline-list.js";
|
||||
import { OutlineListItem } from "../../outline-list-item/outline-list-item.js";
|
||||
|
||||
describe("migration", () => {
|
||||
test(`inline image in paragraph`, async () => {
|
||||
const el = p(["hello", h("img", [], { src: "image.png" }), "world"]);
|
||||
const {
|
||||
builder: { image },
|
||||
editor
|
||||
} = createEditor({
|
||||
initialContent: el.outerHTML,
|
||||
extensions: {
|
||||
image: ImageNode.configure({})
|
||||
}
|
||||
});
|
||||
|
||||
expect(editor.getJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test(`inline image in outline list`, async () => {
|
||||
const el = outlineList(
|
||||
outlineListItem(["item 1"]),
|
||||
outlineListItem(
|
||||
["hello", h("img", [], { src: "image.png" }), "world"],
|
||||
outlineList(
|
||||
outlineListItem(["sub item 2"]),
|
||||
outlineListItem(["sub item 3"])
|
||||
)
|
||||
),
|
||||
outlineListItem(["item 4"])
|
||||
);
|
||||
|
||||
const {
|
||||
builder: { image },
|
||||
editor
|
||||
} = createEditor({
|
||||
initialContent: el.outerHTML,
|
||||
extensions: {
|
||||
outlineList: OutlineList,
|
||||
outlineListItem: OutlineListItem,
|
||||
image: ImageNode
|
||||
}
|
||||
});
|
||||
|
||||
expect(editor.getJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -131,39 +131,6 @@ export const ImageNode = Node.create<ImageOptions>({
|
||||
getAttrs(node) {
|
||||
if (node.querySelectorAll("img").length <= 0) return false;
|
||||
return null;
|
||||
},
|
||||
getContent: (dom, schema) => {
|
||||
const wrapper = document.createElement("div");
|
||||
let buffer = "";
|
||||
|
||||
const flushBuffer = () => {
|
||||
if (buffer.trim().length > 0) {
|
||||
const pEl = document.createElement("p");
|
||||
pEl.innerHTML = buffer;
|
||||
wrapper.appendChild(pEl);
|
||||
buffer = "";
|
||||
}
|
||||
};
|
||||
|
||||
for (const child of dom.childNodes) {
|
||||
if (
|
||||
child.nodeType === globalThis.Node.ELEMENT_NODE &&
|
||||
(child as HTMLElement).tagName === "IMG"
|
||||
) {
|
||||
flushBuffer();
|
||||
wrapper.appendChild(child);
|
||||
} else {
|
||||
if (child.nodeType === globalThis.Node.ELEMENT_NODE) {
|
||||
buffer += (child as HTMLElement).outerHTML;
|
||||
} else if (child.nodeType === globalThis.Node.TEXT_NODE) {
|
||||
buffer += child.textContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
flushBuffer();
|
||||
|
||||
const parser = DOMParser.fromSchema(schema);
|
||||
return parser.parse(wrapper).content;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`outline list item > code block in outline list item 1`] = `
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "item 1",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "hello",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"caretPosition": undefined,
|
||||
"id": "codeblock-test-id-123456",
|
||||
"indentLength": 2,
|
||||
"indentType": "space",
|
||||
"language": null,
|
||||
"lines": [],
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"text": "const x = 1;",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "codeblock",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "world",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "sub item 2",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "sub item 3",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
],
|
||||
"type": "outlineList",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
{
|
||||
"attrs": {
|
||||
"collapsed": false,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "item 4",
|
||||
"type": "text",
|
||||
},
|
||||
],
|
||||
"type": "paragraph",
|
||||
},
|
||||
],
|
||||
"type": "outlineListItem",
|
||||
},
|
||||
],
|
||||
"type": "outlineList",
|
||||
},
|
||||
],
|
||||
"type": "doc",
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
createEditor,
|
||||
h,
|
||||
li,
|
||||
outlineList,
|
||||
outlineListItem
|
||||
} from "../../../../test-utils/index.js";
|
||||
import { test, expect, describe, beforeAll, vi } from "vitest";
|
||||
import { OutlineList } from "../../outline-list/outline-list.js";
|
||||
import { OutlineListItem } from "../outline-list-item.js";
|
||||
import { CodeBlock } from "../../code-block/code-block.js";
|
||||
|
||||
describe("outline list item", () => {
|
||||
beforeAll(() => {
|
||||
vi.mock("nanoid", () => ({
|
||||
nanoid: () => "test-id-123456"
|
||||
}));
|
||||
});
|
||||
|
||||
test(`code block in outline list item`, async () => {
|
||||
const subList = outlineList(
|
||||
outlineListItem(["sub item 2"]),
|
||||
outlineListItem(["sub item 3"])
|
||||
);
|
||||
const listItemWithCodeBlock = li(
|
||||
[
|
||||
h("p", ["hello"]),
|
||||
h("pre", [h("code", ["const x = 1;"])]),
|
||||
h("p", ["world"]),
|
||||
subList
|
||||
],
|
||||
{ "data-type": "outlineListItem" }
|
||||
);
|
||||
const el = outlineList(
|
||||
outlineListItem(["item 1"]),
|
||||
listItemWithCodeBlock,
|
||||
outlineListItem(["item 4"])
|
||||
);
|
||||
|
||||
const {
|
||||
builder: { codeBlock },
|
||||
editor
|
||||
} = createEditor({
|
||||
initialContent: el.outerHTML,
|
||||
extensions: {
|
||||
outlineList: OutlineList,
|
||||
outlineListItem: OutlineListItem,
|
||||
codeBlock: CodeBlock
|
||||
}
|
||||
});
|
||||
|
||||
expect(editor.getJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
import { findParentNodeOfTypeClosestToPos } from "../../utils/prosemirror.js";
|
||||
import { OutlineList } from "../outline-list/outline-list.js";
|
||||
import { keybindings, tiptapKeys } from "@notesnook/common";
|
||||
import { Paragraph } from "../paragraph/paragraph.js";
|
||||
|
||||
export interface ListItemOptions {
|
||||
HTMLAttributes: Record<string, unknown>;
|
||||
@@ -52,13 +53,14 @@ export const OutlineListItem = Node.create<ListItemOptions>({
|
||||
};
|
||||
},
|
||||
|
||||
content: "paragraph+ list?",
|
||||
content: "block+",
|
||||
|
||||
defining: true,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
priority: 100,
|
||||
tag: `li[data-type="${this.name}"]`
|
||||
}
|
||||
];
|
||||
@@ -92,21 +94,37 @@ export const OutlineListItem = Node.create<ListItemOptions>({
|
||||
return true;
|
||||
});
|
||||
},
|
||||
Enter: () => {
|
||||
// const subList = findSublist(editor, this.type);
|
||||
// if (!subList) return this.editor.commands.splitListItem(this.name);
|
||||
|
||||
// const { isCollapsed, subListPos } = subList;
|
||||
|
||||
// if (isCollapsed) {
|
||||
// return this.editor.commands.toggleOutlineCollapse(subListPos, false);
|
||||
// }
|
||||
Enter: ({ editor }) => {
|
||||
const { $anchor } = editor.state.selection;
|
||||
if (
|
||||
$anchor.parent.type.name !== Paragraph.name ||
|
||||
($anchor.parent.type.name === Paragraph.name &&
|
||||
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
|
||||
)
|
||||
return false;
|
||||
|
||||
return this.editor.commands.splitListItem(this.name);
|
||||
},
|
||||
Tab: () => this.editor.commands.sinkListItem(this.name),
|
||||
[keybindings.liftListItem.keys]: () =>
|
||||
this.editor.commands.liftListItem(this.name)
|
||||
Tab: ({ editor }) => {
|
||||
const { $anchor } = editor.state.selection;
|
||||
if (
|
||||
$anchor.parent.type.name !== Paragraph.name ||
|
||||
($anchor.parent.type.name === Paragraph.name &&
|
||||
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
|
||||
)
|
||||
return false;
|
||||
return this.editor.commands.sinkListItem(this.name);
|
||||
},
|
||||
[keybindings.liftListItem.keys]: ({ editor }) => {
|
||||
const { $anchor } = editor.state.selection;
|
||||
if (
|
||||
$anchor.parent.type.name !== Paragraph.name ||
|
||||
($anchor.parent.type.name === Paragraph.name &&
|
||||
$anchor.node($anchor.depth - 1)?.type.name !== this.type.name)
|
||||
)
|
||||
return false;
|
||||
return this.editor.commands.liftListItem(this.name);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -124,7 +142,7 @@ export const OutlineListItem = Node.create<ListItemOptions>({
|
||||
|
||||
function onClick(e: MouseEvent | TouchEvent) {
|
||||
if (e instanceof MouseEvent && e.button !== 0) return;
|
||||
if (!(e.target instanceof HTMLParagraphElement)) return;
|
||||
if (!(e.target instanceof HTMLElement)) return;
|
||||
if (!li.classList.contains("nested")) return;
|
||||
|
||||
const pos = typeof getPos === "function" ? getPos() : 0;
|
||||
|
||||
@@ -536,11 +536,11 @@ p > *::selection {
|
||||
.outline-list li.collapsed .outline-list {
|
||||
display: none;
|
||||
}
|
||||
.outline-list li > :first-child {
|
||||
.outline-list li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.outline-list > li > :first-child::before {
|
||||
.outline-list > li::before {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
cursor: pointer;
|
||||
@@ -560,13 +560,13 @@ p > *::selection {
|
||||
left: -22px;
|
||||
}
|
||||
|
||||
.outline-list li:not(.nested) > :first-child::before {
|
||||
.outline-list li:not(.nested)::before {
|
||||
mask: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9IiM4ODg4ODgiIGQ9Ik0xMiAyQTEwIDEwIDAgMCAwIDIgMTJhMTAgMTAgMCAwIDAgMTAgMTBhMTAgMTAgMCAwIDAgMTAtMTBBMTAgMTAgMCAwIDAgMTIgMloiLz48L3N2Zz4=)
|
||||
no-repeat 50% 50%;
|
||||
scale: 0.4;
|
||||
}
|
||||
|
||||
.outline-list li.collapsed > :first-child::before {
|
||||
.outline-list li.collapsed::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,3 +92,19 @@ export const p = elem("p");
|
||||
export function text(text: string) {
|
||||
return document.createTextNode(text);
|
||||
}
|
||||
|
||||
export function outlineList(...children: HTMLLIElement[]) {
|
||||
return ul(children, { "data-type": "outlineList" });
|
||||
}
|
||||
|
||||
export function outlineListItem(
|
||||
paragraphChildren: (string | HTMLElement)[],
|
||||
subList?: HTMLUListElement
|
||||
) {
|
||||
const children: HTMLElement[] = [h("p", paragraphChildren)];
|
||||
if (subList) children.push(subList);
|
||||
|
||||
return li(children, {
|
||||
"data-type": "outlineListItem"
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user