ep_data_tables 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +77 -0
- package/collectContentPre.js +87 -0
- package/ep.json +28 -0
- package/locales/en.json +45 -0
- package/package.json +47 -0
- package/static/css/caret.css +13 -0
- package/static/css/datatables-editor.css +167 -0
- package/static/css/table-menu.css +100 -0
- package/static/js/client_hooks.js +4441 -0
- package/static/js/collector_hooks.js +13 -0
- package/static/js/datatables-renderer.js +157 -0
- package/static/js/index.js +45 -0
- package/static/js/initialisation.js +249 -0
- package/templates/datatablesEditbarButtons.ejs +50 -0
- package/templates/datatablesScripts.ejs +16 -0
- package/templates/datatablesScriptsTimeslider.ejs +1 -0
- package/templates/styles.ejs +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const log = (...m) => console.debug('[ep_data_tables:collector_hooks]', ...m);
|
|
2
|
+
|
|
3
|
+
exports.collectContentPre = (hook, ctx) => {
|
|
4
|
+
return;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
exports.collectContentLineBreak = (hook, ctx) => {
|
|
8
|
+
return true;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
exports.collectContentLineText = (_hook, ctx) => {
|
|
12
|
+
return ctx.text || '';
|
|
13
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* ep_data_tables – datatables-renderer.js
|
|
2
|
+
*
|
|
3
|
+
* Only used by:
|
|
4
|
+
* • export pipeline (`context === "export"`)
|
|
5
|
+
* • timeslider frame (`context === "timeslider"`)
|
|
6
|
+
* Regular pad rendering is done by client_hooks.js.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const log = (...m) => console.debug('[ep_data_tables:datatables-renderer]', ...m);
|
|
10
|
+
const DELIMITER = '|'; // Used for splitting cell content if ever needed by legacy, export now receives pre-split, pre-rendered cells
|
|
11
|
+
|
|
12
|
+
// buildExportHtml now expects an array of pre-rendered HTML strings for each cell.
|
|
13
|
+
const buildExportHtml = (metadata, richHtmlCellArray) => {
|
|
14
|
+
const funcName = 'buildExportHtml';
|
|
15
|
+
log(`${funcName}: START`, { metadata, numberOfCells: richHtmlCellArray.length });
|
|
16
|
+
|
|
17
|
+
if (!metadata || typeof metadata.tblId === 'undefined' || typeof metadata.row === 'undefined' || typeof metadata.cols !== 'number') {
|
|
18
|
+
log(`${funcName}: ERROR - Invalid or missing metadata. Metadata:`, metadata);
|
|
19
|
+
return `<div>Error: Missing table metadata for export.</div>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (metadata.cols !== richHtmlCellArray.length) {
|
|
23
|
+
log(`${funcName}: WARN - Column count in metadata (${metadata.cols}) does not match provided HTML cell array length (${richHtmlCellArray.length}).`);
|
|
24
|
+
// Adjusting to render only the minimum of available cells or metadata.cols to prevent errors.
|
|
25
|
+
// Or, one might choose to pad with empty cells if segments are fewer.
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get column widths from metadata, or use equal distribution if not set
|
|
29
|
+
const numCols = richHtmlCellArray.length;
|
|
30
|
+
const columnWidths = metadata.columnWidths || Array(numCols).fill(100 / numCols);
|
|
31
|
+
|
|
32
|
+
// Ensure we have the right number of column widths
|
|
33
|
+
while (columnWidths.length < numCols) {
|
|
34
|
+
columnWidths.push(100 / numCols);
|
|
35
|
+
}
|
|
36
|
+
if (columnWidths.length > numCols) {
|
|
37
|
+
columnWidths.splice(numCols);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const tdStyle = `padding: 5px 7px; word-wrap:break-word; vertical-align: top; border: 1px solid #000;`;
|
|
41
|
+
const firstRowClass = metadata.row === 0 ? ' dataTable-first-row' : '';
|
|
42
|
+
|
|
43
|
+
// Each item in richHtmlCellArray is now the complete inner HTML for a cell.
|
|
44
|
+
// Apply column widths to each cell
|
|
45
|
+
const cellsHtml = richHtmlCellArray.map((cellHtml, index) => {
|
|
46
|
+
const widthPercent = columnWidths[index] || (100 / numCols);
|
|
47
|
+
const cellStyle = `${tdStyle} width: ${widthPercent}%;`;
|
|
48
|
+
return `<td style="${cellStyle}">${cellHtml || ' '}</td>`;
|
|
49
|
+
}).join('');
|
|
50
|
+
|
|
51
|
+
const tableHtml =
|
|
52
|
+
`<table class="dataTable${firstRowClass}" data-tblId="${metadata.tblId}" data-row="${metadata.row}" style="width:100%; border-collapse:collapse; table-layout: fixed;">` +
|
|
53
|
+
`<tbody><tr>${cellsHtml}</tr></tbody>` +
|
|
54
|
+
`</table>`;
|
|
55
|
+
|
|
56
|
+
log(`${funcName}: END - Generated HTML for export: ${tableHtml.substring(0,150)}...`);
|
|
57
|
+
return tableHtml;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (typeof DatatablesRenderer === 'undefined') {
|
|
61
|
+
log('Defining DatatablesRenderer');
|
|
62
|
+
var DatatablesRenderer = (() => {
|
|
63
|
+
const buildHtmlLegacy = (rowJSON, props = {}) => {
|
|
64
|
+
log('buildHtmlLegacy: START', { rowJSON, props });
|
|
65
|
+
const p = { borderWidth: 1, width: 100, ...rowJSON.tblProperties, ...props };
|
|
66
|
+
const border = `border:${p.borderWidth}px solid ${p.borderColor || '#ccc'}`;
|
|
67
|
+
const tdStyle = `${border};padding:4px;word-wrap:break-word`;
|
|
68
|
+
const tr = (rowJSON.payload && rowJSON.payload[0] && Array.isArray(rowJSON.payload[0]))
|
|
69
|
+
? rowJSON.payload[0].map(txt => `<td style="${tdStyle}">${txt}</td>`).join('')
|
|
70
|
+
: '<td>Error: Invalid payload structure for buildHtmlLegacy</td>';
|
|
71
|
+
const html = `<table class="dataTable" style="border-collapse:collapse;width:${p.width}%"><tbody><tr>${tr}</tr></tbody></table>`;
|
|
72
|
+
log('buildHtmlLegacy: Generated HTML:', html);
|
|
73
|
+
return html;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
render(ctx, el, metadataJsonString) {
|
|
78
|
+
log('render: START', { ctx, elContext: el, metadataJson: metadataJsonString });
|
|
79
|
+
|
|
80
|
+
if (ctx === 'export') {
|
|
81
|
+
log('render (export context): Processing for export.');
|
|
82
|
+
// el is now expected to be an object like { cellsRichHtml: ["<td>Cell1HTML</td>", ...] }
|
|
83
|
+
if (!el || !el.cellsRichHtml || !Array.isArray(el.cellsRichHtml)) {
|
|
84
|
+
log('render (export context): ERROR - el.cellsRichHtml is missing or not an array.', el);
|
|
85
|
+
return '<div>Error: Missing pre-rendered cell HTML for table export.</div>';
|
|
86
|
+
}
|
|
87
|
+
if (!metadataJsonString) {
|
|
88
|
+
log('render (export context): ERROR - metadataJsonString is undefined.');
|
|
89
|
+
return `<div>Error: Missing table metadata for export.</div>`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let rowMetadata;
|
|
93
|
+
try {
|
|
94
|
+
rowMetadata = JSON.parse(metadataJsonString);
|
|
95
|
+
log('render (export context): Parsed rowMetadata:', rowMetadata);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
log('render (export context): ERROR - Failed to parse metadata JSON:', metadataJsonString, e);
|
|
98
|
+
return `<div>Error: Invalid table metadata.</div>`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const exportHtml = buildExportHtml(rowMetadata, el.cellsRichHtml);
|
|
102
|
+
log('render (export context): Returning HTML string for export.');
|
|
103
|
+
return exportHtml;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Fallback to existing logic for other contexts (e.g., timeslider)
|
|
107
|
+
log(`render (context: ${ctx}): Using legacy/timeslider rendering path.`);
|
|
108
|
+
if (!ctx || (ctx !== 'timeslider')) {
|
|
109
|
+
log('render: Skipping - Invalid context for legacy path:', ctx);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
log('render: Context is valid for legacy path:', ctx);
|
|
113
|
+
|
|
114
|
+
let row;
|
|
115
|
+
if (ctx.parsedJson) {
|
|
116
|
+
log('render: Using provided ctx.parsedJson for legacy path');
|
|
117
|
+
row = ctx.parsedJson;
|
|
118
|
+
} else {
|
|
119
|
+
log('render: Parsing JSON from element text/html for legacy path');
|
|
120
|
+
const txt = ctx === 'timeslider'
|
|
121
|
+
? (typeof $ !== 'undefined' ? $('<div>').html(el.innerHTML).text() : el.innerHTML)
|
|
122
|
+
: el.innerHTML;
|
|
123
|
+
log('render: Text to parse for legacy path:', txt);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
row = JSON.parse(txt);
|
|
127
|
+
log('render: Parsed JSON successfully for legacy path:', row);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
log('render: ERROR - Failed to parse JSON from text for legacy path:', e);
|
|
130
|
+
console.error('[ep_data_tables:datatables-renderer] Failed to parse JSON (legacy path):', txt, e);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const extraPropsLegacy = metadataJsonString && typeof metadataJsonString === 'string' ? JSON.parse(metadataJsonString) : (metadataJsonString || {});
|
|
136
|
+
log('render: Parsed extraProps for legacy path:', extraPropsLegacy);
|
|
137
|
+
const html = buildHtmlLegacy(row, extraPropsLegacy);
|
|
138
|
+
|
|
139
|
+
if (el.innerHTML !== html) {
|
|
140
|
+
log('render: Updating element innerHTML for legacy path (timeslider?)');
|
|
141
|
+
el.innerHTML = html;
|
|
142
|
+
} else {
|
|
143
|
+
log('render: Skipping innerHTML update, content matches for legacy path (timeslider?)');
|
|
144
|
+
}
|
|
145
|
+
log('render: END (legacy path)');
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
})();
|
|
149
|
+
} else {
|
|
150
|
+
log('DatatablesRenderer already defined.');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (typeof exports !== 'undefined') {
|
|
154
|
+
log('Exporting DatatablesRenderer for Node.js');
|
|
155
|
+
exports.DatatablesRenderer = DatatablesRenderer;
|
|
156
|
+
}
|
|
157
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// index.js – server-side EEJS injections for ep_data_tables
|
|
2
|
+
const eejs = require('ep_etherpad-lite/node/eejs/');
|
|
3
|
+
|
|
4
|
+
const log = (...m) => console.debug('[ep_data_tables:index]', ...m);
|
|
5
|
+
log('Loading EEJS templates...');
|
|
6
|
+
|
|
7
|
+
const scriptsTag = eejs.require('ep_data_tables/templates/datatablesScripts.ejs');
|
|
8
|
+
const editbarButtonsTag = eejs.require('ep_data_tables/templates/datatablesEditbarButtons.ejs');
|
|
9
|
+
const styleTag = eejs.require('ep_data_tables/templates/styles.ejs'); // Optional base styles
|
|
10
|
+
// const timesliderTag = eejs.require('ep_data_tables/templates/datatablesScriptsTimeslider.ejs'); // optional
|
|
11
|
+
log('EEJS templates loaded.');
|
|
12
|
+
|
|
13
|
+
exports.eejsBlock_scripts = (_hook, args) => {
|
|
14
|
+
log('eejsBlock_scripts: START');
|
|
15
|
+
args.content += scriptsTag;
|
|
16
|
+
log('eejsBlock_scripts: Appended scriptsTag.');
|
|
17
|
+
log('eejsBlock_scripts: END');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
exports.eejsBlock_editbarMenuLeft = (_hook, args) => {
|
|
21
|
+
log('eejsBlock_editbarMenuLeft: START');
|
|
22
|
+
args.content += editbarButtonsTag;
|
|
23
|
+
log('eejsBlock_editbarMenuLeft: Appended editbarButtonsTag.');
|
|
24
|
+
log('eejsBlock_editbarMenuLeft: END');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Reverted: Only prepend styles from template, do not link external CSS file
|
|
28
|
+
exports.eejsBlock_styles = (_hook, args) => {
|
|
29
|
+
log('eejsBlock_styles: START');
|
|
30
|
+
// If you rely solely on the inline CSS from client_hooks.js, delete this hook.
|
|
31
|
+
args.content = styleTag + args.content;
|
|
32
|
+
log('eejsBlock_styles: Prepended styleTag (from template).');
|
|
33
|
+
log('eejsBlock_styles: END');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/* Uncomment only if you truly need extra JS in the timeslider */
|
|
37
|
+
|
|
38
|
+
exports.eejsBlock_timesliderScripts = (_hook, args) => {
|
|
39
|
+
log('eejsBlock_timesliderScripts: START');
|
|
40
|
+
args.content +='<script src="/static/plugins/ep_data_tables/static/js/datatables-renderer.js"></script>';
|
|
41
|
+
log('eejsBlock_timesliderScripts: Appended timesliderTag.');
|
|
42
|
+
log('eejsBlock_timesliderScripts: END');
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
log('EEJS hooks defined.');
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* global $ */
|
|
2
|
+
|
|
3
|
+
const log = (...m) => console.debug('[ep_data_tables:initialisation]', ...m);
|
|
4
|
+
|
|
5
|
+
// NEW: Helper function to get line number (adapted from ep_image_insert)
|
|
6
|
+
// Needs to be defined before being used in the mousedown handler
|
|
7
|
+
function _getLineNumberOfElement(element) {
|
|
8
|
+
// Implementation similar to ep_image_insert
|
|
9
|
+
let currentElement = element;
|
|
10
|
+
let count = 0;
|
|
11
|
+
while (currentElement = currentElement.previousElementSibling) {
|
|
12
|
+
count++;
|
|
13
|
+
}
|
|
14
|
+
return count;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let $tblContextMenu;
|
|
18
|
+
|
|
19
|
+
exports.postAceInit = (hook, ctx) => {
|
|
20
|
+
// log('postAceInit: START', { hook, ctx });
|
|
21
|
+
// ───────────────────── helpers ─────────────────────
|
|
22
|
+
const $menu = $('#table-context-menu');
|
|
23
|
+
const $gridWrap = $('#create-table-container');
|
|
24
|
+
const $gridCells = $('#new-table-size-selector td');
|
|
25
|
+
const $sizeText = $('#new-table-size');
|
|
26
|
+
const $toolbarBtn = $('#table-menu-button');
|
|
27
|
+
// log('postAceInit: Found UI elements.');
|
|
28
|
+
|
|
29
|
+
function position(el, target, dx = 0, dy = 0) {
|
|
30
|
+
// log('position: Calculating position for', el, 'relative to', target);
|
|
31
|
+
const p = target.offset();
|
|
32
|
+
el.css({ left: p.left + dx, top: p.top + dy });
|
|
33
|
+
// log('position: Set position:', { left: p.left + dx, top: p.top + dy });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ───────────────────── init once DOM is ready ─────────────────────
|
|
37
|
+
// Using setTimeout to ensure DOM elements are ready
|
|
38
|
+
// log('postAceInit: Setting up UI handlers via setTimeout.');
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
// log('postAceInit: setTimeout callback - START');
|
|
41
|
+
try {
|
|
42
|
+
// move pop‑ups to <body> so they are not clipped by #editbar overflow
|
|
43
|
+
$menu.add($gridWrap).appendTo('body').hide();
|
|
44
|
+
// log('postAceInit: setTimeout - Moved popups to body.');
|
|
45
|
+
|
|
46
|
+
// grid hover: live update highlight & label
|
|
47
|
+
$gridCells.hover(function () {
|
|
48
|
+
const cell = $(this);
|
|
49
|
+
const r = cell.parent().index();
|
|
50
|
+
const c = cell.index();
|
|
51
|
+
$gridCells.removeClass('selected');
|
|
52
|
+
for (let i = 0; i <= r; i++) {
|
|
53
|
+
for (let j = 0; j <= c; j++) {
|
|
54
|
+
$('#new-table-size-selector tr').eq(i).find('td').eq(j).addClass('selected');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const size = `${c + 1} X ${r + 1}`;
|
|
58
|
+
$sizeText.text(size);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// main toolbar button toggles context menu
|
|
62
|
+
$toolbarBtn.on('click', (e) => {
|
|
63
|
+
// log('Toolbar Button Click: START');
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
position($menu, $toolbarBtn, 0, $toolbarBtn.outerHeight());
|
|
67
|
+
$menu.toggle();
|
|
68
|
+
$gridWrap.hide();
|
|
69
|
+
// log('Toolbar Button Click: END - Toggled menu visibility:', $menu.is(':visible'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// "Insert table" hover reveals grid chooser
|
|
73
|
+
$('#tbl_prop_create_table').hover(
|
|
74
|
+
function () {
|
|
75
|
+
position($gridWrap, $(this), $(this).outerWidth(), -12);
|
|
76
|
+
$gridWrap.show();
|
|
77
|
+
},
|
|
78
|
+
() => {
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
if (!$gridWrap.is(':hover')) {
|
|
81
|
+
$gridWrap.hide();
|
|
82
|
+
}
|
|
83
|
+
}, 100);
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// keep grid visible while hovered
|
|
88
|
+
$gridWrap.hover(
|
|
89
|
+
() => { $gridWrap.show(); },
|
|
90
|
+
() => { $gridWrap.hide(); }
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// selecting a size calls Ace helper then hides menus
|
|
94
|
+
$gridCells.on('click', () => {
|
|
95
|
+
// log('Grid Cell Click: START');
|
|
96
|
+
const [cols, rows] = $sizeText.text().split(' X ').map(n => parseInt(n, 10));
|
|
97
|
+
// log('Grid Cell Click: Parsed size:', { cols, rows });
|
|
98
|
+
ctx.ace.callWithAce((ace) => {
|
|
99
|
+
// log('Grid Cell Click: Calling ace.ace_createTableViaAttributes...');
|
|
100
|
+
ace.ace_createTableViaAttributes(rows, cols);
|
|
101
|
+
// log('Grid Cell Click: ace.ace_createTableViaAttributes call finished.');
|
|
102
|
+
}, 'tblCreate', true);
|
|
103
|
+
$menu.hide();
|
|
104
|
+
$gridWrap.hide();
|
|
105
|
+
// log('Grid Cell Click: END - Hid menus.');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// other menu actions (row / col insert & delete, etc.)
|
|
109
|
+
$menu.find('.menu-item[data-action]')
|
|
110
|
+
.not('#tbl_prop_create_table')
|
|
111
|
+
.on('click', function (e) {
|
|
112
|
+
const action = $(this).data('action');
|
|
113
|
+
// log('Menu Item Click: START', { action });
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
e.stopPropagation();
|
|
116
|
+
ctx.ace.callWithAce((ace) => {
|
|
117
|
+
// log('Menu Item Click: Calling ace.ace_doDatatableOptions...', { action });
|
|
118
|
+
ace.ace_doDatatableOptions(action);
|
|
119
|
+
// log('Menu Item Click: ace.ace_doDatatableOptions call finished.');
|
|
120
|
+
}, 'tblOptions', true);
|
|
121
|
+
$menu.hide();
|
|
122
|
+
// log('Menu Item Click: END - Hid menu.');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// manual close button
|
|
126
|
+
$('#tbl_prop_menu_hide').on('click', (e) => {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
$menu.hide();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// global click closes pop‑ups when clicking outside
|
|
133
|
+
$(document).on('click', (e) => {
|
|
134
|
+
if (!$(e.target).closest('#table-context-menu, #table-menu-button, #create-table-container').length) {
|
|
135
|
+
$menu.hide();
|
|
136
|
+
$gridWrap.hide();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// --- BEGIN NEW: Add mousedown listener for cell selection ---
|
|
141
|
+
// Wrap in callWithAce to ensure we have the editor instance
|
|
142
|
+
ctx.ace.callWithAce((ace) => {
|
|
143
|
+
// log('postAceInit: Inside callWithAce for attaching mousedown listeners.');
|
|
144
|
+
try {
|
|
145
|
+
const $innerIframe = $('iframe[name="ace_outer"]').contents().find('iframe[name="ace_inner"]');
|
|
146
|
+
if ($innerIframe.length === 0) {
|
|
147
|
+
console.error('ep_data_tables postAceInit: ERROR - Could not find inner iframe (ace_inner) for cell selection.');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const $inner = $($innerIframe.contents().find('body'));
|
|
151
|
+
const innerDoc = $innerIframe.contents(); // Inner document for event delegation
|
|
152
|
+
|
|
153
|
+
if (!$inner || $inner.length === 0) {
|
|
154
|
+
console.error('ep_data_tables postAceInit: ERROR - Could not get body from inner iframe for cell selection.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if ace.editor exists now we are inside callWithAce
|
|
159
|
+
if (!ace.editor) {
|
|
160
|
+
console.error('ep_data_tables postAceInit: ERROR - ace.editor is STILL undefined inside callWithAce. Cannot attach state.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initialize the shared state variable on the editor instance
|
|
165
|
+
if (typeof ace.editor.ep_data_tables_last_clicked === 'undefined') {
|
|
166
|
+
ace.editor.ep_data_tables_last_clicked = null;
|
|
167
|
+
// log('postAceInit: Initialized ace.editor.ep_data_tables_last_clicked');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// log('postAceInit: Attempting to attach mousedown listener to $inner for cell selection...');
|
|
171
|
+
|
|
172
|
+
// Mousedown on table TD elements
|
|
173
|
+
$inner.on('mousedown', 'table.dataTable td', function(evt) {
|
|
174
|
+
// log('[ep_data_tables mousedown] RAW MOUSE DOWN detected inside table.dataTable td.');
|
|
175
|
+
|
|
176
|
+
// Check if the click is on an image or image-related element
|
|
177
|
+
const target = evt.target;
|
|
178
|
+
const $target = $(target);
|
|
179
|
+
const isImageElement = $target.closest('.inline-image, .image-placeholder, .image-inner, .image-resize-handle').length > 0;
|
|
180
|
+
|
|
181
|
+
if (isImageElement) {
|
|
182
|
+
// log('[ep_data_tables mousedown] Click detected on image element within table cell. Completely skipping table processing to avoid interference.');
|
|
183
|
+
// Completely skip all table processing when image is clicked
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// log('[ep_data_tables mousedown] Click detected on table cell (not image). Processing normally.');
|
|
188
|
+
|
|
189
|
+
if (evt.button !== 0) return; // Only left clicks
|
|
190
|
+
|
|
191
|
+
const tdElement = $(this);
|
|
192
|
+
const trElement = tdElement.closest('tr');
|
|
193
|
+
const tableElement = trElement.closest('table.dataTable');
|
|
194
|
+
const lineDiv = tdElement.closest('div.ace-line');
|
|
195
|
+
|
|
196
|
+
if (tdElement.length && trElement.length && tableElement.length && lineDiv.length) {
|
|
197
|
+
const cellIndex = tdElement.index();
|
|
198
|
+
const tblId = tableElement.attr('data-tblId');
|
|
199
|
+
const lineNum = _getLineNumberOfElement(lineDiv[0]);
|
|
200
|
+
|
|
201
|
+
if (tblId !== undefined && cellIndex !== -1 && lineNum !== -1) {
|
|
202
|
+
// log(`[ep_data_tables mousedown] Clicked cell (SUCCESS): Line=${lineNum}, TblId=${tblId}, CellIndex=${cellIndex}`);
|
|
203
|
+
// Store info on the shared ace editor object
|
|
204
|
+
ace.editor.ep_data_tables_last_clicked = { lineNum, cellIndex, tblId };
|
|
205
|
+
|
|
206
|
+
// Visual feedback (Keep this for now)
|
|
207
|
+
// --- TEST: Comment out class manipulation ---
|
|
208
|
+
// tableElement.find('td.selected-table-cell').removeClass('selected-table-cell');
|
|
209
|
+
// tdElement.addClass('selected-table-cell');
|
|
210
|
+
// log('[ep_data_tables mousedown] TEST: Skipped adding/removing selected-table-cell class');
|
|
211
|
+
} else {
|
|
212
|
+
console.warn('[ep_data_tables mousedown] Could not reliably get cell info (FAIL).', {tblId, cellIndex, lineNum});
|
|
213
|
+
ace.editor.ep_data_tables_last_clicked = null; // Clear shared state
|
|
214
|
+
// --- TEST: Comment out class manipulation (for consistency on failure) ---
|
|
215
|
+
// $inner.find('td.selected-table-cell').removeClass('selected-table-cell');
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
// log('[ep_data_tables mousedown] Click was not within a valid TD/TR/TABLE/LINEDIV structure (FAIL).');
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Add listener to clear selection when clicking outside tables
|
|
223
|
+
$(innerDoc).on('mousedown', function(evt) {
|
|
224
|
+
if (!$(evt.target).closest('table.dataTable').length) {
|
|
225
|
+
if (ace.editor.ep_data_tables_last_clicked) {
|
|
226
|
+
// log('[ep_data_tables mousedown] Clicked outside table, clearing cell info.');
|
|
227
|
+
ace.editor.ep_data_tables_last_clicked = null;
|
|
228
|
+
// --- TEST: Comment out class manipulation ---
|
|
229
|
+
// $inner.find('td.selected-table-cell').removeClass('selected-table-cell');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// log('postAceInit: Mousedown listeners for cell selection attached successfully (inside callWithAce).');
|
|
235
|
+
|
|
236
|
+
} catch(e) {
|
|
237
|
+
console.error('[ep_data_tables postAceInit] Error attaching mousedown listener (inside callWithAce):', e);
|
|
238
|
+
}
|
|
239
|
+
}, 'tables5_cell_selection_setup', true); // Unique name for callWithAce task
|
|
240
|
+
// --- END NEW: Add mousedown listener for cell selection ---
|
|
241
|
+
|
|
242
|
+
} catch (error) {
|
|
243
|
+
// log('postAceInit: setTimeout callback - ERROR:', error);
|
|
244
|
+
console.error('[ep_data_tables:initialisation] Error in setTimeout callback:', error);
|
|
245
|
+
}
|
|
246
|
+
// log('postAceInit: setTimeout callback - END');
|
|
247
|
+
}, 400); // delay so #editbar is in DOM
|
|
248
|
+
// log('postAceInit: END');
|
|
249
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<li class="separator acl-write"></li>
|
|
2
|
+
|
|
3
|
+
<li id="table-menu-button" class="acl-write">
|
|
4
|
+
<!-- just a button, no nested <a> -->
|
|
5
|
+
<button title="Table"
|
|
6
|
+
class="buttonicon buttonicon-table"
|
|
7
|
+
aria-haspopup="true"
|
|
8
|
+
aria-expanded="false"></button>
|
|
9
|
+
</li>
|
|
10
|
+
|
|
11
|
+
<!-- context menu -->
|
|
12
|
+
<nav id="table-context-menu" class="table-context-menu" role="menu">
|
|
13
|
+
<ul role="none">
|
|
14
|
+
<li class="menu-item" id="tbl_prop_create_table" role="menuitem">
|
|
15
|
+
<a href="#create-table-container" data-l10n-id="ep_data_tables.menuCreateTable">Insert Table</a>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
|
|
19
|
+
<ul role="none">
|
|
20
|
+
<li class="menu-item" data-action="addTblRowA"><a href="#" data-l10n-id="ep_data_tables.menuInsertRowAbove">Insert Row Above</a></li>
|
|
21
|
+
<li class="menu-item" data-action="addTblRowB"><a href="#" data-l10n-id="ep_data_tables.menuInsertRowBelow">Insert Row Below</a></li>
|
|
22
|
+
<li class="menu-item" data-action="addTblColL"><a href="#" data-l10n-id="ep_data_tables.menuInsertColumnLeft">Insert Column Left</a></li>
|
|
23
|
+
<li class="menu-item" data-action="addTblColR"><a href="#" data-l10n-id="ep_data_tables.menuInsertColumnRight">Insert Column Right</a></li>
|
|
24
|
+
</ul>
|
|
25
|
+
|
|
26
|
+
<ul role="none">
|
|
27
|
+
<li class="menu-item" data-action="delTblRow"><a href="#" data-l10n-id="ep_data_tables.menuDeleteRow">Delete Row</a></li>
|
|
28
|
+
<li class="menu-item" data-action="delTblCol"><a href="#" data-l10n-id="ep_data_tables.menuDeleteColumn">Delete Column</a></li>
|
|
29
|
+
</ul>
|
|
30
|
+
|
|
31
|
+
<ul role="none">
|
|
32
|
+
<li class="menu-item" id="tbl_prop_menu_hide"><a href="#" data-l10n-id="ep_data_tables.menuCloseThisMenu">Close Menu</a></li>
|
|
33
|
+
</ul>
|
|
34
|
+
|
|
35
|
+
<!-- table-size chooser -->
|
|
36
|
+
<div id="create-table-container" class="table-context-menu">
|
|
37
|
+
<div id="new-table-size">1 X 1</div>
|
|
38
|
+
<table id="new-table-size-selector">
|
|
39
|
+
<tbody>
|
|
40
|
+
<% for (let i = 1; i <= 30; i++) { %>
|
|
41
|
+
<tr>
|
|
42
|
+
<% for (let j = 1; j <= 10; j++) { %>
|
|
43
|
+
<td tabindex="-1"></td>
|
|
44
|
+
<% } %>
|
|
45
|
+
</tr>
|
|
46
|
+
<% } %>
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
</div>
|
|
50
|
+
</nav>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script src="../static/plugins/ep_data_tables/static/js/datatables-renderer.js"></script>
|
|
2
|
+
<script type="text/javascript">
|
|
3
|
+
// Ensure DatatablesRenderer is available globally
|
|
4
|
+
if (typeof window.DatatablesRenderer === 'undefined') {
|
|
5
|
+
window.DatatablesRenderer = DatatablesRenderer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Access the Changeset object properly from Etherpad's existing references
|
|
9
|
+
// We need to avoid setting it to an empty object which would break functionality
|
|
10
|
+
// Changeset should remain whatever Etherpad has already defined it as
|
|
11
|
+
|
|
12
|
+
// Create a global Datatables variable if it doesn't exist
|
|
13
|
+
if (typeof window.Datatables === 'undefined' && typeof Datatables !== 'undefined') {
|
|
14
|
+
window.Datatables = Datatables;
|
|
15
|
+
}
|
|
16
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<script src="../static/plugins/ep_data_tables/static/js/datatables-renderer.js"></script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<link rel="stylesheet" href="../static/plugins/ep_data_tables/static/css/table-menu.css" type="text/css" />
|