ueberdosis / tiptap

The headless rich text editor framework for web artisans.
https://tiptap.dev
MIT License
27.26k stars 2.27k forks source link

Community Extensions #819

Closed hanspagel closed 2 weeks ago

hanspagel commented 4 years ago

Hi everyone!

I’ve seen a ton of helpful Gists with custom extensions for Tiptap. It’s amazing what you all come up with! 🥰

Unfortunately, we don’t have the capabilities to maintain all of them in the core packages, but it’s sad to see those gems hidden in some Gists. What if you - the community - could easily provide those as separate npm packages?

Advantages of packages

Proof of Concept

I built a tiny proof of concept for superscript and subscript, see here: https://github.com/hanspagel/tiptap-extension-superscript https://github.com/hanspagel/tiptap-extension-subscript

Usage:

$ yarn add tiptap-extension-superscript
import { Superscript } from 'tiptap-extension-superscript'

new Editor({
  extensions: [
    new Superscript(),
  ],
})

Examples of community Gists, code snippets, PRs and ideas

Tiptap v2

Tiptap v1

Not needed with Tiptap v2

Roadmap

I think we’d need to do a few things to make that easier for everyone:

Your feedback

What do you all think? Would you be up to contribute a community extension?

Feel free to post links to Gists of others you’d love to see published as a package.

SalahAdDin commented 1 year ago
image

I've made an open-source project called to think, which relies on tiptap to develop a lot of extensions, maybe it can help you.

https://github.com/fantasticit/think/tree/main/packages/client/src/tiptap/core/extensions

Man, you have very interesting tools, but your documentation is in Chinese :').

fantasticit commented 1 year ago
image

I've made an open-source project called to think, which relies on tiptap to develop a lot of extensions, maybe it can help you. https://github.com/fantasticit/think/tree/main/packages/client/src/tiptap/core/extensions

Man, you have very interesting tools, but your documentation is in Chinese :').

google translate may help. ^_^

SalahAdDin commented 1 year ago
image

I've made an open-source project called to think, which relies on tiptap to develop a lot of extensions, maybe it can help you. https://github.com/fantasticit/think/tree/main/packages/client/src/tiptap/core/extensions

Man, you have very interesting tools, but your documentation is in Chinese :').

google translate may help. ^_^

I think it is not enough hahahahah

rfgamaral commented 1 year ago

Hey folks, I've been meaning to post this there, but I always end up forgetting about it 😅

This is not an extension per se, but we open-source our Typist editor built on top of Tiptap. It includes a few custom/extended extensions with new and improved features, and it also comes with support for Markdown input/output.

chenyuncai commented 1 year ago

Track Changes like Microsoft Office Word. I have implemented this feature, but the code is just in my project. I will publish one day. mark it

@chenyuncai I'm looking for this functionality these days. Please do share:)

code here

https://github.com/chenyuncai/tiptap-track-change-extension

antoniormrzz commented 1 year ago

Is there a way to limit html size? I read the other thread as well, but I think the custom extensions were for tiptap 1. It's easy to abuse char limit otherwise. What's stoping a user from bolding and italicizing every other char in a 500 long text?

radans commented 1 year ago

Hello everyone, I created two extensions for tiptap, here they are. Hope that it will help out someone

https://www.npmjs.com/package/@rcode-link/tiptap-drawio https://www.npmjs.com/package/@rcode-link/tiptap-comments

linspw commented 1 year ago

@chenyuncai Your idea looks amazing, can you share? Case with, the link seems to be broken

chenyuncai commented 1 year ago

@chenyuncai Your idea looks amazing, can you share? Case with, the link seems to be broken

yes, take a look here https://github.com/chenyuncai/tiptap-track-change-extension

sjdemartini commented 1 year ago

I've released a package called mui-tiptap https://github.com/sjdemartini/mui-tiptap, which adds built-in styling using Material UI, and includes a suite of additional components and extensions. I've been using this code in a production app successfully for months and have incorporated several things that I think add value beyond vanilla Tiptap. For instance:

Here's a quick demo of some of the UI/functionality (check out the README for a CodeSandbox link and more details): mui-tiptap demo

The package is still fairly new—I plan to add more functionality soon—but I figured folks here may be interested. I welcome feedback and/or contributions!

carlosvaldesweb commented 1 year ago

Hello, I want to share an extension I created for uploading images with a loading placeholder. I have based it on the ProseMirror example at "https://prosemirror.net/examples/upload/". I hope you can add it and find it useful.

I'm a backend developer, and this is my first npm package. I hope everything is alright configured.

https://github.com/carlosvaldesweb/tiptap-extension-upload-image

https://github.com/ueberdosis/tiptap/assets/32969705/ba6d89ec-be9f-4942-809f-866bf7c19951

HMarzban commented 12 months ago

New Extensions: Hyperlink & HyperMultimedia 🚀

Hello TipTappers!

We're excited to introduce two new extensions to enhance your Tiptap editing experience: @docs.plus/extension-hyperlink and @docs.plus/extension-hypermultimedia.

Hyperlink Extension 🎩🪄

Inspired by Tiptap's extension-link, our hyperlink extension adds a touch of Google Docs magic, streamlining hyperlinking with customizable protocols, auto-linking, and interactive dialog boxes for a user-friendly touch.

NPM | Github | Demo

HyperMultimedia Extension 🎥🎶

Enhance Tiptap with our HyperMultimedia extension, facilitating the embedding of Image, YouTube, Vimeo, SoundCloud, and Twitter posts directly within the editor. Each media type comes with a snazzy modal—use ours or craft your own!

NPM | Github | Demo

We value your feedback. Share your thoughts to help us refine these extensions! 💫💬

Hyperlink Demo | HyperMultimedia Demo

https://user-images.githubusercontent.com/20157508/279499202-360ed068-57df-472c-bb72-4a80818a4a8f.mp4

hyperlink-demo (g)

peihk2 commented 11 months ago

Hi, has anyone ever written a hashtag extension (like on Facebook) or a similar extension? I need such an extension but don't know how to customize it

puopg commented 11 months ago

Hi, has anyone ever written a hashtag extension (like on Facebook) or a similar extension? I need such an extension but don't know how to customize it

You can likely extend the mention plugin. The idea is pretty similar right? You trigger with the #, display a set of suggestions, each hashtag is colored in some way with a class.

victorfunes commented 9 months ago

Hi, I needed to support div tags in the html editor, and I kind of get a solution. It is not perfect, but at least it allows you to add div to your code using editor.chain().insertContent().

I hope it will help to other people, and if you can help me to improve this extensiion would be great.

DivExtension.ts:

import { getNodeContent } from "./extensionUtils";

export interface DivOptions {
  HTMLAttributes: Record<string, any>;
}

export interface DivStyleAttributes {
  class?: string;
  style?: string;
}

// declare module "@tiptap/core" {
//   interface Commands<ReturnType> {
//     div: {
//       /**
//        * Set the div
//        */
//       setDiv: (options?: DivStyleAttributes) => ReturnType;
//     };
//   }
// }

export const Div = Node.create<DivOptions>({
  name: "div",
  group: "block",
  atom: true,
  draggable: true,
  content: "block*",
  selectable: true,
  isolating: false,
  allowGapCursor: true,
  defining: true,

  addAttributes() {
    return {
      class: {
        default: this.options.HTMLAttributes.class,
      },
      style: {
        default: this.options.HTMLAttributes.style,
      },
    };
  },

  parseHTML: () => {
    return [
      {
        tag: "div",
      },
    ];
  },

  renderHTML({ node, HTMLAttributes }) {
    const content = getNodeContent(node);
    return ["div", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ...content];
  },

  parseDOM: [{ tag: "div" }],

  toDOM: () => ["div", 0],

  // addCommands: () => {
  //   return {
  //     setDiv:
  //       (options) =>
  //       ({ tr, dispatch, editor }) => {
  //         const divNode = editor.schema.nodes.div.createChecked(options, null);
  //         if (dispatch) {
  //           const offset = tr.selection.anchor + 1;

  //           tr.replaceSelectionWith(divNode)
  //             .scrollIntoView()
  //             .setSelection(TextSelection.near(tr.doc.resolve(offset)));
  //         }
  //         return true;
  //       },
  //   };
  // },

  addOptions: () => {
    return {
      HTMLAttributes: {},
    };
  },
});

extensionUtils.ts:

import { DOMOutputSpec, Fragment, Node } from "@tiptap/pm/model";

export const getNodeContent = (node: Node | Fragment) => {
  const childNodes: DOMOutputSpec[] = [];
  for (let i = 0; i < node.childCount; i++) {
    const currentChild = node.child(i);
    if (currentChild.type.spec.toDOM) {
      const nodeDOMOutputSpec = currentChild.type.spec.toDOM(currentChild);
      const htmlTag = (nodeDOMOutputSpec as any)[0] as string;
      const content = getNodeContent(currentChild.content);
      childNodes.push([htmlTag, currentChild.attrs, ...content]);
    } else {
      if (currentChild.text) {
        childNodes.push(currentChild.text);
      }
    }
  }
  return childNodes;
};
victorfunes commented 9 months ago

I am also trying to support icons in the editor, and it seems easy, but for any reason it is not rendering the svg even when it is inserted in the html. Somebody could help me to guess why?

SvgExtension.ts:

import { mergeAttributes, Node } from "@tiptap/core";
import { getNodeContent } from "./extensionUtils";

export interface SvgOptions {
  HTMLAttributes: Record<string, any>;
}

export interface SvgAttributes {
  class?: string;
  style?: string;
  fill?: string;
  height?: string;
  stroke?: string;
  "stroke-width"?: string;
  version?: string;
  viewBox?: string;
  width?: string;
  xmlns?: string;
  "aria-hidden"?: string;
}

export const Svg = Node.create<SvgOptions>({
  name: "svg",
  group: "block",
  // atom: false,
  draggable: true,
  content: "path*",
  selectable: true,
  // isolating: true,
  // allowGapCursor: true,
  // defining: true,

  addAttributes() {
    return {
      class: {
        default: null,
        renderHTML: (attributes) => {
          return attributes.class
            ? {
                style: attributes.class,
              }
            : undefined;
        },
      },
      style: {
        default: this.options.HTMLAttributes.style,
      },
      fill: {
        default: this.options.HTMLAttributes.fill,
      },
      height: {
        default: this.options.HTMLAttributes.height,
      },
      stroke: {
        default: this.options.HTMLAttributes.stroke,
      },
      "stroke-width": {
        default: this.options.HTMLAttributes["stroke-width"],
      },
      "aria-hidden": {
        default: this.options.HTMLAttributes["aria-hidden"],
      },
      version: {
        default: this.options.HTMLAttributes.version,
      },
      viewBox: {
        default: this.options.HTMLAttributes.viewBox,
      },
      width: {
        default: this.options.HTMLAttributes.width,
      },
      xmlns: {
        default: this.options.HTMLAttributes.xmlns,
      },
    };
  },

  parseHTML: () => {
    return [
      {
        tag: "svg",
      },
    ];
  },

  renderHTML({ node, HTMLAttributes }) {
    const content = getNodeContent(node);
    return ["svg", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), ...content];
  },

  parseDOM: [{ tag: "svg" }],

  toDOM: () => ["svg", 0],

  addOptions: () => {
    return {
      HTMLAttributes: {},
    };
  },
});

PathExtension.ts:

import { mergeAttributes, Node } from "@tiptap/core";

export interface PathOptions {
  HTMLAttributes: Record<string, any>;
}

export interface PathAttributes {
  d?: string;
  "stroke-linecap"?: string;
  "stroke-linejoin"?: string;
}

