Plugin Directory

Changeset 3480837


Ignore:
Timestamp:
03/12/2026 06:24:58 AM (3 weeks ago)
Author:
noricku
Message:

Deploy version 2.7.4

  • Updated to version 2.7.4
  • Package created via automated build process
  • Validated package contents and size

🤖 Automated deployment via wporg-deploy.sh

Location:
markdown-renderer-for-github/trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • markdown-renderer-for-github/trunk/assets/js/gfmr-diagrams.js

    r3447454 r3480837  
    384384
    385385                if (renderResult && renderResult.svg) {
    386                     // Apply SVG result
     386                    // Apply SVG result (zoom setup deferred to inside rAF to fix race condition)
    387387                    this.applySvgResult(element, renderResult.svg, diagramId, options);
    388 
    389                     // Add zoom functionality
    390                     if (options.enableZoom || this.utils.getConfig('mermaid.zoom', false)) {
    391                         this.enableZoomForDiagram(element, diagramId);
    392                     }
    393388
    394389                    return renderResult;
     
    534529            }
    535530
     531            // Fit SVG to container width if it overflows (prevents horizontal scroll)
     532            // Zoom setup is deferred into the rAF callback to fix race condition on gfmrFitScale
     533            const svgEl = wrapper.querySelector('svg');
     534            const enableZoom = options.enableZoom || this.utils.getConfig('mermaid.zoom', false);
     535            if (svgEl) {
     536                // preWidth: pass already-measured width to avoid re-measuring (H2)
     537                const doFit = (preWidth) => {
     538                    const containerWidth =
     539                        preWidth || wrapper.parentElement?.offsetWidth || wrapper.offsetWidth;
     540                    if (!containerWidth) return;
     541                    // Prefer viewBox to avoid double-scaling when getBoundingClientRect returns scaled values
     542                    const viewBox = svgEl.viewBox?.baseVal;
     543                    let svgNaturalWidth, svgNaturalHeight;
     544                    if (viewBox && viewBox.width && viewBox.height) {
     545                        svgNaturalWidth = viewBox.width;
     546                        svgNaturalHeight = viewBox.height;
     547                        // Store for applyZoom usage (M3)
     548                        if (!svgEl.dataset.gfmrNaturalWidth) {
     549                            svgEl.dataset.gfmrNaturalWidth = svgNaturalWidth;
     550                            svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     551                        }
     552                    } else if (svgEl.dataset.gfmrNaturalWidth) {
     553                        // Use stored values on re-calls to avoid double-scaling
     554                        svgNaturalWidth = parseFloat(svgEl.dataset.gfmrNaturalWidth);
     555                        svgNaturalHeight = parseFloat(svgEl.dataset.gfmrNaturalHeight);
     556                    } else {
     557                        const rect = svgEl.getBoundingClientRect(); // H1: single call
     558                        svgNaturalWidth = rect.width;
     559                        svgNaturalHeight = rect.height;
     560                        svgEl.dataset.gfmrNaturalWidth = svgNaturalWidth;
     561                        svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     562                    }
     563                    if (svgNaturalWidth && svgNaturalWidth > containerWidth) {
     564                        const scale = containerWidth / svgNaturalWidth;
     565                        svgEl.style.transform = `scale(${scale})`;
     566                        svgEl.style.transformOrigin = 'top left';
     567                        // Left-align SVG so transform-origin matches visual origin
     568                        svgEl.style.setProperty('margin', '0', 'important');
     569                        wrapper.style.height = `${svgNaturalHeight * scale}px`;
     570                        // Use setProperty to override CSS !important rules (M1)
     571                        wrapper.style.setProperty('overflow', 'hidden', 'important');
     572                        svgEl.dataset.gfmrFitScale = scale;
     573                        svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     574                    }
     575                    // Setup zoom after fit scale is stored (fixes rAF race condition)
     576                    if (enableZoom) {
     577                        this.enableZoomForDiagram(wrapper, diagramId);
     578                    }
     579                };
     580                requestAnimationFrame(() => {
     581                    const containerWidth =
     582                        wrapper.parentElement?.offsetWidth || wrapper.offsetWidth;
     583                    if (!containerWidth) {
     584                        // Container not visible yet - retry when it becomes visible
     585                        const resizeObserver = new ResizeObserver((entries) => {
     586                            for (const entry of entries) {
     587                                if (entry.contentRect.width > 0) {
     588                                    resizeObserver.disconnect();
     589                                    doFit(); // fresh measurement after becoming visible
     590                                }
     591                            }
     592                        });
     593                        resizeObserver.observe(wrapper);
     594                    } else {
     595                        doFit(containerWidth); // H2: reuse already-measured width
     596                    }
     597                });
     598            } else if (enableZoom) {
     599                // No SVG yet but zoom was requested - set up controls without fitting
     600                requestAnimationFrame(() => this.enableZoomForDiagram(wrapper, diagramId));
     601            }
     602
    536603            this.utils.debug(`Diagram ${diagramId} rendered successfully`);
    537604        }
     
    596663        enableZoomForDiagram(wrapper, diagramId) {
    597664            if (!wrapper || this.zoomConfig.enabled === false) {
     665                return;
     666            }
     667
     668            // Guard against duplicate setup (M2: ResizeObserver may fire doFit multiple times)
     669            if (wrapper.querySelector('.gfmr-mermaid-zoom-controls')) {
    598670                return;
    599671            }
     
    640712            controls.querySelector('.zoom-in').addEventListener('click', () => {
    641713                currentZoom = Math.min(currentZoom * 1.2, this.zoomConfig.maxZoom);
    642                 this.applyZoom(svg, controls, currentZoom);
     714                this.applyZoom(svg, controls, currentZoom, wrapper); // M4: pass cached wrapper
    643715            });
    644716
     
    646718            controls.querySelector('.zoom-out').addEventListener('click', () => {
    647719                currentZoom = Math.max(currentZoom / 1.2, this.zoomConfig.minZoom);
    648                 this.applyZoom(svg, controls, currentZoom);
     720                this.applyZoom(svg, controls, currentZoom, wrapper); // M4: pass cached wrapper
    649721            });
    650722
     
    652724            controls.querySelector('.zoom-reset').addEventListener('click', () => {
    653725                currentZoom = 1.0;
    654                 this.applyZoom(svg, controls, currentZoom);
     726                this.applyZoom(svg, controls, currentZoom, wrapper); // M4: pass cached wrapper
    655727            });
    656728
     
    661733                currentZoom = Math.max(this.zoomConfig.minZoom,
    662734                               Math.min(this.zoomConfig.maxZoom, currentZoom * delta));
    663                 this.applyZoom(svg, controls, currentZoom);
     735                this.applyZoom(svg, controls, currentZoom, wrapper); // M4: pass cached wrapper
    664736            });
    665737        }
     
    667739        /**
    668740         * Apply zoom
    669          */
    670         applyZoom(svg, controls, zoom) {
    671             svg.style.transform = `scale(${zoom})`;
    672             svg.style.transformOrigin = 'center center';
     741         * @param {SVGElement} svg
     742         * @param {Element} controls
     743         * @param {number} zoom
     744         * @param {Element} [cachedWrapper] - pre-resolved wrapper to avoid DOM traversal on hot path (M4)
     745         */
     746        applyZoom(svg, controls, zoom, cachedWrapper) {
     747            // Compose with fit scale so zoom is relative to the fitted size
     748            const fitScale = parseFloat(svg.dataset.gfmrFitScale) || 1.0;
     749            const totalScale = fitScale * zoom;
     750            svg.style.transform = `scale(${totalScale})`;
     751            svg.style.transformOrigin = 'top left';
     752
     753            // Update wrapper height so zoomed content doesn't overflow or get clipped
     754            const naturalHeight = parseFloat(svg.dataset.gfmrNaturalHeight);
     755            if (naturalHeight) {
     756                // M4: use cachedWrapper from closure to avoid closest() on every zoom event
     757                const wrapperEl = cachedWrapper || svg.closest('.gfmr-mermaid-wrapper');
     758                if (wrapperEl) {
     759                    wrapperEl.style.height = `${naturalHeight * totalScale}px`;
     760                }
     761            }
    673762
    674763            const zoomPercent = Math.round(zoom * 100);
  • markdown-renderer-for-github/trunk/assets/js/gfmr-main.js

    r3478700 r3480837  
    14161416            // Keep SVG at natural size (removing max-width prevents text clipping)
    14171417            svgElement.style.height = "auto";
     1418
     1419            // Fit SVG to container width if it overflows (prevents horizontal scroll)
     1420            // preWidth: pass already-measured width to avoid re-measuring (H2)
     1421            const doFit = (preWidth) => {
     1422              const width =
     1423                preWidth || outerContainer.parentElement?.offsetWidth ||
     1424                outerContainer.offsetWidth;
     1425              if (!width) return;
     1426              // Prefer viewBox to avoid double-scaling when getBoundingClientRect returns scaled values
     1427              const viewBox = svgElement.viewBox?.baseVal;
     1428              let svgNaturalWidth, svgNaturalHeight;
     1429              if (viewBox && viewBox.width && viewBox.height) {
     1430                svgNaturalWidth = viewBox.width;
     1431                svgNaturalHeight = viewBox.height;
     1432                // Store for applyZoom usage and re-call protection (M3)
     1433                if (!svgElement.dataset.gfmrNaturalWidth) {
     1434                  svgElement.dataset.gfmrNaturalWidth = svgNaturalWidth;
     1435                  svgElement.dataset.gfmrNaturalHeight = svgNaturalHeight;
     1436                }
     1437              } else if (svgElement.dataset.gfmrNaturalWidth) {
     1438                // Use stored values on re-calls to avoid double-scaling
     1439                svgNaturalWidth = parseFloat(svgElement.dataset.gfmrNaturalWidth);
     1440                svgNaturalHeight = parseFloat(svgElement.dataset.gfmrNaturalHeight);
     1441              } else {
     1442                const rect = svgElement.getBoundingClientRect(); // H1: single call
     1443                svgNaturalWidth = rect.width;
     1444                svgNaturalHeight = rect.height;
     1445                svgElement.dataset.gfmrNaturalWidth = svgNaturalWidth;
     1446                svgElement.dataset.gfmrNaturalHeight = svgNaturalHeight;
     1447              }
     1448              if (svgNaturalWidth && svgNaturalWidth > width) {
     1449                const scale = width / svgNaturalWidth;
     1450                svgElement.style.transform = `scale(${scale})`;
     1451                svgElement.style.transformOrigin = "top left";
     1452                innerContainer.style.height = `${svgNaturalHeight * scale}px`;
     1453                // Use setProperty to override CSS !important rules
     1454                outerContainer.style.setProperty("justify-content", "flex-start", "important");
     1455                outerContainer.style.setProperty("overflow", "hidden", "important");
     1456                svgElement.dataset.gfmrFitScale = scale;
     1457                svgElement.dataset.gfmrNaturalHeight = svgNaturalHeight;
     1458              }
     1459            };
     1460            requestAnimationFrame(() => {
     1461              const containerWidth =
     1462                outerContainer.parentElement?.offsetWidth ||
     1463                outerContainer.offsetWidth;
     1464              if (!containerWidth) {
     1465                // Container not visible yet - retry when it becomes visible
     1466                const resizeObserver = new ResizeObserver((entries) => {
     1467                  for (const entry of entries) {
     1468                    if (entry.contentRect.width > 0) {
     1469                      resizeObserver.disconnect();
     1470                      doFit(); // fresh measurement after becoming visible
     1471                    }
     1472                  }
     1473                });
     1474                resizeObserver.observe(outerContainer);
     1475              } else {
     1476                doFit(containerWidth); // H2: reuse already-measured width
     1477              }
     1478            });
    14181479          }
    14191480
  • markdown-renderer-for-github/trunk/assets/js/gfmr-multilingual.js

    r3480739 r3480837  
    233233                if ( this.labelStyle === 'flag' ) {
    234234                    btn.className += ' gfmr-lang-btn--flag-only';
    235                     // スクリーンリーダー向けに人間が読める言語名を使用(WCAG準拠)
     235                    // Use human-readable language name for screen readers (WCAG compliance)
    236236                    btn.setAttribute( 'aria-label', this.langNameMap[ lang ] || lang.toUpperCase() );
    237237                }
  • markdown-renderer-for-github/trunk/assets/js/gfmr-plantuml-handler.js

    r3480739 r3480837  
    158158            const utf8 = this.encodeUtf8(content);
    159159
    160             // pako が利用可能なら DEFLATE 圧縮 + PlantUML base64
     160            // Use DEFLATE compression + PlantUML base64 if pako is available
    161161            if (global.pako && global.pako.deflateRaw) {
    162162                const compressed = global.pako.deflateRaw(utf8, { level: 9 });
     
    293293                        svgPromise = this.fetchSvg(url, options);
    294294                    }
    295                     // .catch()は同期チェーンのため、フォールバック込みのPromise全体をセット
     295                    // Store the entire promise (including fallback) since .catch() is part of the sync chain
    296296                    this.inflightRequests.set(cacheKey, svgPromise);
    297297                }
     
    372372                        }
    373373                    } catch (e) {
    374                         // JSONパース失敗(HTMLエラーページなど)はデフォルトメッセージを使用
     374                        // JSON parse failure (e.g. HTML error page) — use default message
    375375                    }
    376376                    throw new Error(errorMsg);
  • markdown-renderer-for-github/trunk/assets/js/gfmr-ssr-client.js

    r3435320 r3480837  
    127127
    128128    /**
     129     * Fit SVG to container width to prevent horizontal scrolling.
     130     * Uses transform: scale() so text is never clipped.
     131     *
     132     * @param {Element} container - The .gfmr-mermaid-container element
     133     */
     134    function fitSvgToContainer(container) {
     135        const svgEl = container.querySelector('svg');
     136        if (!svgEl) return;
     137
     138        // preWidth: pass already-measured width to avoid re-measuring (H2)
     139        const doFit = (preWidth) => {
     140            const containerWidth =
     141                preWidth || container.parentElement?.offsetWidth || container.offsetWidth;
     142            if (!containerWidth) return;
     143            // Prefer viewBox to avoid double-scaling when getBoundingClientRect returns scaled values
     144            const viewBox = svgEl.viewBox?.baseVal;
     145            let svgNaturalWidth, svgNaturalHeight;
     146            if (viewBox && viewBox.width && viewBox.height) {
     147                svgNaturalWidth = viewBox.width;
     148                svgNaturalHeight = viewBox.height;
     149                // Store for applyZoom usage and re-call protection (M3)
     150                if (!svgEl.dataset.gfmrNaturalWidth) {
     151                    svgEl.dataset.gfmrNaturalWidth = svgNaturalWidth;
     152                    svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     153                }
     154            } else if (svgEl.dataset.gfmrNaturalWidth) {
     155                // Use stored values on re-calls to avoid double-scaling
     156                svgNaturalWidth = parseFloat(svgEl.dataset.gfmrNaturalWidth);
     157                svgNaturalHeight = parseFloat(svgEl.dataset.gfmrNaturalHeight);
     158            } else {
     159                const rect = svgEl.getBoundingClientRect(); // H1: single call
     160                svgNaturalWidth = rect.width;
     161                svgNaturalHeight = rect.height;
     162                svgEl.dataset.gfmrNaturalWidth = svgNaturalWidth;
     163                svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     164            }
     165            if (svgNaturalWidth && svgNaturalWidth > containerWidth) {
     166                const scale = containerWidth / svgNaturalWidth;
     167                svgEl.style.transform = `scale(${scale})`;
     168                svgEl.style.transformOrigin = 'top left';
     169                // innerContainer may wrap the SVG — find it or use container itself
     170                const inner = container.querySelector('.gfmr-mermaid-inner-container') || container;
     171                inner.style.height = `${svgNaturalHeight * scale}px`;
     172                // Use setProperty to override CSS !important rules on gfmr-mermaid-container
     173                container.style.setProperty('justify-content', 'flex-start', 'important');
     174                container.style.setProperty('overflow', 'hidden', 'important');
     175                svgEl.dataset.gfmrFitScale = scale;
     176                svgEl.dataset.gfmrNaturalHeight = svgNaturalHeight;
     177            }
     178        };
     179
     180        requestAnimationFrame(() => {
     181            const containerWidth =
     182                container.parentElement?.offsetWidth || container.offsetWidth;
     183            if (!containerWidth) {
     184                // Container not visible yet - retry when it becomes visible
     185                const resizeObserver = new ResizeObserver((entries) => {
     186                    for (const entry of entries) {
     187                        if (entry.contentRect.width > 0) {
     188                            resizeObserver.disconnect();
     189                            doFit(); // fresh measurement after becoming visible
     190                        }
     191                    }
     192                });
     193                resizeObserver.observe(container);
     194            } else {
     195                doFit(containerWidth); // H2: reuse already-measured width
     196            }
     197        });
     198    }
     199
     200    /**
     201     * Apply fitting to all SSR-cached diagrams on the page.
     202     */
     203    function fitSsrDiagrams() {
     204        const ssrDiagrams = document.querySelectorAll('.gfmr-mermaid-container[data-ssr="true"]');
     205        ssrDiagrams.forEach(fitSvgToContainer);
     206    }
     207
     208    /**
    129209     * Initialize SSR client
    130210     */
     
    137217
    138218        console.log('[GFMR SSR] Initializing SSR client');
     219
     220        // Apply fit to already-cached SSR diagrams (served as static HTML)
     221        fitSsrDiagrams();
    139222
    140223        // Process SSR-pending diagrams
  • markdown-renderer-for-github/trunk/changelog.txt

    r3480772 r3480837  
    77
    88## [Unreleased]
     9
     10## [2.7.4] - 2026-03-12
     11### Fixed
     12- fix SVG fit-to-container and zoom quality issues
    913
    1014## [2.7.3] - 2026-03-12
     
    9296- Implement Chart.js integration for diagram rendering
    9397### Fixed
    94 - Mermaid図の文字見切れを修正(useMaxWidth統一・CSSスクロール責務改善)
     98- Fix Mermaid diagram text clipping (unify useMaxWidth and improve CSS scroll responsibility)
    9599- Unify theme settings retrieval between editor and frontend
    96100- Convert remaining Japanese entries to English in changelog.txt
  • markdown-renderer-for-github/trunk/includes/class-gfmr-language-switcher.php

    r3480739 r3480837  
    240240        }
    241241
    242         // Dropdown では flag のみ表示はブラウザ依存の問題が大きいため flag_text にフォールバック
     242        // In dropdown mode, flag-only display has cross-browser issues, so fall back to flag_text
    243243        if ( 'dropdown' === $display_mode && 'flag' === $label_style ) {
    244244            $label_style = 'flag_text';
  • markdown-renderer-for-github/trunk/markdown-renderer-for-github.php

    r3480772 r3480837  
    44 * Plugin URI:        https://github.com/wakalab/markdown-renderer-for-github
    55 * Description:       Renders GFM (GitHub Flavored Markdown) content beautifully on the front end using JavaScript libraries. It supports syntax highlighting for code blocks and diagram rendering with Mermaid.js.
    6  * Version:           2.7.3
     6 * Version:           2.7.4
    77 * Requires at least: 6.5
    88 * Requires PHP:      8.1
  • markdown-renderer-for-github/trunk/readme.txt

    r3480772 r3480837  
    66Tested up to: 6.9.1
    77Requires PHP: 8.1
    8 Stable tag: 2.7.3
     8Stable tag: 2.7.4
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    142142
    143143== Changelog ==
     144
     145= 2.7.4 =
     146* fix SVG fit-to-container and zoom quality issues
    144147
    145148= 2.7.3 =
     
    206209* Filter out developer-only commits from changelog
    207210* Implement Chart.js integration for diagram rendering
    208 * Mermaid図の文字見切れを修正(useMaxWidth統一・CSSスクロール責務改善)
     211* Fix Mermaid diagram text clipping (unify useMaxWidth and improve CSS scroll responsibility)
    209212* Unify theme settings retrieval between editor and frontend
    210213* Convert remaining Japanese entries to English in changelog.txt
     
    298301* Add coverage-js to gitignore
    299302
    300 = 1.11.1 =
    301 * Fix Rust syntax highlighting across editor and language detection
    302 
    303303== Upgrade Notice ==
    304304
Note: See TracChangeset for help on using the changeset viewer.