16 Nov 21

Building my first Figma plugin

Today I was dealing with a very repetitive task: I had to create hundreds of mockups of Tweets with data from a Google Spreadsheet.

Luckily, I was already familiar with Google Sheets Sync plugin, which magically takes data from a spreadsheet and applies it to a component. I successfuly used it to import usernames, handles, the Tweet text, and even people's profile pictures:

The workflow wasn't perfect, however. Importing data from Google Sheets saved me a ton of time, but still I had one annoying step to perform manually: change all #hashtags, @handles, and URLs in the Tweet text to the blue color they're rendered in.

As usual, I Tweeted out to the Universe in hopes that someone had already built a solution to my problem:

But I was met with silence....So I decided to get my hands dirty and build a Figma plugin to help me out.

I had zero experience developing for Figma. It took me about 2h to have a working version. The code is super simple, about 60 lines. It just takes the text layer, finds the substrings matching a Regex, and re-styles them.

const selection = figma.currentPage.selection;
const newFills = { type: "SOLID", color: { r: 0.113, g: 0.607, b: 0.941 } };

const findHashTag = (text: string) => {
const regex = /(^|\B)#(?![0-9_]+\b)([a-zA-Z0-9_]{1,30})(\b|\r)/g;
const matches = text.match(regex);
return matches || [];
};

const findHandle = (text: string) => {
const regex = /(^|\B)@(?![0-9_]+\b)([a-zA-Z0-9_]{1,30})(\b|\r)/g;
const matches = text.match(regex);
console.log(matches);
return matches || [];
};

const findURL = (text: string) => {
const regex =
/\b((http(s?):\/\/)?([a-z0-9\-]+\.)+(MUSEUM|TRAVEL|AERO|ARPA|ASIA|EDU|GOV|MIL|MOBI|COOP|INFO|NAME|BIZ|CAT|COM|INT|JOBS|NET|ORG|PRO|TEL|A[CDEFGILMNOQRSTUWXZ]|B[ABDEFGHIJLMNORSTVWYZ]|C[ACDFGHIKLMNORUVXYZ]|D[EJKMOZ]|E[CEGHRSTU]|F[IJKMOR]|G[ABDEFGHILMNPQRSTUWY]|H[KMNRTU]|I[DELMNOQRST]|J[EMOP]|K[EGHIMNPRWYZ]|L[ABCIKRSTUVY]|M[ACDEFGHKLMNOPQRSTUVWXYZ]|N[ACEFGILOPRUZ]|OM|P[AEFGHKLMNRSTWY]|QA|R[EOSUW]|S[ABCDEGHIJKLMNORTUVYZ]|T[CDFGHJKLMNOPRTVWZ]|U[AGKMSYZ]|V[ACEGINU]|W[FS]|Y[ETU]|Z[AMW])(:[0-9]{1,5})?((\/([a-z0-9_\-\.~]*)*)?((\/)?\?[a-z0-9+_\-\.%=&]*)?)?(#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)/gi;
const matches = text.match(regex);
console.log(matches);
return matches || [];
};

selection.forEach((node) => {
const hashTags = findHashTag(node.characters);
const handles = findHandle(node.characters);
const urls = findURL(node.characters);
console.log(hashTags, handles);
if (hashTags.length > 0) {
hashTags.forEach((hashTag) => {
const hashtagRange = [
node.characters.indexOf(hashTag),
node.characters.indexOf(hashTag) + hashTag.length,
];
node.setRangeFills(hashtagRange[0], hashtagRange[1], [newFills]);
});
}
if (handles.length > 0) {
handles.forEach((handle) => {
const handleRange = [
node.characters.indexOf(handle),
node.characters.indexOf(handle) + handle.length,
];
node.setRangeFills(handleRange[0], handleRange[1], [newFills]);
});
}
if (urls.length > 0) {
console.log(urls);
urls.forEach((url) => {
const urlRange = [
node.characters.indexOf(url),
node.characters.indexOf(url) + url.length,
];
node.setRangeFills(urlRange[0], urlRange[1], [newFills]);
});
}
});

figma.closePlugin();

This yields a very simple plugin. It has no UI, and works with a single selection.

It's pretty amazing that our tools are becoming more open so that anyone can customize them to their needs. This was a very simple build, but it got me excited about Figma plugins and what I can do with them in the future.

About

This website was built using Obsidian, Eleventy and Vercel.
The text is set in Untitled by Klim Type Co.