Created
May 1, 2017 18:43
-
-
Save WinstonFassett/9752d3abaf0257b8a561a326b705223a to your computer and use it in GitHub Desktop.
JS Bin // source http://jsbin.com/defixum
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>JS Bin</title> | |
<link href="http://cdn.quilljs.com/1.2.4/quill.snow.css" rel="stylesheet"> | |
<link href="http://cdn.quilljs.com/1.2.4/quill.bubble.css" rel="stylesheet"> | |
<style id="jsbin-css"> | |
.ql-editor table { | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.ql-editor table td { | |
border: 1px solid black; | |
padding: 5px; | |
height: 25px; | |
} | |
button.ql-table::after { content: "TABLE"; } | |
.ql-picker.ql-table .ql-picker-label::before { content: "TABLE"; } | |
button.ql-contain::after { content: "WRAP"; } | |
button.ql-table-insert-rows::after { content: "ROWS+"; } | |
button.ql-table-insert-columns::after { content: "COLS+"; } | |
.ql-table, | |
.ql-contain { | |
width: auto !important; | |
margin-right: -15px; | |
} | |
.ql-picker.ql-table { | |
margin-right: -15px; | |
font-size: 11px; | |
font-weight: normal; | |
} | |
.ql-picker.ql-table svg { | |
display: none; | |
} | |
.ql-picker.ql-table .ql-picker-label { | |
padding: 0px 3px; | |
} | |
.ql-picker.ql-table .ql-picker-options { | |
width: 190px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
display: block; | |
float: left; | |
width: 30px; | |
height: 30px; | |
line-height: 30px; | |
text-align: center; | |
padding: 0px; | |
margin: 1px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
background: lightgrey; | |
} | |
.ql-picker-item:nth-child(5):before { | |
clear: both; | |
display: block; | |
content: ""; | |
width: 100%; | |
} | |
.ql-picker-item[data-value=newtable_1_1]:before { content: "1x1"; } | |
.ql-picker-item[data-value=newtable_1_2]:before { content: "1x2"; } | |
.ql-picker-item[data-value=newtable_1_3]:before { content: "1x3"; } | |
.ql-picker-item[data-value=newtable_1_4]:before { content: "1x4"; } | |
.ql-picker-item[data-value=newtable_1_5]:before { content: "1x5"; } | |
.ql-picker-item[data-value=newtable_2_1]:before { content: "2x1"; } | |
.ql-picker-item[data-value=newtable_2_2]:before { content: "2x2"; } | |
.ql-picker-item[data-value=newtable_2_3]:before { content: "2x3"; } | |
.ql-picker-item[data-value=newtable_2_4]:before { content: "2x4"; } | |
.ql-picker-item[data-value=newtable_2_5]:before { content: "2x5"; } | |
.ql-picker-item[data-value=newtable_3_1]:before { content: "3x1"; } | |
.ql-picker-item[data-value=newtable_3_2]:before { content: "3x2"; } | |
.ql-picker-item[data-value=newtable_3_3]:before { content: "3x3"; } | |
.ql-picker-item[data-value=newtable_3_4]:before { content: "3x4"; } | |
.ql-picker-item[data-value=newtable_3_5]:before { content: "3x5"; } | |
.ql-picker-item[data-value=newtable_4_1]:before { content: "4x1"; } | |
.ql-picker-item[data-value=newtable_4_2]:before { content: "4x2"; } | |
.ql-picker-item[data-value=newtable_4_3]:before { content: "4x3"; } | |
.ql-picker-item[data-value=newtable_4_4]:before { content: "4x4"; } | |
.ql-picker-item[data-value=newtable_4_5]:before { content: "4x5"; } | |
.ql-picker-item[data-value=newtable_5_1]:before { content: "5x1"; } | |
.ql-picker-item[data-value=newtable_5_2]:before { content: "5x2"; } | |
.ql-picker-item[data-value=newtable_5_3]:before { content: "5x3"; } | |
.ql-picker-item[data-value=newtable_5_4]:before { content: "5x4"; } | |
.ql-picker-item[data-value=newtable_5_5]:before { content: "5x5"; } | |
.ql-picker-item[data-value=newtable_6_1]:before { content: "6x1"; } | |
.ql-picker-item[data-value=newtable_6_2]:before { content: "6x2"; } | |
.ql-picker-item[data-value=newtable_6_3]:before { content: "6x3"; } | |
.ql-picker-item[data-value=newtable_6_4]:before { content: "6x4"; } | |
.ql-picker-item[data-value=newtable_6_5]:before { content: "6x5"; } | |
.ql-picker-item[data-value=newtable_7_1]:before { content: "7x1"; } | |
.ql-picker-item[data-value=newtable_7_2]:before { content: "7x2"; } | |
.ql-picker-item[data-value=newtable_7_3]:before { content: "7x3"; } | |
.ql-picker-item[data-value=newtable_7_4]:before { content: "7x4"; } | |
.ql-picker-item[data-value=newtable_7_5]:before { content: "7x5"; } | |
.ql-picker-item[data-value=newtable_8_1]:before { content: "8x1"; } | |
.ql-picker-item[data-value=newtable_8_2]:before { content: "8x2"; } | |
.ql-picker-item[data-value=newtable_8_3]:before { content: "8x3"; } | |
.ql-picker-item[data-value=newtable_8_4]:before { content: "8x4"; } | |
.ql-picker-item[data-value=newtable_8_5]:before { content: "8x5"; } | |
.ql-picker-item[data-value=newtable_9_1]:before { content: "9x1"; } | |
.ql-picker-item[data-value=newtable_9_2]:before { content: "9x2"; } | |
.ql-picker-item[data-value=newtable_9_3]:before { content: "9x3"; } | |
.ql-picker-item[data-value=newtable_9_4]:before { content: "9x4"; } | |
.ql-picker-item[data-value=newtable_9_5]:before { content: "9x5"; } | |
.ql-picker-item[data-value=newtable_10_1]:before { content: "10x1"; } | |
.ql-picker-item[data-value=newtable_10_2]:before { content: "10x2"; } | |
.ql-picker-item[data-value=newtable_10_3]:before { content: "10x3"; } | |
.ql-picker-item[data-value=newtable_10_4]:before { content: "10x4"; } | |
.ql-picker-item[data-value=newtable_10_5]:before { content: "10x5"; } | |
.tdbr, .trbr { | |
display: none | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Quill Table Breaks</h1> | |
<div id="editor-container"> | |
</div> | |
Output: | |
<textarea id='output_delta' style='width:100%; height:200px; padding: 3px;'></textarea> | |
Orig Delta: | |
<textarea id='orig_delta' style='width:100%; height:200px; padding: 3px;'></textarea> | |
Current HTML: | |
<textarea id='output_html' style='width:100%; height:200px; padding: 3px;'></textarea> | |
HTML: | |
<div id="view_html"> | |
</div> | |
<script src="http://cdn.quilljs.com/1.2.4/quill.js"></script> | |
<script id="jsbin-javascript"> | |
// quill-table-breaks.js | |
let Container = Quill.import('blots/container'); | |
let Scroll = Quill.import('blots/scroll'); | |
let Inline = Quill.import('blots/inline'); | |
let Block = Quill.import('blots/block'); | |
let Delta = Quill.import('delta'); | |
let Parchment = Quill.import('parchment'); | |
let BlockEmbed = Quill.import('blots/block/embed'); | |
let TextBlot = Quill.import('blots/text'); | |
class ContainBlot extends Container { | |
static create(value) { | |
let tagName = 'contain'; | |
let node = super.create(tagName); | |
return node; | |
} | |
insertBefore(blot, ref) { | |
if (blot.statics.blotName == this.statics.blotName) { | |
console.log('############################ Not sure this is clean:') | |
console.log(blot) | |
console.log(blot.children.head) | |
super.insertBefore(blot.children.head, ref); | |
} else { | |
super.insertBefore(blot, ref); | |
} | |
} | |
static formats(domNode) { | |
return domNode.tagName; | |
} | |
formats() { | |
// We don't inherit from FormatBlot | |
return { [this.statics.blotName]: this.statics.formats(this.domNode) } | |
} | |
replace(target) { | |
if (target.statics.blotName !== this.statics.blotName) { | |
let item = Parchment.create(this.statics.defaultChild); | |
target.moveChildren(item); | |
this.appendChild(item); | |
} | |
if (target.parent == null) return; | |
super.replace(target) | |
} | |
} | |
ContainBlot.blotName = 'contain'; | |
ContainBlot.tagName = 'contain'; | |
ContainBlot.scope = Parchment.Scope.BLOCK_BLOT; | |
ContainBlot.defaultChild = 'block'; | |
ContainBlot.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(ContainBlot); | |
class TableRow extends Container { | |
static create(value) { | |
let tagName = 'tr'; | |
let node = super.create(tagName); | |
return node; | |
} | |
optimize() { | |
super.optimize(); | |
var parent = this.parent | |
if (parent != null && parent.statics.blotName != 'table') { | |
this.processTable() | |
} | |
} | |
processTable () { | |
var currentBlot = this | |
var rows = [] | |
while (currentBlot) { | |
if (! (currentBlot instanceof TableRow)) { | |
break | |
} | |
rows.push(currentBlot) | |
currentBlot = currentBlot.next | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
let table = Parchment.create('table'); | |
rows.forEach(function (row) { | |
table.appendChild(row) | |
}) | |
table.replace(mark) | |
} | |
} | |
TableRow.blotName = 'tr'; | |
TableRow.tagName = 'tr'; | |
TableRow.scope = Parchment.Scope.BLOCK_BLOT; | |
TableRow.defaultChild = 'td'; | |
Quill.register(TableRow); | |
class Table extends Container { | |
optimize() { | |
super.optimize(); | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
} | |
Table.blotName = 'table'; | |
Table.tagName = 'table'; | |
Table.scope = Parchment.Scope.BLOCK_BLOT; | |
Table.defaultChild = 'tr'; | |
Table.allowedChildren = [TableRow]; | |
Quill.register(Table); | |
// | |
// | |
// CONTAINER TD | |
// | |
class TableCell extends ContainBlot { | |
format() { | |
return 'td' | |
} | |
optimize() { | |
super.optimize(); | |
let parent = this.parent; | |
if (parent != null && parent.statics.blotName != 'tr') { | |
this.processTR() | |
} | |
// merge same TD id | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
processTR () { | |
// find next row break | |
var currentBlot = this | |
var rowItems = [this] | |
while (currentBlot) { | |
if (currentBlot.statics.tagName !== 'TD') { | |
break | |
} | |
rowItems.push(currentBlot) | |
if (currentBlot instanceof RowBreak) { | |
break | |
} | |
currentBlot = currentBlot.next | |
} | |
// create row, add row items as TDs | |
var prevItem | |
var cellItems = [] | |
var cells = [] | |
rowItems.forEach(function (rowItem) { | |
cellItems.push(rowItem) | |
if (rowItem instanceof TableCell) { | |
prevItem = rowItem | |
} else if (rowItem instanceof CellBreak) { | |
cells.push(cellItems) | |
cellItems = [] | |
} | |
}) | |
if (cellItems.length > 0) { | |
cells.push(cellItems) | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
// create row | |
var row = Parchment.create('tr') | |
cells.forEach(function (cell) { | |
// add row elements | |
cell.forEach(function (cellItem) { | |
row.appendChild(cellItem) | |
}) | |
}) | |
row.replace(mark) | |
} | |
} | |
TableCell.blotName = 'td'; | |
TableCell.tagName = 'td'; | |
TableCell.scope = Parchment.Scope.BLOCK_BLOT; | |
TableCell.defaultChild = 'block'; | |
TableCell.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(TableCell); | |
Container.order = [ | |
'list', 'contain', // Must be lower | |
'td', 'tr', 'table' // Must be higher | |
]; | |
class RowBreak extends BlockEmbed { | |
formats() { | |
return { trbr: true } | |
} | |
} | |
RowBreak.blotName = 'trbr' | |
RowBreak.tagName = 'td' | |
RowBreak.className = 'trbr' | |
Quill.register(RowBreak); | |
class CellBreak extends BlockEmbed { | |
formats() { | |
return { tdbr: true } | |
} | |
} | |
CellBreak.blotName = 'tdbr' | |
CellBreak.tagName = 'td' | |
CellBreak.className = 'tdbr' | |
Quill.register(CellBreak); | |
// END quill-table-breaks.js | |
// Render UI | |
var Keyboard = Quill.import('modules/keyboard') | |
// set up toolbar options | |
let maxRows = 10; | |
let maxCols = 5; | |
let tableOptions = []; | |
for (let r = 1; r <= maxRows; r++) { | |
for (let c = 1; c <= maxCols; c++) { | |
tableOptions.push('newtable_' + r + '_' + c); | |
} | |
} | |
Quill.debug('debug'); | |
var quill = new Quill('#editor-container', { | |
modules: { | |
toolbar: { | |
container: [ | |
[{ 'table': tableOptions }], // new table (cursor needs to be out of table) | |
['table-insert-rows'], // cursor needs to be in the table | |
['table-insert-columns'], // cursor needs to be in the table | |
['bold', 'italic', 'underline', 'strike'], | |
['blockquote', 'code-block'], | |
[{ 'header': 1 }, { 'header': 2 }], | |
[{ 'list': 'ordered'}, { 'list': 'bullet' }], | |
[{ 'script': 'sub'}, { 'script': 'super' }], | |
[{ 'indent': '-1'}, { 'indent': '+1' }], | |
[{ 'direction': 'rtl' }], | |
[{ 'size': ['small', false, 'large', 'huge'] }], | |
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], | |
[{ 'color': [] }, { 'background': [] }], | |
[{ 'align': [] }], | |
['link', 'image', 'code-block'], | |
['clean'] | |
], | |
handlers: { | |
table: function (value) { | |
if(value && value.includes('newtable_')) { | |
let sizes = value.split('_'); | |
let rows = Number.parseInt(sizes[1]) | |
let columns = Number.parseInt(sizes[2]) | |
let table = Parchment.create('table'); | |
const range = this.quill.getSelection() | |
if (!range) return | |
const newLineIndex = getClosestNewLineIndex(this.quill.getContents(), range.index + range.length) | |
let changeDelta = new Delta().retain(newLineIndex) | |
changeDelta = changeDelta.insert('\n') | |
for (let i = 0; i < rows; i++) { | |
for (let j = 0; j < columns; j++) { | |
changeDelta = changeDelta.insert('\n', { | |
td: true | |
}) | |
if (j < columns - 1) { | |
changeDelta = changeDelta.insert({ tdbr: true }) | |
} | |
} | |
changeDelta = changeDelta.insert({ trbr: true }) | |
} | |
this.quill.updateContents(changeDelta, Quill.sources.USER) | |
this.quill.setSelection(newLineIndex + 1) | |
} else { | |
// TODO | |
} | |
}, | |
'table-insert-rows': function() { | |
let td = find_td('td') | |
if(td) { | |
let col_count = 0 | |
td.parent.children.forEach(function (it) { | |
if (it instanceof TableCell) { | |
col_count++ | |
} | |
}) | |
let table = td.parent.parent; | |
let new_row = td.parent.clone() | |
for (var i = col_count - 1; i >= 0; i--) { | |
let td = Parchment.create('td'); | |
new_row.appendChild(td); | |
new_row.appendChild(Parchment.create('tdbr')) | |
}; | |
new_row.appendChild(Parchment.create('trbr')) | |
table.appendChild(new_row); | |
} | |
}, | |
'table-insert-columns': function() { | |
let td = find_td('td') | |
if(td) { | |
let table = td.parent.parent; | |
td.parent.parent.children.forEach(function(tr) { | |
let td = Parchment.create('td'); | |
tr.appendChild(td); | |
tr.appendChild(Parchment.create('tdbr')) | |
}); | |
} | |
} | |
} | |
}, | |
clipboard: { | |
matchers: [ | |
['TD, TH', function (node, delta) { | |
delta.insert("\n", { td: true }) | |
delta.insert({ tdbr: true }) | |
return delta | |
}], | |
['TR', function (node, delta) { | |
delta.insert({ trbr: true }) | |
return delta | |
}], | |
] | |
}, | |
keyboard: { | |
bindings: { | |
'backspaceTable': { | |
key: 8, | |
format: ['td'], | |
// offset: 0, | |
handler: function handleTableBackspace (range, context) { | |
var formats = quill.getFormat(range.index-1, 1) | |
if (formats.tdbr || formats.trbr) { | |
// prevent deletion of table break | |
return false | |
} | |
return true | |
} | |
} | |
} | |
}, | |
}, | |
placeholder: 'Compose an epic...', | |
theme: 'snow' // or 'bubble' | |
}); | |
// global for console debugging | |
QuillInstance = quill | |
quill.on('text-change', function(delta, source) { | |
document.getElementById("output_delta").value=JSON.stringify(quill.editor.getDelta(), null, 2) | |
document.getElementById("output_html").value=quill.root.innerHTML; | |
document.getElementById("view_html").innerHTML=quill.root.innerHTML; | |
}) | |
// use sample delta | |
var delta = getSampleDelta() | |
document.getElementById("orig_delta").value=JSON.stringify(delta, null, 2) | |
quill.setContents(delta); | |
function getClosestNewLineIndex (contents, index) { | |
return index + contents.map((op) => { | |
return typeof op.insert === 'string' ? op.insert : ' ' | |
}).join('') | |
.slice(index) | |
.indexOf('\n') | |
} | |
function find_td(what) { | |
let leaf = quill.getLeaf(quill.getSelection()['index']); | |
let blot = leaf[0]; | |
for(;blot!=null && blot.statics.blotName!=what;) { | |
blot=blot.parent; | |
} | |
return blot; // return TD or NULL | |
} | |
function getSampleDelta () { | |
return { | |
"ops": [ | |
{ | |
"insert": "Test Tables" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 1 | |
} | |
}, | |
{ | |
"insert": "Empty 3x3 table from toolbar" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPopulated 3x3 table (from toolbar)" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Col 1" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 2" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 3" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "a" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "b" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "c" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "123" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "456" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "d" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "4" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "7" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPasted Table" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Company" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Contact" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Country" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Alfreds Futterkiste" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Maria Anders" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Germany" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Centro comercial Moctezuma" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Francisco Chang" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Mexico" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Ernst Handel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Roland Mendel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Austria" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Island Trading" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Helen Bennett" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "UK" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Laughing Bacchus Winecellars" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Yoshi Tannamuri" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Canada" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Magazzini Alimentari Riuniti" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Giovanni Rovelli" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Italy" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n" | |
} | |
] | |
} | |
} | |
</script> | |
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>JS Bin</title> | |
<link href="//cdn.quilljs.com/1.2.4/quill.snow.css" rel="stylesheet"> | |
<link href="//cdn.quilljs.com/1.2.4/quill.bubble.css" rel="stylesheet"> | |
</head> | |
<body> | |
<h1>Quill Table Breaks</h1> | |
<div id="editor-container"> | |
</div> | |
Output: | |
<textarea id='output_delta' style='width:100%; height:200px; padding: 3px;'></textarea> | |
Orig Delta: | |
<textarea id='orig_delta' style='width:100%; height:200px; padding: 3px;'></textarea> | |
Current HTML: | |
<textarea id='output_html' style='width:100%; height:200px; padding: 3px;'></textarea> | |
HTML: | |
<div id="view_html"> | |
</div> | |
<script src="//cdn.quilljs.com/1.2.4/quill.js"><\/script> | |
</body> | |
</html></script> | |
<script id="jsbin-source-css" type="text/css"> | |
.ql-editor table { | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.ql-editor table td { | |
border: 1px solid black; | |
padding: 5px; | |
height: 25px; | |
} | |
button.ql-table::after { content: "TABLE"; } | |
.ql-picker.ql-table .ql-picker-label::before { content: "TABLE"; } | |
button.ql-contain::after { content: "WRAP"; } | |
button.ql-table-insert-rows::after { content: "ROWS+"; } | |
button.ql-table-insert-columns::after { content: "COLS+"; } | |
.ql-table, | |
.ql-contain { | |
width: auto !important; | |
margin-right: -15px; | |
} | |
.ql-picker.ql-table { | |
margin-right: -15px; | |
font-size: 11px; | |
font-weight: normal; | |
} | |
.ql-picker.ql-table svg { | |
display: none; | |
} | |
.ql-picker.ql-table .ql-picker-label { | |
padding: 0px 3px; | |
} | |
.ql-picker.ql-table .ql-picker-options { | |
width: 190px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
display: block; | |
float: left; | |
width: 30px; | |
height: 30px; | |
line-height: 30px; | |
text-align: center; | |
padding: 0px; | |
margin: 1px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
background: lightgrey; | |
} | |
.ql-picker-item:nth-child(5):before { | |
clear: both; | |
display: block; | |
content: ""; | |
width: 100%; | |
} | |
.ql-picker-item[data-value=newtable_1_1]:before { content: "1x1"; } | |
.ql-picker-item[data-value=newtable_1_2]:before { content: "1x2"; } | |
.ql-picker-item[data-value=newtable_1_3]:before { content: "1x3"; } | |
.ql-picker-item[data-value=newtable_1_4]:before { content: "1x4"; } | |
.ql-picker-item[data-value=newtable_1_5]:before { content: "1x5"; } | |
.ql-picker-item[data-value=newtable_2_1]:before { content: "2x1"; } | |
.ql-picker-item[data-value=newtable_2_2]:before { content: "2x2"; } | |
.ql-picker-item[data-value=newtable_2_3]:before { content: "2x3"; } | |
.ql-picker-item[data-value=newtable_2_4]:before { content: "2x4"; } | |
.ql-picker-item[data-value=newtable_2_5]:before { content: "2x5"; } | |
.ql-picker-item[data-value=newtable_3_1]:before { content: "3x1"; } | |
.ql-picker-item[data-value=newtable_3_2]:before { content: "3x2"; } | |
.ql-picker-item[data-value=newtable_3_3]:before { content: "3x3"; } | |
.ql-picker-item[data-value=newtable_3_4]:before { content: "3x4"; } | |
.ql-picker-item[data-value=newtable_3_5]:before { content: "3x5"; } | |
.ql-picker-item[data-value=newtable_4_1]:before { content: "4x1"; } | |
.ql-picker-item[data-value=newtable_4_2]:before { content: "4x2"; } | |
.ql-picker-item[data-value=newtable_4_3]:before { content: "4x3"; } | |
.ql-picker-item[data-value=newtable_4_4]:before { content: "4x4"; } | |
.ql-picker-item[data-value=newtable_4_5]:before { content: "4x5"; } | |
.ql-picker-item[data-value=newtable_5_1]:before { content: "5x1"; } | |
.ql-picker-item[data-value=newtable_5_2]:before { content: "5x2"; } | |
.ql-picker-item[data-value=newtable_5_3]:before { content: "5x3"; } | |
.ql-picker-item[data-value=newtable_5_4]:before { content: "5x4"; } | |
.ql-picker-item[data-value=newtable_5_5]:before { content: "5x5"; } | |
.ql-picker-item[data-value=newtable_6_1]:before { content: "6x1"; } | |
.ql-picker-item[data-value=newtable_6_2]:before { content: "6x2"; } | |
.ql-picker-item[data-value=newtable_6_3]:before { content: "6x3"; } | |
.ql-picker-item[data-value=newtable_6_4]:before { content: "6x4"; } | |
.ql-picker-item[data-value=newtable_6_5]:before { content: "6x5"; } | |
.ql-picker-item[data-value=newtable_7_1]:before { content: "7x1"; } | |
.ql-picker-item[data-value=newtable_7_2]:before { content: "7x2"; } | |
.ql-picker-item[data-value=newtable_7_3]:before { content: "7x3"; } | |
.ql-picker-item[data-value=newtable_7_4]:before { content: "7x4"; } | |
.ql-picker-item[data-value=newtable_7_5]:before { content: "7x5"; } | |
.ql-picker-item[data-value=newtable_8_1]:before { content: "8x1"; } | |
.ql-picker-item[data-value=newtable_8_2]:before { content: "8x2"; } | |
.ql-picker-item[data-value=newtable_8_3]:before { content: "8x3"; } | |
.ql-picker-item[data-value=newtable_8_4]:before { content: "8x4"; } | |
.ql-picker-item[data-value=newtable_8_5]:before { content: "8x5"; } | |
.ql-picker-item[data-value=newtable_9_1]:before { content: "9x1"; } | |
.ql-picker-item[data-value=newtable_9_2]:before { content: "9x2"; } | |
.ql-picker-item[data-value=newtable_9_3]:before { content: "9x3"; } | |
.ql-picker-item[data-value=newtable_9_4]:before { content: "9x4"; } | |
.ql-picker-item[data-value=newtable_9_5]:before { content: "9x5"; } | |
.ql-picker-item[data-value=newtable_10_1]:before { content: "10x1"; } | |
.ql-picker-item[data-value=newtable_10_2]:before { content: "10x2"; } | |
.ql-picker-item[data-value=newtable_10_3]:before { content: "10x3"; } | |
.ql-picker-item[data-value=newtable_10_4]:before { content: "10x4"; } | |
.ql-picker-item[data-value=newtable_10_5]:before { content: "10x5"; } | |
.tdbr, .trbr { | |
display: none | |
}</script> | |
<script id="jsbin-source-javascript" type="text/javascript">// quill-table-breaks.js | |
let Container = Quill.import('blots/container'); | |
let Scroll = Quill.import('blots/scroll'); | |
let Inline = Quill.import('blots/inline'); | |
let Block = Quill.import('blots/block'); | |
let Delta = Quill.import('delta'); | |
let Parchment = Quill.import('parchment'); | |
let BlockEmbed = Quill.import('blots/block/embed'); | |
let TextBlot = Quill.import('blots/text'); | |
class ContainBlot extends Container { | |
static create(value) { | |
let tagName = 'contain'; | |
let node = super.create(tagName); | |
return node; | |
} | |
insertBefore(blot, ref) { | |
if (blot.statics.blotName == this.statics.blotName) { | |
console.log('############################ Not sure this is clean:') | |
console.log(blot) | |
console.log(blot.children.head) | |
super.insertBefore(blot.children.head, ref); | |
} else { | |
super.insertBefore(blot, ref); | |
} | |
} | |
static formats(domNode) { | |
return domNode.tagName; | |
} | |
formats() { | |
// We don't inherit from FormatBlot | |
return { [this.statics.blotName]: this.statics.formats(this.domNode) } | |
} | |
replace(target) { | |
if (target.statics.blotName !== this.statics.blotName) { | |
let item = Parchment.create(this.statics.defaultChild); | |
target.moveChildren(item); | |
this.appendChild(item); | |
} | |
if (target.parent == null) return; | |
super.replace(target) | |
} | |
} | |
ContainBlot.blotName = 'contain'; | |
ContainBlot.tagName = 'contain'; | |
ContainBlot.scope = Parchment.Scope.BLOCK_BLOT; | |
ContainBlot.defaultChild = 'block'; | |
ContainBlot.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(ContainBlot); | |
class TableRow extends Container { | |
static create(value) { | |
let tagName = 'tr'; | |
let node = super.create(tagName); | |
return node; | |
} | |
optimize() { | |
super.optimize(); | |
var parent = this.parent | |
if (parent != null && parent.statics.blotName != 'table') { | |
this.processTable() | |
} | |
} | |
processTable () { | |
var currentBlot = this | |
var rows = [] | |
while (currentBlot) { | |
if (! (currentBlot instanceof TableRow)) { | |
break | |
} | |
rows.push(currentBlot) | |
currentBlot = currentBlot.next | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
let table = Parchment.create('table'); | |
rows.forEach(function (row) { | |
table.appendChild(row) | |
}) | |
table.replace(mark) | |
} | |
} | |
TableRow.blotName = 'tr'; | |
TableRow.tagName = 'tr'; | |
TableRow.scope = Parchment.Scope.BLOCK_BLOT; | |
TableRow.defaultChild = 'td'; | |
Quill.register(TableRow); | |
class Table extends Container { | |
optimize() { | |
super.optimize(); | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
} | |
Table.blotName = 'table'; | |
Table.tagName = 'table'; | |
Table.scope = Parchment.Scope.BLOCK_BLOT; | |
Table.defaultChild = 'tr'; | |
Table.allowedChildren = [TableRow]; | |
Quill.register(Table); | |
// | |
// | |
// CONTAINER TD | |
// | |
class TableCell extends ContainBlot { | |
format() { | |
return 'td' | |
} | |
optimize() { | |
super.optimize(); | |
let parent = this.parent; | |
if (parent != null && parent.statics.blotName != 'tr') { | |
this.processTR() | |
} | |
// merge same TD id | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
processTR () { | |
// find next row break | |
var currentBlot = this | |
var rowItems = [this] | |
while (currentBlot) { | |
if (currentBlot.statics.tagName !== 'TD') { | |
break | |
} | |
rowItems.push(currentBlot) | |
if (currentBlot instanceof RowBreak) { | |
break | |
} | |
currentBlot = currentBlot.next | |
} | |
// create row, add row items as TDs | |
var prevItem | |
var cellItems = [] | |
var cells = [] | |
rowItems.forEach(function (rowItem) { | |
cellItems.push(rowItem) | |
if (rowItem instanceof TableCell) { | |
prevItem = rowItem | |
} else if (rowItem instanceof CellBreak) { | |
cells.push(cellItems) | |
cellItems = [] | |
} | |
}) | |
if (cellItems.length > 0) { | |
cells.push(cellItems) | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
// create row | |
var row = Parchment.create('tr') | |
cells.forEach(function (cell) { | |
// add row elements | |
cell.forEach(function (cellItem) { | |
row.appendChild(cellItem) | |
}) | |
}) | |
row.replace(mark) | |
} | |
} | |
TableCell.blotName = 'td'; | |
TableCell.tagName = 'td'; | |
TableCell.scope = Parchment.Scope.BLOCK_BLOT; | |
TableCell.defaultChild = 'block'; | |
TableCell.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(TableCell); | |
Container.order = [ | |
'list', 'contain', // Must be lower | |
'td', 'tr', 'table' // Must be higher | |
]; | |
class RowBreak extends BlockEmbed { | |
formats() { | |
return { trbr: true } | |
} | |
} | |
RowBreak.blotName = 'trbr' | |
RowBreak.tagName = 'td' | |
RowBreak.className = 'trbr' | |
Quill.register(RowBreak); | |
class CellBreak extends BlockEmbed { | |
formats() { | |
return { tdbr: true } | |
} | |
} | |
CellBreak.blotName = 'tdbr' | |
CellBreak.tagName = 'td' | |
CellBreak.className = 'tdbr' | |
Quill.register(CellBreak); | |
// END quill-table-breaks.js | |
// Render UI | |
var Keyboard = Quill.import('modules/keyboard') | |
// set up toolbar options | |
let maxRows = 10; | |
let maxCols = 5; | |
let tableOptions = []; | |
for (let r = 1; r <= maxRows; r++) { | |
for (let c = 1; c <= maxCols; c++) { | |
tableOptions.push('newtable_' + r + '_' + c); | |
} | |
} | |
Quill.debug('debug'); | |
var quill = new Quill('#editor-container', { | |
modules: { | |
toolbar: { | |
container: [ | |
[{ 'table': tableOptions }], // new table (cursor needs to be out of table) | |
['table-insert-rows'], // cursor needs to be in the table | |
['table-insert-columns'], // cursor needs to be in the table | |
['bold', 'italic', 'underline', 'strike'], | |
['blockquote', 'code-block'], | |
[{ 'header': 1 }, { 'header': 2 }], | |
[{ 'list': 'ordered'}, { 'list': 'bullet' }], | |
[{ 'script': 'sub'}, { 'script': 'super' }], | |
[{ 'indent': '-1'}, { 'indent': '+1' }], | |
[{ 'direction': 'rtl' }], | |
[{ 'size': ['small', false, 'large', 'huge'] }], | |
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], | |
[{ 'color': [] }, { 'background': [] }], | |
[{ 'align': [] }], | |
['link', 'image', 'code-block'], | |
['clean'] | |
], | |
handlers: { | |
table: function (value) { | |
if(value && value.includes('newtable_')) { | |
let sizes = value.split('_'); | |
let rows = Number.parseInt(sizes[1]) | |
let columns = Number.parseInt(sizes[2]) | |
let table = Parchment.create('table'); | |
const range = this.quill.getSelection() | |
if (!range) return | |
const newLineIndex = getClosestNewLineIndex(this.quill.getContents(), range.index + range.length) | |
let changeDelta = new Delta().retain(newLineIndex) | |
changeDelta = changeDelta.insert('\n') | |
for (let i = 0; i < rows; i++) { | |
for (let j = 0; j < columns; j++) { | |
changeDelta = changeDelta.insert('\n', { | |
td: true | |
}) | |
if (j < columns - 1) { | |
changeDelta = changeDelta.insert({ tdbr: true }) | |
} | |
} | |
changeDelta = changeDelta.insert({ trbr: true }) | |
} | |
this.quill.updateContents(changeDelta, Quill.sources.USER) | |
this.quill.setSelection(newLineIndex + 1) | |
} else { | |
// TODO | |
} | |
}, | |
'table-insert-rows': function() { | |
let td = find_td('td') | |
if(td) { | |
let col_count = 0 | |
td.parent.children.forEach(function (it) { | |
if (it instanceof TableCell) { | |
col_count++ | |
} | |
}) | |
let table = td.parent.parent; | |
let new_row = td.parent.clone() | |
for (var i = col_count - 1; i >= 0; i--) { | |
let td = Parchment.create('td'); | |
new_row.appendChild(td); | |
new_row.appendChild(Parchment.create('tdbr')) | |
}; | |
new_row.appendChild(Parchment.create('trbr')) | |
table.appendChild(new_row); | |
} | |
}, | |
'table-insert-columns': function() { | |
let td = find_td('td') | |
if(td) { | |
let table = td.parent.parent; | |
td.parent.parent.children.forEach(function(tr) { | |
let td = Parchment.create('td'); | |
tr.appendChild(td); | |
tr.appendChild(Parchment.create('tdbr')) | |
}); | |
} | |
} | |
} | |
}, | |
clipboard: { | |
matchers: [ | |
['TD, TH', function (node, delta) { | |
delta.insert("\n", { td: true }) | |
delta.insert({ tdbr: true }) | |
return delta | |
}], | |
['TR', function (node, delta) { | |
delta.insert({ trbr: true }) | |
return delta | |
}], | |
] | |
}, | |
keyboard: { | |
bindings: { | |
'backspaceTable': { | |
key: 8, | |
format: ['td'], | |
// offset: 0, | |
handler: function handleTableBackspace (range, context) { | |
var formats = quill.getFormat(range.index-1, 1) | |
if (formats.tdbr || formats.trbr) { | |
// prevent deletion of table break | |
return false | |
} | |
return true | |
} | |
} | |
} | |
}, | |
}, | |
placeholder: 'Compose an epic...', | |
theme: 'snow' // or 'bubble' | |
}); | |
// global for console debugging | |
QuillInstance = quill | |
quill.on('text-change', function(delta, source) { | |
document.getElementById("output_delta").value=JSON.stringify(quill.editor.getDelta(), null, 2) | |
document.getElementById("output_html").value=quill.root.innerHTML; | |
document.getElementById("view_html").innerHTML=quill.root.innerHTML; | |
}) | |
// use sample delta | |
var delta = getSampleDelta() | |
document.getElementById("orig_delta").value=JSON.stringify(delta, null, 2) | |
quill.setContents(delta); | |
function getClosestNewLineIndex (contents, index) { | |
return index + contents.map((op) => { | |
return typeof op.insert === 'string' ? op.insert : ' ' | |
}).join('') | |
.slice(index) | |
.indexOf('\n') | |
} | |
function find_td(what) { | |
let leaf = quill.getLeaf(quill.getSelection()['index']); | |
let blot = leaf[0]; | |
for(;blot!=null && blot.statics.blotName!=what;) { | |
blot=blot.parent; | |
} | |
return blot; // return TD or NULL | |
} | |
function getSampleDelta () { | |
return { | |
"ops": [ | |
{ | |
"insert": "Test Tables" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 1 | |
} | |
}, | |
{ | |
"insert": "Empty 3x3 table from toolbar" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPopulated 3x3 table (from toolbar)" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Col 1" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 2" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 3" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "a" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "b" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "c" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "123" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "456" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "d" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "4" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "7" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPasted Table" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Company" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Contact" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Country" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Alfreds Futterkiste" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Maria Anders" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Germany" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Centro comercial Moctezuma" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Francisco Chang" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Mexico" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Ernst Handel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Roland Mendel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Austria" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Island Trading" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Helen Bennett" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "UK" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Laughing Bacchus Winecellars" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Yoshi Tannamuri" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Canada" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Magazzini Alimentari Riuniti" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Giovanni Rovelli" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Italy" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n" | |
} | |
] | |
} | |
} | |
</script></body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.ql-editor table { | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.ql-editor table td { | |
border: 1px solid black; | |
padding: 5px; | |
height: 25px; | |
} | |
button.ql-table::after { content: "TABLE"; } | |
.ql-picker.ql-table .ql-picker-label::before { content: "TABLE"; } | |
button.ql-contain::after { content: "WRAP"; } | |
button.ql-table-insert-rows::after { content: "ROWS+"; } | |
button.ql-table-insert-columns::after { content: "COLS+"; } | |
.ql-table, | |
.ql-contain { | |
width: auto !important; | |
margin-right: -15px; | |
} | |
.ql-picker.ql-table { | |
margin-right: -15px; | |
font-size: 11px; | |
font-weight: normal; | |
} | |
.ql-picker.ql-table svg { | |
display: none; | |
} | |
.ql-picker.ql-table .ql-picker-label { | |
padding: 0px 3px; | |
} | |
.ql-picker.ql-table .ql-picker-options { | |
width: 190px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
display: block; | |
float: left; | |
width: 30px; | |
height: 30px; | |
line-height: 30px; | |
text-align: center; | |
padding: 0px; | |
margin: 1px; | |
} | |
.ql-picker.ql-table .ql-picker-item { | |
background: lightgrey; | |
} | |
.ql-picker-item:nth-child(5):before { | |
clear: both; | |
display: block; | |
content: ""; | |
width: 100%; | |
} | |
.ql-picker-item[data-value=newtable_1_1]:before { content: "1x1"; } | |
.ql-picker-item[data-value=newtable_1_2]:before { content: "1x2"; } | |
.ql-picker-item[data-value=newtable_1_3]:before { content: "1x3"; } | |
.ql-picker-item[data-value=newtable_1_4]:before { content: "1x4"; } | |
.ql-picker-item[data-value=newtable_1_5]:before { content: "1x5"; } | |
.ql-picker-item[data-value=newtable_2_1]:before { content: "2x1"; } | |
.ql-picker-item[data-value=newtable_2_2]:before { content: "2x2"; } | |
.ql-picker-item[data-value=newtable_2_3]:before { content: "2x3"; } | |
.ql-picker-item[data-value=newtable_2_4]:before { content: "2x4"; } | |
.ql-picker-item[data-value=newtable_2_5]:before { content: "2x5"; } | |
.ql-picker-item[data-value=newtable_3_1]:before { content: "3x1"; } | |
.ql-picker-item[data-value=newtable_3_2]:before { content: "3x2"; } | |
.ql-picker-item[data-value=newtable_3_3]:before { content: "3x3"; } | |
.ql-picker-item[data-value=newtable_3_4]:before { content: "3x4"; } | |
.ql-picker-item[data-value=newtable_3_5]:before { content: "3x5"; } | |
.ql-picker-item[data-value=newtable_4_1]:before { content: "4x1"; } | |
.ql-picker-item[data-value=newtable_4_2]:before { content: "4x2"; } | |
.ql-picker-item[data-value=newtable_4_3]:before { content: "4x3"; } | |
.ql-picker-item[data-value=newtable_4_4]:before { content: "4x4"; } | |
.ql-picker-item[data-value=newtable_4_5]:before { content: "4x5"; } | |
.ql-picker-item[data-value=newtable_5_1]:before { content: "5x1"; } | |
.ql-picker-item[data-value=newtable_5_2]:before { content: "5x2"; } | |
.ql-picker-item[data-value=newtable_5_3]:before { content: "5x3"; } | |
.ql-picker-item[data-value=newtable_5_4]:before { content: "5x4"; } | |
.ql-picker-item[data-value=newtable_5_5]:before { content: "5x5"; } | |
.ql-picker-item[data-value=newtable_6_1]:before { content: "6x1"; } | |
.ql-picker-item[data-value=newtable_6_2]:before { content: "6x2"; } | |
.ql-picker-item[data-value=newtable_6_3]:before { content: "6x3"; } | |
.ql-picker-item[data-value=newtable_6_4]:before { content: "6x4"; } | |
.ql-picker-item[data-value=newtable_6_5]:before { content: "6x5"; } | |
.ql-picker-item[data-value=newtable_7_1]:before { content: "7x1"; } | |
.ql-picker-item[data-value=newtable_7_2]:before { content: "7x2"; } | |
.ql-picker-item[data-value=newtable_7_3]:before { content: "7x3"; } | |
.ql-picker-item[data-value=newtable_7_4]:before { content: "7x4"; } | |
.ql-picker-item[data-value=newtable_7_5]:before { content: "7x5"; } | |
.ql-picker-item[data-value=newtable_8_1]:before { content: "8x1"; } | |
.ql-picker-item[data-value=newtable_8_2]:before { content: "8x2"; } | |
.ql-picker-item[data-value=newtable_8_3]:before { content: "8x3"; } | |
.ql-picker-item[data-value=newtable_8_4]:before { content: "8x4"; } | |
.ql-picker-item[data-value=newtable_8_5]:before { content: "8x5"; } | |
.ql-picker-item[data-value=newtable_9_1]:before { content: "9x1"; } | |
.ql-picker-item[data-value=newtable_9_2]:before { content: "9x2"; } | |
.ql-picker-item[data-value=newtable_9_3]:before { content: "9x3"; } | |
.ql-picker-item[data-value=newtable_9_4]:before { content: "9x4"; } | |
.ql-picker-item[data-value=newtable_9_5]:before { content: "9x5"; } | |
.ql-picker-item[data-value=newtable_10_1]:before { content: "10x1"; } | |
.ql-picker-item[data-value=newtable_10_2]:before { content: "10x2"; } | |
.ql-picker-item[data-value=newtable_10_3]:before { content: "10x3"; } | |
.ql-picker-item[data-value=newtable_10_4]:before { content: "10x4"; } | |
.ql-picker-item[data-value=newtable_10_5]:before { content: "10x5"; } | |
.tdbr, .trbr { | |
display: none | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// quill-table-breaks.js | |
let Container = Quill.import('blots/container'); | |
let Scroll = Quill.import('blots/scroll'); | |
let Inline = Quill.import('blots/inline'); | |
let Block = Quill.import('blots/block'); | |
let Delta = Quill.import('delta'); | |
let Parchment = Quill.import('parchment'); | |
let BlockEmbed = Quill.import('blots/block/embed'); | |
let TextBlot = Quill.import('blots/text'); | |
class ContainBlot extends Container { | |
static create(value) { | |
let tagName = 'contain'; | |
let node = super.create(tagName); | |
return node; | |
} | |
insertBefore(blot, ref) { | |
if (blot.statics.blotName == this.statics.blotName) { | |
console.log('############################ Not sure this is clean:') | |
console.log(blot) | |
console.log(blot.children.head) | |
super.insertBefore(blot.children.head, ref); | |
} else { | |
super.insertBefore(blot, ref); | |
} | |
} | |
static formats(domNode) { | |
return domNode.tagName; | |
} | |
formats() { | |
// We don't inherit from FormatBlot | |
return { [this.statics.blotName]: this.statics.formats(this.domNode) } | |
} | |
replace(target) { | |
if (target.statics.blotName !== this.statics.blotName) { | |
let item = Parchment.create(this.statics.defaultChild); | |
target.moveChildren(item); | |
this.appendChild(item); | |
} | |
if (target.parent == null) return; | |
super.replace(target) | |
} | |
} | |
ContainBlot.blotName = 'contain'; | |
ContainBlot.tagName = 'contain'; | |
ContainBlot.scope = Parchment.Scope.BLOCK_BLOT; | |
ContainBlot.defaultChild = 'block'; | |
ContainBlot.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(ContainBlot); | |
class TableRow extends Container { | |
static create(value) { | |
let tagName = 'tr'; | |
let node = super.create(tagName); | |
return node; | |
} | |
optimize() { | |
super.optimize(); | |
var parent = this.parent | |
if (parent != null && parent.statics.blotName != 'table') { | |
this.processTable() | |
} | |
} | |
processTable () { | |
var currentBlot = this | |
var rows = [] | |
while (currentBlot) { | |
if (! (currentBlot instanceof TableRow)) { | |
break | |
} | |
rows.push(currentBlot) | |
currentBlot = currentBlot.next | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
let table = Parchment.create('table'); | |
rows.forEach(function (row) { | |
table.appendChild(row) | |
}) | |
table.replace(mark) | |
} | |
} | |
TableRow.blotName = 'tr'; | |
TableRow.tagName = 'tr'; | |
TableRow.scope = Parchment.Scope.BLOCK_BLOT; | |
TableRow.defaultChild = 'td'; | |
Quill.register(TableRow); | |
class Table extends Container { | |
optimize() { | |
super.optimize(); | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
} | |
Table.blotName = 'table'; | |
Table.tagName = 'table'; | |
Table.scope = Parchment.Scope.BLOCK_BLOT; | |
Table.defaultChild = 'tr'; | |
Table.allowedChildren = [TableRow]; | |
Quill.register(Table); | |
// | |
// | |
// CONTAINER TD | |
// | |
class TableCell extends ContainBlot { | |
format() { | |
return 'td' | |
} | |
optimize() { | |
super.optimize(); | |
let parent = this.parent; | |
if (parent != null && parent.statics.blotName != 'tr') { | |
this.processTR() | |
} | |
// merge same TD id | |
let next = this.next; | |
if (next != null && next.prev === this && | |
next.statics.blotName === this.statics.blotName && | |
next.domNode.tagName === this.domNode.tagName | |
) { | |
next.moveChildren(this); | |
next.remove(); | |
} | |
} | |
processTR () { | |
// find next row break | |
var currentBlot = this | |
var rowItems = [this] | |
while (currentBlot) { | |
if (currentBlot.statics.tagName !== 'TD') { | |
break | |
} | |
rowItems.push(currentBlot) | |
if (currentBlot instanceof RowBreak) { | |
break | |
} | |
currentBlot = currentBlot.next | |
} | |
// create row, add row items as TDs | |
var prevItem | |
var cellItems = [] | |
var cells = [] | |
rowItems.forEach(function (rowItem) { | |
cellItems.push(rowItem) | |
if (rowItem instanceof TableCell) { | |
prevItem = rowItem | |
} else if (rowItem instanceof CellBreak) { | |
cells.push(cellItems) | |
cellItems = [] | |
} | |
}) | |
if (cellItems.length > 0) { | |
cells.push(cellItems) | |
} | |
let mark = Parchment.create('block'); | |
this.parent.insertBefore(mark, this.next); | |
// create row | |
var row = Parchment.create('tr') | |
cells.forEach(function (cell) { | |
// add row elements | |
cell.forEach(function (cellItem) { | |
row.appendChild(cellItem) | |
}) | |
}) | |
row.replace(mark) | |
} | |
} | |
TableCell.blotName = 'td'; | |
TableCell.tagName = 'td'; | |
TableCell.scope = Parchment.Scope.BLOCK_BLOT; | |
TableCell.defaultChild = 'block'; | |
TableCell.allowedChildren = [Block, BlockEmbed, Container]; | |
Quill.register(TableCell); | |
Container.order = [ | |
'list', 'contain', // Must be lower | |
'td', 'tr', 'table' // Must be higher | |
]; | |
class RowBreak extends BlockEmbed { | |
formats() { | |
return { trbr: true } | |
} | |
} | |
RowBreak.blotName = 'trbr' | |
RowBreak.tagName = 'td' | |
RowBreak.className = 'trbr' | |
Quill.register(RowBreak); | |
class CellBreak extends BlockEmbed { | |
formats() { | |
return { tdbr: true } | |
} | |
} | |
CellBreak.blotName = 'tdbr' | |
CellBreak.tagName = 'td' | |
CellBreak.className = 'tdbr' | |
Quill.register(CellBreak); | |
// END quill-table-breaks.js | |
// Render UI | |
var Keyboard = Quill.import('modules/keyboard') | |
// set up toolbar options | |
let maxRows = 10; | |
let maxCols = 5; | |
let tableOptions = []; | |
for (let r = 1; r <= maxRows; r++) { | |
for (let c = 1; c <= maxCols; c++) { | |
tableOptions.push('newtable_' + r + '_' + c); | |
} | |
} | |
Quill.debug('debug'); | |
var quill = new Quill('#editor-container', { | |
modules: { | |
toolbar: { | |
container: [ | |
[{ 'table': tableOptions }], // new table (cursor needs to be out of table) | |
['table-insert-rows'], // cursor needs to be in the table | |
['table-insert-columns'], // cursor needs to be in the table | |
['bold', 'italic', 'underline', 'strike'], | |
['blockquote', 'code-block'], | |
[{ 'header': 1 }, { 'header': 2 }], | |
[{ 'list': 'ordered'}, { 'list': 'bullet' }], | |
[{ 'script': 'sub'}, { 'script': 'super' }], | |
[{ 'indent': '-1'}, { 'indent': '+1' }], | |
[{ 'direction': 'rtl' }], | |
[{ 'size': ['small', false, 'large', 'huge'] }], | |
[{ 'header': [1, 2, 3, 4, 5, 6, false] }], | |
[{ 'color': [] }, { 'background': [] }], | |
[{ 'align': [] }], | |
['link', 'image', 'code-block'], | |
['clean'] | |
], | |
handlers: { | |
table: function (value) { | |
if(value && value.includes('newtable_')) { | |
let sizes = value.split('_'); | |
let rows = Number.parseInt(sizes[1]) | |
let columns = Number.parseInt(sizes[2]) | |
let table = Parchment.create('table'); | |
const range = this.quill.getSelection() | |
if (!range) return | |
const newLineIndex = getClosestNewLineIndex(this.quill.getContents(), range.index + range.length) | |
let changeDelta = new Delta().retain(newLineIndex) | |
changeDelta = changeDelta.insert('\n') | |
for (let i = 0; i < rows; i++) { | |
for (let j = 0; j < columns; j++) { | |
changeDelta = changeDelta.insert('\n', { | |
td: true | |
}) | |
if (j < columns - 1) { | |
changeDelta = changeDelta.insert({ tdbr: true }) | |
} | |
} | |
changeDelta = changeDelta.insert({ trbr: true }) | |
} | |
this.quill.updateContents(changeDelta, Quill.sources.USER) | |
this.quill.setSelection(newLineIndex + 1) | |
} else { | |
// TODO | |
} | |
}, | |
'table-insert-rows': function() { | |
let td = find_td('td') | |
if(td) { | |
let col_count = 0 | |
td.parent.children.forEach(function (it) { | |
if (it instanceof TableCell) { | |
col_count++ | |
} | |
}) | |
let table = td.parent.parent; | |
let new_row = td.parent.clone() | |
for (var i = col_count - 1; i >= 0; i--) { | |
let td = Parchment.create('td'); | |
new_row.appendChild(td); | |
new_row.appendChild(Parchment.create('tdbr')) | |
}; | |
new_row.appendChild(Parchment.create('trbr')) | |
table.appendChild(new_row); | |
} | |
}, | |
'table-insert-columns': function() { | |
let td = find_td('td') | |
if(td) { | |
let table = td.parent.parent; | |
td.parent.parent.children.forEach(function(tr) { | |
let td = Parchment.create('td'); | |
tr.appendChild(td); | |
tr.appendChild(Parchment.create('tdbr')) | |
}); | |
} | |
} | |
} | |
}, | |
clipboard: { | |
matchers: [ | |
['TD, TH', function (node, delta) { | |
delta.insert("\n", { td: true }) | |
delta.insert({ tdbr: true }) | |
return delta | |
}], | |
['TR', function (node, delta) { | |
delta.insert({ trbr: true }) | |
return delta | |
}], | |
] | |
}, | |
keyboard: { | |
bindings: { | |
'backspaceTable': { | |
key: 8, | |
format: ['td'], | |
// offset: 0, | |
handler: function handleTableBackspace (range, context) { | |
var formats = quill.getFormat(range.index-1, 1) | |
if (formats.tdbr || formats.trbr) { | |
// prevent deletion of table break | |
return false | |
} | |
return true | |
} | |
} | |
} | |
}, | |
}, | |
placeholder: 'Compose an epic...', | |
theme: 'snow' // or 'bubble' | |
}); | |
// global for console debugging | |
QuillInstance = quill | |
quill.on('text-change', function(delta, source) { | |
document.getElementById("output_delta").value=JSON.stringify(quill.editor.getDelta(), null, 2) | |
document.getElementById("output_html").value=quill.root.innerHTML; | |
document.getElementById("view_html").innerHTML=quill.root.innerHTML; | |
}) | |
// use sample delta | |
var delta = getSampleDelta() | |
document.getElementById("orig_delta").value=JSON.stringify(delta, null, 2) | |
quill.setContents(delta); | |
function getClosestNewLineIndex (contents, index) { | |
return index + contents.map((op) => { | |
return typeof op.insert === 'string' ? op.insert : ' ' | |
}).join('') | |
.slice(index) | |
.indexOf('\n') | |
} | |
function find_td(what) { | |
let leaf = quill.getLeaf(quill.getSelection()['index']); | |
let blot = leaf[0]; | |
for(;blot!=null && blot.statics.blotName!=what;) { | |
blot=blot.parent; | |
} | |
return blot; // return TD or NULL | |
} | |
function getSampleDelta () { | |
return { | |
"ops": [ | |
{ | |
"insert": "Test Tables" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 1 | |
} | |
}, | |
{ | |
"insert": "Empty 3x3 table from toolbar" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPopulated 3x3 table (from toolbar)" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Col 1" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 2" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Col 3" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "a" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "b" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": "c" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "123" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "456" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "d" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "4" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "7" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\nPasted Table" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"header": 2 | |
} | |
}, | |
{ | |
"insert": "Company" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Contact" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Country" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Alfreds Futterkiste" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Maria Anders" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Germany" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Centro comercial Moctezuma" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Francisco Chang" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Mexico" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Ernst Handel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Roland Mendel" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Austria" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Island Trading" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Helen Bennett" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "UK" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Laughing Bacchus Winecellars" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Yoshi Tannamuri" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Canada" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "Magazzini Alimentari Riuniti" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Giovanni Rovelli" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": "Italy" | |
}, | |
{ | |
"insert": "\n", | |
"attributes": { | |
"td": "TD" | |
} | |
}, | |
{ | |
"insert": { | |
"tdbr": true | |
}, | |
"attributes": { | |
"tdbr": true | |
} | |
}, | |
{ | |
"insert": { | |
"trbr": true | |
}, | |
"attributes": { | |
"trbr": true | |
} | |
}, | |
{ | |
"insert": "\n" | |
} | |
] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment