slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.98k stars 3.41k forks source link

Extending BlockEmbed with typescript #1233

Closed pixelpax closed 10 months ago

pixelpax commented 7 years ago

I'd like to be able to extend native blots as seen in cloning medium w/ parchment. I am using angular2 with typescript, and typescript does not believe that BlockEmbed is a real constructor. Steps for Reproduction

Using the following code (pretty much directly from above tutorial): let BlockEmbed = Quill.import('blots/block/embed');

class DividerBlot extends BlockEmbed { }
DividerBlot.blotName = 'divider';
DividerBlot.tagName = 'hr';

Expected behavior: I expected this to extend BlockEmbed blots.

Actual behavior: I get TS2507: Type 'any' is not a constructor function type.

Version: 1.1.9

iblank commented 7 years ago

We ran into the same issue and were able to get around it by importing the embed blot from quill and setting the type of the block embed to that type. Also, had to set the class properties within the new class.

import EmbedBlot from 'quill';

let BlockEmbed: EmbedBlot = Quill.import('blots/block/embed');
class DividerBlot extends BlockEmbed {
      static blotName = 'divider';
      static tagName = 'hr';
}
Quill.register(DividerBlot);
rajivchugh commented 7 years ago

@iblank I am facing the same issue. However, I am unable to import EmbedBlot from quill. I am getting the following error:

Module '"quill"' has no exported member 'EmbedBlot'.)

Can you please help. Thanks.

Percipient24 commented 7 years ago

I had trouble with this, also.

The best I was able to do was work in (the more forgiving) ES6 when defining the CustomBlock:

// in some CustomBlock.js file:

import * as Quill from 'quill';

const BlockEmbed = Quill.import('blots/block/embed');

export class CustomBlock extends BlockEmbed {
  // whatever you need your block to do
}
CustomBlock.blotName = 'custom';
CustomBlock.tagName = 'div';

export default CustomBlock;

And then use require() on the TypeScript side:

// in some Editor.ts file:
// (these are the relevant parts of a larger file)

import * as Quill from 'quill';
const customBlockModule = require('path/to/CustomBlock');

Quill.register(customBlockModule.CustomBlock);
this.editor = new Quill(someElement, someOptions);
this.editor.insertEmbed(someIndex, 'custom');

Is it ideal? No.

Does it work? Yes.

But I like the approach @noomz found much better. (see below)

noomz commented 7 years ago

In my Angular 2 typescript environment,Quill.import() return any type which cannot be extended. So this is my approach:

import * as Quill from 'quill';
import * as Parchment from "parchment";
const QuillBlockEmbed = Quill.import('blots/block/embed');

class BlockEmbed extends Parchment.default.Embed {}
BlockEmbed.prototype = QuillBlockEmbed.prototype;

class Video extends BlockEmbed {

}
amccampos commented 7 years ago

Thanks @noomz. I took several hours to figure out how to get Quill and Parchment working together in Typescript (Angular 2 env). Your approach saved the day. It would be much easer if parchment typescript definitions were inside quill ts definitions.

mrmurphy commented 7 years ago

@noomz's solution helps my code to type check, but I get a runtime error from webpack when I use it:

vivaldi_2017-04-18 14 32 04
Cannot assign to read only property 'prototype' of function 'class BlockEmbed extends __WEBPACK_IMPORTED_MODULE_4_parchment__["default"].Embed
mrmurphy commented 7 years ago

I was able to appease the compiler and avoid the runtime errors by declaring the type of BlockEmbed as Parchment.default.Embed:

import * as Parchment from "parchment"
const BlockEmbed = Quill.import("blots/block/embed") as typeof Parchment.default.Embed;

class Foo extends BlockEmbed {
    ...
}
Percipient24 commented 7 years ago

I had been using @noomz's approach ( https://github.com/quilljs/quill/issues/1233#issuecomment-288968965 ) - but ran into an error when reading in a saved delta:

ERROR TypeError: Cannot read property 'length' of undefined

(Which seemed to have something to do with the prototype shenanigans.)

After switching to @splodingsocks' approach ( https://github.com/quilljs/quill/issues/1233#issuecomment-294973856 ) that error went away!

noomz commented 7 years ago

Is it related to tsconfig ? Below is mine:

{
  "compilerOptions": {
    "baseUrl": "",
    "declaration": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "lib": [
      "es2015",
      "dom"
    ],
    "mapRoot": "./",
    "module": "es2015",
    "moduleResolution": "node",
    "outDir": "../dist/out-tsc",
    "sourceMap": true,
    "target": "es5"
  }
}
LouisWayne commented 7 years ago

