I have a script that performs a spell check against all of the text nodes in a file. This works fine, however I’d like to locate the closest component to the text node and get the name of the component’s containing frame to help my editorial team locate the mistake within our site’s layout.
I see that containing_frame is a property of the component type, but I’m getting the following error: TypeError: Cannot read properties of undefined (reading 'containing_frame')
.
Any help would be greatly appreciated, here’s my current code:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//@ts-nocheck
const request = require("request");
const traverse = require("traverse");
const spellchecker = require("spellchecker");
const ExcelJS = require("exceljs");
// REPLACE ME: replace these values with a file you own and with your own developer token.
const file_id = $FILE_ID;
const personal_access_token = $ACCESS_TOKEN;
const api_endpoint = 'https://api.figma.com/v1';
function hasKey(node, key) {
return node && typeof node === 'object' && key in node;
}
function getTextNodes(figFile) {
return traverse.nodes(figFile)
.filter(node => hasKey(node, 'type') && node.type === 'TEXT');
}
function buildParentMap(node, parent, parentMap = {}) {
parentMap[node.id] = parent;
if (node.children) {
node.children.forEach(child => buildParentMap(child, node, parentMap));
}
return parentMap;
}
function getClosestComponentId(nodeId, parentMap) {
let node = parentMap[nodeId];
while (node) {
if (node.type === 'INSTANCE' || node.type === 'COMPONENT') {
return node.id;
}
node = parentMap[node.id];
}
return '';
}
function requestErrorHandler(error, response, body) {
if (error) {
console.log(error);
console.log(body);
process.exit(1);
}
}
function spellCheckTextNodes(textNodes, sheet, figFile, parentMap) {
const componentIds = new Set();
textNodes.forEach((node, index) => {
if (node.characters) {
const misspelledWords = spellchecker.checkSpelling(node.characters).map((error) => {
return {
word: node.characters.slice(error.start, error.end),
start: error.start,
end: error.end,
};
});
if (misspelledWords.length > 0) {
misspelledWords.forEach((word) => {
const corrections = spellchecker.getCorrectionsForMisspelling(word.word);
if (corrections.length > 0) {
// Add corrections to the Corrections column
const correctionCell = sheet.getCell(`H${index + 2}`);
correctionCell.value = corrections.slice(0, 3).join(', ');
}
});
}
}
const componentId = getClosestComponentId(node.id, parentMap);
componentIds.add(componentId);
});
componentIds.forEach((componentId) => {
request.get(`${api_endpoint}/components/${componentId}`, {
headers: {
"Content-Type": "application/json",
"x-figma-token": personal_access_token,
},
}, function(error, response, body) {
requestErrorHandler(error, response, body);
const component = JSON.parse(body).component;
let context = '';
if (component.containing_frame) {
context += `Frame: ${component.containing_frame}`;
}
textNodes.forEach((node, index) => {
if (getClosestComponentId(node.id, parentMap) === componentId) {
const closestComponentCell = sheet.getCell(`G${index + 2}`);
closestComponentCell.value = context;
}
});
});
});
}
request.get(`${api_endpoint}/files/${file_id}`, {
headers: {
"Content-Type": "application/json",
"x-figma-token": personal_access_token,
},
}, function(error, response, body) {
requestErrorHandler(error, response, body);
const figmaFile = JSON.parse(body);
const textNodes = getTextNodes(figmaFile);
const parentMap = buildParentMap(figmaFile.document, null);
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('Text Nodes');
sheet.addRow(['ID', 'Label', 'Text', 'Corrections', 'Component Context']);
textNodes.forEach((node) => {
sheet.addRow([
node.id,
node.name,
node.characters,
]);
});
spellCheckTextNodes(textNodes, sheet, figmaFile, parentMap);
workbook.xlsx.writeFile('text_nodes.xlsx').catch((error) => {
console.error('Error exporting to Excel:', error);
});
});```