export const Path = Node.create<PathOptions>({
  name: "path",
  group: "path",
  draggable: false,
  selectable: false,

  addAttributes() {
    return {
      d: {
        default: this.options.HTMLAttributes.d,
      },
      "stroke-linecap": {
        default: this.options.HTMLAttributes["stroke-linecap"],
      },
      "stroke-linejoin": {
        default: this.options.HTMLAttributes["stroke-linejoin"],
      },
    };
  },

  parseHTML: () => {
    return [
      {
        tag: "path",
      },
    ];
  },

  renderHTML({ HTMLAttributes }) {
    return ["path", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)];
  },

  parseDOM: [{ tag: "path" }],

  toDOM: () => ["path", 0],

  addOptions: () => {
    return {
      HTMLAttributes: {},
    };
  },
});

I insert this icon in the editor, but for any reason it is not being displayed. If I add this in a plane html file, it works... Can somebody help me? Thank you!

<svg class="h-6 w-6 m-0.5 rounded-sm text-red-700 dark:text-red-700 dark:bg-gray-200" style="color:rgb(185,28,28);height:1.5rem;width:1.5rem;margin:0.125rem;border-radius:0.125rem" fill="currentColor" height="1em" stroke="currentColor" stroke-width="0" version="1.1" viewbox="0 0 16 16" width="1em" xmlns="http://www.w3.org/2000/svg">
<path d="M13.156 9.211c-0.213-0.21-0.686-0.321-1.406-0.331-0.487-0.005-1.073 0.038-1.69 0.124-0.276-0.159-0.561-0.333-0.784-0.542-0.601-0.561-1.103-1.34-1.415-2.197 0.020-0.080 0.038-0.15 0.054-0.222 0 0 0.339-1.923 0.249-2.573-0.012-0.089-0.020-0.115-0.044-0.184l-0.029-0.076c-0.092-0.212-0.273-0.437-0.556-0.425l-0.171-0.005c-0.316 0-0.573 0.161-0.64 0.403-0.205 0.757 0.007 1.889 0.39 3.355l-0.098 0.239c-0.275 0.67-0.619 1.345-0.923 1.94l-0.040 0.077c-0.32 0.626-0.61 1.157-0.873 1.607l-0.271 0.144c-0.020 0.010-0.485 0.257-0.594 0.323-0.926 0.553-1.539 1.18-1.641 1.678-0.032 0.159-0.008 0.362 0.156 0.456l0.263 0.132c0.114 0.057 0.234 0.086 0.357 0.086 0.659 0 1.425-0.821 2.48-2.662 1.218-0.396 2.604-0.726 3.819-0.908 0.926 0.521 2.065 0.883 2.783 0.883 0.128 0 0.238-0.012 0.327-0.036 0.138-0.037 0.254-0.115 0.325-0.222 0.139-0.21 0.168-0.499 0.13-0.795-0.011-0.088-0.081-0.196-0.157-0.271zM3.307 12.72c0.12-0.329 0.596-0.979 1.3-1.556 0.044-0.036 0.153-0.138 0.253-0.233-0.736 1.174-1.229 1.642-1.553 1.788zM7.476 3.12c0.212 0 0.333 0.534 0.343 1.035s-0.107 0.853-0.252 1.113c-0.12-0.385-0.179-0.992-0.179-1.389 0 0-0.009-0.759 0.088-0.759v0zM6.232 9.961c0.148-0.264 0.301-0.543 0.458-0.839 0.383-0.724 0.624-1.29 0.804-1.755 0.358 0.651 0.804 1.205 1.328 1.649 0.065 0.055 0.135 0.111 0.207 0.166-1.066 0.211-1.987 0.467-2.798 0.779v0zM12.952 9.901c-0.065 0.041-0.251 0.064-0.37 0.064-0.386 0-0.864-0.176-1.533-0.464 0.257-0.019 0.493-0.029 0.705-0.029 0.387 0 0.502-0.002 0.88 0.095s0.383 0.293 0.318 0.333v0z"></path><path d="M14.341 3.579c-0.347-0.473-0.831-1.027-1.362-1.558s-1.085-1.015-1.558-1.362c-0.806-0.591-1.197-0.659-1.421-0.659h-7.75c-0.689 0-1.25 0.561-1.25 1.25v13.5c0 0.689 0.561 1.25 1.25 1.25h11.5c0.689 0 1.25-0.561 1.25-1.25v-9.75c0-0.224-0.068-0.615-0.659-1.421v0zM12.271 2.729c0.48 0.48 0.856 0.912 1.134 1.271h-2.406v-2.405c0.359 0.278 0.792 0.654 1.271 1.134v0zM14 14.75c0 0.136-0.114 0.25-0.25 0.25h-11.5c-0.135 0-0.25-0.114-0.25-0.25v-13.5c0-0.135 0.115-0.25 0.25-0.25 0 0 7.749-0 7.75 0v3.5c0 0.276 0.224 0.5 0.5 0.5h3.5v9.75z">
</path>
</svg>
NiclasDev63 commented 7 months ago

