Skip to content

Commit c69fc33

Browse files
matthewlipskiYousefEDMatthew Lipski
authored
Block Restructuring (TypeCellOS#64)
* Restructured text & heading blocks (excluding drag handle add implementation) * Small cleanup * Cleaned up drag handle code * Added basic list item implementation * Updated block commands and keyboard handling * Added ordered list item indexing * Finished block manipulation logic * Fixed bug causing suggestion menu to not work properly in list item blocks * Refactoring changes * Minor fix * fix animations for headings and indents (TypeCellOS#67) * fix animations for headings and indents * remove submodule * fix css * Fixed bug where splitting a block created an additional empty block * Cleaned up drag handle code * Fixed bug where the suggestion plugin tried to process transactions created by the ordered list indexing plugin * Improvements to block content naming and file structure * Code style changes and small fix to block keyboard shortcuts * Fixed bugs regarding selection when deleting blocks and using the suggestion menu with the keyboard * Block content attribute naming fixes * Fixed list item copy/pasting within the editor * Updated element selectors for tests * Updated snapshots for copy/paste and drag/drop tests * Adjusted enter key handler behavior * Changed ordered list item indices to start at 1 instead of 0 * Fixed list styling (no animations) * Updated remaining test snapshots * try clean playwright cache * Fixed bug where splitting a block duplicates its ID * Fixed bug where ordered list item indices were being updated unnecessarily * clean up OrderedListItemIndexPlugin * Fixed animation issue caused by change in naming convention * Added animations for unordered lists * Added animations for ordered lists * Added dispatch to block commands * Made backspace revert all block typing instead of just list items * Removed redundant conditions in block keyboard handlers * Changed ordered list index type from number to string in plugin * Optimized `getPosFromBlock` function * Fixed multiple block dragging * Improved transaction filtering for `PreviousBlockTypePlugin` * Improved paste behaviour for content outside the editor * Removed dispatch calls from block commands * Added edge cases for block commands (not currently being triggered) * Added test for Enter keyboard handler with selection spanning multiple blocks * Added functions to allow testing block IDs * Tests in which blocks are deleted/moved now verify block IDs * Added updated snapshots * Added updated screenshots for drag handle menu * Removed `joinBackward` as it is no longer used * Improved paste reliability from other sites, broke inline styles. * Improved paste reliability from other sites, broke inline styles. * Revert "Improved paste reliability from other sites, broke inline styles." This reverts commit 5c5c448. * Small fixes * Improved block ID mocking implementation. * Updated snapshot Co-authored-by: Yousef <[email protected]> Co-authored-by: Matthew Lipski <[email protected]>
1 parent d0a4c27 commit c69fc33

84 files changed

Lines changed: 2849 additions & 1885 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
uses: actions/cache@v2
3939
with:
4040
path: ~/.cache/ms-playwright
41-
key: pw-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
41+
key: pw-new-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
4242

4343
- name: Install Dependencies
4444
run: npm install
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
// Object containing all possible block attributes.
22
const BlockAttributes: Record<string, string> = {
3-
listType: "data-list-type",
43
blockColor: "data-block-color",
54
blockStyle: "data-block-style",
6-
headingType: "data-heading-type",
75
id: "data-id",
86
depth: "data-depth",
97
depthChange: "data-depth-change",
108
};
119

12-
export default BlockAttributes;
10+
export default BlockAttributes;

packages/core/src/extensions/Blocks/OrderedListPlugin.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

packages/core/src/extensions/Blocks/PreviousBlockTypePlugin.ts

Lines changed: 71 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ import {
55
} from "@tiptap/core";
66
import { Plugin, PluginKey } from "prosemirror-state";
77
import { Decoration, DecorationSet } from "prosemirror-view";
8-
import BlockAttributes from "./BlockAttributes";
98

109
const PLUGIN_KEY = new PluginKey(`previous-blocks`);
1110

12-
// Inserts "prev-" string into an HTML attribute name with a "data-" prefix, e.g. "data-depth" -> "data-prev-depth".
13-
// Assumes "data-" prefix is in the attribute name.
14-
const insertPrev = (attr: string) => attr.slice(0, 5) + "prev-" + attr.slice(5);
11+
const nodeAttributes: Record<string, string> = {
12+
listItemType: "list-item-type",
13+
listItemIndex: "list-item-index",
14+
headingLevel: "heading-level",
15+
type: "type",
16+
depth: "depth",
17+
"depth-change": "depth-change",
18+
};
1519

1620
/**
1721
* This plugin tracks transformation of Block node attributes, so we can support CSS transitions.
1822
*
19-
* Problem it solves: Prosemirror recreates the DOM when transactions happen. So when a transaction changes an Node attribute,
23+
* Problem it solves: ProseMirror recreates the DOM when transactions happen. So when a transaction changes a Node attribute,
2024
* it results in a completely new DOM element. This means CSS transitions don't work.
2125
*
2226
* Solution: When attributes change on a node, this plugin sets a data-* attribute with the "previous" value. This way we can still use CSS transitions. (See block.module.css)
@@ -75,7 +79,6 @@ export const PreviousBlockTypePlugin = () => {
7579

7680
changes.forEach(() => {
7781
const oldNodes = findChildren(oldState.doc, (node) => node.attrs.id);
78-
7982
const oldNodesById = new Map(
8083
oldNodes.map((node) => [node.node.attrs.id, node])
8184
);
@@ -84,28 +87,68 @@ export const PreviousBlockTypePlugin = () => {
8487

8588
for (let node of newNodes) {
8689
const oldNode = oldNodesById.get(node.node.attrs.id);
87-
if (oldNode) {
90+
const oldContentNode = oldNode?.node.firstChild;
91+
const newContentNode = node.node.firstChild;
92+
if (oldNode && oldContentNode && newContentNode) {
8893
const newAttrs = {
89-
listType: node.node.attrs.listType,
90-
blockColor: node.node.attrs.blockColor,
91-
blockStyle: node.node.attrs.blockStyle,
92-
headingType: node.node.attrs.headingType,
94+
listItemType: newContentNode.attrs.listItemType,
95+
listItemIndex: newContentNode.attrs.listItemIndex,
96+
headingLevel: newContentNode.attrs.headingLevel,
97+
type: newContentNode.type.name,
9398
depth: newState.doc.resolve(node.pos).depth,
9499
};
95100

96101
const oldAttrs = {
97-
listType: oldNode.node.attrs.listType,
98-
blockColor: oldNode.node.attrs.blockColor,
99-
blockStyle: oldNode.node.attrs.blockStyle,
100-
headingType: oldNode.node.attrs.headingType,
102+
listItemType: oldContentNode.attrs.listItemType,
103+
listItemIndex: oldContentNode.attrs.listItemIndex,
104+
headingLevel: oldContentNode.attrs.headingLevel,
105+
type: oldContentNode.type.name,
101106
depth: oldState.doc.resolve(oldNode.pos).depth,
102107
};
103108

109+
// Hacky fix to avoid processing certain transactions created by ordered list indexing plugin.
110+
111+
// True when an existing ordered list item is assigned an index for the first time, which happens
112+
// immediately after it's created. Using this condition to start an animation ensures it's not
113+
// immediately overridden by a different transaction created by the ordered list indexing plugin.
114+
const indexInitialized =
115+
oldAttrs.listItemIndex === null &&
116+
newAttrs.listItemIndex !== null;
117+
118+
// True when an existing ordered list item changes nesting levels, before its index is updated by the
119+
// ordered list indexing plugin. This condition ensures that animations for indentation still work with
120+
// ordered list items, while preventing unnecessary animations being done when dragging/dropping them.
121+
const depthChanged =
122+
oldAttrs.listItemIndex !== null &&
123+
newAttrs.listItemIndex !== null &&
124+
oldAttrs.listItemIndex === newAttrs.listItemIndex;
125+
126+
// Only false for transactions in which the block remains an ordered list item before & after, but neither
127+
// of the previous conditions apply.
128+
const shouldUpdate =
129+
oldAttrs.listItemType === "ordered" &&
130+
newAttrs.listItemType === "ordered"
131+
? indexInitialized || depthChanged
132+
: true;
133+
104134
if (
105-
JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs) // TODO: faster deep equal?
135+
JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs) && // TODO: faster deep equal?
136+
shouldUpdate
106137
) {
107-
(oldAttrs as any).depthChange = oldAttrs.depth - newAttrs.depth;
138+
(oldAttrs as any)["depth-change"] =
139+
oldAttrs.depth - newAttrs.depth;
108140
prev.prevBlockAttrs[node.node.attrs.id] = oldAttrs;
141+
142+
// for debugging:
143+
console.log(
144+
"id:",
145+
node.node.attrs.id,
146+
"previousBlockTypePlugin changes detected, oldAttrs",
147+
oldAttrs,
148+
"new",
149+
newAttrs
150+
);
151+
109152
prev.needsUpdate = true;
110153
}
111154
}
@@ -119,25 +162,35 @@ export const PreviousBlockTypePlugin = () => {
119162
decorations(state) {
120163
const pluginState = (this as Plugin).getState(state);
121164
if (!pluginState.needsUpdate) {
165+
// console.log("0");
122166
return undefined;
123167
}
124168

125169
const decorations: Decoration[] = [];
126170

127171
state.doc.descendants((node, pos) => {
128172
if (!node.attrs.id) {
173+
// console.log("1");
129174
return;
130175
}
131176
const prevAttrs = pluginState.prevBlockAttrs[node.attrs.id];
132177
if (!prevAttrs) {
178+
// console.log("2");
133179
return;
134180
}
135181

136182
const decorationAttributes: any = {};
137183
for (let [nodeAttr, val] of Object.entries(prevAttrs)) {
138-
decorationAttributes[insertPrev(BlockAttributes[nodeAttr])] =
184+
decorationAttributes["data-prev-" + nodeAttributes[nodeAttr]] =
139185
val || "none";
140186
}
187+
188+
// for debugging:
189+
console.log(
190+
"previousBlockTypePlugin committing decorations",
191+
decorationAttributes
192+
);
193+
141194
const decoration = Decoration.node(pos, pos + node.nodeSize, {
142195
...decorationAttributes,
143196
});

0 commit comments

Comments
 (0)