slab / quill

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

Add support for tables #117

Open jhchen opened 10 years ago

jhchen commented 10 years ago

As an administrative note, I will delete all non-substantive comments from this thread to cut down the clutter and mass emails to everyone. Please use the Reaction feature to show support.

arturocr commented 10 years ago

Right now I'm taking Quill as the first option to use in a big education related software, but the support for tables is a "deal breaker", is there any progress in the implementation of this feature?

jhchen commented 10 years ago

From some preliminary research, tables are going to be complex to implement and probably won't be added in the immediate future.

arturocr commented 10 years ago

@jhchen thanks for the answer, I understand. Thanks anyway, QuillJS looks really promising, will keep an eye on it and see if I can contribute with something.

benbro commented 9 years ago

Table editor in closure library for reference https://rawgit.com/google/closure-library/master/closure/goog/demos/editor/tableeditor.html

cubitouch commented 9 years ago

I would need that for my project too. I think I can spend some time on this, if someone can advice me some API endpoints or something. Where should I begin with ?

benbro commented 8 years ago

quill supports tabs and automatically expand and shrink the width of each block when adding/removing text. Maybe we can define columns with tabs and add the table borders as a layer ontop of the text. The column width in the table can be synced with the width of the text in each block separated by tabs. Column width in all rows should be synced. Maybe a custom element can be inserted with a configurable width. (similar to how images are inserted).

First Header (tab) Second Header (tab)
Content from cell 1 (tab) Content from cell 2 (tab)
Content in the first column (tab) Content in the second column (tab)

A simple table GUI to add/remove rows and columns might be enough: https://wymeditor.github.io/wymeditor/dist/examples/21-table-plugin.html

ghost commented 8 years ago

would it be possible to allow pasting of tables to not convert them to divs? tried to add the tags to the normalizer whitelist, but it still converted them. I don't mind working with a tweaked core lib until 1.0 is out. Any kind of direction would be greatly appreciated!

saw commented 7 years ago

I really want to take this on but I have some questions. The first one being that I'm really curious what thoughts @jhchen and @george-norris-salesforce have about how to go about doing this. The way I see it there are three levels of support: 1. Handling pasted tables. 2. Providing APIs for interacting with tables 3. Adding UI support for tables

  1. Creating blots in parchment for <table> ,<thead>, <tbody>, <tr> and <th>
  2. Adding a tables module that brings in these blots
  3. Possibly updating clipboard.js to handle tables properly, I think the blots will make this work, but I'm not that familiar with the code.
  4. Chasing down editor bugs when interacting with tables. Content-editable does...things with tables that I don't understand

The next level is the hard part!

  1. Define APIs required (insert/delete col, row, do things with header, etc)
  2. Write test suite
  3. Implement APIs

And finally

  1. Decide proper UI for managing tables
  2. Build UI and wire up with API

Oh and one last thing: how about a Quill slack team?

jhchen commented 7 years ago

Adding tables would be a great addition to Quill, both in beneficial impact but also work required.

I think the key to success here is to start small. So I would scope the initial feature set to:

The first major decision is whether tables should be defined/implemented with multiple Blots (like how lists are implemented) or a single Embed Blot (like formulas or videos). The latter is more straightforward but also more constraining but as a starting point of the discussion we'll start here.

For reference, an expected table would look like this:

<table>
  <tbody>
    <tr>  
      <td>A1</td>
      <td>B1</td>
      <td>C1</td>
    </tr>
    <tr>
      <td>A2</td>
      <td>B2</td>
      <td>C2</td>
    </tr>
  </tbody>
</table>

Inserting such a table could look like this:

insertEmbed(index, 'table', [
  ['A1', 'B1', 'C1'],
  ['A2', 'B2', 'C2']
]);

Deleting the table can use the existing deleteText with appropriate index.

Embeds currently have the limitation that they cannot be updated--one would delete and insert a new table to update. This also more so affects large tables and realtime collaboration use cases. Some sort of update semantic for embeds could probably be introduced to Deltas and tables can just piggyback off of that. The only ramification I can think of is it may standardize around using objects so our definition might change to:

insertEmbed(index, 'table', {
  '1': {
    '1': 'A1', '2': 'B1', '3': 'C1'
  },
  '2': {
    '1': 'A2', '2': 'B2', '3': 'C2'
  }
});

// Theoretical update embed
updateEmbed(index, {
  '1': {
    '3': 'Updated C1 value'
  }
});

The keys dont neccessarily have to be indexes and can be used to bear more meaning, for example to indicate header status.

The other major limitation to implementing as an Embed Blot is: it is not clear how to support formatting within cells. I personally don't think it's a good idea to nest complex formats inside a small table cell anyways but simple formats like bolding within cells seem reasonable. Perhaps this could be an acceptable limitation of an inital table implementation however.

Implementing tables as multple blots would have neither of Embed Blot's limitations, but introduces more decision points to clear up ambiguity it introduces:

There are a lot of paths here and I would encourage others interested to try and prototype one of these routes and report any unexpected issues or unexpected lack of issues.

Once a table is defined with Parchment, pasting should just work since initialization actually uses the clipboard.convert function. The only additional effort is if Quill's tables support a subset of HTML table feature (which will be the case) and you'd like to handle this specially. For example, if the above Embed Blot implementation route is taken without special treatment of <thead> Quill will just clump that with regular <tbody> rows, but if you'd rather strip <thead> content for some reason, you would implement a custom clipboard matcher.

To be clear tables need to coexist within the Delta format and semantic. Otherwise general and important methods like getContents, updateContents, setContents would break. With this satisfied, I don't see a need for top level table specific APIs. No other format requires this and it's not clear why tables would be special.

As far as UI I think doing what existing products like Word/Docs does is a good starting point. There are unused icons in quill/assets/icons/ that can be helpful for tables. Implementing good UIs is non-trivial work but does not introduce any implementation design risk I can think of.

Let's see how far discussion in this thread can get us.

jhchen commented 7 years ago

As an administrative note, I will delete all non-substantive comments from this thread to cut down the clutter and mass emails to everyone. Please use the Reaction feature to show support.

saw commented 7 years ago

Embeds seem like the simplest solution, I like the approach to creating a table @jhchen laid out. One problem with this is pasted tables, which often come with a lot of formatting. We can assume people are using a reset style sheet that applies border-collapse:collapse and border-spacing:0; but all the attributes in the pasted table will need to then be stripped.

In the use cases I'm dealing with right now people really, really want to past tables from Word and Excel, which come with backgrounds and borders in inline styles.

Is there a way to strip these attributes without creating blots for td and td?

saw commented 7 years ago

I've had some time to work on this today and I bumped into the issues @jhchen alluded to regarding formatting. Formatting cells with background colors as well as having contents with inline styles seems important. The trouble is making this work without a parallel api. Heres one way a table API with formatting could look:

insertEmbed(index,'table',[
  [{
    content:'Hello',
    style: {
      backgroundColor: 'red',
      borderTop: '1px solid black',
      color: 'white',
      weight: 'bold'
    }
  },'World'],
  ['A', 'B']
]);

Resulting in:

<table>
  <tbody>
    <tr>
      <td style="background:red;border-top:1px solid black;color:white;"><b>Hello</b></td>
      <td>World</td>
    </tr>
    <tr>
      <td>A</td>
      <td>B</td>
    </tr>
  </tbody>
</table>

Or are we getting to far into a world of parallel apis? Thoughts on this approach?

saw commented 7 years ago

I'm trying to get some real data on our users that are currently pasting tables into CK Editor from word so I get a sense on what the "normal" interaction for pasted tables is.

saw commented 7 years ago

I think a sensible "limited" table to emulate is tables in github markdown:

kolesoffac commented 7 years ago

Hi, jhchen! I would like to know when planning add support table. Thanks

ccashwell commented 7 years ago

I posted a proof-of-concept Gist based on @saw's example, extending it to work with a React component that builds the table structure given some data. It works okay, but it's far from a complete implementation.

saw commented 7 years ago

Yeah, it works ok, it has some limitations. I do want to finish this PR as soon as I can, I've built more extensive table support for work, but our use case right now is just pasting tables, not actually creating or editing them.

On Thu, Oct 6, 2016 at 12:07 PM, Chris Cashwell notifications@github.com wrote:

