Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [v3.9.6] - 2026-11-10

- Fix: keep the block editor's default styles for classic themes with editor styles (WP 7.0)
- Fix: rebuild TinyMCE editors whose iframe reloads when the block editor moves the meta boxes (blank editors in Firefox)
- Fix: skip the TinyMCE caret restore when the editor has no selection

## [v3.9.5] - 2026-10-30

- Fix: don't propagate all query params to paginator params
Expand Down
1 change: 1 addition & 0 deletions config/core.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'admin' => [
OWC\OpenPub\Base\Settings\SettingsServiceProvider::class,
OWC\OpenPub\Base\Expired\ExpiredServiceProvider::class,
OWC\OpenPub\Base\TinyMce\TinyMceServiceProvider::class,
],

'cli' => [
Expand Down
64 changes: 64 additions & 0 deletions js/editor-selection-guard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* The block editor moves the meta box forms into its own layout, and moving an
* iframe reloads it. When that happens after TinyMCE wrote its document (Firefox,
* or a slow load elsewhere), TinyMCE keeps the old one: the editor shows blank,
* getSelection() is null, and core's caret restore after a Visual/Code switch
* (focusHTMLBookmarkInVisualEditor() in wp-admin/js/editor.js) throws from
* select()/setRng(), taking marker cleanup and editor.save() with it.
* See https://core.trac.wordpress.org/ticket/62450.
*
* Rebuild the editor on its textarea when its iframe reloads, and skip
* select()/setRng() while there is no selection.
*/
tinymce.PluginManager.add('owcselectionguard', function (editor) {
// PreInit, not init: the wordpress plugin's init handler fires tinymce-editor-init, which can
// already restore the caret, and external plugins register their handlers after it. An iframe
// that reloads before init also leaves TinyMCE waiting on stylesheets in the dead document, so
// init never fires.
editor.on('PreInit', function () {
var selection = editor.selection;
var select = selection.select.bind(selection);
var setRng = selection.setRng.bind(selection);
var iframe = editor.iframeElement;

selection.select = function (node, content) {
return selection.getSel() === null ? node : select(node, content);
};

selection.setRng = function (rng, forward) {
if (selection.getSel() !== null) {
setRng(rng, forward);
}
};

if (!iframe) {
return;
}

iframe.addEventListener('load', function () {
if (editor.removed || iframe.contentDocument === editor.getDoc()) {
return;
}

var id = editor.id;
var settings = editor.settings;
var wrap = document.getElementById('wp-' + id + '-wrap');
var mode = wrap && wrap.classList.contains('html-active') ? 'html' : 'tmce';

// remove() saves first; for a hidden editor the wordpress plugin keeps the textarea value.
editor.remove();
tinymce.init(settings);

var rebuilt = tinymce.get(id);

if (rebuilt) {
rebuilt.on('init', function () {
// After init_instance_callback, where Meta Box switches to the mode the field rendered in.
setTimeout(function () {
switchEditors.go(id, mode);
});
});
}
});
});
});
2 changes: 1 addition & 1 deletion openpub-base.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Plugin Name: Yard | OpenPub Base
* Plugin URI: https://www.openwebconcept.nl/
* Description: Acts as foundation for other OpenPub related content plugins. This plugin implements actions to allow for other plugins to add and/or change Custom Posttypes, Metaboxes, Taxonomies, en Posts 2 posts relations.
* Version: 3.9.5
* Version: 3.9.6
* Author: Yard | Digital Agency
* Author URI: https://www.yard.nl/
* License: GPL-3.0
Expand Down
26 changes: 26 additions & 0 deletions src/Base/Admin/AdminServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,32 @@ public function register(): void
$this->plugin->loader->addFilter('post_type_link', $this, 'filterPostLink', 10, 4);
$this->plugin->loader->addFilter('preview_post_link', $this, 'filterPreviewLink', 10, 2);
$this->plugin->loader->addAction('rest_prepare_openpub-item', $this, 'filterPreviewInNewTabLink', 10, 2);
$this->plugin->loader->addFilter('block_editor_settings_all', $this, 'filterEditorStyles', 20);
}

/**
* Keep the block editor's default styles for classic themes with editor styles (WP 7.0).
*/
public function filterEditorStyles(array $settings): array
{
// Block themes manage their own editor styles.
if (function_exists('wp_theme_has_theme_json') && wp_theme_has_theme_json()) {
return $settings;
}

if (empty($settings['styles']) || ! is_array($settings['styles'])) {
return $settings;
}

// While any style is typed 'theme' (and "Use theme styles" is on), the editor drops its default and layout styles.
foreach ($settings['styles'] as &$style) {
if ('theme' === ($style['__unstableType'] ?? null)) {
$style['__unstableType'] = 'base-layout';
}
}
unset($style);

return $settings;
}

/**
Expand Down
10 changes: 9 additions & 1 deletion src/Base/Foundation/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
class Plugin
{
public const NAME = 'openpub-base';
public const VERSION = '3.9.5';
public const VERSION = '3.9.6';

protected string $rootPath;
public Config $config;
Expand Down Expand Up @@ -157,4 +157,12 @@ public function getRootPath(): string
{
return $this->rootPath;
}

/**
* Return the url of the plugin.
*/
public function getPluginUrl(): string
{
return \plugins_url($this->getName());
}
}
21 changes: 21 additions & 0 deletions src/Base/TinyMce/TinyMceServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace OWC\OpenPub\Base\TinyMce;

use OWC\OpenPub\Base\Foundation\ServiceProvider;

class TinyMceServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->plugin->loader->addFilter('mce_external_plugins', $this, 'addSelectionGuard');
}

public function addSelectionGuard(array $plugins): array
{
// A TinyMCE plugin rides along in tinyMCEPreInit.mceInit, so Meta Box clones get it too. See the script.
$plugins['owcselectionguard'] = $this->plugin->getPluginUrl() . '/js/editor-selection-guard.js?ver=' . $this->plugin->getVersion();

return $plugins;
}
}
Loading