Plugin Directory

Changeset 3365629


Ignore:
Timestamp:
09/22/2025 08:23:48 AM (7 months ago)
Author:
levelsdev
Message:

add Firefox Support

Location:
ready-made-oxygen-integration
Files:
14 added
6 edited

Legend:

Unmodified
Added
Removed
  • ready-made-oxygen-integration/trunk/README.md

    r3322581 r3365629  
    11# Ready-Made Oxygen Integration
    22
    3 [![Version](https://img.shields.io/badge/version-1.0.1-blue.svg)](https://levels.dev)
     3[![Version](https://img.shields.io/badge/version-1.2.0-blue.svg)](https://levels.dev)
    44[![WordPress](https://img.shields.io/badge/wordpress-5.0%2B-green.svg)](https://wordpress.org)
    55[![Oxygen](https://img.shields.io/badge/oxygen-4.0%2B-purple.svg)](https://oxygenbuilder.com)
     
    8181## 🛠️ Troubleshooting
    8282
    83 If pasting doesnt work:
     83If pasting doesn't work:
    8484
    8585- Ensure plugin is active
    8686- Clear browser cache
    87 - Confirm youre using Oxygen 4+
     87- Confirm you're using Oxygen 4+
    8888- Disable conflicting plugins temporarily
     89
     90### Firefox Users
     91- **First time**: A permission modal will appear - paste your JSON and click "Allow & Paste"
     92- **Subsequent times**: Direct pasting works without modal
     93- **If issues persist**: Refresh page to reset permissions
    8994
    9095---
     
    112117## 📓 Changelog
    113118
     119### 1.2.0 – Firefox Compatibility
     120- ✨ **Firefox Support**: Complete Firefox clipboard compatibility with unified permission modal
     121- 🛡️ **Context Menu Fix**: Eliminated unwanted "Einfügen" context menu in Firefox
     122- 🔧 **Browser Detection**: Chrome users get direct clipboard access, Firefox users get specialized handling
     123- 📱 **Session Memory**: Firefox permission modal only appears once per session
     124- 🚀 **Improved UX**: Streamlined workflow - permission + paste in single action
     125- 🔄 **Error Recovery**: Automatic permission reset and retry on clipboard failures
     126
    114127### 1.1.0 – Font Matching Implementation
    115128- Font matching feature added
  • ready-made-oxygen-integration/trunk/includes/js/addPasteButton-modular.js

    r3322576 r3365629  
    33 * Modular structure for better maintainability
    44 *
    5  * Version: 1.1.0 (Font Matching)
    6  */
    7 
    8 console.log("READOXIN: ✅ Plugin loaded v1.1.0");
     5 * Version: 1.2.0 (Firefox Compatibility)
     6 */
     7
     8console.log("READOXIN: ✅ Plugin loaded v1.2.0");
    99
    1010// Verify modules are loaded
  • ready-made-oxygen-integration/trunk/includes/js/modules/pasteHandler.js

    r3356947 r3365629  
    44 */
    55
    6 /**
    7  * Enhanced paste logic with comprehensive logging
     6// Browser detection
     7const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');
     8
     9// Session-based Firefox permission tracking
     10let firefoxPermissionGranted = false;
     11let modalCurrentlyOpen = false;
     12
     13/**
     14 * Clean Firefox-compatible clipboard reading with session awareness
     15 * @returns {Promise<string>} Clipboard text content
     16 */
     17async function readClipboardText() {
     18  // For Chrome and other browsers, try clipboard API directly
     19  if (!isFirefox) {
     20    if (navigator.clipboard && navigator.clipboard.readText) {
     21      try {
     22        const text = await navigator.clipboard.readText();
     23        return text;
     24      } catch (error) {
     25        console.warn("READOXIN: ❌ Clipboard access failed:", error.message);
     26        throw error;
     27      }
     28    } else {
     29      throw new Error('Clipboard API not available in this browser');
     30    }
     31  }
     32
     33  // Firefox-specific handling below
     34  // If we have session permission, try clipboard directly
     35  if (firefoxPermissionGranted) {
     36    if (navigator.clipboard && navigator.clipboard.readText) {
     37      try {
     38        const text = await navigator.clipboard.readText();
     39        return text;
     40      } catch (error) {
     41        // Reset permission and fall through to modal
     42        firefoxPermissionGranted = false;
     43      }
     44    }
     45  }
     46
     47  // No permission or clipboard failed - show unified modal (if not already open)
     48  if (modalCurrentlyOpen) {
     49    throw new Error('Modal already open');
     50  }
     51
     52  const text = await showUnifiedFirefoxModal();
     53
     54  // If we got text from the modal, mark permission as granted
     55  if (text) {
     56    firefoxPermissionGranted = true;
     57  }
     58
     59  return text;
     60}
     61
     62/**
     63 * Unified Firefox modal - combines permission request with paste functionality
     64 * @returns {Promise<string>} Clipboard text content from user
     65 */
     66function showUnifiedFirefoxModal() {
     67  return new Promise((resolve, reject) => {
     68    // Set modal flag
     69    modalCurrentlyOpen = true;
     70
     71    // Create modal overlay
     72    const overlay = document.createElement('div');
     73    overlay.style.cssText = `
     74      position: fixed;
     75      top: 0;
     76      left: 0;
     77      width: 100%;
     78      height: 100%;
     79      background: rgba(0, 0, 0, 0.7);
     80      z-index: 999999;
     81      display: flex;
     82      align-items: center;
     83      justify-content: center;
     84    `;
     85
     86    // Create modal content
     87    const modal = document.createElement('div');
     88    modal.style.cssText = `
     89      background: white;
     90      border-radius: 12px;
     91      padding: 24px;
     92      max-width: 500px;
     93      width: 90%;
     94      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
     95    `;
     96
     97    modal.innerHTML = `
     98      <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
     99        <div style="font-size: 32px;">🦊</div>
     100        <h3 style="margin: 0; color: #333; font-family: system-ui, -apple-system, sans-serif; font-size: 18px;">
     101          Firefox Paste Permission
     102        </h3>
     103      </div>
     104      <p style="margin: 0 0 16px 0; color: #666; font-family: system-ui, -apple-system, sans-serif; line-height: 1.4; font-size: 14px;">
     105        Paste your Figma JSON below to grant clipboard permission and paste automatically:
     106      </p>
     107      <textarea id="unifiedPasteInput" placeholder="Paste your Figma JSON here (Ctrl+V)" style="
     108        width: 100%;
     109        height: 150px;
     110        border: 2px solid #ddd;
     111        border-radius: 6px;
     112        padding: 12px;
     113        font-family: 'Courier New', monospace;
     114        font-size: 12px;
     115        resize: vertical;
     116        box-sizing: border-box;
     117        outline: none;
     118        background: #fafafa;
     119      "></textarea>
     120      <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 16px;">
     121        <button id="unifiedCancel" style="
     122          padding: 8px 16px;
     123          border: 2px solid #ddd;
     124          background: white;
     125          border-radius: 6px;
     126          cursor: pointer;
     127          font-family: system-ui, -apple-system, sans-serif;
     128          color: #666;
     129        ">Cancel</button>
     130        <button id="unifiedAllowPaste" style="
     131          padding: 8px 16px;
     132          border: 2px solid #9580FF;
     133          background: #9580FF;
     134          color: white;
     135          border-radius: 6px;
     136          cursor: pointer;
     137          font-family: system-ui, -apple-system, sans-serif;
     138          font-weight: 500;
     139        ">Allow & Paste</button>
     140      </div>
     141    `;
     142
     143    overlay.appendChild(modal);
     144    document.body.appendChild(overlay);
     145
     146    // Focus textarea
     147    const textarea = modal.querySelector('#unifiedPasteInput');
     148    textarea.focus();
     149
     150    // Handle Allow & Paste button
     151    modal.querySelector('#unifiedAllowPaste').addEventListener('click', async () => {
     152      const text = textarea.value.trim();
     153
     154      // Clean up modal
     155      document.body.removeChild(overlay);
     156      modalCurrentlyOpen = false;
     157
     158      if (text) {
     159        // Try to grant clipboard permission
     160        try {
     161          if (navigator.clipboard && navigator.clipboard.readText) {
     162            await navigator.clipboard.readText();
     163          }
     164        } catch (error) {
     165          // Permission attempt completed
     166        }
     167
     168        resolve(text);
     169      } else {
     170        reject(new Error('No text provided'));
     171      }
     172    });
     173
     174    // Handle cancel button
     175    modal.querySelector('#unifiedCancel').addEventListener('click', () => {
     176      document.body.removeChild(overlay);
     177      modalCurrentlyOpen = false;
     178      reject(new Error('User cancelled'));
     179    });
     180
     181    // Auto-submit on Ctrl+V
     182    textarea.addEventListener('keydown', (e) => {
     183      if (e.ctrlKey && e.key === 'v') {
     184        setTimeout(() => {
     185          if (textarea.value.trim()) {
     186            modal.querySelector('#unifiedAllowPaste').click();
     187          }
     188        }, 100);
     189      }
     190    });
     191
     192    // Handle escape key
     193    document.addEventListener('keydown', function escapeHandler(e) {
     194      if (e.key === 'Escape') {
     195        document.removeEventListener('keydown', escapeHandler);
     196        if (document.body.contains(overlay)) {
     197          document.body.removeChild(overlay);
     198          modalCurrentlyOpen = false;
     199          reject(new Error('User cancelled'));
     200        }
     201      }
     202    });
     203  });
     204}
     205
     206
     207/**
     208 * Enhanced paste logic with comprehensive logging and Firefox compatibility
    8209 * @param {Object} scope - Angular scope from Oxygen builder
    9210 */
     
    14215  }
    15216
    16   navigator.clipboard.readText().then(async (clipboardText) => {
     217  readClipboardText().then(async (clipboardText) => {
    17218    try {
    18219      let json = JSON.parse(clipboardText);
  • ready-made-oxygen-integration/trunk/includes/js/modules/uiComponents.js

    r3322576 r3365629  
    4343    border: "2px solid #8d8bd9",
    4444  });
    45   button.addEventListener("click", onClick);
     45  // Firefox-specific: Disable all default behaviors that could interfere
     46  button.addEventListener("mousedown", (e) => {
     47    e.preventDefault();
     48    e.stopPropagation();
     49  });
     50
     51  button.addEventListener("mouseup", (e) => {
     52    e.preventDefault();
     53    e.stopPropagation();
     54  });
     55
     56  button.addEventListener("click", (e) => {
     57    e.preventDefault();
     58    e.stopPropagation();
     59    e.stopImmediatePropagation();
     60
     61    // Execute immediately without waiting for browser events
     62    setTimeout(() => onClick(), 0);
     63  });
     64
     65  // Completely disable context menu on this button
     66  button.addEventListener("contextmenu", (e) => {
     67    e.preventDefault();
     68    e.stopPropagation();
     69    e.stopImmediatePropagation();
     70    return false;
     71  });
     72
     73  // Disable Firefox's paste-related shortcuts on this button
     74  button.addEventListener("keydown", (e) => {
     75    if (e.ctrlKey && e.key === 'v') {
     76      e.preventDefault();
     77      e.stopPropagation();
     78      e.stopImmediatePropagation();
     79    }
     80  });
     81
     82  // Override any tooltip attributes
     83  button.removeAttribute("data-tooltip");
     84  button.removeAttribute("aria-label");
     85
     86  // Add Firefox-specific attributes to disable context menu behavior
     87  button.setAttribute('oncontextmenu', 'return false;');
     88  button.setAttribute('onselectstart', 'return false;');
     89  button.setAttribute('ondragstart', 'return false;');
     90
    4691  // document.body.appendChild(button);
    4792  toolbar.parentNode.insertBefore(button, toolbar.nextSibling);
     93
     94  // Global Firefox context menu suppression near our button
     95  suppressFirefoxContextMenu(button);
    4896
    4997  const style = document.createElement("style");
     
    59107
    60108/**
    61  * Set up keyboard shortcuts for paste functionality
     109 * Suppress Firefox context menu behavior around our paste button
     110 * @param {HTMLElement} button - The paste button element
     111 */
     112function suppressFirefoxContextMenu(button) {
     113
     114  // Create invisible overlay to capture context menu events
     115  const overlay = document.createElement('div');
     116  overlay.style.cssText = `
     117    position: absolute;
     118    pointer-events: none;
     119    z-index: 9998;
     120  `;
     121
     122  // Position overlay over button
     123  function updateOverlayPosition() {
     124    const rect = button.getBoundingClientRect();
     125    overlay.style.left = rect.left - 10 + 'px';
     126    overlay.style.top = rect.top - 10 + 'px';
     127    overlay.style.width = rect.width + 20 + 'px';
     128    overlay.style.height = rect.height + 20 + 'px';
     129    overlay.style.pointerEvents = 'all';
     130  }
     131
     132  updateOverlayPosition();
     133  document.body.appendChild(overlay);
     134
     135  // Block all context menu events in this area
     136  overlay.addEventListener('contextmenu', (e) => {
     137    e.preventDefault();
     138    e.stopPropagation();
     139    e.stopImmediatePropagation();
     140    return false;
     141  });
     142
     143  // Update overlay position when window resizes or scrolls
     144  window.addEventListener('resize', updateOverlayPosition);
     145  window.addEventListener('scroll', updateOverlayPosition);
     146
     147  // Enhanced Firefox context menu suppression
     148  let contextMenuBlocked = false;
     149  let suppressionActive = false;
     150
     151  // Block context menu during any interaction with our button
     152  function enableContextMenuSuppression() {
     153    if (!suppressionActive) {
     154      suppressionActive = true;
     155
     156      // Multiple event listeners for comprehensive blocking
     157      document.addEventListener('contextmenu', blockContextMenu, {capture: true, passive: false});
     158      document.addEventListener('selectstart', blockContextMenu, {capture: true, passive: false});
     159      document.addEventListener('dragstart', blockContextMenu, {capture: true, passive: false});
     160
     161      // Also block on window level (for Firefox edge cases)
     162      window.addEventListener('contextmenu', blockContextMenu, {capture: true, passive: false});
     163    }
     164  }
     165
     166  function disableContextMenuSuppression() {
     167    if (suppressionActive) {
     168      suppressionActive = false;
     169
     170      // Longer delay to prevent context menu after modal interactions
     171      setTimeout(() => {
     172        if (!suppressionActive) { // Only disable if not re-activated
     173          document.removeEventListener('contextmenu', blockContextMenu, {capture: true});
     174          document.removeEventListener('selectstart', blockContextMenu, {capture: true});
     175          document.removeEventListener('dragstart', blockContextMenu, {capture: true});
     176          window.removeEventListener('contextmenu', blockContextMenu, {capture: true});
     177        }
     178      }, 500); // Longer delay for modal interactions
     179    }
     180  }
     181
     182  function blockContextMenu(e) {
     183    e.preventDefault();
     184    e.stopPropagation();
     185    e.stopImmediatePropagation();
     186    return false;
     187  }
     188
     189  // Activate suppression on any interaction
     190  button.addEventListener('mouseenter', enableContextMenuSuppression);
     191  button.addEventListener('mousedown', enableContextMenuSuppression);
     192  button.addEventListener('focus', enableContextMenuSuppression);
     193
     194  // Deactivate suppression when leaving
     195  button.addEventListener('mouseleave', disableContextMenuSuppression);
     196  button.addEventListener('blur', disableContextMenuSuppression);
     197}
     198
     199/**
     200 * Set up keyboard shortcuts for paste functionality with Firefox support
    62201 * @param {Function} pasteHandler - Function to call when shortcut is triggered
    63202 */
     
    65204  document.addEventListener("keydown", function (event) {
    66205    if (event.ctrlKey && event.altKey && event.key === "v") {
     206      event.preventDefault();
    67207      pasteHandler();
    68208    }
    69209  });
     210
     211  // Firefox-specific: Also listen for regular Ctrl+V when in Oxygen iframe
     212  document.addEventListener("keydown", function (event) {
     213    if (event.ctrlKey && event.key === "v" && !event.altKey) {
     214      // Check if we're in the Oxygen builder context
     215      const isInBuilder = document.querySelector('.oxygen-toolbar-menus') ||
     216                         document.querySelector('[ng-controller="BuilderController"]');
     217
     218      if (isInBuilder && event.target.tagName !== 'TEXTAREA' && event.target.tagName !== 'INPUT') {
     219        event.preventDefault();
     220        pasteHandler();
     221      }
     222    }
     223  });
    70224}
  • ready-made-oxygen-integration/trunk/readme.txt

    r3356950 r3365629  
    7171== Changelog ==
    7272
     73= 1.2.0 =
     74* Firefox support added
     75
    7376= 1.1.1 =
    7477* Enhanced image processor and paste handler modules
  • ready-made-oxygen-integration/trunk/ready-made-oxygen-integration.php

    r3356950 r3365629  
    77 * Plugin URI: https://levels.dev/automatic-webdevelopment-from-figma-via-copy-paste
    88 * Description: Essential integration for the Ready-Made Figma plugin to enable copy-paste functionality with Oxygen 4.
    9  * Version: 1.1.1
     9 * Version: 1.2.0
    1010 * Author: levels.dev
    1111 * Author URI: https://levels.dev
     
    2222define('SCRIPT_DEBUG', true);
    2323// Define plugin version
    24 define('READOXIN_JS_VERSION', '1.1.1');
     24define('READOXIN_JS_VERSION', '1.2.0');
    2525
    2626// Use global namespace for WordPress hooks
Note: See TracChangeset for help on using the changeset viewer.