release: v1.0.3
Fix double rendering when "Easy Table of Contents" (or any plugin that re-runs the_content filters) is active. - Shortcode re-entry guard via md5 fingerprint of post_id + resolved render settings — silently skips duplicate passes while preserving legitimate multi-embed with different attributes - Wrapper DOM id is now unique per instance (breznflow-wrap-<POST>-<N>), enabling multiple embeds of the same workflow in one post - Share-anchor span id="breznflow-<POST>" emitted only on the first instance per post to keep the DOM valid and existing deep-links working - View counter increments moved after the dedupe check so re-entrant scans do not overcount views - JS renderer tracks mounted containers in a WeakSet — defensive guard that catches any duplicates server-side dedupe might miss - readme: add Learn more section with website, FAQ, and demo links Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
066414724b
commit
c6e07d51c9
4 changed files with 91 additions and 8 deletions
|
|
@ -1520,9 +1520,18 @@
|
|||
function init() {
|
||||
if (typeof breznflowData === 'undefined' || !Array.isArray(breznflowData)) return;
|
||||
|
||||
// Defensive guard: if any filter re-ran the shortcode, breznflowData may
|
||||
// contain duplicate entries pointing at the same container. Mounting twice
|
||||
// would stack two full UIs via appendChild. Track mounted containers and
|
||||
// skip repeats — independent of server-side dedupe.
|
||||
const mounted = new WeakSet();
|
||||
|
||||
for (const data of breznflowData) {
|
||||
const container = document.getElementById('breznflow-wrap-' + data.id);
|
||||
const domId = data.dom_id || ('breznflow-wrap-' + data.id);
|
||||
const container = document.getElementById(domId);
|
||||
if (!container) continue;
|
||||
if (mounted.has(container)) continue;
|
||||
mounted.add(container);
|
||||
|
||||
try {
|
||||
const renderer = new BreznFlowRenderer(data);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* Plugin Name: BreznFlow
|
||||
* Plugin URI: https://breznflow.com/
|
||||
* Description: Display n8n automation workflows with an interactive SVG diagram, node detail panel, and sensitive data masking.
|
||||
* Version: 1.0.2
|
||||
* Version: 1.0.3
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.0
|
||||
* Author: NoSchmarrn.dev
|
||||
|
|
@ -23,7 +23,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
exit;
|
||||
}
|
||||
|
||||
define( 'BREZNFLOW_VERSION', '1.0.2' );
|
||||
define( 'BREZNFLOW_VERSION', '1.0.3' );
|
||||
define( 'BREZNFLOW_FILE', __FILE__ );
|
||||
define( 'BREZNFLOW_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'BREZNFLOW_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
|
|
|||
|
|
@ -39,6 +39,31 @@ class Shortcode {
|
|||
*/
|
||||
private static bool $assets_enqueued = false;
|
||||
|
||||
/**
|
||||
* Monotonic counter for unique wrapper DOM IDs within a request.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private static int $instance_counter = 0;
|
||||
|
||||
/**
|
||||
* Fingerprints of already-rendered shortcode invocations (post_id + resolved settings).
|
||||
* Used to silently skip re-entrant passes triggered by plugins like Easy Table of Contents,
|
||||
* which run the_content filters a second time to scan for headings.
|
||||
*
|
||||
* @var array<string, true>
|
||||
*/
|
||||
private static array $fingerprints = array();
|
||||
|
||||
/**
|
||||
* Post IDs that have already emitted the share-anchor span in this request.
|
||||
* The anchor id="breznflow-<POSTID>" must be unique in the DOM, so only the
|
||||
* first instance per post emits it; later instances get a wrapper only.
|
||||
*
|
||||
* @var array<int, true>
|
||||
*/
|
||||
private static array $anchored_posts = array();
|
||||
|
||||
/**
|
||||
* Registers the shortcode and footer hook.
|
||||
*
|
||||
|
|
@ -154,7 +179,27 @@ class Shortcode {
|
|||
|
||||
$max_code_lines = '' !== $atts['max_code_lines'] ? max( 1, (int) $atts['max_code_lines'] ) : (int) $settings['max_code_lines'];
|
||||
|
||||
// Increment view count.
|
||||
// Re-entry guard: plugins like Easy Table of Contents run the_content filters
|
||||
// twice to scan for headings, which re-executes this shortcode. The returned
|
||||
// HTML is often deduplicated by the outer filter, but the static $render_queue
|
||||
// below would still accumulate a duplicate entry and the JS renderer would
|
||||
// mount the workflow twice onto the same container. Hashing post_id + fully
|
||||
// resolved render settings lets us silently skip those re-entrant passes
|
||||
// while still allowing legitimate multi-embed (same post, different atts).
|
||||
$fingerprint = md5(
|
||||
(string) $post_id . '|' .
|
||||
$mode . '|' . (string) $zoom . '|' . (string) $show_title . '|' .
|
||||
(string) $show_infobox . '|' . (string) $allow_download . '|' .
|
||||
(string) $show_minimap . '|' . (string) $max_code_lines . '|' .
|
||||
$theme . '|' . (string) $allow_share . '|' . (string) $allow_embed . '|' .
|
||||
(string) $allow_get_json
|
||||
);
|
||||
if ( isset( self::$fingerprints[ $fingerprint ] ) ) {
|
||||
return '';
|
||||
}
|
||||
self::$fingerprints[ $fingerprint ] = true;
|
||||
|
||||
// Increment view count (after dedupe check, so re-entrant scans don't overcount).
|
||||
ViewCounter::increment( $post_id );
|
||||
|
||||
// Enqueue assets once.
|
||||
|
|
@ -163,9 +208,15 @@ class Shortcode {
|
|||
self::$assets_enqueued = true;
|
||||
}
|
||||
|
||||
// Unique per-instance DOM id so multiple embeds of the same workflow coexist.
|
||||
++self::$instance_counter;
|
||||
$instance_id = $post_id . '-' . self::$instance_counter;
|
||||
$dom_id = 'breznflow-wrap-' . $instance_id;
|
||||
|
||||
// Queue workflow data for JS output.
|
||||
self::$render_queue[] = array(
|
||||
'id' => $post_id,
|
||||
'dom_id' => $dom_id,
|
||||
'workflow' => $workflow,
|
||||
'mode' => $mode,
|
||||
'zoom' => $zoom ? $zoom : 100,
|
||||
|
|
@ -209,10 +260,18 @@ class Shortcode {
|
|||
);
|
||||
$html .= InfoBoxBuilder::build( $categorized );
|
||||
} else {
|
||||
// Anchor span is only emitted for the first instance per post so the
|
||||
// id="breznflow-<POSTID>" target stays unique; legacy deep-links keep working.
|
||||
$anchor_html = '';
|
||||
if ( ! isset( self::$anchored_posts[ $post_id ] ) ) {
|
||||
$anchor_html = '<span id="breznflow-' . esc_attr( (string) $post_id ) . '" '
|
||||
. 'aria-hidden="true" style="position:absolute;top:-60px;left:0"></span>';
|
||||
self::$anchored_posts[ $post_id ] = true;
|
||||
}
|
||||
|
||||
$html .= '<div style="position:relative">'
|
||||
. '<span id="breznflow-' . esc_attr( (string) $post_id ) . '" '
|
||||
. 'aria-hidden="true" style="position:absolute;top:-60px;left:0"></span>'
|
||||
. '<div id="breznflow-wrap-' . esc_attr( (string) $post_id ) . '" '
|
||||
. $anchor_html
|
||||
. '<div id="' . esc_attr( $dom_id ) . '" '
|
||||
. 'class="breznflow-embed" data-id="' . esc_attr( (string) $post_id ) . '">'
|
||||
. '</div>'
|
||||
. '</div>';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Contributors: mifupadev
|
|||
Tags: n8n, workflow, automation, diagram, svg
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.9
|
||||
Stable tag: 1.0.2
|
||||
Stable tag: 1.0.3
|
||||
Requires PHP: 8.0
|
||||
License: GPL-2.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
|
@ -16,6 +16,12 @@ BreznFlow turns n8n workflow JSON exports into interactive, zoomable SVG diagram
|
|||
|
||||
The plugin was built for mifupa.com, a personal blog where n8n automations are documented regularly. Screenshots get outdated. Embedding the n8n editor is impractical. BreznFlow solves this: one shortcode, one interactive diagram, zero external dependencies.
|
||||
|
||||
= Learn more =
|
||||
|
||||
* Website: <a href="https://breznflow.com/">breznflow.com</a>
|
||||
* FAQ: <a href="https://breznflow.com/faq.html">breznflow.com/faq</a>
|
||||
* Live demo: <a href="https://breznflow.com/demo.html">breznflow.com/demo</a>
|
||||
|
||||
= At a glance =
|
||||
|
||||
* Renders n8n workflow JSON as interactive SVG diagrams with zoom, pan, and click
|
||||
|
|
@ -158,6 +164,12 @@ For security, requests to private and internal network addresses are blocked: lo
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.3 =
|
||||
* Fixed double rendering when "Easy Table of Contents" (or any plugin that re-runs `the_content` filters) is active. The shortcode now silently deduplicates re-entrant invocations via a fingerprint of post id + resolved render settings.
|
||||
* Wrapper `id` is now unique per instance (`breznflow-wrap-<POSTID>-<COUNTER>`), enabling multiple embeds of the same workflow with different attributes in one post.
|
||||
* Anchor span `id="breznflow-<POSTID>"` is emitted only for the first instance per post to keep the DOM valid and preserve existing share links.
|
||||
* Renderer now guards against mounting twice onto the same container.
|
||||
|
||||
= 1.0.2 =
|
||||
* Fixed WordPress.org plugin review issues.
|
||||
* Embed page now uses wp_enqueue_style/wp_enqueue_script with wp_head/wp_footer instead of direct HTML tags.
|
||||
|
|
@ -196,6 +208,9 @@ For security, requests to private and internal network addresses are blocked: lo
|
|||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 1.0.3 =
|
||||
Fixes duplicate workflow rendering when "Easy Table of Contents" is active, and enables reliable multi-embed of the same workflow in one post.
|
||||
|
||||
= 1.0.2 =
|
||||
Fixes WordPress.org plugin review issues: proper asset enqueueing, nonce verification, input sanitization, and output escaping.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue