The cell-editor was already complete (drafts, row-blur saves, etag concurrency, validation). This commit adds the missing row-level ops: - "+ Add row" appends a draft row inline; first cell focused. Row-blur POSTs to <dir>/form.html (the existing form-create endpoint); 201 swaps the synthetic id for the server-returned URL/ETag. Empty rows the user walks away from are silently discarded. - Right-click a row → "Delete row" (or "Delete N rows" when a cell range spans multiple rows). DELETE the row YAML with If-Match; 412 surfaces a conflict warning. - Multi-row clipboard paste creates new rows for grid content that extends past the last existing row, instead of dropping cells past the end. Each new row saves via its own row-blur. - Empty rows now have a 2.4em minimum height so a freshly-added row is visible. Without the floor it collapses to cell-padding (~8px) and looks like a divider line. Server-side: no new endpoints. Form-create (POST <dir>/form.html → 201 + Location) and file-API DELETE carry the new client capabilities. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
109 lines
4.1 KiB
JavaScript
109 lines
4.1 KiB
JavaScript
// add-row.js — inline new-row creation.
|
|
//
|
|
// Click "+ Add row" → append a draft row at the end of state.rows,
|
|
// focus its first editable cell, accumulate user typing into the
|
|
// drafts buffer like any other row. On row-blur, save.js detects the
|
|
// row.isNew flag and POSTs to <dir>/form.html (the form-create
|
|
// endpoint). The 201 response carries the new row's Location; we swap
|
|
// the synthetic url/yamlUrl for the real ones and the draft row
|
|
// becomes a normal saved row.
|
|
//
|
|
// Synthetic identity: each new row gets a temporary "__new-<N>" url
|
|
// so rowKey() returns something unique for selection + draft tracking.
|
|
// The temporary url is replaced after a successful POST. There is no
|
|
// "save on click" UX — the existing row-blur trigger is the save path,
|
|
// same as for edits.
|
|
(function (app) {
|
|
'use strict';
|
|
|
|
let _counter = 0;
|
|
|
|
function makeSyntheticKey() {
|
|
_counter += 1;
|
|
return '__new-' + _counter;
|
|
}
|
|
|
|
// Compute the form-create URL for the current page. Both
|
|
// /<dir>/table.html and /<dir>/ (default_tool: tables) shape work;
|
|
// /<dir>/form.html is the form handler's "create" endpoint either
|
|
// way (the form handler keys off the in-dir convention, not the
|
|
// visiting URL shape).
|
|
function formCreateUrl() {
|
|
let dir = (location.pathname || '/').replace(/\/table\.html$/, '/');
|
|
if (!dir.endsWith('/')) dir += '/';
|
|
return dir + 'form.html';
|
|
}
|
|
|
|
// Create-and-paint: the user-facing path.
|
|
function invoke() {
|
|
const key = createSilent();
|
|
if (typeof app.repaint === 'function') app.repaint();
|
|
focusNewRow(key);
|
|
}
|
|
|
|
// Push a draft row WITHOUT painting or focusing. Used by multi-row
|
|
// paste (clipboard.js) to create N rows in a single batch, with one
|
|
// paint at the end. Returns the synthetic url so callers can address
|
|
// the new row in their draft writes.
|
|
function createSilent() {
|
|
const key = makeSyntheticKey();
|
|
const draftRow = {
|
|
url: key,
|
|
yamlUrl: null,
|
|
data: {},
|
|
etag: null,
|
|
editable: true,
|
|
isNew: true,
|
|
};
|
|
if (!Array.isArray(app.state.rows)) {
|
|
app.state.rows = [];
|
|
}
|
|
app.state.rows.push(draftRow);
|
|
return key;
|
|
}
|
|
|
|
function focusNewRow(key) {
|
|
// After repaint, find the tr with our synthetic data-row-id and
|
|
// tell the editor to select its first cell. Filtering may have
|
|
// hidden the new row if a default filter excludes it; we accept
|
|
// that — clearing filters surfaces it.
|
|
const tbody = document.querySelector('#table-root tbody');
|
|
if (!tbody) return;
|
|
const trs = tbody.querySelectorAll('tr');
|
|
for (let i = 0; i < trs.length; i++) {
|
|
if (trs[i].getAttribute('data-row-id') === key) {
|
|
const editor = app.modules.editor;
|
|
if (editor && typeof editor.setSelected === 'function') {
|
|
// Scroll into view so the user sees the new row.
|
|
trs[i].scrollIntoView({ block: 'nearest', behavior: 'auto' });
|
|
editor.setSelected(i, 0);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cancel-new-row helper: drop the synthetic row entirely. Used when
|
|
// the user adds a row, makes no edits, and clicks Add again or
|
|
// navigates away — there's nothing to save and an empty draft just
|
|
// clutters the table. The save module calls this from row-blur when
|
|
// it sees a new row with no drafts.
|
|
function discardEmpty(rowId) {
|
|
const rows = app.state.rows || [];
|
|
for (let i = 0; i < rows.length; i++) {
|
|
if (rows[i].isNew && rows[i].url === rowId) {
|
|
rows.splice(i, 1);
|
|
if (typeof app.repaint === 'function') app.repaint();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
app.modules.addRow = {
|
|
invoke: invoke,
|
|
createSilent: createSilent,
|
|
formCreateUrl: formCreateUrl,
|
|
discardEmpty: discardEmpty,
|
|
};
|
|
})(window.tablesApp);
|