Even though I am still quite new to tiptap/prosemirror, I have managed to develope a working drag handle based on the drag handle from https://github.com/steven-tey/novel. However, unlike with the drag handle in novel, it is possible to drag single list items or whole lists through the editor as expected. Furthermore, it is also possible to select several nodes of different types and drag them as well. I look forward to your feedback and any suggestions for improvement. I have uploaded the drag handle as an extension to npm and of course opensourced it on github.

timomeh commented 6 months ago

I threw together a little extension to add Shiki syntax highlighting to Tiptap. I only did it for myself, but thought it might be useful for someone else.

https://github.com/timomeh/tiptap-extension-code-block-shiki

maggie-chintaro commented 5 months ago

Hey everyone,

I am using TipTap as a document service for sending bulk emails to clients using prefilled variables. I need to import the documents as HTML and maintain the original styling.

From what I have read it is considered a feature of TipTap that the styles are stripped away and I need to make an extension. I am really struggling with this.

Has anyone had any luck?

mfrezghi commented 4 months ago

Hey everyone! I have been trying forever to get a space between functionality in my Tiptap editor. Basically, I would love to be able to have text aligned left and right on the same line - I want to be able to add dates to my header text and have the dates aligned to the right of the page. My current custom extension achieves this, but the text becomes uneditable after I use it:

import { Node, mergeAttributes } from '@tiptap/core';

export const LeftRightJustifyExtension = Node.create({
  name: 'leftRightJustify',

  group: 'block',

  content: 'inline*',

  parseHTML() {
    return [
      {
        tag: 'div[left-right-justify]',
      },
    ];
  },

  renderHTML({ node }) {
    // Extract text content from the node's children
    const text = node.textContent;

    // Find the middle point or some split logic to divide the text
    const middleIndex = text.indexOf(' | '); // Assuming the split point is ' | '
    const leftText = text.slice(0, middleIndex);
    const rightText = text.slice(middleIndex + 3); // Skipping ' | '

    return ['div', mergeAttributes(node.attrs, { 'left-right-justify': 'true' }),
      ['p', { style: 'display: flex' }, leftText],
      ['p', { style: 'display: flex;' }, rightText],
    ];
  },

  addCommands() {
    return {
      setLeftRightJustify: () => ({ commands }) => {
        return commands.setNode(this.name);
      },
      splitLeftRight: () => ({ state, dispatch }) => {
        const { selection, schema } = state;
        const { from, to } = selection;

        if (from !== to) {
          return false; // Do nothing if there's a text selection
        }

        const node = selection.$from.node();
        const pos = selection.$from.parentOffset;
        const textContent = node.textContent;
        const leftText = textContent.slice(0, pos);
        const rightText = textContent.slice(pos);

        const newNode = schema.nodes.leftRightJustify.create({}, [
          schema.text(leftText),
          schema.text(' | '),
          schema.text(rightText),
        ]);

        const tr = state.tr.replaceWith(from - leftText.length, to + rightText.length, newNode);

        dispatch(tr);
        return true;
      },
    };
  },
});
bdbch commented 2 weeks ago

I'm closing this issue for now as this is super legacy.

You can submit your community extensions here: https://github.com/ueberdosis/tiptap/discussions/categories/community-extensions

I'd ask everyone else who had questions in this thread to move it over to Discord or Github Discussions as we can actually mark things as "answered" there :)