Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[figma] Fix outdated links #74

Open
oliviertassinari opened this issue Aug 14, 2022 · 3 comments
Open

[figma] Fix outdated links #74

oliviertassinari opened this issue Aug 14, 2022 · 3 comments
Labels
bug 🐛 Something doesn't work figma

Comments

@oliviertassinari
Copy link
Member

oliviertassinari commented Aug 14, 2022

The problem

In the latest version of the "MUI for Figma design kit", many of the links don't return an HTTP status 200. Most of them are redirections that might break in the future and slow down the UX when opening them. Others might be 404 which breaks the designer experience.

Examples

  1. Missing leading slash: https://www.figma.com/file/1dK7YWmdCdwHXZibO05DhY/MUI-for-Figma-for-issue-%2374?node-id=6706%3A38914&t=CAaGBNt5Jo1JXyma-0

Screenshot 2023-02-26 at 12 22 39

  1. Outdated links in the text: https://www.figma.com/file/1dK7YWmdCdwHXZibO05DhY/MUI-for-Figma-for-issue-%2374?node-id=6706%3A38914&t=CAaGBNt5Jo1JXyma-0

Screenshot 2023-02-26 at 12 23 13

  1. Outdated component links https://www.figma.com/file/1dK7YWmdCdwHXZibO05DhY/MUI-for-Figma-for-issue-%2374?node-id=11048%3A151147&t=CAaGBNt5Jo1JXyma-0

Screenshot 2023-02-26 at 12 25 14

Proposed solution

We could spend a couple of hours, likely less than 8, to create a custom Figma plugin that iterate on all the URLs in the Figma file (links, document URLs, etc.). If the link is a redirection, we can automatically replace it with the new destination (and log what happened). If the link is something else than a 200, we can report it, in a way that is easy to fix manually in the Figma editor.

@oliviertassinari
Copy link
Member Author

oliviertassinari commented Aug 14, 2022

I have spent 1 hour looking into it, out of curiosity for the needs of #10 and mui/toolpad#791. I could come up with the following Figma plugin to automate it all

// This plugin will open a window to prompt the user to enter a number, and
// it will then create that many rectangles on the screen.

// This file holds the main code for the plugins. It has access to the *document*.
// You can access browser APIs in the <script> tag inside "ui.html" which has a
// full browser environment (see documentation).

// This shows the HTML page in "ui.html".
figma.showUI(__html__);

// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage = msg => {
  // One way of distinguishing between different types of messages sent from
  // your HTML page is to use an object with a "type" property like this.
  if (msg.type === 'click-fix') {
    // This plugin counts the number of layers, ignoring instance sublayers,
    // in the document
    let count = 0;
    let changed = [];
    function traverse(node) {
      if (!node) {
        return
      }

      count += 1;

      // https://www.figma.com/plugin-docs/api/TextSublayer/#hyperlink
      if (node.hyperlink && node.hyperlink.type === 'URL') {
        const oldURL = node.hyperlink.value;
        const newURL = replaceUrl(oldURL);
        if (oldURL !== newURL) {
          node.hyperlink = { type: 'URL', value: newURL };
          changed.push({ oldURL, newURL });
        }
        // console.log('postMessage', { type: 'resolve-url', id: node.id, url: node.hyperlink.value });
        // figma.ui.postMessage({ type: 'resolve-url', id: node.id, url: node.hyperlink.value })
      }

      // https://www.figma.com/plugin-docs/api/properties/nodes-documentationlinks/
      if (node.documentationLinks && node.documentationLinks.length > 0) {
        const oldURL= node.documentationLinks[0].uri;
        const newURL = replaceUrl(oldURL);

        if (oldURL !== newURL) {
          node.documentationLinks = [{
            uri: newURL,
          }]
          changed.push({ oldURL, newURL });
        }
        // console.log('postMessage', { type: 'resolve-url', id: node.id, url: node.documentationLinks[0].uri });
        // console.log('replaceUrl', node.documentationLinks[0].uri, replaceUrl(node.documentationLinks[0].uri))
        // figma.ui.postMessage({ type: 'resolve-url', id: node.id, url: node.documentationLinks[0].uri })
      }

      // if (count > 100) {
      //   return;
      // }

      if ("children" in node) {
        // if (node.type !== "INSTANCE") {
          for (const child of node.children) {
            traverse(child)
          }
        // }
      }
    }
    traverse(figma.root); // start the traversal at the root
    // traverse(figma.currentPage.selection[0]); // start the traversal at the root
    console.log('changed', changed);
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  figma.closePlugin();
};
<button id="create">Run</button>
<button id="cancel">Cancel</button>
<script>

document.getElementById('create').onclick = () => {
  const count = 3;
  parent.postMessage({ pluginMessage: { type: 'click-fix', count } }, '*')
}

document.getElementById('cancel').onclick = () => {
  parent.postMessage({ pluginMessage: { type: 'cancel' } }, '*')
}

// window.onmessage = async (event) => {
//   console.log('message')

//   if (event.data.pluginMessage.type === 'resolve-url') {
//     console.log('resolve-url', event.data.pluginMessage.url)
//     var request = new XMLHttpRequest()
//     // This link has random lorem ipsum text
//     request.open('GET', event.data.pluginMessage.url)
//     request.responseType = 'text'
//     request.onload = () => {
//       // window.parent.postMessage({pluginMessage: { type: 'url-resolved' }}, '*')
//     };
//     request.send()
//   }
// }

</script>

It almost works:

Screenshot 2022-08-14 at 14 25 32

But I faced two challenges:

  1. I couldn't make a CORS request to https://mui.com/ to resolve the 301 or find the 404. We would need to use a proxy (e.g. localhost) or update the configuration of mui.com. https://www.figma.com/plugin-docs/making-network-requests.
  2. Instead I have used a manual rewrite logic, the one that @siriwatknp had written for the migration of the page, but it's not guarantee to work reliably. e.g. in my above screenshot, I did a mistake with the domain name rewrite.

@oliviertassinari oliviertassinari added the bug 🐛 Something doesn't work label Aug 15, 2022
@oliviertassinari
Copy link
Member Author

oliviertassinari commented May 2, 2023

Some more recent work I did on this on the side:

code.ts

function handleClickFix() {
  let count = 0;
  const linkElements = [];

  function traverse(node) {
    if (!node) {
      return
    }

    count += 1;

    if (node.hyperlink && node.hyperlink.type === 'URL') {
      linkElements.push({
        node: node.hyperlink,
        type: 'link',
      });
    }

    if (node.documentationLinks && node.documentationLinks.length > 0) {
      linkElements.push({
        node: node.documentationLinks,
        type: 'docs',
      })
    }

    if ('children' in node) {
      for (const child of node.children) {
        traverse(child)
      }
    }
  }

  traverse(figma.currentPage.selection[0]); // start the traversal at the root
  // traverse(figma.root); // start from the traversal from the root

  // type = link
  // const oldURL = node.hyperlink.value;
  // const newURL = replaceUrl(oldURL);
  // if (oldURL !== newURL) {
  //   node.hyperlink = { type: 'URL', value: newURL };
  //   changed.push({ oldURL, newURL });
  // }
  // // console.log('postMessage', { type: 'resolve-url', id: node.id, url: node.hyperlink.value });
  // // figma.ui.postMessage({ type: 'resolve-url', id: node.id, url: node.hyperlink.value })

  // type = docs
  // const oldURL = node.documentationLinks[0].uri;
  // const newURL = replaceUrl(oldURL);
  // if (oldURL !== newURL) {
  //   node.documentationLinks = [{
  //     uri: newURL,
  //   }]
  //   changed.push({ oldURL, newURL });
  // }
  // // console.log('postMessage', { type: 'resolve-url', id: node.id, url: node.documentationLinks[0].uri });
  // // figma.ui.postMessage({ type: 'resolve-url', id: node.id, url: node.documentationLinks[0].uri })

  linkElements.forEach((linkElement) => {
    if (linkElement.type === 'docs') {
      figma.ui.postMessage({ type: 'resolveUrl', url: linkElement.node.documentationLinks[0].uri })
    } else if (linkElement.type === 'link') {
      figma.ui.postMessage({ type: 'resolveUrl', url: linkElement.node.value })
    }
  });

  console.log('linkElements', linkElements);
}

// This shows the HTML page in "ui.html".
figma.showUI(__html__);

// Calls to "parent.postMessage" from within the HTML page will trigger this
// callback. The callback will be passed the "pluginMessage" property of the
// posted message.
figma.ui.onmessage = msg => {
  // One way of distinguishing between different types of messages sent from
  // your HTML page is to use an object with a "type" property like this.
  if (msg.type === 'click-fix') {
    handleClickFix();
    return;
  }

  // Make sure to close the plugin when you're done. Otherwise the plugin will
  // keep running, which shows the cancel button at the bottom of the screen.
  figma.closePlugin();
};

proxy.mjs

import http from 'http';

http.createServer((req, res) => {
  const url = new URL(`https://0.0.0.0${req.url}`).searchParams;
  console.log('url', url.get('url'));

  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
}).listen(3008);

ui.html

<button id="run">Run</button>

<script>

document.getElementById('run').onclick = () => {
  parent.postMessage({ pluginMessage: { type: 'click-fix' } }, '*')
}

window.onmessage = async (event) => {
  const message = event.data.pluginMessage;
  console.log('message', message)

  if (message.type === 'resolveUrl') {
    console.log('resolveUrl', message.url)

    var request = new XMLHttpRequest()
    // This link has random lorem ipsum text
    request.open('GET', message.url)
    request.responseType = 'text'
    request.onload = () => {
      // window.parent.postMessage({pluginMessage: { type: 'resolvedUrl' }}, '*')
    };
    request.send()
  }
}

</script>

@oliviertassinari
Copy link
Member Author

oliviertassinari commented Jul 22, 2023

@DavidCnoops What do you think about taking this problem on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work figma
Projects
None yet
Development

No branches or pull requests

1 participant