• Hello Andrew,

    It’s been a long time since I wrote these two posts:
    https://wordpress.org/support/topic/no-flyover-toolbar-on-tables-tab-key-doesnt-select-text/
    https://wordpress.org/support/topic/two-separate-fullscreen-options/

    But I haven’t actually found any solutions to the issues raised in all these years – until now.

    With the help of Copilot, I was able to create a PHP script that actually solved most of these issues. The script has three parts. The first part does everything related to the tables:

    • Tab/Shift+Tab now selects the entire cell content after each press
    • Ctrl+A selects the entire content of the cell where the cursor is located
    • If you press Ctrl+A multiple times, the entire row is selected in the next step, followed by the entire table, and then the cell again (as in OneNote)

    Then I mentioned the WordPress native full screen or DFW button, which cannot be hidden (because it does not really provide a correct full screen view). As you replied, it can be hidden via the menu at the top right. However, if you have multiple users, this is a bit tedious because it cannot be controlled globally. Therefore, the second script simply hides this button. I actually wanted to completely disable the DFW function (with button and option), but I couldn’t do that with the script because my theme or WPBakery keeps reactivating this mode.

    Then the last part of the script activates the flyout menu for tables. Then I don’t have to install “Advanced TinyMCE Configuration” separately if I can solve it right here.

    So I would be particularly interested to hear what you think about the first script for tables. I can’t judge how good the quality of the PHP code is. But would it be possible to check this and build such a function directly into “Advanced Editor Tools”? Of course, it would be great if you could also hide the WP’s own full-screen button there, as only the full-screen button that can be displayed with “Advanced Editor Tools” works correctly! But I think that if you did, you wouldn’t want to simply hide the button using CSS, but rather have an option to completely disable DFW mode. But I can’t implement that. Then there’s the functionality of “Advanced TinyMCE Configuration.” Couldn’t you integrate those options into the “Advanced Editor Tools” plugin? Then you wouldn’t have to install another plugin, and the functionalities are very closely related thematically.

    So, those are my “nice to haves.” Let’s see what you think… 😉

    Many thanks, Stefan

    /**
    * Plugin Name: SK TinyMCE Modifier
    * Description: Sammel-Snippet für TinyMCE/Classic-Editor-Optimierungen
    * Version: 1.2.2
    * Author: Synor Media/Stefan Krapf mit Copilot 365
    */

    if (!defined('ABSPATH')) exit;


    /* =============================================================================
    * SECTION A — TinyMCE Tabellen-Booster
    * -----------------------------------------------------------------------------
    * FUNKTION:
    * - Tab / Shift+Tab:
    * springt zur nächsten/vorigen Tabellenzelle und markiert
    * den *kompletten ZellINHALT*. Funktioniert auch in WPBakery/Impreza-Overlays.
    *
    * - Ctrl/Cmd + A (nur wenn Cursor in einer Tabellenzelle steht):
    * 1× = Zelle, 2× = ganze Zeile (TR), 3× = ganze Tabelle (TABLE),
    * dann wieder Zelle (Zyklus wie in OneNote).
    *
    * - Beschränkung auf Editor-IDs:
    * • content (Haupteditor)
    * • wpb_tinymce_content (WPBakery/Impreza-Modal)
    * ========================================================================== */

    /**
    * Externes TinyMCE-Plugin registrieren (JavaScript wird via admin-ajax ausgeliefert).
    * Hinweis: Verwende in der URL normales '&' (kein HTML-escaped '&').
    * Erhöhe 'ver=' als Cachebuster, wenn du änderst.
    */
    add_filter('mce_external_plugins', function($external, $editor_id){
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $external;

    $external['selectnextcell'] = admin_url('admin-ajax.php?action=sm_selectnextcell_js&ver=122');
    return $external;
    }, 10, 2);

    /**
    * Pluginname 'selectnextcell' in die TinyMCE-Pluginliste hängen.
    * Hinweis: 'table' NICHT erzwingen (kann 404 erzeugen, wenn im Stack nicht vorhanden).
    */
    add_filter('tiny_mce_before_init', function($init, $editor_id){
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $init;

    $plugins = isset($init['plugins']) ? $init['plugins'] : '';
    if (is_array($plugins)) {
    if (!in_array('selectnextcell', $plugins, true)) $plugins[] = 'selectnextcell';
    $init['plugins'] = $plugins;
    } else {
    $set = array_filter(array_map('trim', preg_split('/[\s,]+/', (string)$plugins)));
    if (!in_array('selectnextcell', $set, true)) $set[] = 'selectnextcell';
    $init['plugins'] = implode(' ', $set);
    }
    return $init;
    }, 10, 2);

    /**
    * AJAX: Auslieferung des TinyMCE-Plugins (JavaScript)
    */
    add_action('wp_ajax_sm_selectnextcell_js', function(){
    header('Content-Type: application/javascript; charset=utf-8');
    ?>
    (function(){
    if (!window.tinymce || !tinymce.PluginManager) return;

    // --- Timing-Helfer: fn in n Frames ausführen ---
    function afterFrames(fn, n){
    var raf = window.requestAnimationFrame || function(f){ return setTimeout(f, 16); };
    (function step(i){
    if (i <= 0) { try { fn(); } catch(e){} return; }
    raf(function(){ step(i-1); });
    })(n||1);
    }

    // --- Auswahl-Helfer (TinyMCE-Selection, stabil für MCE4) ---
    function setRangeOnNodeContents(editor, node){
    var rng = editor.dom.createRng();
    rng.setStart(node, 0);
    rng.setEnd(node, node.childNodes.length);
    editor.selection.setRng(rng);
    editor.nodeChanged();
    }

    function selectCell(editor, cell){
    if (!cell) return;
    try {
    editor.focus();
    if (editor.getWin && editor.getWin().focus) editor.getWin().focus();
    setRangeOnNodeContents(editor, cell);
    } catch(ex){
    try { editor.selection.select(cell, true); editor.nodeChanged(); } catch(_){}
    }
    }

    function selectRow(editor, row){
    if (!row) return;
    try { setRangeOnNodeContents(editor, row); } catch(e){}
    }

    function selectTable(editor, table){
    if (!table) return;
    try { setRangeOnNodeContents(editor, table); } catch(e){}
    }

    // --- Nachbarzelle mit Wrap-Around innerhalb *derselben* Tabelle ---
    function getNeighborCell(editor, curCell, dir){
    if (!curCell) return null;
    var table = editor.dom.getParent(curCell, 'table');
    if (!table) return null;

    // Alle Zellen (thead/tbody/tfoot inkl.)
    var cells = editor.dom.select('td,th', table);
    if (!cells || !cells.length) return null;

    var idx = Array.prototype.indexOf.call(cells, curCell);
    if (idx < 0) return null;

    var nextIdx = (idx + (dir > 0 ? 1 : -1) + cells.length) % cells.length;
    return cells[nextIdx];
    }

    // --- Kontext-Resolver: Bleib im Tabellenkontext, auch wenn Cursor auf TR/TABLE liegt ---
    function getTableContext(editor){
    // Nutze sowohl getNode() als auch den Range-Start
    var node = editor.selection.getNode();
    var rng = editor.selection.getRng ? editor.selection.getRng() : null;
    var sc = rng ? rng.startContainer : null;

    // Finde die Tabelle zuverlässig
    var table = editor.dom.getParent(node, 'table') ||
    (sc && editor.dom.getParent(sc, 'table')) || null;
    if (!table) return {};

    // Versuche eine Zelle zu finden
    var cell = editor.dom.getParent(node, 'td,th') ||
    (sc && editor.dom.getParent(sc, 'td,th')) || null;

    // Versuche die Zeile zu finden (falls gebraucht)
    var row = editor.dom.getParent(node, 'tr') ||
    (cell && editor.dom.getParent(cell, 'tr')) ||
    (sc && editor.dom.getParent(sc, 'tr')) || null;

    // Falls keine Zelle ermittelbar: nimm letzte gemerkte Zelle innerhalb derselben Tabelle…
    if (!cell) {
    var st = editor.__sm_cycle && editor.__sm_cycle.lastCell;
    if (st && editor.dom.getParent(st, 'table') === table) {
    cell = st;
    } else {
    // …oder fallback auf die erste Zelle der Tabelle
    var cells = editor.dom.select('td,th', table);
    cell = (cells && cells[0]) ? cells[0] : null;
    // Row ggf. aus dieser Zelle ableiten
    if (!row && cell) row = editor.dom.getParent(cell, 'tr');
    }
    }

    return { table: table, row: row, cell: cell };
    }

    // --- OneNote-Style Ctrl/Cmd + A: 1) Zelle 2) Zeile 3) Tabelle (dann wieder Zelle) ---
    function cycleSelect(editor, cell){
    var row = editor.dom.getParent(cell, 'tr');
    var table = editor.dom.getParent(cell, 'table');
    if (!row || !table) { selectCell(editor, cell); return; }

    editor.__sm_cycle = editor.__sm_cycle || { lastTable: null, lastCell: null, level: 0 };

    // Kontextwechsel? Dann mit Level 1 beginnen
    if (editor.__sm_cycle.lastTable !== table || editor.__sm_cycle.lastCell !== cell) {
    editor.__sm_cycle.level = 1;
    } else {
    editor.__sm_cycle.level = (editor.__sm_cycle.level % 3) + 1; // 1..3
    }
    editor.__sm_cycle.lastTable = table;
    editor.__sm_cycle.lastCell = cell;

    var doSelect = function(){
    if (editor.__sm_cycle.level === 1) selectCell(editor, cell);
    else if (editor.__sm_cycle.level === 2) selectRow(editor, row);
    else selectTable(editor, table);
    };
    // Sofort + verzögert wiederholen (Caret/DOM-Resets überfahren)
    doSelect();
    afterFrames(doSelect, 1);
    afterFrames(doSelect, 2);
    }

    function attach(editor){
    if (!editor || editor.settings.__sm_attached) return;
    editor.settings.__sm_attached = true;

    // --- TAB / SHIFT+TAB: nächste/vorige Zelle + ZellINHALT markieren ---
    function onTab(e){
    if (e.keyCode !== 9) return; // Tab
    var node = editor.selection.getNode();
    var curCell = editor.dom.getParent(node, 'td,th');
    if (!curCell) return; // außerhalb von Tabellen: Standardverhalten beibehalten

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    var dir = e.shiftKey ? -1 : 1;
    var target = getNeighborCell(editor, curCell, dir);
    if (!target) return; // theoretisch nie, wegen Wrap-Around

    // sofort + 1-2 Frames später auswählen (überfährt Caret-Resets)
    selectCell(editor, target);
    afterFrames(function(){ selectCell(editor, target); }, 1);
    afterFrames(function(){ selectCell(editor, target); }, 2);
    }

    // --- CTRL/CMD + A im Tabellenkontext: Zelle → Zeile → Tabelle (Zyklus) ---
    function onCtrlA(e){
    var isCtrlA = (e.keyCode === 65) && (e.ctrlKey || e.metaKey);
    if (!isCtrlA) return;

    // Neu: Kontext ermitteln (auch wenn Cursor auf TR/TABLE sitzt)
    var ctx = getTableContext(editor);
    if (!ctx.table) return; // außerhalb von Tabellen: Standardverhalten

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();
    cycleSelect(editor, ctx.cell);
    }

    editor.on('keydown', onTab);
    editor.on('keydown', onCtrlA);

    // Capturing im IFRAME (hilft bei Modals/Overlays)
    editor.on('init', function(){
    try {
    var doc = editor.getDoc();

    var capTab = function(e){
    if (e.keyCode !== 9) return;
    var node = editor.selection.getNode();
    var curCell = editor.dom.getParent(node, 'td,th');
    if (!curCell) return;
    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    var dir = e.shiftKey ? -1 : 1;
    var target = getNeighborCell(editor, curCell, dir);
    if (!target) return;

    selectCell(editor, target);
    afterFrames(function(){ selectCell(editor, target); }, 1);
    afterFrames(function(){ selectCell(editor, target); }, 2);
    };

    var capCtrlA = function(e){
    var isCtrlA = (e.keyCode === 65) && (e.ctrlKey || e.metaKey);
    if (!isCtrlA) return;

    var ctx = getTableContext(editor);
    if (!ctx.table) return;

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();
    cycleSelect(editor, ctx.cell);
    };

    doc.addEventListener('keydown', capTab, true);
    doc.addEventListener('keydown', capCtrlA, true);

    editor.on('remove', function(){
    try {
    doc.removeEventListener('keydown', capTab, true);
    doc.removeEventListener('keydown', capCtrlA, true);
    } catch(_){}
    });
    } catch(_) {}
    });
    }

    // Registrierung als TinyMCE-Plugin
    tinymce.PluginManager.add('selectnextcell', function(editor){
    attach(editor);
    return {};
    });

    // Für später erzeugte Editoren (z. B. Modal)
    tinymce.on('AddEditor', function(e){ try { attach(e.editor); } catch(_){ } });
    })();
    <?php
    exit;
    });



    /* =============================================================================
    * SECTION B — Classic Editor: DFW/Vollhöhe-Buttons ausblenden
    * -----------------------------------------------------------------------------
    * Ziel: Nur UI verstecken. (Keine Änderung an der WP-Option "editor_expand"/DFW.)
    * - TinyMCE (visuell): .mce-wp-dfw (Container + Icon)
    * - Quicktags (Text/HTML): .qt-dfw und alle IDs qt_*_dfw (z. B. qt_content_dfw, qt_wpb_tinymce_content_dfw)
    * ========================================================================== */

    add_action('admin_head', function () {
    if ( ! is_admin() ) return;
    ?>
    <style id="sm-hide-dfw-classic-editor">
    /* (1) Quicktags – DFW-Buttons im Text-/HTML-Modus ausblenden
    - deckt Haupteditor UND WPBakery/Overlays ab
    - Beispiele: #qt_content_dfw, #qt_wpb_tinymce_content_dfw */
    .quicktags-toolbar .qt-dfw,
    [id^="qt_"][id$="_dfw"] {
    display: none !important;
    }

    /* (2) TinyMCE – kompletter DFW-Button-Container + Inhalte ausblenden */
    .mce-toolbar .mce-wp-dfw,
    .mce-toolbar .mce-wp-dfw * {
    display: none !important;
    }

    /* Lücke/Spacing vermeiden */
    .mce-toolbar .mce-wp-dfw {
    width: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    }

    /* Optional enger scopen – nur Haupteditor:
    #wp-content-editor-container .mce-toolbar .mce-wp-dfw { display:none !important; } */
    </style>
    <?php
    });



    /* =============================================================================
    * SECTION C — TinyMCE: Table Toolbar Preset (ersetzt Advanced TinyMCE Config)
    * -----------------------------------------------------------------------------
    * Setzt die table_toolbar wie im ATE-Plugin und stellt sicher, dass das
    * eingebaute 'table'-Plugin aktiv ist.
    * Gilt nur für die Editor-IDs: 'content', 'wpb_tinymce_content'
    * ========================================================================== */

    add_filter('tiny_mce_before_init', function($init, $editor_id){
    // Nur unsere gewünschten Editoren
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $init;

    // 1) table_toolbar wie definiert
    $init['table_toolbar'] =
    'tableprops tablerowprops tablecellprops | ' .
    'tableinsertrowbefore tableinsertrowafter tabledeleterow | ' .
    'tableinsertcolbefore tableinsertcolafter tabledeletecol | ' .
    'tablemergecells tablesplitcells | tabledelete';

    // 2) Sicherstellen, dass das 'table'-Plugin aktiv ist
    $plugins = isset($init['plugins']) ? $init['plugins'] : '';
    if (is_array($plugins)) {
    if (!in_array('table', $plugins, true)) $plugins[] = 'table';
    $init['plugins'] = $plugins;
    } else {
    $set = array_filter(array_map('trim', preg_split('/[\s,]+/', (string)$plugins)));
    if (!in_array('table', $set, true)) $set[] = 'table';
    $init['plugins'] = implode(' ', $set);
    }

    return $init;
    }, 10, 2);

    The page I need help with: [log in to see the link]

