Repository: ueberdosis/tiptap
File: packages/extension-link/src/link.ts (TypeScript)

Code before:
      !this.options.isAllowedUri(HTMLAttributes.href, {
        defaultValidate: href => !!isAllowedUri(href, this.options.protocols),
        protocols: this.options.protocols,
        defaultProtocol: this.options.defaultProtocol,
      })
    ) {
      // strip out the href
      return [
        'a',
        mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }),
        0,
      ]
    }

    return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
  },

  addCommands() {
    return {
      setLink:
        attributes => ({ chain }) => {
          const { href } = attributes

          if (!isAllowedUri(href, this.options.protocols)) {
            throw new Error('Invalid protocol')
          }

          return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()
        },

      toggleLink:
        attributes => ({ chain }) => {
          const { href } = attributes

          if (!isAllowedUri(href, this.options.protocols)) {
            throw new Error('Invalid protocol')
          }

          return chain()
            .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
            .setMeta('preventAutolink', true)
            .run()
        },

      unsetLink:
        () => ({ chain }) => {
          return chain()
            .unsetMark(this.name, { extendEmptyMarkRange: true })
            .setMeta('preventAutolink', true)
            .run()
        },

Review comment (suggestion):
I don't think throwing an error is appropriate here. This makes the caller have to deal with the error.

The more common pattern for commands would be to return false, i.e. I refuse to execute this command

Code after:
      !this.options.isAllowedUri(HTMLAttributes.href, {
        defaultValidate: href => !!isAllowedUri(href, this.options.protocols),
        protocols: this.options.protocols,
        defaultProtocol: this.options.defaultProtocol,
      })
    ) {
      // strip out the href
      return [
        'a',
        mergeAttributes(this.options.HTMLAttributes, { ...HTMLAttributes, href: '' }),
        0,
      ]
    }

    return ['a', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
  },

  addCommands() {
    return {
      setLink:
        attributes => ({ chain }) => {
          const { href } = attributes

          if (!this.options.isAllowedUri(href, {
            defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
            protocols: this.options.protocols,
            defaultProtocol: this.options.defaultProtocol,
          })) {
            return false
          }

          return chain().setMark(this.name, attributes).setMeta('preventAutolink', true).run()
        },

      toggleLink:
        attributes => ({ chain }) => {
          const { href } = attributes

          if (!this.options.isAllowedUri(href, {
            defaultValidate: url => !!isAllowedUri(url, this.options.protocols),
            protocols: this.options.protocols,
            defaultProtocol: this.options.defaultProtocol,
          })) {
            return false
          }

          return chain()
            .toggleMark(this.name, attributes, { extendEmptyMarkRange: true })
            .setMeta('preventAutolink', true)
            .run()
        },