Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
36 changes: 35 additions & 1 deletion examples/07-collaboration/11-yhub/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import "@blocknote/core/fonts/inter.css";
import "@blocknote/mantine/style.css";
import { BlockNoteView } from "@blocknote/mantine";
import { useCreateBlockNote } from "@blocknote/react";
import { Awareness } from "@y/protocols/awareness";
import {
Awareness,
encodeAwarenessUpdate,
applyAwarenessUpdate,
} from "@y/protocols/awareness";
import { withCollaboration } from "@blocknote/core/y";
import * as Y from "@y/y";

Expand Down Expand Up @@ -80,6 +84,36 @@ function setupTwoWaySync(doc1: Y.Doc, doc2: Y.Doc) {
setupTwoWaySync(doc, doc2);
setupTwoWaySync(suggestingDoc, suggestionModeDoc);

// Sync awareness states so cursors show up across editors
function setupAwarenessSync(a1: Awareness, a2: Awareness) {
// Initial sync
applyAwarenessUpdate(
a2,
encodeAwarenessUpdate(a1, [a1.clientID]),
"sync",
);
applyAwarenessUpdate(
a1,
encodeAwarenessUpdate(a2, [a2.clientID]),
"sync",
);

a1.on("update", ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
const update = encodeAwarenessUpdate(a1, added.concat(updated).concat(removed));
applyAwarenessUpdate(a2, update, "sync");
});

a2.on("update", ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
const update = encodeAwarenessUpdate(a2, added.concat(updated).concat(removed));
applyAwarenessUpdate(a1, update, "sync");
});
}

setupAwarenessSync(provider.awareness, provider2.awareness);
setupAwarenessSync(suggestingProvider.awareness, suggestionModeProvider.awareness);
setupAwarenessSync(provider.awareness, suggestingProvider.awareness);
setupAwarenessSync(provider.awareness, suggestionModeProvider.awareness);

function Editor({
fragment,
provider,
Expand Down
59 changes: 57 additions & 2 deletions packages/core/src/editor/Block.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ BASIC STYLES
transition: all 0.2s;
/* Workaround for selection issue on Chrome, see #1588 and also here:
https://discuss.prosemirror.net/t/mouse-down-selection-behaviour-different-on-chrome/8426
The :before element causes the selection to be set in the wrong place vs
other browsers. Setting no height fixes this, while list item indicators are
The :before element causes the selection to be set in the wrong place vs
other browsers. Setting no height fixes this, while list item indicators are
still displayed fine as overflow is not hidden. */
height: 0;
overflow: visible;
Expand Down Expand Up @@ -740,3 +740,58 @@ div[data-type="modification"] {
text-decoration: line-through;
text-decoration-thickness: 1px;
}

/* Suggestion decoration styling (data-diff-type drives everything) */
[data-diff-type="inline-insert"],
[data-diff-type="block-insert"] {
background-color: color-mix(
in srgb,
var(--author-color, #28a745) 22%,
transparent
);
text-decoration: underline;
text-decoration-color: var(--author-color, #28a745);
text-decoration-thickness: 2px;
border-radius: 2px;
}

[data-diff-type="inline-delete"],
[data-diff-type="block-delete"] {
background-color: color-mix(
in srgb,
var(--author-color, #dc3545) 14%,
transparent
);
text-decoration: line-through;
text-decoration-color: var(--author-color, #dc3545);
color: #555;
border-radius: 2px;
}
[data-diff-type="block-delete"] {
padding: 2px 4px;
margin: 2px 0;
}
[data-diff-type="block-delete"] * {
text-decoration: line-through;
text-decoration-color: var(--author-color, #dc3545);
color: #555;
}
[data-diff-type="inline-delete"] {
padding: 0 1px;
}

[data-diff-type="inline-update"],
[data-diff-type="block-update"] {
outline: 1.5px dashed var(--author-color, #ffc107);
outline-offset: 1px;
border-radius: 2px;
}

[data-diff-type="block-delete"] strong,
[data-diff-type="inline-delete"] strong {
font-weight: normal;
}
[data-diff-type="block-delete"] em,
[data-diff-type="inline-delete"] em {
font-style: normal;
}
3 changes: 1 addition & 2 deletions packages/core/src/schema/blocks/createSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ export function addNodeAndExtensionsToSpec<
// Gets the block
const block = getBlockFromPos(
props.getPos,
editor,
this.editor,
props.view.state.doc,
blockConfig.type,
);
// Gets the custom HTML attributes for `blockContent` nodes
Expand Down
38 changes: 12 additions & 26 deletions packages/core/src/schema/blocks/internal.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { Attribute, Attributes, Editor, Node } from "@tiptap/core";
import { Attribute, Attributes, Node } from "@tiptap/core";
import type { Node as PMNode } from "prosemirror-model";
import { getBlock } from "../../api/blockManipulation/getBlock/getBlock.js";
import { defaultBlockToHTML } from "../../blocks/defaultBlockHelpers.js";
import type { BlockNoteEditor } from "../../editor/BlockNoteEditor.js";
import type { ExtensionFactoryInstance } from "../../editor/BlockNoteExtension.js";
import { mergeCSSClasses } from "../../util/browser.js";
import { camelToDataKebab } from "../../util/string.js";
import { InlineContentSchema } from "../inlineContent/types.js";
import { PropSchema, Props } from "../propTypes.js";
import { StyleSchema } from "../styles/types.js";
import {
BlockConfig,
BlockSchemaWithBlock,
LooseBlockSpec,
SpecificBlock,
} from "./types.js";
import { BlockConfig, BlockFromConfig, LooseBlockSpec } from "./types.js";

// Function that uses the 'propSchema' of a blockConfig to create a TipTap
// node's `addAttributes` property.
Expand Down Expand Up @@ -85,22 +79,16 @@ export function propsToAttributes(propSchema: PropSchema): Attributes {
export function getBlockFromPos<
BType extends string,
Config extends BlockConfig,
BSchema extends BlockSchemaWithBlock<BType, Config>,
I extends InlineContentSchema,
S extends StyleSchema,
>(
getPos: () => number | undefined,
editor: BlockNoteEditor<BSchema, I, S>,
tipTapEditor: Editor,
type: BType,
) {
>(getPos: () => number | undefined, doc: PMNode, type: BType) {
// TODO is there a cleaner implementation of this? Probably...
const pos = getPos();
// Gets position of the node
if (pos === undefined) {
throw new Error("Cannot find node position");
}

// Gets parent blockContainer node
const blockContainer = tipTapEditor.state.doc.resolve(pos!).node();
const blockContainer = doc.resolve(pos).node();
// Gets block identifier
const blockIdentifier = blockContainer.attrs.id;

Expand All @@ -109,16 +97,14 @@ export function getBlockFromPos<
}

// Gets the block
const block = editor.getBlock(blockIdentifier)! as SpecificBlock<
BSchema,
BType,
I,
S
const block = getBlock(doc, blockIdentifier) as BlockFromConfig<
Config,
any,
any
>;
if (block.type !== type) {
throw new Error("Block type does not match");
}

return block;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function setupTwoWaySync(doc1: Y.Doc, doc2: Y.Doc) {
});
}

describe("RelativePositionMapping (@y/y)", () => {
describe.skip("RelativePositionMapping (@y/y)", () => {
it("should return the same position when no changes are made", () => {
const ydoc = new Y.Doc();
const remoteYdoc = new Y.Doc();
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/y/extensions/RelativePositionMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const RelativePositionMappingExtension = createExtension(
position + (side === "right" ? 1 : -1),
),
ySyncPluginState.ytype,
ySyncPluginState.attributionManager,
ySyncPluginState.attributionManager || undefined,
);

return () => {
Expand All @@ -32,8 +32,8 @@ export const RelativePositionMappingExtension = createExtension(
) as typeof ySyncPluginState;
const pos = posStore(
editor.prosemirrorState.doc,
curYSyncPluginState.ytype,
curYSyncPluginState.attributionManager,
curYSyncPluginState.ytype || undefined,
curYSyncPluginState.attributionManager || undefined,
);

// This can happen if the element is garbage collected
Expand Down
Loading
Loading