Viewing 2 replies - 1 through 2 (of 2 total)
  • Thread Starter Stefan Krapf

    (@synormedia)

    I have updated my script because I noticed that Advanced Editor Tool actually had a function that automatically created a new row in the last cell of a table when you pressed the tab key. This no longer worked with my script. I have now corrected this. Here is the updated version. I also tweaked the table flyout menu a bit with CSS to make it more visible:

    /**
    * Plugin Name: Synor TinyMCE Modifier
    * Description: Sammel-Snippet für TinyMCE/Classic-Editor-Optimierungen
    * Version: 1.2.6
    * Author: Synor Media/Stefan Krapf mit Copilot 365
    */

    if (!defined('ABSPATH')) exit;


    /* =============================================================================
    * SECTION A — TinyMCE Tabellen-Booster
    * -----------------------------------------------------------------------------
    * FUNKTION:
    * - Tab / Shift+Tab:
    * springt zur nächsten/vorigen Tabellenzelle und markiert
    * den *kompletten ZellINHALT*. Funktioniert auch in WPBakery/Impreza-Overlays.
    *
    * - Ctrl/Cmd + A (nur wenn Cursor in einer Tabellenzelle steht):
    * 1× = Zelle, 2× = ganze Zeile (TR), 3× = ganze Tabelle (TABLE),
    * dann wieder Zelle (Zyklus wie in OneNote).
    *
    * - Beschränkung auf Editor-IDs:
    * • content (Haupteditor)
    * • wpb_tinymce_content (WPBakery/Impreza-Modal)
    * ========================================================================== */

    /**
    * Externes TinyMCE-Plugin registrieren (JavaScript wird via admin-ajax ausgeliefert).
    * Hinweis: Verwende in der URL normales '&' (kein HTML-escaped '&amp;').
    * Erhöhe 'ver=' als Cachebuster, wenn du änderst.
    */
    add_filter('mce_external_plugins', function($external, $editor_id){
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $external;

    $external['selectnextcell'] = admin_url('admin-ajax.php?action=sm_selectnextcell_js&ver=126');
    return $external;
    }, 10, 2);

    /**
    * Pluginname 'selectnextcell' in die TinyMCE-Pluginliste hängen.
    * Hinweis: 'table' NICHT erzwingen (kann 404 erzeugen, wenn im Stack nicht vorhanden).
    */
    add_filter('tiny_mce_before_init', function($init, $editor_id){
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $init;

    $plugins = isset($init['plugins']) ? $init['plugins'] : '';
    if (is_array($plugins)) {
    if (!in_array('selectnextcell', $plugins, true)) $plugins[] = 'selectnextcell';
    $init['plugins'] = $plugins;
    } else {
    $set = array_filter(array_map('trim', preg_split('/[\s,]+/', (string)$plugins)));
    if (!in_array('selectnextcell', $set, true)) $set[] = 'selectnextcell';
    $init['plugins'] = implode(' ', $set);
    }
    return $init;
    }, 10, 2);

    /**
    * AJAX: Auslieferung des TinyMCE-Plugins (JavaScript)
    */
    add_action('wp_ajax_sm_selectnextcell_js', function(){
    header('Content-Type: application/javascript; charset=utf-8');
    ?>
    (function(){
    if (!window.tinymce || !tinymce.PluginManager) return;

    // --- Timing-Helfer: fn in n Frames ausführen ---
    function afterFrames(fn, n){
    var raf = window.requestAnimationFrame || function(f){ return setTimeout(f, 16); };
    (function step(i){
    if (i <= 0) { try { fn(); } catch(e){} return; }
    raf(function(){ step(i-1); });
    })(n||1);
    }

    // --- Auswahl-Helfer (TinyMCE-Selection, stabil für MCE4) ---
    function setRangeOnNodeContents(editor, node){
    var rng = editor.dom.createRng();
    rng.setStart(node, 0);
    rng.setEnd(node, node.childNodes.length);
    editor.selection.setRng(rng);
    editor.nodeChanged();
    }

    function selectCell(editor, cell){
    if (!cell) return;
    try {
    editor.focus();
    if (editor.getWin && editor.getWin().focus) editor.getWin().focus();
    setRangeOnNodeContents(editor, cell);
    } catch(ex){
    try { editor.selection.select(cell, true); editor.nodeChanged(); } catch(_){}
    }
    }

    function selectRow(editor, row){
    if (!row) return;
    try { setRangeOnNodeContents(editor, row); } catch(e){}
    }

    function selectTable(editor, table){
    if (!table) return;
    try { setRangeOnNodeContents(editor, table); } catch(e){}
    }

    // --- Nachbarzelle mit Wrap-Around innerhalb *derselben* Tabelle ---
    function getNeighborCell(editor, curCell, dir){
    if (!curCell) return null;
    var table = editor.dom.getParent(curCell, 'table');
    if (!table) return null;

    // Alle Zellen (thead/tbody/tfoot inkl.)
    var cells = editor.dom.select('td,th', table);
    if (!cells || !cells.length) return null;

    var idx = Array.prototype.indexOf.call(cells, curCell);
    if (idx < 0) return null;

    var nextIdx = (idx + (dir > 0 ? 1 : -1) + cells.length) % cells.length;
    return cells[nextIdx];
    }

    // --- Kontext-Resolver: Bleib im Tabellenkontext, auch wenn Cursor auf TR/TABLE liegt ---
    function getTableContext(editor){
    // Nutze sowohl getNode() als auch den Range-Start
    var node = editor.selection.getNode();
    var rng = editor.selection.getRng ? editor.selection.getRng() : null;
    var sc = rng ? rng.startContainer : null;

    // Finde die Tabelle zuverlässig
    var table = editor.dom.getParent(node, 'table') ||
    (sc && editor.dom.getParent(sc, 'table')) || null;
    if (!table) return {};

    // Versuche eine Zelle zu finden
    var cell = editor.dom.getParent(node, 'td,th') ||
    (sc && editor.dom.getParent(sc, 'td,th')) || null;

    // Versuche die Zeile zu finden (falls gebraucht)
    var row = editor.dom.getParent(node, 'tr') ||
    (cell && editor.dom.getParent(cell, 'tr')) ||
    (sc && editor.dom.getParent(sc, 'tr')) || null;

    // Falls keine Zelle ermittelbar: nimm letzte gemerkte Zelle innerhalb derselben Tabelle…
    if (!cell) {
    var st = editor.__sm_cycle && editor.__sm_cycle.lastCell;
    if (st && editor.dom.getParent(st, 'table') === table) {
    cell = st;
    } else {
    // …oder fallback auf die erste Zelle der Tabelle
    var cells = editor.dom.select('td,th', table);
    cell = (cells && cells[0]) ? cells[0] : null;
    // Row ggf. aus dieser Zelle ableiten
    if (!row && cell) row = editor.dom.getParent(cell, 'tr');
    }
    }

    return { table: table, row: row, cell: cell };
    }

    // --- OneNote-Style Ctrl/Cmd + A: 1) Zelle 2) Zeile 3) Tabelle (dann wieder Zelle) ---
    function cycleSelect(editor, cell){
    var row = editor.dom.getParent(cell, 'tr');
    var table = editor.dom.getParent(cell, 'table');
    if (!row || !table) { selectCell(editor, cell); return; }

    editor.__sm_cycle = editor.__sm_cycle || { lastTable: null, lastCell: null, level: 0 };

    // Kontextwechsel? Dann mit Level 1 beginnen
    if (editor.__sm_cycle.lastTable !== table || editor.__sm_cycle.lastCell !== cell) {
    editor.__sm_cycle.level = 1;
    } else {
    editor.__sm_cycle.level = (editor.__sm_cycle.level % 3) + 1; // 1..3
    }
    editor.__sm_cycle.lastTable = table;
    editor.__sm_cycle.lastCell = cell;

    var doSelect = function(){
    if (editor.__sm_cycle.level === 1) selectCell(editor, cell);
    else if (editor.__sm_cycle.level === 2) selectRow(editor, row);
    else selectTable(editor, table);
    };
    // Sofort + verzögert wiederholen (Caret/DOM-Resets überfahren)
    doSelect();
    afterFrames(doSelect, 1);
    afterFrames(doSelect, 2);
    }

    function attach(editor){
    if (!editor || editor.settings.__sm_attached) return;
    editor.settings.__sm_attached = true;

    // --- TAB / SHIFT+TAB: nächste/vorige Zelle + ZellINHALT markieren ---
    function onTab(e){
    if (e.keyCode !== 9) return; // Tab
    var node = editor.selection.getNode();
    var curCell = editor.dom.getParent(node, 'td,th');
    if (!curCell) return; // außerhalb von Tabellen: Standardverhalten beibehalten

    var table = editor.dom.getParent(curCell, 'table');
    if (!table) return;

    var cells = editor.dom.select('td,th', table);
    if (!cells || !cells.length) return;

    // LETZTE ZELLE + TAB (ohne Shift): Zeile selbst einfügen + in neue Zeile springen (Fokus sicher halten)
    if (!e.shiftKey && curCell === cells[cells.length - 1]) {
    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    // aktuelle TR bestimmen
    var curRow = editor.dom.getParent(curCell, 'tr');

    // Zeile *nach* der aktuellen einfügen (TinyMCE Table-Command)
    try { editor.execCommand('mceTableInsertRowAfter'); } catch(_){}

    // Nach Einfügen die nächste Zeile ermitteln und deren erste Zelle fokussieren
    afterFrames(function(){
    try {
    var tableNow = editor.dom.getParent(curCell, 'table') || table;
    var rows = editor.dom.select('tr', tableNow);
    var rowIdx = Array.prototype.indexOf.call(rows, curRow);
    var nextRow = (rowIdx >= 0) ? rows[rowIdx + 1] : null;
    var firstCell = nextRow ? editor.dom.select('td,th', nextRow)[0] : null;

    if (firstCell) {
    // Deine gewohnte Auswahl-Logik beibehalten (ZellINHALT markieren)
    selectCell(editor, firstCell);
    afterFrames(function(){ selectCell(editor, firstCell); }, 1);
    afterFrames(function(){ selectCell(editor, firstCell); }, 2);
    }
    } catch(_){}
    }, 1);

    return;
    }

    // --- Standardfall: Navigation innerhalb der Tabelle (inkl. Wrap-Around) ---
    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    var dir = e.shiftKey ? -1 : 1;
    var target = getNeighborCell(editor, curCell, dir);
    if (!target) return; // theoretisch nie, wegen Wrap-Around

    // sofort + 1-2 Frames später auswählen (überfährt Caret-Resets)
    selectCell(editor, target);
    afterFrames(function(){ selectCell(editor, target); }, 1);
    afterFrames(function(){ selectCell(editor, target); }, 2);
    }

    // --- CTRL/CMD + A im Tabellenkontext: Zelle → Zeile → Tabelle (Zyklus) ---
    function onCtrlA(e){
    var isCtrlA = (e.keyCode === 65) && (e.ctrlKey || e.metaKey);
    if (!isCtrlA) return;

    // Neu: Kontext ermitteln (auch wenn Cursor auf TR/TABLE sitzt)
    var ctx = getTableContext(editor);
    if (!ctx.table) return; // außerhalb von Tabellen: Standardverhalten

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();
    cycleSelect(editor, ctx.cell);
    }

    editor.on('keydown', onTab);
    editor.on('keydown', onCtrlA);

    // Capturing im IFRAME (hilft bei Modals/Overlays)
    editor.on('init', function(){
    try {
    var doc = editor.getDoc();

    var capTab = function(e){
    if (e.keyCode !== 9) return;
    var node = editor.selection.getNode();
    var curCell = editor.dom.getParent(node, 'td,th');
    if (!curCell) return;
    var table = editor.dom.getParent(curCell, 'table');
    if (!table) return;

    var cells = editor.dom.select('td,th', table);
    if (!cells || !cells.length) return;

    // LETZTE ZELLE – Zeile selbst einfügen + in neue Zeile springen (Fokus sicher halten)
    if (!e.shiftKey && curCell === cells[cells.length - 1]) {
    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    var curRow = editor.dom.getParent(curCell, 'tr');
    try { editor.execCommand('mceTableInsertRowAfter'); } catch(_){}

    afterFrames(function(){
    try {
    var tableNow = editor.dom.getParent(curCell, 'table') || table;
    var rows = editor.dom.select('tr', tableNow);
    var rowIdx = Array.prototype.indexOf.call(rows, curRow);
    var nextRow = (rowIdx >= 0) ? rows[rowIdx + 1] : null;
    var firstCell = nextRow ? editor.dom.select('td,th', nextRow)[0] : null;

    if (firstCell) {
    selectCell(editor, firstCell);
    afterFrames(function(){ selectCell(editor, firstCell); }, 1);
    afterFrames(function(){ selectCell(editor, firstCell); }, 2);
    }
    } catch(_){}
    }, 1);

    return;
    }

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();

    var dir = e.shiftKey ? -1 : 1;
    var target = getNeighborCell(editor, curCell, dir);
    if (!target) return;

    selectCell(editor, target);
    afterFrames(function(){ selectCell(editor, target); }, 1);
    afterFrames(function(){ selectCell(editor, target); }, 2);
    };

    var capCtrlA = function(e){
    var isCtrlA = (e.keyCode === 65) && (e.ctrlKey || e.metaKey);
    if (!isCtrlA) return;

    var ctx = getTableContext(editor);
    if (!ctx.table) return;

    e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation();
    cycleSelect(editor, ctx.cell);
    };

    doc.addEventListener('keydown', capTab, true);
    doc.addEventListener('keydown', capCtrlA, true);

    editor.on('remove', function(){
    try {
    doc.removeEventListener('keydown', capTab, true);
    doc.removeEventListener('keydown', capCtrlA, true);
    } catch(_){}
    });
    } catch(_) {}
    });
    }

    // Registrierung als TinyMCE-Plugin
    tinymce.PluginManager.add('selectnextcell', function(editor){
    attach(editor);
    return {};
    });

    // Für später erzeugte Editoren (z. B. Modal)
    tinymce.on('AddEditor', function(e){ try { attach(e.editor); } catch(_){ } });
    })();
    <?php
    exit;
    });



    /* =============================================================================
    * SECTION B — Classic Editor: DFW/Vollhöhe-Buttons ausblenden
    * -----------------------------------------------------------------------------
    * Ziel: Nur UI verstecken. (Keine Änderung an der WP-Option "editor_expand"/DFW.)
    * - TinyMCE (visuell): .mce-wp-dfw (Container + Icon)
    * - Quicktags (Text/HTML): .qt-dfw und alle IDs qt_*_dfw (z. B. qt_content_dfw, qt_wpb_tinymce_content_dfw)
    * ========================================================================== */

    add_action('admin_head', function () {
    if ( ! is_admin() ) return;
    ?>
    <style id="sm-hide-dfw-classic-editor">
    /* (1) Quicktags – DFW-Buttons im Text-/HTML-Modus ausblenden
    - deckt Haupteditor UND WPBakery/Overlays ab
    - Beispiele: #qt_content_dfw, #qt_wpb_tinymce_content_dfw */
    .quicktags-toolbar .qt-dfw,
    [id^="qt_"][id$="_dfw"] {
    display: none !important;
    }

    /* (2) TinyMCE – kompletter DFW-Button-Container + Inhalte ausblenden */
    .mce-toolbar .mce-wp-dfw,
    .mce-toolbar .mce-wp-dfw * {
    display: none !important;
    }

    /* Lücke/Spacing vermeiden */
    .mce-toolbar .mce-wp-dfw {
    width: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    }

    /* Optional enger scopen – nur Haupteditor:
    #wp-content-editor-container .mce-toolbar .mce-wp-dfw { display:none !important; } */
    </style>
    <?php
    });



    /* =============================================================================
    * SECTION C — TinyMCE: Table Toolbar Preset (ersetzt Advanced TinyMCE Config)
    * -----------------------------------------------------------------------------
    * Setzt die table_toolbar wie im ATE-Plugin und stellt sicher, dass das
    * eingebaute 'table'-Plugin aktiv ist.
    * Gilt nur für die Editor-IDs: 'content', 'wpb_tinymce_content'
    * ========================================================================= */

    add_filter('tiny_mce_before_init', function($init, $editor_id){
    // Nur unsere gewünschten Editoren
    $allow = array('content', 'wpb_tinymce_content');
    if (!in_array($editor_id, $allow, true)) return $init;

    // 1) table_toolbar wie definiert
    $init['table_toolbar'] =
    'tableprops tablerowprops tablecellprops | ' .
    'tableinsertrowbefore tableinsertrowafter tabledeleterow | ' .
    'tableinsertcolbefore tableinsertcolafter tabledeletecol | ' .
    'tablemergecells tablesplitcells | tabledelete';

    // 2) Sicherstellen, dass das 'table'-Plugin aktiv ist
    $plugins = isset($init['plugins']) ? $init['plugins'] : '';
    if (is_array($plugins)) {
    if (!in_array('table', $plugins, true)) $plugins[] = 'table';
    $init['plugins'] = $plugins;
    } else {
    $set = array_filter(array_map('trim', preg_split('/[\s,]+/', (string)$plugins)));
    if (!in_array('table', $set, true)) $set[] = 'table';
    $init['plugins'] = implode(' ', $set);
    }

    return $init;
    }, 10, 2);

    /* -----------------------------------------------------------------------------
    * TinyMCE Table-Flyout: Schatten (richtiger Container: .mce-floatpanel)
    * -------------------------------------------------------------------------- */
    add_action('admin_head', function () {
    ?>
    <style id="sm-tinymce-flyout-shadow">
    /* Flyout-Container */
    .mce-floatpanel.mce-tinymce-inline {
    box-shadow:
    0 1px 2px rgba(0,0,0,0.25),
    0 4px 10px rgba(0,0,0,0.12),
    0 12px 24px rgba(0,0,0,0.10);
    border-radius: 6px; /* dezente Abrundung */
    border-color: #5c5c5c;
    border-style: solid;
    overflow: visible; /* falls TinyMCE clipped */
    }
    </style>
    <?php
    });
    Plugin Author Andrew Ozz

    (@azaozz)

    Hi @synormedia, good job making a WP plugin!

    would it be possible to check this and build such a function directly into “Advanced Editor Tools”

    Frankly I think it would be better as it is now: a separate WP plugin. You can add it to the WordPress.org plugin repository.

    what you think about the first script for tables. I can’t judge how good the quality of the PHP code is

    TinyMCE (the old version that’s in WP) has a way to load its own plugins. Unfortunately the documentation for TinyMCE 4.x is not on a website any more, only the code is on Github. It will be harder to make the JS into a proper TinyMCE plugin. But if the plugin works satisfactory as it is at the moment, perhaps it would be good to release it so others can use it too 🙂

Viewing 2 replies - 1 through 2 (of 2 total)

You must be logged in to reply to this topic.