I posted a Gist https://gist.github.com/ccashwell/e2ceac25d7195e4715a93587da89d747 based on @saw https://github.com/saw's example, extending it to work with a React component that builds the table structure given some data. It works okay.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/quilljs/quill/issues/117#issuecomment-252059016, or mute the thread https://github.com/notifications/unsubscribe-auth/AABT5zr5uJpkgA0rIKH1oUoSJF96BCYBks5qxUbUgaJpZM4B9jc3 .

mscreenie commented 7 years ago

Would like to see this pretty please :) :+1:

rikh42 commented 7 years ago

I've also been working on adding tables to Quill and I think it's going quite well at the moment. I'm using Quill as the heart of a collaborative editing experience in our app - multiple users editing the same stuff, like Google Docs. Quill has been awesome and has made this part of the application really clean.

So onto my tables. I did not like the embed approach, as it meant tables were really just a blob in the document, and editing the contents of a table cell was super limited with that approach.

Instead, I have approached the problem by treating tables in a similar was to lists in Quill. A ListItem is a node that lives inside a List and Quill handles this structured content well, creating the outer most <UL> when needed and adding <LI> elements inside it. I figured the same approach would work for tables. They are basically the same, but with a few extra layers 👍

So, I have a Table, a TableRow and a TableCell that all wrap the things below them.

The other benefit is that table cells can now contain any block level elements, like headings, lists, paragraphs, etc. All inline formatting works as well. They also work seamlessly with my collaborative editing features.

I'd say it has been a total nightmare getting the structure to remain sound, mostly due to my poor understanding of the internals of Parchment, but it is possible to add cells and rows and have everything correctly merged into place.

There is still a ton to do, but if anyone else has taken this approach, I'd be interested in talking.

Here is a quick screenshot of Quill, with tables, and someone else editing them...

gathercontent_editor_prototype
saw commented 7 years ago

Nice work @rikh42 ! We've been using the tables I created in production but I haven't had time to build the quill ui for it (we do not use the quill buttons but our own custom ui). The embed approach works, but to add features beyond "just tables" requires making more or less an entire parallel UI for tables. I spent a day or two going down the path you have but it was very complex and didn't seem realistic given our time constraints. Do you have your table format in a fork?

rikh42 commented 7 years ago

@saw I certainly learnt a lot by going over your PR a few times, so thanks for pioneering that!

It's in a branch of our internal project at the moment. It has turned out to be pretty complex, and I am sure there are still a few edge cases to figure out. (Well, there isn't a lot of code to get it working, but figuring out what the code should be, that was tricky :-)

I'm just working on all the keyboard handlers and UI components we'll need at the moment. My goal is to make something comparable to Tables in Google Docs.

jhchen commented 7 years ago

Nice work @rikh42 ! What does a Delta look like for a document with a table?

rikh42 commented 7 years ago

Here is an extract from the middle of a larger document showing the first 3 columns in a table (JSON)...

There is some additional styling in there that you can ignore. who is a kind of authorship feature, but the bit you'll want to look at is the table-cell property. You'll see that they all have the same table and row id's as everything here belongs in a single table row in the same table. Additional blocks would just have the same cellId, and everything is resolved during insertion. No tbody or th at the moment, but I can live with that for now.

{
    "insert": "Description",
    "attributes": {
        "who": 28167,
        "bold": true
    }
}, {
    "insert": "\n",
    "attributes": {
        "who": 47194,
        "align": "center",
        "table-cell": {
            "cellId": "table-cell-zxwscqtywssf",
            "rowId": "table-row-oksghsnmwiwc",
            "id": "table-id-tpbprwzxdlvn"
        }
    }
}, {
    "insert": "Owner",
    "attributes": {
        "who": 28167
    }
}, {
    "insert": "\n",
    "attributes": {
        "who": 28167,
        "table-cell": {
            "cellId": "table-cell-epkzpvnrkoeu",
            "rowId": "table-row-oksghsnmwiwc",
            "id": "table-id-tpbprwzxdlvn"
        }
    }
}, {
    "insert": "Cell 3",
    "attributes": {
        "who": 6939
    }
}, {
    "insert": "\n",
    "attributes": {
        "who": 28167,
        "table-cell": {
            "cellId": "table-cell-aerxfqxowhfd",
            "rowId": "table-row-oksghsnmwiwc",
            "id": "table-id-tpbprwzxdlvn"
        }
    }
}

Which results in the following HTML in the editor...

<table data-wrap-id="table-id-tpbprwzxdlvn">
    <tr data-wrap-id="table-row-oksghsnmwiwc">
        <td data-wrap-id="table-cell-zxwscqtywssf">
            <p class="ql-align-center"><strong>Description</strong></p>
        </td>

        <td data-wrap-id="table-cell-epkzpvnrkoeu">
            <p>Owner</p>
        </td>

        <td data-wrap-id="table-cell-aerxfqxowhfd">
            <p>Cell 3</p>
        </td>
    </tr>
</table>
monolithed commented 7 years ago

It would be nice to have a simple implementation of tables in the Quill. Really. I don't ask to give me something hard and serious — only basic support.

MrTin commented 7 years ago

@rikh42 impressive work on table support! 🚀 Looks like there are many more that would like to play around with this, is it perhaps something or at least parts of it you could extract and share with the community?

cray0000 commented 7 years ago

I think I have an idea of how @rikh42 implemented it. I tried to hack my implementation based on the list and list-item blots. Lets say we are implementing 3 new blots: table, table-row, table-cell. The main differences from the way the list is implemented are:

  1. Instead of working with format on the level of the outermost container (in lists the ul and ol are responsible for the format, not the li), we have to work with the format on the level of the inner-most container (table-cell).
  2. When we create a list -- we create the outermost container. For example, when you initially render 10 list items, quill actually renders each list item in a separate ul, and then it creates defaultChild item within each ul. Then the optimize() function fires for each ul and it ends up moving all li items into the first ul. But since in the tables we create the inner-most container (table-cell), we need its optimize() to also do some work by first wrapping itself into table-row. Then table-row's optimize() needs to wrap itself into table.
  3. And that's not it! If we want to have paragraphs and lists inside table-cell then the table-cell should also be a Container and we need to patch Block and probably List to be aware of the table logic and to be able to wrap itself into table-cell (which then wraps itself into table-row, which then wraps itself into table)

Well, that's what my current thoughts are, I don't actually have a working solution yet, so it would be really nice to hear from @rikh42 or anyone else hacking this feature.

saw commented 7 years ago

I agree @rikh42 has the right idea, but it's very tricky, I would love to see their code to understand how they made it happen On Mon, Jan 30, 2017 at 6:20 AM Pavel Zhukov notifications@github.com wrote:

I think I have an idea of how @rikh42 https://github.com/rikh42 implemented it. I tried to hack my implementation based on the list and list-item blots. Lets say we are implementing 3 new blots: table, table-row, table-cell. The main differences from the way the list is implemented are:

  1. Instead of working with format on the level of the outermost container (in lists the ul and ol are responsible for the format, not the li), we have to work with the format on the level of the inner-most container (table-cell).
  2. When we create a list -- we create the outermost container. For example, when you initially render 10 list items, quill actually renders each list item in a separate ul, and then it creates defaultChild item within each ul. Then the optimize() function fires for each ul and it ends up moving all li items into the first ul. But since in the tables we create the inner-most container (table-cell), we need its optimize() to also do some work by first wrapping itself into table-row. Then table-row's optimize() needs to wrap itself into table.
  3. And that's not it! If we want to have paragraphs and lists inside table-cell then the table-cell should also be a Container and we need to patch Block and probably List to be aware of the table logic and to be able to wrap itself into table-cell (which then wraps itself into table-row, which then wraps itself into table)

Well, that's what my current thoughts are, I don't actually have a working solution yet, so it would be really nice to hear from @rikh42 https://github.com/rikh42 or anyone else hacking this feature.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/quilljs/quill/issues/117#issuecomment-276073767, or mute the thread https://github.com/notifications/unsubscribe-auth/AABT5x9FUOm1XeuFJxwGbTotm6kOwH_dks5rXfG7gaJpZM4B9jc3 .

saw commented 7 years ago

