UNPKG

editorjs-table

Version:

Table for Editor.js with configurable rows and columns

324 lines (301 loc) 9.21 kB
const { TableConstructor } = require("./tableConstructor"); const toolboxIcon = require("./img/toolboxIcon.svg"); const insertColBefore = require("./img/insertColBeforeIcon.svg"); const insertColAfter = require("./img/indertColAfterIcon.svg"); const insertRowBefore = require("./img/insertRowBeforeIcon.svg"); const insertRowAfter = require("./img/insertRowAfter.svg"); const deleteRow = require("./img/deleteRowIcon.svg"); const deleteCol = require("./img/deleteColIcon.svg"); const Icons = { Toolbox: toolboxIcon, InsertColBefore: insertColBefore, InsertColAfter: insertColAfter, InsertRowBefore: insertRowBefore, InsertRowAfter: insertRowAfter, DeleteRow: deleteRow, DeleteCol: deleteCol, }; const CSS = { input: "tc-table__inp", }; /** * Tool for table's creating * @typedef {object} TableData - object with the data transferred to form a table * @property {string[][]} content - two-dimensional array which contains table content */ class Table { /** * Allow to press Enter inside the CodeTool textarea * @returns {boolean} * @public */ static get enableLineBreaks() { return true; } /** * Get Tool toolbox settings * icon - Tool icon's SVG * title - title to show in toolbox * * @return {{icon: string, title: string}} */ static get toolbox() { return { icon: Icons.Toolbox, title: "Table", }; } /** * Render plugin`s main Element and fill it with saved data * @param {TableData} data — previously saved data * @param {object} config - user config for Tool * @param {object} api - Editor.js API */ constructor({ data, config, api }) { this.api = api; this.wrapper = undefined; this.config = config; this.data = data; this._tableConstructor = new TableConstructor(data, config, api); this.actions = [ { actionName: "InsertColBefore", icon: Icons.InsertColBefore, label: "Insert column before", }, { actionName: "InsertColAfter", icon: Icons.InsertColAfter, label: "Insert column after", }, { actionName: "InsertRowBefore", icon: Icons.InsertRowBefore, label: "Insert row before", }, { actionName: "InsertRowAfter", icon: Icons.InsertRowAfter, label: "Insert row after", }, { actionName: "DeleteRow", icon: Icons.DeleteRow, label: "Delete row", }, { actionName: "DeleteCol", icon: Icons.DeleteCol, label: "Delete column", }, ]; } /** * perform selected action * @param actionName {string} - action name * @return {undefined} */ performAction(actionName) { switch (actionName) { case "InsertColBefore": this._tableConstructor.table.insertColumnBefore(); break; case "InsertColAfter": this._tableConstructor.table.insertColumnAfter(); break; case "InsertRowBefore": this._tableConstructor.table.insertRowBefore(); break; case "InsertRowAfter": this._tableConstructor.table.insertRowAfter(); break; case "DeleteRow": this._tableConstructor.table.deleteRow(); break; case "DeleteCol": this._tableConstructor.table.deleteColumn(); break; } } /** * render actions toolbar * @returns {HTMLDivElement} */ renderSettings() { const wrapper = document.createElement("div"); this.actions.forEach(({ actionName, label, icon }) => { const title = this.api.i18n.t(label); const button = document.createElement("div"); button.classList.add("cdx-settings-button"); button.innerHTML = icon; button.title = actionName; this.api.tooltip.onHover(button, title, { placement: "top", }); button.addEventListener( "click", this.performAction.bind(this, actionName) ); wrapper.appendChild(button); if(this._tableConstructor.table.selectedCell) { this._tableConstructor.table.focusCellOnSelectedCell(); } }); return wrapper; } /** * Return Tool's view * @returns {HTMLDivElement} * @public */ render() { this.wrapper = document.createElement("div"); if ((this.data && this.data.content)) { //Creates table if Data is Present this._createTableConfiguration(); } else { // Create table preview if New table is initialised this.wrapper.classList.add("table-selector"); this.wrapper.setAttribute("data-hoveredClass", "m,n"); const rows = 6; this.createCells(rows); //Hover to select cells if (this.wrapper.className === "table-selector") { this.wrapper.addEventListener("mouseover", (event) => { const selectedCell = event.target.id; if (selectedCell.length) { const selectedRow = event.target.attributes.row.value; const selectedColumn = event.target.attributes.column.value; this.wrapper.setAttribute( "data-hoveredClass", `${selectedRow},${selectedColumn}` ); } }); } //set the select cell to load table config this.wrapper.addEventListener("click", (event) => { const selectedCell = event.target.id; if (selectedCell.length) { const selectedRow = event.target.attributes.row.value; const selectedColumn = event.target.attributes.column.value; this.wrapper.removeEventListener("mouseover", () => {}); this.config.rows = selectedRow; this.config.cols = selectedColumn; this._createTableConfiguration(); } }); } return this.wrapper; } createCells(rows) { if (rows !== 0) { for (let i = 0; i < rows; i++) { let rowDiv = document.createElement("div"); rowDiv.setAttribute("class", "table-row"); for (let j = 0; j < rows; j++) { let columnDivContainer = document.createElement("div"); let columnDiv = document.createElement("div"); columnDivContainer.setAttribute("class", "table-cell-container"); columnDiv.setAttribute("class", "table-cell"); columnDivContainer.setAttribute("id", `row_${i + 1}_cell_${j + 1}`); columnDivContainer.setAttribute("column", j + 1); columnDivContainer.setAttribute("row", i + 1); columnDiv.setAttribute("id", `cell_${j + 1}`); columnDiv.setAttribute("column", j + 1); columnDiv.setAttribute("row", i + 1); columnDivContainer.appendChild(columnDiv); rowDiv.appendChild(columnDivContainer); } this.wrapper.appendChild(rowDiv); } } const hiddenEl = document.createElement('input'); hiddenEl.classList.add('hidden-element'); hiddenEl.setAttribute('tabindex', 0); this.wrapper.appendChild(hiddenEl); } _createTableConfiguration() { this.wrapper.innerHTML = ""; this._tableConstructor = new TableConstructor( this.data, this.config, this.api ); this.wrapper.appendChild(this._tableConstructor.htmlElement); } /** * Extract Tool's data from the view * @returns {TableData} - saved data * @public */ save(toolsContent) { const table = toolsContent.querySelector("table"); const data = []; const rows = table ? table.rows : 0; if(rows.length) { for (let i = 0; i < rows.length; i++) { const row = rows[i]; const cols = Array.from(row.cells); const inputs = cols.map((cell) => cell.querySelector("." + CSS.input)); const isWorthless = inputs.every(this._isEmpty); if (isWorthless) { continue; } data.push(inputs.map((input) => input.innerHTML)); } return { content: data, }; } } /** * @private * * Check input field is empty * @param {HTMLElement} input - input field * @return {boolean} */ _isEmpty(input) { return !input.textContent.trim(); } static get pasteConfig() { return { tags: ['TABLE', 'TR', 'TD', 'TBODY', 'TH'], }; } async onPaste(event) { const table = event.detail.data; this.data = this.pasteHandler(table); this._createTableConfiguration(); } pasteHandler(element) { const {tagName: tag} = element; const data = { content: [], config: { rows: 0, cols: 0 } } if(tag ==='TABLE') { let tableBody = Array.from(element.childNodes); tableBody = tableBody.find(el => el.nodeName === 'TBODY'); let tableRows = Array.from(tableBody.childNodes); tableRows = [tableRows].map(obj => { return obj.filter((tr) => tr.nodeName === 'TR'); }); data.config.rows = tableRows[0].length; data.content = tableRows[0].map((tr) => { let tableData = tr.childNodes; data.config.cols = tableData.length; tableData = [...tableData].map((td) => { return td.innerHTML; }); return tableData; }) } return data; } } module.exports = Table;