diff --git a/webview/src/editor.js b/webview/src/editor.js index 3557949..9388ab4 100644 --- a/webview/src/editor.js +++ b/webview/src/editor.js @@ -379,7 +379,7 @@ export function createEditor({ parent, text, onApplyChanges }) { case 'hr': return insertHr(view, selection); case 'table': - return insertTable(view, selection); + return insertTable(view, selection, level?.cols, level?.rows); } const newMarkerLen = insert.length; diff --git a/webview/src/helpers/tables.js b/webview/src/helpers/tables.js index b00fb9a..4088dac 100644 --- a/webview/src/helpers/tables.js +++ b/webview/src/helpers/tables.js @@ -1018,12 +1018,19 @@ export const sourceTableHeaderLineField = StateField.define({ provide: (field) => EditorView.decorations.from(field) }); -export function insertTable(view, selection) { +export function insertTable(view, selection, cols = 3, rows = 2) { const line = view.state.doc.lineAt(selection.from); const lineText = view.state.doc.sliceString(line.from, line.to); const leadingWhitespace = /^(\s*)/.exec(lineText)?.[1] ?? ''; - const table = `${leadingWhitespace}| Header 1 | Header 2 | Header 3 |\n${leadingWhitespace}| --- | --- | --- |\n${leadingWhitespace}| Cell 1 | Cell 2 | Cell 3 |`; + const headerCells = Array.from({ length: cols }, () => ' ').join('|'); + const separatorCells = Array.from({ length: cols }, () => ' --- ').join('|'); + const bodyRows = Array.from({ length: rows }, () => { + const cells = Array.from({ length: cols }, () => ' ').join('|'); + return `${leadingWhitespace}|${cells}|`; + }).join('\n'); + + const table = `${leadingWhitespace}|${headerCells}|\n${leadingWhitespace}|${separatorCells}|\n${bodyRows}`; view.dispatch({ changes: { from: line.from, to: line.to, insert: table }, diff --git a/webview/src/index.js b/webview/src/index.js index f41e954..b360888 100644 --- a/webview/src/index.js +++ b/webview/src/index.js @@ -199,7 +199,75 @@ tableBtn.dataset.action = 'table'; tableBtn.title = 'Table'; tableBtn.appendChild(createElement(Table, { width: 18, height: 18 })); -formatGroup.append(headingWrapper, bulletListBtn, numberedListBtn, taskBtn, separator, codeBlockBtn, inlineCodeBtn, quoteBtn, hrBtn, tableBtn); +const tableDropdown = document.createElement('div'); +tableDropdown.className = 'table-dropdown'; + +const tableDropdownWrapper = document.createElement('div'); +tableDropdownWrapper.className = 'table-dropdown-wrapper'; + +const tableGrid = document.createElement('div'); +tableGrid.className = 'table-grid'; + +const gridSize = 5; +for (let row = 0; row < gridSize; row++) { + for (let col = 0; col < gridSize; col++) { + const cell = document.createElement('div'); + cell.className = 'table-grid-cell'; + cell.dataset.row = row + 1; + cell.dataset.col = col + 1; + if (row === 0 && col === 0) { + cell.classList.add('is-highlighted'); + } + tableGrid.appendChild(cell); + } +} + +const tableSizeLabel = document.createElement('div'); +tableSizeLabel.className = 'table-size-label'; +tableSizeLabel.textContent = '1 x 1'; + +tableDropdown.append(tableGrid, tableSizeLabel); +tableDropdownWrapper.appendChild(tableDropdown); + +const tableWrapper = document.createElement('div'); +tableWrapper.className = 'table-wrapper'; +tableWrapper.append(tableBtn, tableDropdownWrapper); + +let selectedTableCols = 1; +let selectedTableRows = 1; + +const updateTableGridHighlight = (hoveredCol, hoveredRow) => { + const cells = tableGrid.querySelectorAll('.table-grid-cell'); + cells.forEach((cell) => { + const cellCol = parseInt(cell.dataset.col, 10); + const cellRow = parseInt(cell.dataset.row, 10); + cell.classList.toggle('is-highlighted', cellCol <= hoveredCol && cellRow <= hoveredRow); + }); + tableSizeLabel.textContent = `${hoveredCol} x ${hoveredRow}`; + selectedTableCols = hoveredCol; + selectedTableRows = hoveredRow; +}; + +tableGrid.addEventListener('mouseover', (event) => { + const cell = event.target.closest('.table-grid-cell'); + if (!cell) return; + const col = parseInt(cell.dataset.col, 10); + const row = parseInt(cell.dataset.row, 10); + updateTableGridHighlight(col, row); +}); + +tableGrid.addEventListener('mouseleave', () => { + updateTableGridHighlight(1, 1); +}); + +tableGrid.addEventListener('click', (event) => { + const cell = event.target.closest('.table-grid-cell'); + if (!cell || !editor) return; + editor.insertFormat('table', { cols: selectedTableCols, rows: selectedTableRows }); + editor.focus(); +}); + +formatGroup.append(headingWrapper, bulletListBtn, numberedListBtn, taskBtn, separator, codeBlockBtn, inlineCodeBtn, quoteBtn, hrBtn, tableWrapper); const rightGroup = document.createElement('div'); rightGroup.className = 'right-group'; @@ -591,7 +659,6 @@ codeBlockBtn.addEventListener('click', () => handleFormatAction('codeBlock')); inlineCodeBtn.addEventListener('click', () => handleFormatAction('inlineCode')); quoteBtn.addEventListener('click', () => handleFormatAction('quote')); hrBtn.addEventListener('click', () => handleFormatAction('hr')); -tableBtn.addEventListener('click', () => handleFormatAction('table')); autoSaveBtn.addEventListener('click', toggleAutoSave); outlineBtn.addEventListener('click', toggleOutline); diff --git a/webview/src/styles.css b/webview/src/styles.css index ba8b4cf..b2862ad 100644 --- a/webview/src/styles.css +++ b/webview/src/styles.css @@ -101,6 +101,63 @@ body { pointer-events: auto; } +.table-wrapper { + position: relative; +} + +.table-dropdown-wrapper { + position: absolute; + top: 100%; + left: calc(50% + 1px); + transform: translateX(-50%); + padding: 5px 25px; + z-index: 100; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; +} + +.table-dropdown { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 8px; + background-color: var(--vscode-sideBar-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; +} + +.table-wrapper:hover .table-dropdown-wrapper { + opacity: 1; + pointer-events: auto; +} + +.table-grid { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 2px; +} + +.table-grid-cell { + width: 16px; + height: 16px; + border: 1px solid var(--vscode-panel-border); + border-radius: 2px; + background-color: transparent; + cursor: pointer; +} + +.table-grid-cell.is-highlighted { + background-color: var(--vscode-toolbar-hoverBackground); +} + +.table-size-label { + font-size: 11px; + color: var(--vscode-editor-foreground); + text-align: center; +} + .format-button, .heading-dropdown-option, .meo-mermaid-zoom-btn {