@cray0000 in that approach how do you determine what <td> belong to which <tr>? It seems if you work up from a the cell you have no way to determine when to wrap in <tr> that matches the defined table during the value() phase, unless I am not seeing it.

cray0000 commented 7 years ago

@saw,

table-cell will have ids for <tr> and for <table>. And if we want to have lists and paragraphs within td then we also need the id for td too). But for now lets take the simple case of 2 ids -- row and table

Actually the tableId is optional, depends on whether we want 2 separate tables to automerge together or not. For example list does automatically merge together with the list next to it. If you have 2 of them separated by <p> and you remove that <p> -- then those 2 lists will be merged into one. The same is true for the table implementation. If we take into consideration tableId, then 2 separate tables can exist next to each other. Otherwise they will be always merged into a single table (even though the amount of columns may not be equal).

So lets say we want both rowId and tableId and we don't want <p>/<li> within <td>. Here is my suggested algorithm (I didn't actually test it, it's just an idea):

table-cell

  1. we create table-cell with the value of { rowId: 'aaa', tableId: 'bbb' }
  2. we specify the format treatment in static create() and static formats() to attributes data-table-id, data-row-id.
  3. in optimize() we check whether the parent is table-row. If not -- we wrap it into table-row by creating that blot and passing it the same value -- Parchment.create('table-row', this.statics.formats(this.domNode))

table-row

Here is where the merging of <tr>s is going to happen (moving <td> into a single <tr> they belong to).

  1. table-row will be created by its child (table-cell) with the same value. static create() and static formats() will set data-table-id, data-row-id
  2. optimize() will do 2 things: a) merge sibling table-rows together by data-row-id -- this is done the same way as currently in list:

    if (next != null && next.prev === this &&
      next.statics.blotName === this.statics.blotName &&
      next.domNode.getAttribute('data-row-id') === this.domNode.getAttribute('data-row-id')
    ) {
      // move stuff from one <tr> to another, the same as in 'list':
      next.moveChildren(this);
      next.remove();
    }

    b) check whether the parent is table. If not -- we wrap it into table while passing the same value to Parchment.create.

table

The table blot is basically the same as table-row but it's not interested in rowId anymore and only cares about the tableId and the corresponding attribute data-table-id. And it has the same optimize() as table-row which merges tables together based on the data-table-id attribute, but since the table blot is the highest level we don't need to wrap it into anything.

wells commented 7 years ago

I think <thead><th></th>...</thead> will need some love in all this. Some people will want to use this sort of thing against GFM.

danielschwartz commented 7 years ago

@rikh42 I'm pretty sure you'd be a hero to the Quill community if you open sourced the code for your table implementation (even if it's not something that's mergeable back into Quill right now).

I know I'd be happy to work on it in the open, and it sounds like you'd have plenty of other people willing to help out!

2Pacalypse- commented 7 years ago

Hey @cray0000 do you mind posting any code that you've been hacking on related to this? I'm also working on it, but my experience with Quill is still quite limited, so having some reference code to compare my work with would be very helpful.

cray0000 commented 7 years ago

@2Pacalypse- I didn't have time to actually start seriously hacking it yet.

The only implementation I have right now is a pretty silly one with just the trs and tds as custom tags with flex layout -- implemented in exactly the same way as quill's list and list-item. Here it is: https://gist.github.com/cray0000/9e9eeca6a6ce292453a09ddf6c0c4ad1

I plan to get to the coding of the real table/tr/td sometime this week. As soon as I have it working (or when I at least have some part of it working) I'll open source it right away -- as a gist or as a quill plugin.

2Pacalypse- commented 7 years ago

Thanks!

I've been following your design outline that you wrote above and I think that's a good way to structure the code. However, there are a couple of things that needs clearing up. What should be the blot types of table, table-row and table-cell formats? In my code, I've made table extend the Container blot (same as list), table-row extends the Block blot and table-cell also extends a Block blot. I've tried making both the table and table-row a Container blots, but bad things happened :d.

Another thing that I'm still trying to decide is how to insert an initial table. Most text editors allow you to specify on the toolbar the number of columns and rows you want and then the table of those dimensions is created. I wanted to do something similar in my code (for now I'm just hardcoding the number of columns and rows, but later on will focus on creating some nice popup for handling the tables).

