Skip to content

Commit be68a1f

Browse files
committed
Merge branch 'y-prosemirror-decorations' into y-prosemirror-tests-decorations
2 parents 0ccde78 + 6befda3 commit be68a1f

13 files changed

Lines changed: 2482 additions & 703 deletions

File tree

examples/07-collaboration/11-yhub/src/App.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import "@blocknote/core/fonts/inter.css";
33
import "@blocknote/mantine/style.css";
44
import { BlockNoteView } from "@blocknote/mantine";
55
import { useCreateBlockNote } from "@blocknote/react";
6-
import { Awareness } from "@y/protocols/awareness";
6+
import {
7+
Awareness,
8+
encodeAwarenessUpdate,
9+
applyAwarenessUpdate,
10+
} from "@y/protocols/awareness";
711
import { withCollaboration } from "@blocknote/core/y";
812
import * as Y from "@y/y";
913

@@ -80,6 +84,36 @@ function setupTwoWaySync(doc1: Y.Doc, doc2: Y.Doc) {
8084
setupTwoWaySync(doc, doc2);
8185
setupTwoWaySync(suggestingDoc, suggestionModeDoc);
8286

87+
// Sync awareness states so cursors show up across editors
88+
function setupAwarenessSync(a1: Awareness, a2: Awareness) {
89+
// Initial sync
90+
applyAwarenessUpdate(
91+
a2,
92+
encodeAwarenessUpdate(a1, [a1.clientID]),
93+
"sync",
94+
);
95+
applyAwarenessUpdate(
96+
a1,
97+
encodeAwarenessUpdate(a2, [a2.clientID]),
98+
"sync",
99+
);
100+
101+
a1.on("update", ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
102+
const update = encodeAwarenessUpdate(a1, added.concat(updated).concat(removed));
103+
applyAwarenessUpdate(a2, update, "sync");
104+
});
105+
106+
a2.on("update", ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
107+
const update = encodeAwarenessUpdate(a2, added.concat(updated).concat(removed));
108+
applyAwarenessUpdate(a1, update, "sync");
109+
});
110+
}
111+
112+
setupAwarenessSync(provider.awareness, provider2.awareness);
113+
setupAwarenessSync(suggestingProvider.awareness, suggestionModeProvider.awareness);
114+
setupAwarenessSync(provider.awareness, suggestingProvider.awareness);
115+
setupAwarenessSync(provider.awareness, suggestionModeProvider.awareness);
116+
83117
function Editor({
84118
fragment,
85119
provider,

packages/core/src/editor/Block.css

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ BASIC STYLES
3333
transition: all 0.2s;
3434
/* Workaround for selection issue on Chrome, see #1588 and also here:
3535
https://discuss.prosemirror.net/t/mouse-down-selection-behaviour-different-on-chrome/8426
36-
The :before element causes the selection to be set in the wrong place vs
37-
other browsers. Setting no height fixes this, while list item indicators are
36+
The :before element causes the selection to be set in the wrong place vs
37+
other browsers. Setting no height fixes this, while list item indicators are
3838
still displayed fine as overflow is not hidden. */
3939
height: 0;
4040
overflow: visible;
@@ -740,3 +740,58 @@ div[data-type="modification"] {
740740
text-decoration: line-through;
741741
text-decoration-thickness: 1px;
742742
}
743+
744+
/* Suggestion decoration styling (data-diff-type drives everything) */
745+
[data-diff-type="inline-insert"],
746+
[data-diff-type="block-insert"] {
747+
background-color: color-mix(
748+
in srgb,
749+
var(--author-color, #28a745) 22%,
750+
transparent
751+
);
752+
text-decoration: underline;
753+
text-decoration-color: var(--author-color, #28a745);
754+
text-decoration-thickness: 2px;
755+
border-radius: 2px;
756+
}
757+
758+
[data-diff-type="inline-delete"],
759+
[data-diff-type="block-delete"] {
760+
background-color: color-mix(
761+
in srgb,
762+
var(--author-color, #dc3545) 14%,
763+
transparent
764+
);
765+
text-decoration: line-through;
766+
text-decoration-color: var(--author-color, #dc3545);
767+
color: #555;
768+
border-radius: 2px;
769+
}
770+
[data-diff-type="block-delete"] {
771+
padding: 2px 4px;
772+
margin: 2px 0;
773+
}
774+
[data-diff-type="block-delete"] * {
775+
text-decoration: line-through;
776+
text-decoration-color: var(--author-color, #dc3545);
777+
color: #555;
778+
}
779+
[data-diff-type="inline-delete"] {
780+
padding: 0 1px;
781+
}
782+
783+
[data-diff-type="inline-update"],
784+
[data-diff-type="block-update"] {
785+
outline: 1.5px dashed var(--author-color, #ffc107);
786+
outline-offset: 1px;
787+
border-radius: 2px;
788+
}
789+
790+
[data-diff-type="block-delete"] strong,
791+
[data-diff-type="inline-delete"] strong {
792+
font-weight: normal;
793+
}
794+
[data-diff-type="block-delete"] em,
795+
[data-diff-type="inline-delete"] em {
796+
font-style: normal;
797+
}

packages/core/src/schema/blocks/createSpec.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,7 @@ export function addNodeAndExtensionsToSpec<
197197
// Gets the block
198198
const block = getBlockFromPos(
199199
props.getPos,
200-
editor,
201-
this.editor,
200+
props.view.state.doc,
202201
blockConfig.type,
203202
);
204203
// Gets the custom HTML attributes for `blockContent` nodes

packages/core/src/schema/blocks/internal.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
import { Attribute, Attributes, Editor, Node } from "@tiptap/core";
1+
import { Attribute, Attributes, Node } from "@tiptap/core";
2+
import type { Node as PMNode } from "prosemirror-model";
3+
import { getBlock } from "../../api/blockManipulation/getBlock/getBlock.js";
24
import { defaultBlockToHTML } from "../../blocks/defaultBlockHelpers.js";
3-
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
45
import type { ExtensionFactoryInstance } from "../../editor/BlockNoteExtension.js";
56
import { mergeCSSClasses } from "../../util/browser.js";
67
import { camelToDataKebab } from "../../util/string.js";
7-
import { InlineContentSchema } from "../inlineContent/types.js";
88
import { PropSchema, Props } from "../propTypes.js";
9-
import { StyleSchema } from "../styles/types.js";
10-
import {
11-
BlockConfig,
12-
BlockSchemaWithBlock,
13-
LooseBlockSpec,
14-
SpecificBlock,
15-
} from "./types.js";
9+
import { BlockConfig, BlockFromConfig, LooseBlockSpec } from "./types.js";
1610

1711
// Function that uses the 'propSchema' of a blockConfig to create a TipTap
1812
// node's `addAttributes` property.
@@ -85,22 +79,16 @@ export function propsToAttributes(propSchema: PropSchema): Attributes {
8579
export function getBlockFromPos<
8680
BType extends string,
8781
Config extends BlockConfig,
88-
BSchema extends BlockSchemaWithBlock<BType, Config>,
89-
I extends InlineContentSchema,
90-
S extends StyleSchema,
91-
>(
92-
getPos: () => number | undefined,
93-
editor: BlockNoteEditor<BSchema, I, S>,
94-
tipTapEditor: Editor,
95-
type: BType,
96-
) {
82+
>(getPos: () => number | undefined, doc: PMNode, type: BType) {
83+
// TODO is there a cleaner implementation of this? Probably...
9784
const pos = getPos();
9885
// Gets position of the node
9986
if (pos === undefined) {
10087
throw new Error("Cannot find node position");
10188
}
89+
10290
// Gets parent blockContainer node
103-
const blockContainer = tipTapEditor.state.doc.resolve(pos!).node();
91+
const blockContainer = doc.resolve(pos).node();
10492
// Gets block identifier
10593
const blockIdentifier = blockContainer.attrs.id;
10694

@@ -109,16 +97,14 @@ export function getBlockFromPos<
10997
}
11098

11199
// Gets the block
112-
const block = editor.getBlock(blockIdentifier)! as SpecificBlock<
113-
BSchema,
114-
BType,
115-
I,
116-
S
100+
const block = getBlock(doc, blockIdentifier) as BlockFromConfig<
101+
Config,
102+
any,
103+
any
117104
>;
118105
if (block.type !== type) {
119106
throw new Error("Block type does not match");
120107
}
121-
122108
return block;
123109
}
124110

packages/core/src/y/extensions/RelativePositionMapping.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function setupTwoWaySync(doc1: Y.Doc, doc2: Y.Doc) {
2727
});
2828
}
2929

30-
describe("RelativePositionMapping (@y/y)", () => {
30+
describe.skip("RelativePositionMapping (@y/y)", () => {
3131
it("should return the same position when no changes are made", () => {
3232
const ydoc = new Y.Doc();
3333
const remoteYdoc = new Y.Doc();

packages/core/src/y/extensions/RelativePositionMapping.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const RelativePositionMappingExtension = createExtension(
2323
position + (side === "right" ? 1 : -1),
2424
),
2525
ySyncPluginState.ytype,
26-
ySyncPluginState.attributionManager,
26+
ySyncPluginState.attributionManager || undefined,
2727
);
2828

2929
return () => {
@@ -32,8 +32,8 @@ export const RelativePositionMappingExtension = createExtension(
3232
) as typeof ySyncPluginState;
3333
const pos = posStore(
3434
editor.prosemirrorState.doc,
35-
curYSyncPluginState.ytype,
36-
curYSyncPluginState.attributionManager,
35+
curYSyncPluginState.ytype || undefined,
36+
curYSyncPluginState.attributionManager || undefined,
3737
);
3838

3939
// This can happen if the element is garbage collected

0 commit comments

Comments
 (0)