diff --git a/public/notes.css b/public/notes.css
index a6dafb61..215caf21 100644
--- a/public/notes.css
+++ b/public/notes.css
@@ -1,5 +1,6 @@
:root {
--default-sidebar-width: 25%;
+ --resizing-div-overflow: -10000px;
}
html {
@@ -372,6 +373,7 @@ body.resizing-sidebar-locked-max { cursor: w-resize; }
border: var(--table-border);
border-spacing: 0;
border-collapse: collapse;
+ overflow: hidden !important;
}
#content table td {
@@ -461,26 +463,33 @@ body.with-control.resizing-img #content {
.table-resizing-div {
position: absolute;
+ z-index: 100;
}
-.table-resizing-div.active {
+body:not(.resizing-table) .table-resizing-div:hover, .table-resizing-div.active {
background: var(--resizing-line-color);
}
.table-column-resizing-div {
- top: 0;
+ top: var(--resizing-div-overflow);
+ bottom: var(--resizing-div-overflow);
right: 0;
- bottom: 0;
+ width: var(--table-resizing-div-thickness);
+}
+
+body:not(.resizing-table) .table-column-resizing-div {
cursor: col-resize;
- width: 4px;
}
.table-row-resizing-div {
- left: 0;
- right: 0;
+ left: var(--resizing-div-overflow);
+ right: var(--resizing-div-overflow);
bottom: 0;
+ height: var(--table-resizing-div-thickness);
+}
+
+body:not(.resizing-table) .table-row-resizing-div {
cursor: row-resize;
- height: 4px;
}
body.resizing-table-column { cursor: col-resize; }
diff --git a/public/themes/dark.css b/public/themes/dark.css
index 9a34a79a..ed24c72d 100644
--- a/public/themes/dark.css
+++ b/public/themes/dark.css
@@ -88,6 +88,7 @@
--table-border: 3px solid #454545;
--table-td-border: 1px solid #222;
--table-td-heading-background-color: #222;
+ --table-resizing-div-thickness: 4px;
--resizing-line-color: #0000ff;
diff --git a/public/themes/light.css b/public/themes/light.css
index 069a93c8..c79b1d98 100644
--- a/public/themes/light.css
+++ b/public/themes/light.css
@@ -88,6 +88,7 @@
--table-border: 3px solid #171717;
--table-td-border: 1px solid silver;
--table-td-heading-background-color: #dddddd;
+ --table-resizing-div-thickness: 4px;
--resizing-line-color: #0000ff;
diff --git a/src/notes/content/__tests__/table.test.ts b/src/notes/content/__tests__/table.test.ts
deleted file mode 100644
index 3deae2f6..00000000
--- a/src/notes/content/__tests__/table.test.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-import { JSDOM } from "jsdom";
-import {
- getAllCellsInColumn,
- MakeTableResizableProps,
- makeTableResizable,
- initTable,
-} from "../table";
-
-// A B C
-// D E F
-// G H I
-const tableHtml = `
-
- A |
- B |
- C |
-
-
- D |
- E |
- F |
-
-
- G |
- H |
- I |
-
-
`;
-
-const tableHtmlHoverE = `
-
- A |
- B |
- C |
-
-
- D |
- E |
- F |
-
-
- G |
- H |
- I |
-
-
`;
-
-const tableHtmlHoverI = `
-
- A |
- B |
- C |
-
-
- D |
- E |
- F |
-
-
- G |
- H |
- I |
-
-
`;
-
-const prepareDom = (tableHtmlToInsert: string): { dom: JSDOM, table: HTMLTableElement } => {
- const dom = new JSDOM();
- dom.window.document.body.insertAdjacentHTML("afterbegin", tableHtmlToInsert);
- const table = dom.window.document.body.children[0] as HTMLTableElement;
- return { dom, table };
-};
-
-test("getAllCellsInColumn() returns cells in a column", () => {
- const { table } = prepareDom(tableHtml);
-
- expect(getAllCellsInColumn(table, 0).map((cell) => cell.innerHTML)).toEqual(["A", "D", "G"]);
- expect(getAllCellsInColumn(table, 1).map((cell) => cell.innerHTML)).toEqual(["B", "E", "H"]);
- expect(getAllCellsInColumn(table, 2).map((cell) => cell.innerHTML)).toEqual(["C", "F", "I"]);
- expect(getAllCellsInColumn(table, 99).map((cell) => cell.innerHTML)).toEqual([]);
-});
-
-describe("initTable", () => {
- let dom: JSDOM;
- let table: HTMLTableElement;
- let resizableProps: MakeTableResizableProps;
- let makeTableResizableFunction: jest.Mock;
-
- beforeEach(() => {
- const prepared = prepareDom(tableHtmlHoverE);
- dom = prepared.dom;
- table = prepared.table;
- resizableProps = {
- document: dom.window.document,
- computedStyleFunction: dom.window.getComputedStyle,
- table,
- onResize: () => { },
- };
- makeTableResizableFunction = jest.fn();
- initTable({
- table,
- resizableProps,
- makeTableResizableFunction,
- onContextMenu: () => {},
- });
- });
-
- it("removes resizing divs if left over", () => {
- expect(table.outerHTML).toBe(tableHtml);
- });
-
- it("calls resizable function", () => {
- expect(makeTableResizableFunction).toHaveBeenCalledTimes(1);
- expect(makeTableResizableFunction).toHaveBeenCalledWith(resizableProps);
- });
-});
-
-describe("makeTableResizable()", () => {
- let dom: JSDOM;
- let table: HTMLTableElement;
- let onResize: jest.Mock;
-
- beforeEach(() => {
- const prepared = prepareDom(tableHtml);
- dom = prepared.dom;
- table = prepared.table;
- onResize = jest.fn();
-
- makeTableResizable({
- document: dom.window.document,
- computedStyleFunction: dom.window.getComputedStyle,
- table,
- onResize,
- });
- });
-
- it("keeps table markup unchanged", () => {
- expect(table.outerHTML).toBe(tableHtml);
- });
-
- it("does NOT call onResize on init", () => {
- expect(onResize).not.toHaveBeenCalled();
- });
-
- it("adds resizing divs on mouseenter, removes them on mouseleave", () => {
- const enterAndLeave = (cell: HTMLTableCellElement, expectedTableHtmlOnEnter: string) => {
- cell.dispatchEvent(new dom.window.Event("mouseenter"));
- expect(table.outerHTML).toBe(expectedTableHtmlOnEnter);
-
- cell.dispatchEvent(new dom.window.Event("mouseleave"));
- expect(table.outerHTML).toBe(tableHtml);
- };
-
- enterAndLeave(table.rows[1].cells[1], tableHtmlHoverE); // enter and leave E
- enterAndLeave(table.rows[2].cells[2], tableHtmlHoverI); // enter and leave I
- });
-});
diff --git a/src/notes/content/init.ts b/src/notes/content/init.ts
index b4dcc37e..2bf4ed5b 100644
--- a/src/notes/content/init.ts
+++ b/src/notes/content/init.ts
@@ -1,10 +1,13 @@
+import { dispatchNoteEdited } from "notes/events";
+
import { initImgs } from "./img";
import { initTables } from "./table";
-import renderTableContextMenu from "./render-table-context-menu";
+import renderTableContextMenu from "./table/render/render-table-context-menu";
export default () => {
initImgs();
initTables({
+ onResize: dispatchNoteEdited,
contextMenuRenderFunction: renderTableContextMenu,
});
};
diff --git a/src/notes/content/table.ts b/src/notes/content/table.ts
deleted file mode 100644
index 96c549fc..00000000
--- a/src/notes/content/table.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-/* eslint-disable no-param-reassign */
-import { dispatchNoteEdited } from "notes/events";
-
-const TABLE_RESIZING_DIV_CLASSNAME = "table-resizing-div";
-const TABLE_COLUMN_RESIZING_DIV_CLASSNAME = "table-column-resizing-div";
-const TABLE_ROW_RESIZING_DIV_CLASSNAME = "table-row-resizing-div";
-
-type ResizingType = "column" | "row";
-type ComputedStyleFunction = (elt: Element) => CSSStyleDeclaration;
-type OnResizeCallback = () => void;
-
-export const getAllCellsInColumn = (table: HTMLTableElement, referenceCellIndex: number): HTMLTableCellElement[] => {
- const cells: HTMLTableCellElement[] = [];
-
- for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
- for (let cellIndex = 0; cellIndex < table.rows[rowIndex].cells.length; cellIndex += 1) {
- if (cellIndex === referenceCellIndex) {
- cells.push(table.rows[rowIndex].cells[cellIndex]);
- }
- }
- }
-
- return cells;
-};
-
-const isResizingTable = (document: Document) => document.body.classList.contains("resizing-table");
-
-interface CreateResizingDivProps {
- document: Document
- computedStyleFunction: ComputedStyleFunction
- type: ResizingType
- table: HTMLTableElement
- cell: HTMLTableCellElement
- allCells: HTMLTableCellElement[]
- onResize: OnResizeCallback
-}
-
-const createResizingDiv = ({
- document,
- computedStyleFunction,
- type,
- table,
- cell,
- allCells,
- onResize,
-}: CreateResizingDivProps) => {
- const resizingDiv = document.createElement("div");
- const specificClassname = type === "column" ? TABLE_COLUMN_RESIZING_DIV_CLASSNAME : TABLE_ROW_RESIZING_DIV_CLASSNAME;
- resizingDiv.classList.add(TABLE_RESIZING_DIV_CLASSNAME, specificClassname);
-
- const isLast = allCells[allCells.length - 1] === cell;
- if (!isLast) {
- if (type === "column") {
- resizingDiv.style.bottom = `-${computedStyleFunction(cell).borderBottomWidth}`;
- }
-
- if (type === "row") {
- resizingDiv.style.right = `-${computedStyleFunction(cell).borderRightWidth}`;
- }
- }
-
- const property = type === "column" ? "width" : "height";
-
- const toggleActiveCells = (toggle: boolean) =>
- allCells.forEach((oneCell) =>
- oneCell.querySelector(`.${TABLE_RESIZING_DIV_CLASSNAME}.${specificClassname}`)?.classList.toggle("active", toggle));
-
- const resizeAllCells = (value: number) => allCells.forEach((oneCell) => {
- oneCell.style[property] = `${value}px`;
- oneCell.style.wordBreak = "break-all";
- });
-
- const resetSizeAllCells = () => allCells.forEach((oneCell) => {
- oneCell.style[property] = "";
- oneCell.style.wordBreak = "";
- });
-
- resizingDiv.addEventListener("mouseenter", () => {
- if (isResizingTable(document) || cell.querySelector(`.${TABLE_RESIZING_DIV_CLASSNAME}.active`)) {
- return;
- }
- toggleActiveCells(true);
- });
-
- const getAchor = (event: MouseEvent) => (type === "column" ? event.x : event.y);
-
- resizingDiv.addEventListener("mousedown", (mousedownEvent) => {
- document.body.classList.add("resizing-table", `resizing-table-${type}`);
- table.classList.add("locked");
-
- let anchor = getAchor(mousedownEvent);
-
- const mousemoveListener = (mousemoveEvent: MouseEvent): void => {
- const delta = (anchor - getAchor(mousemoveEvent)) * -1;
- anchor = getAchor(mousemoveEvent);
-
- const currentValue = parseInt(computedStyleFunction(cell)[property], 10);
- const newValue = currentValue + delta;
- resizeAllCells(newValue);
- };
-
- document.addEventListener("mousemove", mousemoveListener);
-
- const mouseupListener = () => {
- if (isResizingTable(document)) {
- toggleActiveCells(false);
- }
- document.body.classList.remove("resizing-table", "resizing-table-column", "resizing-table-row");
- table.classList.remove("locked");
- document.removeEventListener("mousemove", mousemoveListener);
- document.removeEventListener("mouseup", mouseupListener);
- onResize();
- };
-
- document.addEventListener("mouseup", mouseupListener);
- });
-
- resizingDiv.addEventListener("dblclick", () => {
- resetSizeAllCells();
- onResize();
- });
-
- resizingDiv.addEventListener("mouseleave", () => {
- if (isResizingTable(document)) {
- return;
- }
-
- toggleActiveCells(false);
- });
-
- return resizingDiv;
-};
-
-export interface MakeTableResizableProps {
- document: Document
- computedStyleFunction: ComputedStyleFunction
- table: HTMLTableElement
- onResize: OnResizeCallback
-}
-
-export const makeTableResizable = ({
- document,
- computedStyleFunction,
- table,
- onResize,
-}: MakeTableResizableProps): void => {
- for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
- for (let cellIndex = 0; cellIndex < table.rows[rowIndex].cells.length; cellIndex += 1) {
- const cell = table.rows[rowIndex].cells[cellIndex];
-
- const allCellsInColumn = getAllCellsInColumn(table, cellIndex);
- const allCellsInRow = [...table.rows[rowIndex].cells];
-
- cell.onmouseleave = () => {
- if (isResizingTable(document)) {
- return;
- }
- [
- ...allCellsInColumn,
- ...allCellsInRow,
- ].forEach((oneCell) => oneCell.querySelectorAll(`.${TABLE_RESIZING_DIV_CLASSNAME}`).forEach((div) => div.remove()));
- };
-
- cell.onmouseenter = () => {
- if (isResizingTable(document) || cell.querySelector(`.${TABLE_RESIZING_DIV_CLASSNAME}`)) {
- return;
- }
-
- allCellsInColumn.forEach((oneCell) => oneCell.appendChild(createResizingDiv({
- document,
- computedStyleFunction,
- type: "column",
- table,
- cell: oneCell,
- allCells: allCellsInColumn,
- onResize,
- })));
-
- allCellsInRow.forEach((oneCell) => oneCell.appendChild(createResizingDiv({
- document,
- computedStyleFunction,
- type: "row",
- table,
- cell: oneCell,
- allCells: allCellsInRow,
- onResize,
- })));
- };
- }
- }
-};
-
-interface InitTableProps {
- table: HTMLTableElement
- resizableProps: MakeTableResizableProps
- makeTableResizableFunction: (props: MakeTableResizableProps) => void
- onContextMenu: (event: MouseEvent) => void
-}
-
-export const initTable = ({
- table, resizableProps, makeTableResizableFunction, onContextMenu,
-}: InitTableProps): void => {
- const cells = resizableProps.table.querySelectorAll("td");
- cells.forEach((oneCell) => {
- oneCell.onmouseleave = null;
- oneCell.onmouseenter = null;
- oneCell.querySelectorAll(`.${TABLE_RESIZING_DIV_CLASSNAME}`).forEach((div) => div.remove());
- });
-
- makeTableResizableFunction(resizableProps);
- table.oncontextmenu = onContextMenu;
-};
-
-interface InitTables {
- contextMenuRenderFunction: (table: HTMLTableElement, event: MouseEvent) => void
-}
-
-export const initTables = ({
- contextMenuRenderFunction,
-}: InitTables): void => {
- const tables = document.querySelectorAll("body > #content-container > #content table");
- tables.forEach((table) => initTable({
- table,
- resizableProps: {
- document,
- computedStyleFunction: window.getComputedStyle,
- table,
- onResize: dispatchNoteEdited,
- },
- makeTableResizableFunction: makeTableResizable,
- onContextMenu: (event) => {
- if (!document.body.classList.contains("with-control")) {
- return;
- }
-
- event.preventDefault();
- contextMenuRenderFunction(table, event);
- },
- }));
-};
diff --git a/src/notes/content/table/__tests__/cells.test.ts b/src/notes/content/table/__tests__/cells.test.ts
new file mode 100644
index 00000000..c6905187
--- /dev/null
+++ b/src/notes/content/table/__tests__/cells.test.ts
@@ -0,0 +1,90 @@
+import { prepareDom, tableHtml } from "./fixtures";
+import {
+ getCellsInRow,
+ getCellsInColumn,
+
+ ResizeProperty,
+ resizeCells,
+ resetCellsSize,
+} from "../cells";
+
+const mapFn = (cell: HTMLTableCellElement) => cell.innerHTML;
+
+test("getCellsInRow() returns cells in a row", () => {
+ const { table } = prepareDom(tableHtml);
+
+ expect(getCellsInRow(table, 0).map(mapFn)).toEqual(["A", "B", "C"]);
+ expect(getCellsInRow(table, 1).map(mapFn)).toEqual(["D", "E", "F"]);
+ expect(getCellsInRow(table, 2).map(mapFn)).toEqual(["G", "H", "I"]);
+
+ expect(getCellsInRow(table, -1).map(mapFn)).toEqual([]);
+ expect(getCellsInRow(table, 99).map(mapFn)).toEqual([]);
+});
+
+test("getCellsInColumn() returns cells in a column", () => {
+ const { table } = prepareDom(tableHtml);
+
+ expect(getCellsInColumn(table, 0).map(mapFn)).toEqual(["A", "D", "G"]);
+ expect(getCellsInColumn(table, 1).map(mapFn)).toEqual(["B", "E", "H"]);
+ expect(getCellsInColumn(table, 2).map(mapFn)).toEqual(["C", "F", "I"]);
+
+ expect(getCellsInColumn(table, -1).map(mapFn)).toEqual([]);
+ expect(getCellsInColumn(table, 99).map(mapFn)).toEqual([]);
+});
+
+const expectCellsProperty = (cells: HTMLTableCellElement[], property: ResizeProperty, value: string) => (
+ cells.forEach((cell) => expect(cell.style.getPropertyValue(property)).toBe(value))
+);
+
+test("resizeCells() sets cells width or height", () => {
+ const { table } = prepareDom(tableHtml);
+
+ const columnCells = getCellsInColumn(table, 1);
+ resizeCells(columnCells, "width", 400);
+ expectCellsProperty(columnCells, "width", "400px");
+
+ const rowCells = getCellsInRow(table, 1);
+ resizeCells(rowCells, "height", 200);
+ expectCellsProperty(rowCells, "height", "200px");
+});
+
+describe("resetCellsSize()", () => {
+ let columnCells: HTMLTableCellElement[];
+ let rowCells: HTMLTableCellElement[];
+
+ beforeEach(() => {
+ const { table } = prepareDom(tableHtml);
+
+ columnCells = getCellsInColumn(table, 1);
+ resizeCells(columnCells, "width", 400);
+ expectCellsProperty(columnCells, "width", "400px");
+
+ rowCells = getCellsInRow(table, 1);
+ resizeCells(rowCells, "height", 200);
+ expectCellsProperty(rowCells, "height", "200px");
+ });
+
+ it("resets width only", () => {
+ resetCellsSize(columnCells, "width");
+ expectCellsProperty(columnCells, "width", "");
+
+ // height should remain unchanged
+ expectCellsProperty(rowCells, "height", "200px");
+ });
+
+ it("resets height only", () => {
+ resetCellsSize(rowCells, "height");
+ expectCellsProperty(rowCells, "height", "");
+
+ // width should remain unchanged
+ expectCellsProperty(columnCells, "width", "400px");
+ });
+
+ it("resets width and height", () => {
+ resetCellsSize(columnCells, "width");
+ resetCellsSize(rowCells, "height");
+
+ expectCellsProperty(columnCells, "width", "");
+ expectCellsProperty(rowCells, "height", "");
+ });
+});
diff --git a/src/notes/content/table/__tests__/fixtures.ts b/src/notes/content/table/__tests__/fixtures.ts
new file mode 100644
index 00000000..d150edd3
--- /dev/null
+++ b/src/notes/content/table/__tests__/fixtures.ts
@@ -0,0 +1,75 @@
+import { JSDOM } from "jsdom";
+
+// A B C
+// D E F
+// G H I
+export const tableHtml = `
+
+ A |
+ B |
+ C |
+
+
+ D |
+ E |
+ F |
+
+
+ G |
+ H |
+ I |
+
+
`;
+
+const tableRowResizingDiv = (
+ ''
+);
+
+const tableColumnResizingDiv = (
+ ''
+);
+
+const tableRowColumnResizingDivs = `${tableRowResizingDiv}${tableColumnResizingDiv}`;
+
+export const tableHtmlHoverE = `
+
+ A |
+ B |
+ C |
+
+
+ D |
+ E${tableRowColumnResizingDivs} |
+ F |
+
+
+ G |
+ H |
+ I |
+
+
`;
+
+export const tableHtmlHoverI = `
+
+ A |
+ B |
+ C |
+
+
+ D |
+ E |
+ F |
+
+
+ G |
+ H |
+ I${tableRowColumnResizingDivs} |
+
+
`;
+
+export const prepareDom = (tableHtmlToInsert: string): { dom: JSDOM, table: HTMLTableElement } => {
+ const dom = new JSDOM();
+ dom.window.document.body.insertAdjacentHTML("afterbegin", tableHtmlToInsert);
+ const table = dom.window.document.body.children[0] as HTMLTableElement;
+ return { dom, table };
+};
diff --git a/src/notes/content/table/__tests__/make-table-resizable.test.ts b/src/notes/content/table/__tests__/make-table-resizable.test.ts
new file mode 100644
index 00000000..b0802825
--- /dev/null
+++ b/src/notes/content/table/__tests__/make-table-resizable.test.ts
@@ -0,0 +1,60 @@
+import { JSDOM } from "jsdom";
+import makeTableResizable from "../make-table-resizable";
+import {
+ tableHtml,
+ tableHtmlHoverE,
+ tableHtmlHoverI,
+ prepareDom,
+} from "./fixtures";
+
+describe("makeTableResizable()", () => {
+ let dom: JSDOM;
+ let table: HTMLTableElement;
+ let onResize: jest.Mock;
+
+ const prepareTable = (html: string) => {
+ const prepared = prepareDom(html);
+ dom = prepared.dom;
+ table = prepared.table;
+ onResize = jest.fn();
+
+ makeTableResizable({
+ document: dom.window.document,
+ table,
+ onResize: () => {},
+ });
+ };
+
+ it("keeps table markup unchanged", () => {
+ prepareTable(tableHtml);
+ expect(table.outerHTML).toBe(tableHtml);
+ });
+
+ it("does NOT call onResize on init", () => {
+ prepareTable(tableHtml);
+ expect(onResize).not.toHaveBeenCalled();
+ });
+
+ const enterAndLeave = (
+ cell: HTMLTableCellElement,
+ expectedTableHtmlOnEnter: string,
+ expectedTableHtmlOnLeave: string,
+ ) => {
+ cell.dispatchEvent(new dom.window.Event("mouseenter"));
+ expect(table.outerHTML).toBe(expectedTableHtmlOnEnter);
+
+ cell.dispatchEvent(new dom.window.Event("mouseleave"));
+ expect(table.outerHTML).toBe(expectedTableHtmlOnLeave);
+ };
+
+ it("adds resizing divs on mouseenter, removes them on mouseleave", () => {
+ prepareTable(tableHtml);
+ enterAndLeave(table.rows[1].cells[1], tableHtmlHoverE, tableHtml);
+ enterAndLeave(table.rows[2].cells[2], tableHtmlHoverI, tableHtml);
+ });
+
+ it("removes any leftover resizing divs", () => {
+ prepareTable(tableHtmlHoverE);
+ enterAndLeave(table.rows[2].cells[2], tableHtmlHoverI, tableHtml);
+ });
+});
diff --git a/src/notes/content/table/cells.ts b/src/notes/content/table/cells.ts
new file mode 100644
index 00000000..205bbcec
--- /dev/null
+++ b/src/notes/content/table/cells.ts
@@ -0,0 +1,36 @@
+/* eslint-disable no-param-reassign */
+
+export const getCellsInRow = (table: HTMLTableElement, row: number): HTMLTableCellElement[] => {
+ const theRow = table.rows[row] as HTMLTableRowElement | undefined;
+ return theRow ? [...theRow.cells] : [];
+};
+
+export const getCellsInColumn = (table: HTMLTableElement, column: number): HTMLTableCellElement[] => {
+ const cells: HTMLTableCellElement[] = [];
+
+ for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
+ for (let cellIndex = 0; cellIndex < table.rows[rowIndex].cells.length; cellIndex += 1) {
+ if (cellIndex === column) {
+ cells.push(table.rows[rowIndex].cells[cellIndex]);
+ }
+ }
+ }
+
+ return cells;
+};
+
+export type ResizeProperty = "width" | "height";
+
+const setCellsProperty = (cells: HTMLTableCellElement[], property: ResizeProperty, value: string) => {
+ cells.forEach((cell) => {
+ cell.style[property] = value;
+ });
+};
+
+export const resizeCells = (cells: HTMLTableCellElement[], property: ResizeProperty, value: number) => {
+ setCellsProperty(cells, property, `${value}px`);
+};
+
+export const resetCellsSize = (cells: HTMLTableCellElement[], property: ResizeProperty) => {
+ setCellsProperty(cells, property, "");
+};
diff --git a/src/notes/content/table/index.ts b/src/notes/content/table/index.ts
new file mode 100644
index 00000000..beb4c00c
--- /dev/null
+++ b/src/notes/content/table/index.ts
@@ -0,0 +1,24 @@
+import makeTableHavingContextMenu, { ContextMenuRenderFunction } from "./make-table-having-context-menu";
+import makeTableResizable, { OnResizeCallback } from "./make-table-resizable";
+
+interface InitTables {
+ onResize: OnResizeCallback
+ contextMenuRenderFunction: ContextMenuRenderFunction
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export const initTables = ({
+ onResize,
+ contextMenuRenderFunction,
+}: InitTables): void => {
+ const tables = document.querySelectorAll("body > #content-container > #content table");
+ tables.forEach((table) => {
+ makeTableHavingContextMenu(table, contextMenuRenderFunction);
+
+ makeTableResizable({
+ document,
+ table,
+ onResize,
+ });
+ });
+};
diff --git a/src/notes/content/table/make-table-having-context-menu.ts b/src/notes/content/table/make-table-having-context-menu.ts
new file mode 100644
index 00000000..3e5a1f36
--- /dev/null
+++ b/src/notes/content/table/make-table-having-context-menu.ts
@@ -0,0 +1,11 @@
+export type ContextMenuRenderFunction = (table: HTMLTableElement, event: MouseEvent) => void;
+
+export default (table: HTMLTableElement, renderContextMenu: ContextMenuRenderFunction) => {
+ // eslint-disable-next-line no-param-reassign
+ table.oncontextmenu = (event) => {
+ if (document.body.classList.contains("with-control")) {
+ event.preventDefault();
+ renderContextMenu(table, event);
+ }
+ };
+};
diff --git a/src/notes/content/table/make-table-resizable.ts b/src/notes/content/table/make-table-resizable.ts
new file mode 100644
index 00000000..5548801b
--- /dev/null
+++ b/src/notes/content/table/make-table-resizable.ts
@@ -0,0 +1,127 @@
+import * as cells from "./cells";
+
+const TABLE_RESIZING_DIV_CLASSNAME = "table-resizing-div";
+const TABLE_COLUMN_RESIZING_DIV_CLASSNAME = "table-column-resizing-div";
+const TABLE_ROW_RESIZING_DIV_CLASSNAME = "table-row-resizing-div";
+
+type ResizeType = "column" | "row";
+export type OnResizeCallback = () => void;
+
+interface MakeTableResizableProps {
+ document: Document
+ table: HTMLTableElement
+ onResize: OnResizeCallback
+}
+
+interface CreateResizingDivProps extends MakeTableResizableProps {
+ type: ResizeType
+ cell: HTMLTableCellElement
+ allCells: HTMLTableCellElement[]
+}
+
+const isResizingTable = (document: Document) => document.body.classList.contains("resizing-table");
+
+const createResizingDiv = ({
+ document,
+ table,
+ onResize,
+
+ type,
+ cell,
+ allCells,
+}: CreateResizingDivProps) => {
+ const resizingDiv = document.createElement("div");
+ const specificClassname = type === "column" ? TABLE_COLUMN_RESIZING_DIV_CLASSNAME : TABLE_ROW_RESIZING_DIV_CLASSNAME;
+ resizingDiv.classList.add(TABLE_RESIZING_DIV_CLASSNAME, specificClassname);
+
+ const property: cells.ResizeProperty = type === "column" ? "width" : "height";
+
+ const getAnchor = (event: MouseEvent) => (type === "column" ? event.x : event.y);
+
+ resizingDiv.onmousedown = (mousedownEvent: MouseEvent) => {
+ resizingDiv.classList.add("active");
+ document.body.classList.add("resizing-table", `resizing-table-${type}`);
+ table.classList.add("locked");
+
+ let anchor = getAnchor(mousedownEvent);
+
+ const mousemoveListener = (mousemoveEvent: MouseEvent): void => {
+ const delta = (anchor - getAnchor(mousemoveEvent)) * -1;
+ anchor = getAnchor(mousemoveEvent);
+
+ const currentValue = parseInt(window.getComputedStyle(cell)[property], 10);
+ const newValue = currentValue + delta;
+ cells.resizeCells(allCells, property, newValue);
+ };
+
+ document.addEventListener("mousemove", mousemoveListener);
+
+ const mouseupListener = () => {
+ document.body.classList.remove(
+ "resizing-table",
+ "resizing-table-column",
+ "resizing-table-row",
+ );
+ resizingDiv.classList.remove("active");
+ table.classList.remove("locked");
+ document.removeEventListener("mousemove", mousemoveListener);
+ document.removeEventListener("mouseup", mouseupListener);
+ onResize();
+ };
+
+ document.addEventListener("mouseup", mouseupListener);
+ };
+
+ resizingDiv.ondblclick = () => {
+ cells.resetCellsSize(allCells, property);
+ onResize();
+ };
+
+ return resizingDiv;
+};
+
+const removeResizingDivs = (table: HTMLTableElement) => (
+ table.querySelectorAll(`.${TABLE_RESIZING_DIV_CLASSNAME}`).forEach((div) => div.remove())
+);
+
+export default (props: MakeTableResizableProps): void => {
+ const { document, table } = props;
+ for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
+ for (let cellIndex = 0; cellIndex < table.rows[rowIndex].cells.length; cellIndex += 1) {
+ const cell = table.rows[rowIndex].cells[cellIndex];
+
+ cell.onmouseleave = () => {
+ if (isResizingTable(document)) {
+ return;
+ }
+
+ removeResizingDivs(table);
+ };
+
+ const allCellsInRow = cells.getCellsInRow(table, rowIndex);
+ const allCellsInColumn = cells.getCellsInColumn(table, cellIndex);
+
+ cell.onmouseenter = () => {
+ if (isResizingTable(document)) {
+ return;
+ }
+
+ removeResizingDivs(table);
+
+ cell.appendChild(createResizingDiv({
+ ...props,
+ type: "row",
+ cell,
+ allCells: allCellsInRow,
+ }));
+
+ cell.appendChild(createResizingDiv({
+ ...props,
+ type: "column",
+ cell,
+ allCells: allCellsInColumn,
+ }));
+ };
+ }
+ }
+};
diff --git a/src/notes/content/render-table-context-menu.tsx b/src/notes/content/table/render/render-table-context-menu.tsx
similarity index 100%
rename from src/notes/content/render-table-context-menu.tsx
rename to src/notes/content/table/render/render-table-context-menu.tsx