And the last thing which I'd really like if someone can clear up for me is which methods exactly do we need to override in our table blots to make the tables functional. Only create, formats and optimize? Is there some set of methods you need to override to achieve a specific functionality? Parchment's docs are kinda hazy on this.

Again, thanks for your help. I'd really like if we could collaborate on this to make the table support in Quill a very near reality! ^^

dmitryuv commented 7 years ago

2 cents on the overall idea: i support that having TD's based on actual text rows is a right way, but thinking one step further - there's more work for collaborative environment. For example, common problem to solve is - two persons editing document at the same time, first inserts row, second inserts column. The OT implementation should be aware of tables to handle this properly, for ex. there should be special operation like "insert row at line X" that will behind the scenes will insert proper number of '\n' with TD attributes at specified location, instead of just multiple insert operations. Since i'm very new to Quill, i'm still researching how Delta generation works and if it's possible to have proper Delta representation that will help handling this.

danielschwartz commented 7 years ago

@dmitryuv I think for any Table solution to be considered complete (and think about being folded into Quill core) @jhchen (and correct me if i'm wrong) is likely going to be able to want a Table solution where you can do:

let contents = this.quill.getContents()
this.quill.setContents(contents)

And have the document look exactly the same.

In addition to the above constraint, you will likely have to be able call compose and transform without the Table being completely overwritten by any one change. Meaning User A should be able to enter text in Row 1, Column 1, and User B should be able to insert text in Row 1, Column 2, simultaneously, and the transform or compose operations on the Delta should correctly insert both changes.

dmitryuv commented 7 years ago

@danielschwartz that's all achievable with proposed solution as cell content is technically equal to list item, with different outside wrap. But it won't survive transform("insert column" against "insert row") operation without special table-aware logic. That's my point :)

2Pacalypse- commented 7 years ago

Let's assume it's possible for a user to select in the toolbar that they want to insert an empty table with N columns and M rows. Can we try to figure out how this could/should be done with the existing API.

Something like this maybe?

// Assume this is the handler that gets called when a user clicks 'Create table' in the toolbar
const insertTable = (columns, rows) => {
  const tableId = 'bbb'
  for (let i = 0; i < rows; i++) {
    const rowId = i + '-aaa'
    for (let j = 0; j < columns; j++) {
      quill.format('table-cell', { rowId, tableId }, Quill.sources.USER)
    }
  }
}

Then @cray0000's design takes effect and each cell wraps itself in appropriate row, and row into a table. Feedback is welcome.

cray0000 commented 7 years ago

@2Pacalypse- I don't think this is going to work -- it'll probably just rewrite the same block item many times over and over again, trying to change the formatting of the same block item where you currently have your selection.

I think the solution here depends on whether we want for whatever was already selected to end up within the top left cell or not.

If we want that -- we need to use quill.format() once. And all the other cells need to be inserted after the closest \n to the right directly via the delta operations by doing quill.updateContents().

Or if we don't care about the selected text and just want to add table after the selection (possibly splitting the current block) -- we should just do the quill.updateContents() right away.

I'd vote for just putting the table after the current selection. In which case the code can look something like this:

import Quill from 'quill'
const Delta = Quill.import('delta')

// suppose we have `this.quill`
addTable (columns, rows) {
  let range = this.quill.getSelection()
  if (!range) return
  let tableId = 'table-' + Math.random().toString(36).slice(2) // dirty random id
  let changeDelta = new Delta().retain(range.index + range.length)
  for (let i = 0; i < rows; i++) {
    let rowId = 'row-' + Math.random().toString(36).slice(2) // dirty random id
    for (let j = 0; j < columns; j++) {
      changeDelta = changeDelta.insert('\n', { 'table-cell': {rowId, tableId} })
    }
  }
  this.quill.updateContents(changeDelta, Quill.sources.USER)
}
cray0000 commented 7 years ago

@2Pacalypse- On the second thought though, the ideal behaviour would probably be to insert the empty table on the next line (after the nearest \n to the right of the current selection):

import Quill from 'quill'
const Delta = Quill.import('delta')

addTable (columns, rows) {
  let range = this.quill.getSelection()
  if (!range) return
  let newLineIndex = this.getClosestNewLineIndex(range.index + range.length)
  let changeDelta = new Delta().retain(newLineIndex + 1)
  let tableId = 'table-' + Math.random().toString(36).slice(2)
  for (let i = 0; i < rows; i++) {
    let rowId = 'row-' + Math.random().toString(36).slice(2)
    for (let j = 0; j < columns; j++) {
      changeDelta = changeDelta.insert('\n', { 'table-cell': {rowId, tableId} })
    }
  }
  this.quill.updateContents(changeDelta, Quill.sources.USER)
}

getClosestNewLineIndex (index) {
  return index + this.quill.getContents()
    .map((op) => typeof op.insert === 'string' ? op.insert : ' ')
    .join('')
    .slice(index)
    .indexOf('\n')
}
2Pacalypse- commented 7 years ago

Thanks a lot for the feedback. I'm still very new to the Quill, so any help is appreciated ^^. I think I understand how that code can work, although shouldn't this line actually insert table cells instead of a new line?

changeDelta = changeDelta.insert('\n', { rowId, tableId })

Something like this perhaps:

changeDelta = changeDelta.insert({ 'table-cell': { tableId, rowId } })

When I do that in my code, it actually works and it inserts a correct number of table cells in the editor. But this is the part where I struggle with Quill's internals a bit, to make sure each table-cell properly wraps itself into a table-row parent. Any ideas on what's the best way to achieve that?

cray0000 commented 7 years ago

@2Pacalypse- oh, you are right, I forgot table-cell. But you still need to insert the new line. Since the table cell is the block element, the same as list. And all block elements in Quill are marked with the new lines.

So it should be:

changeDelta = changeDelta.insert('\n', { 'table-cell': { tableId, rowId } })

And actually another thing which needs to also be addressed is that only having tableId and rowId won't allow us to have lists or several paragraphs within the table cell.

If we want to be able to press Enter within a table cell to create a new paragraph, then we should actually add another ID -- cellId. And we need to overload Block's optimize() (and possibly ListItem) to support wrapping themselves into the table-cell -- the same way as table-cell wraps itself into table-row.

2Pacalypse- commented 7 years ago

Yeah, that makes sense.

I'm struggling a bit on writing the code which should wrap table-cell into the table-row. I've managed to create a parent table-row element and correctly set its children to the appropriate table-cell elements. But this is all on the level of the Parchment's document model. Not sure how to apply that to the actual DOM as well.

dost commented 7 years ago

We would also like to help with adding tables to quill for our project. Following discussion here by @cray0000, @2Pacalypse- and @rikh42 we kicked out this solution so far: https://github.com/dost/quilljs-table Since it actually generates tables, html code and delta well, it looks like good start so we would like you all and some day even @jhchen to look at it and help us to put it all together :) Thanks to you all for code and inspiration...

2Pacalypse- commented 7 years ago

Nice!

Will go over your code and see if there's anything I can help with :)

dost commented 7 years ago

there is live demo on server for quick preview: http://quilljstable.timepress.cz/quilljs-extended-toolbar/contain.html

2Pacalypse- commented 7 years ago

Hey @cray0000, could you please clarify this part a bit more:

If we want to be able to press Enter within a table cell to create a new paragraph, then we should actually add another ID -- cellId. And we need to overload Block's optimize() (and possibly ListItem) to support wrapping themselves into the table-cell -- the same way as table-cell wraps itself into table-row.

What would the condition be to check if a Block blot should wrap itself into table-cell?

2Pacalypse- commented 7 years ago

Also, do you (or anyone else) might have an idea what can cause this error: Uncaught Error: diff() called with non-document

It happens when a delta operation doesn't have an insert property in certain cases, but not sure what those cases are exactly.

2Pacalypse- commented 7 years ago

One last question (for now ^^): Why does quill.getLeaf() method returns a TableCell blot even though when I inspect that blot, it has a Break child. Shouldn't the quill.getLeaf() method return the actual leaf at the particular index, which in this case would be the Break blot?

danielschwartz commented 7 years ago

@2Pacalypse- to answer your question about the diff() error. Basically, when you call a.diff(b) both a and b must be Quill delta's that only include inserts. This is where the error gets thrown.