seehar / quill-better-table-plus

fork by quill-better-table. Module for better table in Quill, more useful features are supported.
MIT License
8 stars 3 forks source link

Suggestion: include toolbar button matrix picker in package (working code) #8

Open enzedonline opened 2 months ago

enzedonline commented 2 months ago

This comes from an idea posted on jsfiddle. Rewritten for a better efficiency (too many repeated queries, ES5 + jQuery etc). There are tooltips on the boxes (1x1, 2x1 ... etc), they just don't show on the gif.

2024-06-20-140634-ezgif com-video-to-gif-converter

// quill-insert-better-table-plus.js
// create button with dropdown matrix representing number of rows and columns to create table with
// options:
//    maxColumns, maxRows - number of columns and rows to display in picker matrix
//    tableClassList - classes to add to created table element, may be list or space separated string of classes
class InsertBetterTablePlus {
    constructor(quill, options={}) {
        this.quill = quill;
        this.columns = options['maxColumns'] ?? 5;
        this.rows = options['maxRows'] ?? 7;
        if (!Number.isInteger(this.columns) || this.columns <= 0) {
            console.warn('Invalid max_columns value. It should be a positive integer. Setting to default value of 5.');
            this.columns = 5;
        }
        if (!Number.isInteger(this.rows) || this.rows <= 0) {
            console.warn('Invalid max_rows value. It should be a positive integer. Setting to default value of 7.');
            this.rows = 7;
        }
        this.tableClassList = options['tableClassList'] ?? ['table'];
        if (!Array.isArray(this.tableClassList)) {
            if (this.tableClassList.includes(' ')) {
                this.tableClassList = this.tableClassList.split(' ')
            } else {
                this.tableClassList = [this.tableClassList];
            }
        }
        this.toolbar = this.quill.getModule("toolbar");
        if (this.toolbar) {
            this.toolbar.addHandler("insert-better-table-plus", this._toolbarResponse.bind(this));
            this.button = this.toolbar.container.querySelector('span.ql-insert-better-table-plus')
            if (this.button) {
                this._buildDropdownMenu();
            }
        }
    }

    _buildDropdownMenu() {
        const pickerLabel = this.button.querySelector('span.ql-picker-label');
        pickerLabel.innerHTML = this._icon();
        const pickerOptions = this.button.querySelector('span.ql-picker-options');
        pickerOptions.innerHTML = 
            Array.from({ length: this.rows }, (_, r) => 
                Array.from({ length: this.columns }, (_, c) => 
                    `<span tabindex="0" role="button" class="ql-picker-item" 
                        data-row-count="${r + 1}" 
                        data-col-count="${c + 1}" 
                        title="${r + 1}x${c + 1}"></span>`
                )
            ).flat().join('').replace(/\s+/g, ' ').trim();
        pickerOptions.style = `grid-template-columns: repeat(${this.columns}, 1fr);`;
        const pickerItems = pickerOptions.querySelectorAll('span.ql-picker-item');
        pickerItems.forEach(span => {
            span.addEventListener('click', (event) => {
                const betterTablePlus = this.quill.getModule('better-table-plus');
                this.button.classList.remove('ql-expanded');
                pickerLabel.setAttribute('aria-expanded', 'false');
                betterTablePlus.insertTable(
                    Number(event.target.dataset.rowCount),
                    Number(event.target.dataset.colCount)
                );
                // Remove the selected and active classes
                this.button.querySelectorAll('.ql-selected, .ql-active').forEach(el => {
                    el.classList.remove('ql-selected', 'ql-active');
                });
                // add additional table css classes
                const range = this.quill.getSelection(true);
                const currentBlot = this.quill.getLeaf(range.index)[0];
                const addedTable = currentBlot.parent.domNode.closest('table.quill-better-table');
                if (addedTable) {
                    addedTable.classList.add(...this.tableClassList);
                }
            });
            span.addEventListener('mouseenter', (event) => {
                pickerItems.forEach(sibling => {
                    if (
                        Number(sibling.dataset.rowCount) <= Number(event.target.dataset.rowCount) &&
                        Number(sibling.dataset.colCount) <= Number(event.target.dataset.colCount)
                    ) {
                        sibling.classList.add('ql-picker-item-highlight');
                    }
                });
            });
            span.addEventListener('mouseleave', () => {
                pickerOptions.querySelectorAll('.ql-picker-item-highlight').forEach(sibling => {
                    sibling.classList.remove('ql-picker-item-highlight');
                });
            });
        });

    }

    // dummy handler to add to toolbar handlers
    _toolbarResponse() {
        return true;
    }

    _icon() {
        return `
            <svg viewbox="0 0 18 18" style="left: 0;">
                <rect class="ql-stroke" height="14.76924" width="14.769239" x="0.61538005" y="0.61538005"/>
                <rect class="ql-fill" height="2.46154" width="3.6923099" x="3.0769198" y="3.0769203" />
                <rect class="ql-fill" height="2.46154" width="4.92308" x="8.000001" y="3.0769203" />
                <g style="opacity:0.5;" class="ql-fill" transform="matrix(1.23077,0,0,1.23077,-3.0769299,-3.0769298)">
                    <rect height="2" width="3" x="5" y="8"/>
                    <rect height="2" width="4" x="9" y="8"/>
                    <rect height="2" width="3" x="5" y="11"/>
                    <rect height="2" width="4" x="9" y="11"/>
                </g>
            </svg>
        `;
    }
}
/* quill-insert-better-table-plus */
div.ql-toolbar span.ql-insert-better-table-plus>span.ql-picker-label {
    margin-inline-start: 5px;
    width: 18px;
    height: 18px;
}
div.ql-toolbar span.ql-insert-better-table-plus>span.ql-picker-label>svg {
    margin-top: -5px !important;
}
div.ql-toolbar span.ql-insert-better-table-plus>span.ql-picker-label.ql-active .ql-stroke {
    stroke: #444;
}
div.ql-toolbar span.ql-insert-better-table-plus>span.ql-picker-label.ql-active:hover .ql-stroke {
    stroke: #06c;
}
div.ql-toolbar span.ql-insert-better-table-plus span.ql-picker-options {
    padding: 3px 5px;
}
div.ql-toolbar span.ql-insert-better-table-plus.ql-expanded span.ql-picker-label {
    border: 0;
}
div.ql-toolbar span.ql-insert-better-table-plus.ql-expanded span.ql-picker-options {
    display: grid;
}
div.ql-toolbar span.ql-insert-better-table-plus .ql-picker-item {
    border: 1px solid transparent;
    float: left;
    height: 16px;
    margin: 2px;
    padding: 0px;
    width: 16px;
    background: lightsteelblue;
}
div.ql-toolbar span.ql-insert-better-table-plus .ql-picker-item:hover,
div.ql-toolbar span.ql-insert-better-table-plus .ql-picker-item.ql-picker-item-highlight {
    border-color: #000;
    background: steelblue;
}
div.ql-toolbar span.ql-insert-better-table-plus.ql-picker {
    width: 28px;
}
div.ql-toolbar span.ql-insert-better-table-plus.ql-picker .ql-picker-label {
    padding: 2px 4px;
}

Register with

Quill.register("modules/insert-better-table-plus", InsertBetterTablePlus);

In modules: insert-better-table-plus: {},

or with options maxColumns, maxRows, tableClassList

and in toolbar.handlers:

        toolbar: {
            container: [
                ....,
                [{insert-better-table-plus: []}],
            ],
            handlers: {
                insert-better-table-plus: '_toolbarResponse'
            }
        },