UNPKG

jodit

Version:

Jodit is an awesome and useful wysiwyg editor with filebrowser

344 lines (343 loc) 12.5 kB
/*! * Jodit Editor (https://xdsoft.net/jodit/) * Released under MIT see LICENSE.txt in the project root for license information. * Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { KEY_TAB } from "../../core/constants.js"; import { autobind, watch } from "../../core/decorators/index.js"; import { Dom } from "../../core/dom/dom.js"; import { pluginSystem } from "../../core/global.js"; import { $$, alignElement, position } from "../../core/helpers/index.js"; import { Plugin } from "../../core/plugin/index.js"; import { Table } from "../../modules/table/table.js"; import "./config.js"; const key = 'table_processor_observer'; const MOUSE_MOVE_LABEL = 'onMoveTableSelectCell'; export class selectCells extends Plugin { constructor() { super(...arguments); /** * First selected cell */ this.__selectedCell = null; /** * User is selecting cells now */ this.__isSelectionMode = false; } /** * Shortcut for Jodit.modules.Table */ get __tableModule() { return this.j.getInstance(Table, this.j.o); } afterInit(jodit) { if (!jodit.o.tableAllowCellSelection) { return; } jodit.e .on('keydown.select-cells', (event) => { if (event.key === KEY_TAB) { this.unselectCells(); } }) .on('beforeCommand.select-cells', this.onExecCommand) .on('afterCommand.select-cells', this.onAfterCommand) // see `plugins/select.ts` .on([ 'clickEditor', 'mousedownTd', 'mousedownTh', 'touchstartTd', 'touchstartTh' ] .map(e => e + '.select-cells') .join(' '), this.onStartSelection) // For `clickEditor` correct working. Because `mousedown` on first cell // and mouseup on another cell call `click` only for `TR` element. .on('clickTr clickTbody', () => { var _a; const cellsCount = this.__tableModule.getAllSelectedCells().length; if (cellsCount) { if (cellsCount > 1) { (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges(); } return false; } }); } /** * Mouse click inside the table */ onStartSelection(cell) { if (this.j.o.readonly) { return; } this.unselectCells(); if (cell === this.j.editor) { return; } const table = Dom.closest(cell, 'table', this.j.editor); if (!cell || !table) { return; } if (!cell.firstChild) { cell.appendChild(this.j.createInside.element('br')); } this.__isSelectionMode = true; this.__selectedCell = cell; this.__tableModule.addSelection(cell); this.j.e .on(table, 'mousemove.select-cells touchmove.select-cells', // Don't use decorator because need clear label on mouseup this.j.async.throttle(this.__onMove.bind(this, table), { label: MOUSE_MOVE_LABEL, timeout: this.j.defaultTimeout / 2 })) .on(table, 'mouseup.select-cells touchend.select-cells', this.__onStopSelection.bind(this, table)); return false; } onOutsideClick() { this.__selectedCell = null; this.__onRemoveSelection(); } onChange() { if (!this.j.isLocked && !this.__isSelectionMode) { this.__onRemoveSelection(); } } /** * Mouse move inside the table */ __onMove(table, e) { var _a; if (this.j.o.readonly && !this.j.isLocked) { return; } if (this.j.isLockedNotBy(key)) { return; } const node = this.j.ed.elementFromPoint(e.clientX, e.clientY); if (!node) { return; } const cell = Dom.closest(node, ['td', 'th'], table); if (!cell || !this.__selectedCell) { return; } if (cell !== this.__selectedCell) { this.j.lock(key); } this.unselectCells(); const bound = this.__tableModule.getSelectedBound(table, [ cell, this.__selectedCell ]), box = this.__tableModule.formalMatrix(table); for (let i = bound[0][0]; i <= bound[1][0]; i += 1) { for (let j = bound[0][1]; j <= bound[1][1]; j += 1) { this.__tableModule.addSelection(box[i][j]); } } const cellsCount = this.__tableModule.getAllSelectedCells().length; if (cellsCount > 1) { (_a = this.j.s.sel) === null || _a === void 0 ? void 0 : _a.removeAllRanges(); } this.j.e.fire('hidePopup'); e.stopPropagation(); // Hack for FireFox for force redraw selection (() => { const n = this.j.createInside.fromHTML('<div style="color:rgba(0,0,0,0.01);width:0;height:0">&nbsp;</div>'); cell.appendChild(n); this.j.async.setTimeout(() => { var _a; (_a = n.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(n); }, this.j.defaultTimeout / 5); })(); } /** * On click in outside - remove selection */ __onRemoveSelection(e) { var _a; if (!((_a = e === null || e === void 0 ? void 0 : e.buffer) === null || _a === void 0 ? void 0 : _a.actionTrigger) && !this.__selectedCell && this.__tableModule.getAllSelectedCells().length) { this.j.unlock(); this.unselectCells(); this.j.e.fire('hidePopup', 'cells'); return; } this.__isSelectionMode = false; this.__selectedCell = null; } /** * Stop a selection process */ __onStopSelection(table, e) { if (!this.__selectedCell) { return; } this.__isSelectionMode = false; this.j.unlock(); const node = this.j.ed.elementFromPoint(e.clientX, e.clientY); if (!node) { return; } const cell = Dom.closest(node, ['td', 'th'], table); if (!cell) { return; } const ownTable = Dom.closest(cell, 'table', table); if (ownTable && ownTable !== table) { return; // Nested tables } const bound = this.__tableModule.getSelectedBound(table, [ cell, this.__selectedCell ]), box = this.__tableModule.formalMatrix(table); const max = box[bound[1][0]][bound[1][1]], min = box[bound[0][0]][bound[0][1]]; this.j.e.fire('showPopup', table, () => { const minOffset = position(min, this.j), maxOffset = position(max, this.j); return { left: minOffset.left, top: minOffset.top, width: maxOffset.left - minOffset.left + maxOffset.width, height: maxOffset.top - minOffset.top + maxOffset.height }; }, 'cells'); $$('table', this.j.editor).forEach(table => { this.j.e.off(table, 'mousemove.select-cells touchmove.select-cells mouseup.select-cells touchend.select-cells'); }); this.j.async.clearTimeout(MOUSE_MOVE_LABEL); } /** * Remove selection for all cells */ unselectCells(currentCell) { const module = this.__tableModule; const cells = module.getAllSelectedCells(); if (cells.length) { cells.forEach(cell => { if (!currentCell || currentCell !== cell) { module.removeSelection(cell); } }); } } /** * Execute custom commands for table */ onExecCommand(command) { if (/table(splitv|splitg|merge|empty|bin|binrow|bincolumn|addcolumn|addrow)/.test(command)) { command = command.replace('table', ''); const cells = this.__tableModule.getAllSelectedCells(); if (cells.length) { const [cell] = cells; if (!cell) { return; } const table = Dom.closest(cell, 'table', this.j.editor); if (!table) { return; } switch (command) { case 'splitv': this.__tableModule.splitVertical(table); break; case 'splitg': this.__tableModule.splitHorizontal(table); break; case 'merge': this.__tableModule.mergeSelected(table); break; case 'empty': cells.forEach(td => Dom.detach(td)); break; case 'bin': Dom.safeRemove(table); break; case 'binrow': new Set(cells.map(td => td.parentNode)).forEach(row => { this.__tableModule.removeRow(table, row.rowIndex); }); break; case 'bincolumn': { const columnsSet = new Set(); const columns = []; cells.forEach(td => { const [, col] = this.__tableModule.formalCoordinate(table, td); if (!columnsSet.has(col)) { columns.push(col); columnsSet.add(col); } }); columns .sort((a, b) => b - a) .forEach(col => { this.__tableModule.removeColumn(table, col); }); } break; case 'addcolumnafter': case 'addcolumnbefore': this.__tableModule.appendColumn(table, cell, command === 'addcolumnafter'); break; case 'addrowafter': case 'addrowbefore': this.__tableModule.appendRow(table, cell.parentNode, command === 'addrowafter'); break; } } return false; } } /** * Add some align after native command */ onAfterCommand(command) { if (/^justify/.test(command)) { this.__tableModule .getAllSelectedCells() .forEach(elm => alignElement(command, elm)); } } /** @override */ beforeDestruct(jodit) { this.__onRemoveSelection(); jodit.e.off('.select-cells'); } } selectCells.requires = ['select']; __decorate([ autobind ], selectCells.prototype, "onStartSelection", null); __decorate([ watch(':outsideClick') ], selectCells.prototype, "onOutsideClick", null); __decorate([ watch(':change') ], selectCells.prototype, "onChange", null); __decorate([ autobind ], selectCells.prototype, "__onRemoveSelection", null); __decorate([ autobind ], selectCells.prototype, "__onStopSelection", null); __decorate([ autobind ], selectCells.prototype, "onExecCommand", null); __decorate([ autobind ], selectCells.prototype, "onAfterCommand", null); pluginSystem.add('selectCells', selectCells);