diff --git a/public/assets/js/bridge/table-builder.js b/public/assets/js/bridge/table-builder.js
new file mode 100644
index 0000000..fc9f772
--- /dev/null
+++ b/public/assets/js/bridge/table-builder.js
@@ -0,0 +1,230 @@
+/* /assets/js/bridge/table-builder.js */
+(function () {
+ const PluginName = 'bridge-table-builder';
+ const B = window.BridgeParts || (window.BridgeParts = {});
+
+ if (B.__tableBuilderLoaded) return;
+ B.__tableBuilderLoaded = true;
+
+ const log = (type, message, color = '#94a3b8', logType = 'info', force = false) => {
+ if (typeof B.log === 'function') {
+ B.log(PluginName, `[${type}] ${message}`, color, logType, force);
+ }
+ };
+
+ B.setupTableBuilder = (editor) => {
+ const domc = editor && editor.DomComponents;
+ if (!domc) return;
+ const icon = (path) => ``;
+ const tableIcon = icon('M3 4h18v16H3V4zm2 2v3h6V6H5zm8 0v3h6V6h-6zM5 11v3h6v-3H5zm8 0v3h6v-3h-6zM5 16v2h6v-2H5zm8 0v2h6v-2h-6z');
+
+ const ensureTableTags = () => {
+ const baseType = domc.getType('default');
+ if (!baseType) return;
+ const BaseModel = baseType.model;
+ const BaseView = baseType.view;
+ const tags = ['table', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th'];
+ tags.forEach((tag) => {
+ if (domc.getType(tag)) return;
+ domc.addType(tag, {
+ model: BaseModel,
+ view: BaseView,
+ isComponent: (el) => {
+ if (el.tagName && el.tagName.toLowerCase() === tag) {
+ return { type: tag };
+ }
+ return '';
+ },
+ });
+ });
+ };
+
+ const collectTableCells = (component) => {
+ const el = component?.view?.el;
+ if (!el) return [];
+ return Array.from(el.querySelectorAll('tr')).map(row =>
+ Array.from(row.children || []).map(cell => cell.innerHTML || '')
+ );
+ };
+
+ const buildTableHtml = (rows, cols, existing) => {
+ const safeRows = Math.max(1, Math.min(20, Number(rows) || 1));
+ const safeCols = Math.max(2, Math.min(2, Number(cols) || 2));
+ const cellStyle = "padding:8px;border:1px solid #e2e8f0;font-size:13px";
+ const headStyle = "text-align:left;padding:8px;border:1px solid #e2e8f0;background-color:#f8fafc;font-size:13px";
+ let html = '';
+ for (let r = 0; r < safeRows; r++) {
+ html += '
';
+ for (let c = 0; c < safeCols; c++) {
+ const existingVal = existing?.[r]?.[c] || '';
+ const label = existingVal || (r === 0 ? `Spalte ${String.fromCharCode(65 + c)}` : `Zeile ${r} / ${c + 1}`);
+ if (r === 0) {
+ html += `| ${label} | `;
+ } else {
+ html += `${label} | `;
+ }
+ }
+ html += '
';
+ }
+ return html;
+ };
+
+ const openTableModal = (component) => {
+ if (!component) return;
+ const modal = editor.Modal;
+ if (!modal) return;
+ const attrs = component.getAttributes ? component.getAttributes() : {};
+ const rows = Number(attrs['data-bridge-rows'] || 3) || 3;
+
+ const container = document.createElement('div');
+ container.style.display = 'flex';
+ container.style.flexDirection = 'column';
+ container.style.gap = '12px';
+ container.style.minWidth = '280px';
+
+ const label = document.createElement('label');
+ label.textContent = 'Anzahl Zeilen';
+ label.style.fontSize = '13px';
+ label.style.fontWeight = '600';
+
+ const input = document.createElement('input');
+ input.type = 'number';
+ input.min = '1';
+ input.max = '20';
+ input.value = String(rows);
+ input.style.width = '100%';
+ input.style.padding = '6px 8px';
+ input.style.border = '1px solid #cbd5f5';
+ input.style.borderRadius = '4px';
+
+ label.appendChild(input);
+ container.appendChild(label);
+
+ const actions = document.createElement('div');
+ actions.style.display = 'flex';
+ actions.style.justifyContent = 'flex-end';
+ actions.style.gap = '8px';
+
+ const cancelBtn = document.createElement('button');
+ cancelBtn.type = 'button';
+ cancelBtn.textContent = 'Abbrechen';
+ cancelBtn.className = 'btn';
+ cancelBtn.addEventListener('click', () => modal.close());
+
+ const saveBtn = document.createElement('button');
+ saveBtn.type = 'button';
+ saveBtn.textContent = 'Uebernehmen';
+ saveBtn.className = 'btn';
+ saveBtn.addEventListener('click', () => {
+ const nextRows = Math.max(1, Math.min(20, Number(input.value) || 1));
+ const existing = collectTableCells(component);
+ const html = buildTableHtml(nextRows, 2, existing);
+ component.addAttributes && component.addAttributes({
+ 'data-bridge-rows': String(nextRows),
+ 'data-bridge-cols': '2',
+ });
+ if (component.components) {
+ component.components(html);
+ }
+ if (component.view && component.view.render) {
+ component.view.render();
+ }
+ modal.close();
+ });
+
+ actions.appendChild(cancelBtn);
+ actions.appendChild(saveBtn);
+ container.appendChild(actions);
+
+ modal.setTitle('Tabelle konfigurieren');
+ modal.setContent(container);
+ const mdl = modal.getModel && modal.getModel();
+ if (mdl && typeof mdl.set === 'function') {
+ mdl.set('closeOnEsc', false);
+ mdl.set('closeOnClick', false);
+ }
+ modal.open();
+ };
+
+ if (editor.Commands && editor.Commands.add) {
+ editor.Commands.add('bridge-table:edit', {
+ run(ed, sender, opts = {}) {
+ if (sender && sender.set) sender.set('active', 0);
+ const component = opts.component || ed.getSelected();
+ if (component && component.is && component.is('bridge-table')) {
+ openTableModal(component);
+ }
+ },
+ });
+ }
+
+ if (!domc.getType('bridge-table')) {
+ const baseType = domc.getType('table') || domc.getType('default');
+ const BaseModel = baseType.model;
+ const BaseView = baseType.view;
+ domc.addType('bridge-table', {
+ model: BaseModel.extend({
+ initialize(props = {}, opts = {}) {
+ if (BaseModel.prototype.initialize) {
+ BaseModel.prototype.initialize.apply(this, [props, opts]);
+ }
+ const attrs = this.getAttributes ? this.getAttributes() : {};
+ const nextAttrs = Object.assign({
+ 'data-bridge-table': '1',
+ 'data-bridge-rows': '3',
+ 'data-bridge-cols': '2',
+ role: 'presentation',
+ }, attrs || {});
+ if (this.addAttributes) {
+ this.addAttributes(nextAttrs);
+ } else if (this.set) {
+ this.set('attributes', nextAttrs, { silent: true });
+ }
+ const toolbar = this.get && this.get('toolbar');
+ if (!Array.isArray(toolbar) || !toolbar.some(btn => btn && btn.command === 'bridge-table:edit')) {
+ this.set && this.set('toolbar', [
+ ...(Array.isArray(toolbar) ? toolbar : []),
+ {
+ label: tableIcon,
+ command: 'bridge-table:edit',
+ attributes: { title: 'Tabelle bearbeiten' },
+ },
+ ]);
+ }
+ },
+ }),
+ view: BaseView,
+ isComponent: (el) => {
+ if (el.tagName === 'TABLE' && el.getAttribute('data-bridge-table')) {
+ return { type: 'bridge-table' };
+ }
+ return '';
+ },
+ });
+ }
+
+ editor.on('component:selected', (model) => {
+ if (model && model.is && model.is('bridge-table')) {
+ const toolbar = model.get('toolbar') || [];
+ const exists = toolbar.some(btn => btn && btn.command === 'bridge-table:edit');
+ if (!exists) {
+ toolbar.push({
+ label: tableIcon,
+ command: 'bridge-table:edit',
+ attributes: { title: 'Tabelle bearbeiten' },
+ });
+ model.set('toolbar', toolbar);
+ }
+ }
+ });
+
+ editor.on('component:dblclick', (model) => {
+ if (model && model.is && model.is('bridge-table')) {
+ openTableModal(model);
+ }
+ });
+
+ ensureTableTags();
+ log('INIT', 'Table-Builder geladen.', '#10b981');
+ };
+})();
diff --git a/public/editor/bridge-core.js b/public/editor/bridge-core.js
index ea047c4..af479ca 100644
--- a/public/editor/bridge-core.js
+++ b/public/editor/bridge-core.js
@@ -150,8 +150,11 @@
];
const initialLoadList = B.ENABLE_EDITOR_EXTENSIONS === false
- ? [base + 'general-functions.js']
- : [...coreFiles];
+ ? [base + 'general-functions.js']
+ : [...coreFiles];
+ if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_TABLE_BUILDER) {
+ initialLoadList.push(base + 'table-builder.js');
+ }
function recursiveLoader(list, index = 0) {
if (index >= list.length) {
@@ -881,220 +884,7 @@
editor.on('component:add', (model) => rememberIfPresent(model));
};
- const setupTableBuilder = (editor) => {
- const domc = editor.DomComponents;
- if (!domc) return;
- const icon = (path) => ``;
- const tableIcon = icon('M3 4h18v16H3V4zm2 2v3h6V6H5zm8 0v3h6V6h-6zM5 11v3h6v-3H5zm8 0v3h6v-3h-6zM5 16v2h6v-2H5zm8 0v2h6v-2h-6z');
-
- const ensureTableTags = () => {
- const baseType = domc.getType('default');
- if (!baseType) return;
- const BaseModel = baseType.model;
- const BaseView = baseType.view;
- const tags = ['table', 'tbody', 'thead', 'tfoot', 'tr', 'td', 'th'];
- tags.forEach((tag) => {
- if (domc.getType(tag)) return;
- domc.addType(tag, {
- model: BaseModel,
- view: BaseView,
- isComponent: el => {
- if (el.tagName && el.tagName.toLowerCase() === tag) {
- return { type: tag };
- }
- return '';
- },
- });
- });
- };
-
- const collectTableCells = (component) => {
- const el = component?.view?.el;
- if (!el) return [];
- return Array.from(el.querySelectorAll('tr')).map(row =>
- Array.from(row.children || []).map(cell => cell.innerHTML || '')
- );
- };
-
- const buildTableHtml = (rows, cols, existing) => {
- const safeRows = Math.max(1, Math.min(20, Number(rows) || 1));
- const safeCols = Math.max(2, Math.min(2, Number(cols) || 2));
- const cellStyle = "padding:8px;border:1px solid #e2e8f0;font-size:13px";
- const headStyle = "text-align:left;padding:8px;border:1px solid #e2e8f0;background-color:#f8fafc;font-size:13px";
- let html = '';
- for (let r = 0; r < safeRows; r++) {
- html += '';
- for (let c = 0; c < safeCols; c++) {
- const existingVal = existing?.[r]?.[c] || '';
- const label = existingVal || (r === 0 ? `Spalte ${String.fromCharCode(65 + c)}` : `Zeile ${r} / ${c + 1}`);
- if (r === 0) {
- html += `| ${label} | `;
- } else {
- html += `${label} | `;
- }
- }
- html += '
';
- }
- return html;
- };
-
- const openTableModal = (component) => {
- if (!component) return;
- const modal = editor.Modal;
- if (!modal) return;
- const attrs = component.getAttributes ? component.getAttributes() : {};
- const rows = Number(attrs['data-bridge-rows'] || 3) || 3;
-
- const container = document.createElement('div');
- container.style.display = 'flex';
- container.style.flexDirection = 'column';
- container.style.gap = '12px';
- container.style.minWidth = '280px';
-
- const label = document.createElement('label');
- label.textContent = 'Anzahl Zeilen';
- label.style.fontSize = '13px';
- label.style.fontWeight = '600';
-
- const input = document.createElement('input');
- input.type = 'number';
- input.min = '1';
- input.max = '20';
- input.value = String(rows);
- input.style.width = '100%';
- input.style.padding = '6px 8px';
- input.style.border = '1px solid #cbd5f5';
- input.style.borderRadius = '4px';
-
- label.appendChild(input);
- container.appendChild(label);
-
- const actions = document.createElement('div');
- actions.style.display = 'flex';
- actions.style.justifyContent = 'flex-end';
- actions.style.gap = '8px';
-
- const cancelBtn = document.createElement('button');
- cancelBtn.type = 'button';
- cancelBtn.textContent = 'Abbrechen';
- cancelBtn.className = 'btn';
- cancelBtn.addEventListener('click', () => modal.close());
-
- const saveBtn = document.createElement('button');
- saveBtn.type = 'button';
- saveBtn.textContent = 'Uebernehmen';
- saveBtn.className = 'btn';
- saveBtn.addEventListener('click', () => {
- const nextRows = Math.max(1, Math.min(20, Number(input.value) || 1));
- const existing = collectTableCells(component);
- const html = buildTableHtml(nextRows, 2, existing);
- component.addAttributes && component.addAttributes({
- 'data-bridge-rows': String(nextRows),
- 'data-bridge-cols': '2',
- });
- if (component.components) {
- component.components(html);
- }
- if (component.view && component.view.render) {
- component.view.render();
- }
- modal.close();
- });
-
- actions.appendChild(cancelBtn);
- actions.appendChild(saveBtn);
- container.appendChild(actions);
-
- modal.setTitle('Tabelle konfigurieren');
- modal.setContent(container);
- const mdl = modal.getModel && modal.getModel();
- if (mdl && typeof mdl.set === 'function') {
- mdl.set('closeOnEsc', false);
- mdl.set('closeOnClick', false);
- }
- modal.open();
- };
-
- if (editor.Commands && editor.Commands.add) {
- editor.Commands.add('bridge-table:edit', {
- run(ed, sender, opts = {}) {
- if (sender && sender.set) sender.set('active', 0);
- const component = opts.component || ed.getSelected();
- if (component && component.is && component.is('bridge-table')) {
- openTableModal(component);
- }
- },
- });
- }
-
- if (!domc.getType('bridge-table')) {
- const baseType = domc.getType('table') || domc.getType('default');
- const BaseModel = baseType.model;
- const BaseView = baseType.view;
- domc.addType('bridge-table', {
- model: BaseModel.extend({
- initialize(props = {}, opts = {}) {
- if (BaseModel.prototype.initialize) {
- BaseModel.prototype.initialize.apply(this, [props, opts]);
- }
- const attrs = this.getAttributes ? this.getAttributes() : {};
- const nextAttrs = Object.assign({
- 'data-bridge-table': '1',
- 'data-bridge-rows': '3',
- 'data-bridge-cols': '2',
- role: 'presentation',
- }, attrs || {});
- if (this.addAttributes) {
- this.addAttributes(nextAttrs);
- } else if (this.set) {
- this.set('attributes', nextAttrs, { silent: true });
- }
- const toolbar = this.get && this.get('toolbar');
- if (!Array.isArray(toolbar) || !toolbar.some(btn => btn && btn.command === 'bridge-table:edit')) {
- this.set && this.set('toolbar', [
- ...(Array.isArray(toolbar) ? toolbar : []),
- {
- label: tableIcon,
- command: 'bridge-table:edit',
- attributes: { title: 'Tabelle bearbeiten' },
- },
- ]);
- }
- },
- }),
- view: BaseView,
- isComponent: el => {
- if (el.tagName === 'TABLE' && el.getAttribute('data-bridge-table')) {
- return { type: 'bridge-table' };
- }
- return '';
- },
- });
- }
-
- editor.on('component:selected', (model) => {
- if (model && model.is && model.is('bridge-table')) {
- const toolbar = model.get('toolbar') || [];
- const exists = toolbar.some(btn => btn && btn.command === 'bridge-table:edit');
- if (!exists) {
- toolbar.push({
- label: tableIcon,
- command: 'bridge-table:edit',
- attributes: { title: 'Tabelle bearbeiten' },
- });
- model.set('toolbar', toolbar);
- }
- }
- });
-
- editor.on('component:dblclick', (model) => {
- if (model && model.is && model.is('bridge-table')) {
- openTableModal(model);
- }
- });
-
- ensureTableTags();
- };
+ // Table-Builder ausgelagert nach /assets/js/bridge/table-builder.js
const setupBlurLogger = (editor) => {
if (!editor || !editor.Canvas || !editor.Canvas.getBody) return;
@@ -1243,7 +1033,11 @@
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && (B.ENABLE_EDITOR_BEHAVIOR !== false || B.ENABLE_TABLE_BUILDER)) {
- setupTableBuilder(ed);
+ if (typeof B.setupTableBuilder === 'function') {
+ B.setupTableBuilder(ed);
+ } else {
+ log('TABLE WARN', 'Table-Builder nicht geladen.', 'orange', 'warn');
+ }
}
if (B.ENABLE_EDITOR_EXTENSIONS !== false && B.ENABLE_EDITOR_BEHAVIOR !== false) {
setupPlainTextPreserver(ed);