Try this:

import * as Quill from 'quill';
import * as Parchment from "parchment";
const Block = Quill.import('blots/block') as typeof Parchment.default.Block;

export class CustomBlockBlot extends Block {
    static blotName: string = 'customBlock';
    static tagName: string = 'span';
}

Updates

After 1.3.6, the below code works fine:

import { Quill } from 'quill';
import Parchment from 'parchment';
const Block = Quill.import('blots/block') as typeof Parchment.Block;

export class CustomBlockBlot extends Block {
    static blotName: string = 'customBlock';
    static tagName: string = 'span';
}
antonyboom commented 6 years ago

I extended emded block as described above, but still getting an error

Error: [Parchment] Unable to create marker blot

what am I doing wrong?

import {Editor} from 'primeng/editor';

import * as Quill from 'quill';
import * as Parchment from 'parchment';
const Block = Quill.import('blots/block/embed');
class BlockEmbed extends Parchment.default.Embed {}
BlockEmbed.prototype = Block.prototype;

export class Variable extends BlockEmbed {

  static blotName = 'marker';
  static tagName = 'marker';

  static create(value: any) {
    console.log(value);
    const node = (super.create(value) as any);
    node.innerHTML = '<span contenteditable=false>' + value + '</span>';
    node.setAttribute('contenteditable', false);
    return node;
  }

}

Variable.blotName = 'marker';
Variable.tagName = 'marker';

Quill.register('formats/marker', Variable);

@Component({
  selector: 'manager',
  templateUrl: './manager.component.html',
  styleUrls: ['./manager.component.css']
})

export class ManagerComponent implements OnInit, AfterViewInit {

   private quill: any;
  @ViewChild(Editor) editorComponent: Editor;

  ngOnInit() {}

 // based on primeng github issue this how we can get references to quill 
  ngAfterViewInit() {
    this.quill = this.editorComponent.quill;
  }

 variableSelected(event) {
    // grubbing string variable from event 
    this.quill.insertEmbed(this.cursor.index || 0, 'marker', event.value);
  }

}
iblank commented 6 years ago

Ok, I tried experimenting with your code in my own project and was able to reproduce the error you are getting. Try changing this:

import * as Quill from 'quill';
import * as Parchment from 'parchment';
const Block = Quill.import('blots/block/embed');
class BlockEmbed extends Parchment.default.Embed {}
BlockEmbed.prototype = Block.prototype;

to this:

declare var Quill: any;
const BlockEmbed = Quill.import('blots/block/embed');
antonyboom commented 6 years ago

@iblank Ian, I'm giving up to understand it.... Why does it work ?

iblank commented 6 years ago

It took a lot of trial and error for me to figure out too. This might not be exactly right but here goes...

Since Quill is defined globally from the JS file, you can use it here. declare var Quill: any; just let's typescript know that the variable is defined from outside of the scope of this file. Then your custom blot needs to extend BlockEmbed, so that is the only class you need to import from quill.

antonyboom commented 6 years ago

@iblank my respect to you, thanks a lot!

Hussein-Dahir commented 6 years ago

That's worked perfectly for me (and without Parchment)

`import * as QuillNamespace from 'quill';

const Quill: any = QuillNamespace; const BlockEmbed = Quill.import('blots/block/embed');

export class HorizontalRule extends BlockEmbed { }

HorizontalRule.blotName = 'divider'; HorizontalRule.tagName = 'hr';

Quill.register(HorizontalRule);`

put this code in the component or module you need, and then Quill will support "\


" tag. same thing applies to other html tags (but may need different package to be imported).

Source: StackOverFlow

benbro commented 10 months ago

v2.0.0-beta.2 provides TypeScript definitions.

SCWR commented 3 months ago

@benbro Do you have any documentation? I couldn't find a description in the documentation https://quilljs.com/docs/guides/cloning-medium-with-parchment#tweets

mattes3 commented 2 months ago

Today, in 2024 with Quill V2, I am using this:

import { EmbedBlot } from 'parchment';

class Foo extends EmbedBlot {
...
}
imab001 commented 2 days ago

this works as well

import Quill, { Parchment } from 'quill';

const EmbedBlot = Quill.import(
  'blots/block/embed'
) as typeof Parchment.EmbedBlot;