Skip to content

Commit

Permalink
Sync types with tiptap
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Nov 5, 2024
1 parent 765a781 commit 3cc6cfb
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 12 deletions.
87 changes: 75 additions & 12 deletions src/lib/SvelteNodeViewRenderer.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NodeView, Editor } from '@tiptap/core';
import { NodeView, Editor, getRenderedAttributes } from '@tiptap/core';
import type { NodeViewRenderer, NodeViewProps, NodeViewRendererOptions, DecorationWithType } from '@tiptap/core';
import type { Decoration } from '@tiptap/pm/view';
import type { Decoration, DecorationSource } from '@tiptap/pm/view';
import type { Node as ProseMirrorNode } from '@tiptap/pm/model';
import { type Component, mount } from 'svelte';

Expand All @@ -10,15 +10,22 @@ import { invariant } from './utils';

interface RendererUpdateProps {
oldNode: ProseMirrorNode;
oldDecorations: Decoration[];
oldDecorations: readonly Decoration[];
oldInnerDecorations: DecorationSource;
newNode: ProseMirrorNode;
newDecorations: Decoration[];
newDecorations: readonly Decoration[];
newInnerDecorations: DecorationSource;
updateProps: () => void;
}

type AttrProps =
| Record<string, string>
| ((props: { node: ProseMirrorNode; HTMLAttributes: Record<string, any> }) => Record<string, string>);

export interface SvelteNodeViewRendererOptions extends NodeViewRendererOptions {
update: ((props: RendererUpdateProps) => boolean) | null;
as?: string;
attrs?: AttrProps;
}

class SvelteNodeView extends NodeView<Component<NodeViewProps>, Editor, SvelteNodeViewRendererOptions> {
Expand All @@ -31,9 +38,12 @@ class SvelteNodeView extends NodeView<Component<NodeViewProps>, Editor, SvelteNo
const props = $state<NodeViewProps>({
editor: this.editor,
node: this.node,
decorations: this.decorations,
decorations: this.decorations as DecorationWithType[],
innerDecorations: this.innerDecorations,
view: this.view,
selected: false,
extension: this.extension,
HTMLAttributes: this.HTMLAttributes,
getPos: () => this.getPos(),
updateAttributes: (attributes = {}) => this.updateAttributes(attributes),
deleteNode: () => this.deleteNode(),
Expand Down Expand Up @@ -68,6 +78,7 @@ class SvelteNodeView extends NodeView<Component<NodeViewProps>, Editor, SvelteNo
});

this.appendContendDom();
this.updateElementAttributes();
}

private appendContendDom() {
Expand Down Expand Up @@ -97,63 +108,115 @@ class SvelteNodeView extends NodeView<Component<NodeViewProps>, Editor, SvelteNo

handleSelectionUpdate() {
const { from, to } = this.editor.state.selection;
const pos = this.getPos();

if (typeof pos !== 'number') {
return;
}

if (from <= pos && to >= pos + this.node.nodeSize) {
if (this.renderer.props.selected) {
return;
}

if (from <= this.getPos() && to >= this.getPos() + this.node.nodeSize) {
this.selectNode();
} else {
if (!this.renderer.props.selected) {
return;
}

this.deselectNode();
}
}

update(node: ProseMirrorNode, decorations: DecorationWithType[]): boolean {
const updateProps = () => {
this.renderer.updateProps({ node, decorations });
update(node: ProseMirrorNode, decorations: readonly Decoration[], innerDecorations: DecorationSource): boolean {
const updateProps = (props: Partial<NodeViewProps>) => {
this.renderer.updateProps(props);

if (typeof this.options.attrs === 'function') {
this.updateElementAttributes();
}
};

if (typeof this.options.update === 'function') {
const oldNode = this.node;
const oldDecorations = this.decorations;
const oldInnerDecorations = this.innerDecorations;

this.node = node;
this.decorations = decorations;
this.innerDecorations = innerDecorations;

return this.options.update({
oldNode,
oldDecorations,
oldInnerDecorations,
newNode: node,
newDecorations: decorations,
updateProps: () => updateProps(),
newInnerDecorations: innerDecorations,
updateProps: () =>
updateProps({
node,
decorations: decorations as DecorationWithType[],
innerDecorations,
}),
});
}

if (node.type !== this.node.type) {
return false;
}

if (node === this.node && this.decorations === decorations) {
if (node === this.node && this.decorations === decorations && this.innerDecorations === innerDecorations) {
return true;
}

this.node = node;
this.decorations = decorations;
updateProps();
this.innerDecorations = innerDecorations;

updateProps({
node,
decorations: decorations as DecorationWithType[],
innerDecorations,
});

return true;
}

selectNode(): void {
this.renderer.updateProps({ selected: true });
this.renderer.dom.classList.add('ProseMirror-selectednode');
}

deselectNode(): void {
this.renderer.updateProps({ selected: false });
this.renderer.dom.classList.remove('ProseMirror-selectednode');
}

destroy(): void {
this.renderer.destroy();
this.editor.off('selectionUpdate', this.handleSelectionUpdate);
this.contentDOMElement = null;
}

/**
* Update the attributes of the top-level element that holds the React component.
* Applying the attributes defined in the `attrs` option.
*/
updateElementAttributes() {
if (this.options.attrs) {
let attrsObj: Record<string, string> = {};
if (typeof this.options.attrs === 'function') {
const extensionAttributes = this.editor.extensionManager.attributes;
const HTMLAttributes = getRenderedAttributes(this.node, extensionAttributes);
attrsObj = this.options.attrs({ node: this.node, HTMLAttributes });
} else {
attrsObj = this.options.attrs;
}
this.renderer.updateAttributes(attrsObj);
}
}
}

const SvelteNodeViewRenderer = (
Expand Down
6 changes: 6 additions & 0 deletions src/lib/SvelteRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class SvelteRenderer {
Object.assign(this.props, props);
}

updateAttributes(attributes: Record<string, string>): void {
Object.keys(attributes).forEach((key) => {
this.dom.setAttribute(key, attributes[key]);
});
}

destroy(): void {
unmount(this.component);
}
Expand Down

0 comments on commit 3cc6cfb

Please sign in to comment.