├── .gitignore ├── LICENSE ├── README.md ├── figma-to-react ├── .env ├── README.md ├── lib │ └── figma.js ├── main.js ├── package-lock.json ├── package.json ├── public │ └── index.html └── src │ ├── components │ └── CClock.js │ ├── figmaComponents.js │ ├── index.css │ ├── index.js │ └── registerServiceWorker.js ├── kaleidescope ├── README.md └── kaleidescope.py ├── projector ├── README.md ├── images │ ├── _bottom.png │ ├── _top.png │ ├── busstop.png │ ├── phone-in-hand.jpeg │ └── reflection.png ├── index.html └── perspective.js └── spellchecker ├── .gitignore ├── README.md ├── app.ts ├── package-lock.json ├── package.json ├── tsconfig.json └── types └── spellchecker.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 Figma, Inc. http://figma.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # figma-api-demo 2 | 3 | This project contains demo apps using the Figma API. They are 4 | meant to demonstrate how to get started with the various aspects of the API. 5 | 6 | See the developer documentation at http://www.figma.com/developers/docs -------------------------------------------------------------------------------- /figma-to-react/.env: -------------------------------------------------------------------------------- 1 | DEV_TOKEN= 2 | -------------------------------------------------------------------------------- /figma-to-react/README.md: -------------------------------------------------------------------------------- 1 | # Figma to React Converter 2 | 3 | This demonstrates using the Figma REST API to convert a Figma document to React Components. 4 | Disclaimer: this code is likely incomplete, and may have bugs. It is not intended to be used 5 | in production. This is simply a proof of concept to show what possibilities exist. 6 | 7 | ## API Usage 8 | 9 | We use 2 endpoints in this project: 10 | 11 | - `GET /v1/files/:file_key` - Get the JSON tree from a file. This is the main workhorse of this project and lays the skeleton of the React Components. 12 | - `GET /v1/images/:file_key` - When we identify nodes that are vectors or other nodes that can't directly be represented by `div`s, we have to render them as svgs. 13 | 14 | ## Set up 15 | 16 | 1. Install [Node](https://nodejs.org/). You'll need a recent version that supports `async / await` 17 | 2. In this directory, run `npm install` 18 | 3. Run the converter per instructions below 19 | 20 | ## Usage 21 | 22 | By default this project comes with a prerendered component in `src/figmaComponents.js`. You can see a page that uses this component if you 23 | run `npm start`. This will start a React server and a webpage will open to the root page. This webpage will automatically refresh as 24 | you make changes to the source documents. To follow along with the example component, the source Figma file is located [here](https://www.figma.com/file/VGULlnz44R0Ooe4FZKDxlhh4/Untitled). 25 | 26 | When we run the converter, we will convert any *top level frames* in the document to React Components *as long as their name starts with `#`*. 27 | In the example document you can see that we have one top level frame named `#Clock`. The component resulting from this will be exported in 28 | `src/figmaComponents.js` as `MasterClock`, a `React.PureComponent`. 29 | 30 | In addition, *any* node with a name starting with a `#` will have a code stub generated for it in `src/components`. These code stubs can be 31 | modified to affect the rendering of those components as well as modifying variables within the component (see variables section below). 32 | 33 | To run the converter on a file, you will need a personal access token from Figma. Refer to the [Figma API documentation](https://www.figma.com/developers/docs) 34 | for more information on how to obtain a token. The other piece of information you will need is the file key of the file you wish to convert, 35 | which is located in the file's URL (for example, this is `VGULlnz44R0Ooe4FZKDxlhh4` for the example file). So, an example conversion would look 36 | like: 37 | 38 | ``` 39 | node main.js VGULlnz44R0Ooe4FZKDxlhh4 40 | ``` 41 | 42 | where `` would be repaced with your developer token. 43 | 44 | You can also change the .env file and insert your token there 45 | 46 | ``` 47 | //.env 48 | DEV_TOKEN= 49 | ``` 50 | then you only need to pass the project id 51 | ``` 52 | node main.js VGULlnz44R0Ooe4FZKDxlhh4 53 | ``` 54 | 55 | 56 | 57 | *IMPORTANT*: The index.css file is required to be included for components to render completely. 58 | 59 | ## Variables 60 | 61 | The real vision of this converter is to separate design concerns from coding concerns. Toward this end, we introduce the concept of 62 | `variables` in Figma. Variables in a Figma file are denoted by text nodes (this can be expanded in the future) with names starting with 63 | `$`. In the example document there are three variables: `$time`, `$seconds`, and `$ampm`. By setting state in the component stubs defined in 64 | `src/components`, we can *change the text of the variable nodes*. For an example, take a look at `src/components/CClock.js`. This 65 | allows us to change the design of a component without touching the code at all. 66 | 67 | ## Why React? 68 | 69 | This particular example uses React since it was convenient. There is no reason that the same logic couldn't be applied to any other 70 | frontend framework. 71 | 72 | ## Examples 73 | 74 | Here's an example of using Figma to React to update the style on a sortable list: 75 | 76 | ![duck-list](https://static.figma.com/uploads/9e647f547b9487af4d879627de3bae84591671c1) 77 | 78 | 79 | Here's an example of using Figma to React to attach code to a component dragged in from the Team Library: 80 | 81 | ![clock](https://static.figma.com/uploads/3e4a5e166e295433c29c0e3e78d3a436efc64353) 82 | 83 | ## Credits 84 | 85 | This repo contains the service worker code from React. 86 | -------------------------------------------------------------------------------- /figma-to-react/lib/figma.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const VECTOR_TYPES = ['VECTOR', 'LINE', 'REGULAR_POLYGON', 'ELLIPSE']; 4 | const GROUP_TYPES = ['GROUP', 'BOOLEAN_OPERATION']; 5 | 6 | function colorString(color) { 7 | return `rgba(${Math.round(color.r*255)}, ${Math.round(color.g*255)}, ${Math.round(color.b*255)}, ${color.a})`; 8 | } 9 | 10 | function dropShadow(effect) { 11 | return `${effect.offset.x}px ${effect.offset.y}px ${effect.radius}px ${colorString(effect.color)}`; 12 | } 13 | 14 | function innerShadow(effect) { 15 | return `inset ${effect.offset.x}px ${effect.offset.y}px ${effect.radius}px ${colorString(effect.color)}`; 16 | } 17 | 18 | function imageURL(hash) { 19 | const squash = hash.split('-').join(''); 20 | return `url(https://s3-us-west-2.amazonaws.com/figma-alpha/img/${squash.substring(0, 4)}/${squash.substring(4, 8)}/${squash.substring(8)})`; 21 | } 22 | 23 | function backgroundSize(scaleMode) { 24 | if (scaleMode === 'FILL') { 25 | return 'cover'; 26 | } 27 | } 28 | 29 | function nodeSort(a, b) { 30 | if (a.absoluteBoundingBox.y < b.absoluteBoundingBox.y) return -1; 31 | else if (a.absoluteBoundingBox.y === b.absoluteBoundingBox.y) return 0; 32 | else return 1; 33 | } 34 | 35 | function getPaint(paintList) { 36 | if (paintList && paintList.length > 0) { 37 | return paintList[paintList.length - 1]; 38 | } 39 | 40 | return null; 41 | } 42 | 43 | function paintToLinearGradient(paint) { 44 | const handles = paint.gradientHandlePositions; 45 | const handle0 = handles[0]; 46 | const handle1 = handles[1]; 47 | 48 | const ydiff = handle1.y - handle0.y; 49 | const xdiff = handle0.x - handle1.x; 50 | 51 | const angle = Math.atan2(-xdiff, -ydiff); 52 | const stops = paint.gradientStops.map((stop) => { 53 | return `${colorString(stop.color)} ${Math.round(stop.position * 100)}%`; 54 | }).join(', '); 55 | return `linear-gradient(${angle}rad, ${stops})`; 56 | } 57 | 58 | function paintToRadialGradient(paint) { 59 | const stops = paint.gradientStops.map((stop) => { 60 | return `${colorString(stop.color)} ${Math.round(stop.position * 60)}%`; 61 | }).join(', '); 62 | 63 | return `radial-gradient(${stops})`; 64 | } 65 | 66 | function expandChildren(node, parent, minChildren, maxChildren, centerChildren, offset) { 67 | const children = node.children; 68 | let added = offset; 69 | 70 | if (children) { 71 | for (let i=0; i= 0) { 79 | added += expandChildren(child, parent, minChildren, maxChildren, centerChildren, added+i); 80 | continue; 81 | } 82 | 83 | child.order = i + added; 84 | 85 | if (child.constraints && child.constraints.vertical === 'BOTTOM') { 86 | maxChildren.push(child); 87 | } else if (child.constraints && child.constraints.vertical === 'TOP') { 88 | minChildren.push(child); 89 | } else { 90 | centerChildren.push(child); 91 | } 92 | } 93 | 94 | minChildren.sort(nodeSort); 95 | maxChildren.sort(nodeSort); 96 | 97 | return added + children.length - offset; 98 | } 99 | 100 | return added - offset; 101 | } 102 | 103 | const createComponent = (component, imgMap, componentMap) => { 104 | const name = 'C' + component.name.replace(/\W+/g, ''); 105 | const instance = name + component.id.replace(';', 'S').replace(':', 'D'); 106 | 107 | let doc = ''; 108 | print(`class ${instance} extends PureComponent {`, ''); 109 | print(` render() {`, ''); 110 | print(` return (`, ''); 111 | 112 | const path = `src/components/${name}.js`; 113 | 114 | if (!fs.existsSync(path)) { 115 | const componentSrc = `import React, { PureComponent } from 'react'; 116 | import { getComponentFromId } from '../figmaComponents'; 117 | 118 | export class ${name} extends PureComponent { 119 | state = {}; 120 | 121 | render() { 122 | const Component = getComponentFromId(this.props.nodeId); 123 | return ; 124 | } 125 | } 126 | `; 127 | fs.writeFile(path, componentSrc, function(err) { 128 | if (err) console.log(err); 129 | console.log(`wrote ${path}`); 130 | }); 131 | } 132 | 133 | function print(msg, indent) { 134 | doc += `${indent}${msg}\n`; 135 | } 136 | 137 | const visitNode = (node, parent, lastVertical, indent) => { 138 | let content = null; 139 | let img = null; 140 | const styles = {}; 141 | let minChildren = []; 142 | const maxChildren = []; 143 | const centerChildren = []; 144 | let bounds = null; 145 | let nodeBounds = null; 146 | 147 | if (parent != null) { 148 | nodeBounds = node.absoluteBoundingBox; 149 | const nx2 = nodeBounds.x + nodeBounds.width; 150 | const ny2 = nodeBounds.y + nodeBounds.height; 151 | const parentBounds = parent.absoluteBoundingBox; 152 | const px = parentBounds.x; 153 | const py = parentBounds.y; 154 | 155 | bounds = { 156 | left: nodeBounds.x - px, 157 | right: px + parentBounds.width - nx2, 158 | top: lastVertical == null ? nodeBounds.y - py : nodeBounds.y - lastVertical, 159 | bottom: py + parentBounds.height - ny2, 160 | width: nodeBounds.width, 161 | height: nodeBounds.height, 162 | } 163 | } 164 | 165 | expandChildren(node, parent, minChildren, maxChildren, centerChildren, 0); 166 | 167 | let outerClass = 'outerDiv'; 168 | let innerClass = 'innerDiv'; 169 | const cHorizontal = node.constraints && node.constraints.horizontal; 170 | const cVertical = node.constraints && node.constraints.vertical; 171 | const outerStyle = {}; 172 | 173 | if (node.order) { 174 | outerStyle.zIndex = node.order; 175 | } 176 | 177 | if (cHorizontal === 'LEFT_RIGHT') { 178 | if (bounds != null) { 179 | styles.marginLeft = bounds.left; 180 | styles.marginRight = bounds.right; 181 | styles.flexGrow = 1; 182 | } 183 | } else if (cHorizontal === 'RIGHT') { 184 | outerStyle.justifyContent = 'flex-end'; 185 | if (bounds != null) { 186 | styles.marginRight = bounds.right; 187 | styles.width = bounds.width; 188 | styles.minWidth = bounds.width; 189 | } 190 | } else if (cHorizontal === 'CENTER') { 191 | outerStyle.justifyContent = 'center'; 192 | if (bounds != null) { 193 | styles.width = bounds.width; 194 | styles.marginLeft = bounds.left && bounds.right ? bounds.left - bounds.right : null; 195 | } 196 | } else if (cHorizontal === 'SCALE') { 197 | if (bounds != null) { 198 | const parentWidth = bounds.left + bounds.width + bounds.right; 199 | styles.width = `${bounds.width*100/parentWidth}%`; 200 | styles.marginLeft = `${bounds.left*100/parentWidth}%`; 201 | } 202 | } else { 203 | if (bounds != null) { 204 | styles.marginLeft = bounds.left; 205 | styles.width = bounds.width; 206 | styles.minWidth = bounds.width; 207 | } 208 | } 209 | 210 | if (bounds && bounds.height && cVertical !== 'TOP_BOTTOM') styles.height = bounds.height; 211 | if (cVertical === 'TOP_BOTTOM') { 212 | outerClass += ' centerer'; 213 | if (bounds != null) { 214 | styles.marginTop = bounds.top; 215 | styles.marginBottom = bounds.bottom; 216 | } 217 | } else if (cVertical === 'CENTER') { 218 | outerClass += ' centerer'; 219 | outerStyle.alignItems = 'center'; 220 | if (bounds != null) { 221 | styles.marginTop = bounds.top - bounds.bottom; 222 | } 223 | } else if (cVertical === 'SCALE') { 224 | outerClass += ' centerer'; 225 | if (bounds != null) { 226 | const parentHeight = bounds.top + bounds.height + bounds.bottom; 227 | styles.height = `${bounds.height*100/parentHeight}%`; 228 | styles.top = `${bounds.top*100/parentHeight}%`; 229 | } 230 | } else { 231 | if (bounds != null) { 232 | styles.marginTop = bounds.top; 233 | styles.marginBottom = bounds.bottom; 234 | styles.minHeight = styles.height; 235 | styles.height = null; 236 | } 237 | } 238 | 239 | if (['FRAME', 'RECTANGLE', 'INSTANCE', 'COMPONENT'].indexOf(node.type) >= 0) { 240 | if (['FRAME', 'COMPONENT', 'INSTANCE'].indexOf(node.type) >= 0) { 241 | styles.backgroundColor = colorString(node.backgroundColor); 242 | if (node.clipsContent) styles.overflow = 'hidden'; 243 | } else if (node.type === 'RECTANGLE') { 244 | const lastFill = getPaint(node.fills); 245 | if (lastFill) { 246 | if (lastFill.type === 'SOLID') { 247 | styles.backgroundColor = colorString(lastFill.color); 248 | styles.opacity = lastFill.opacity; 249 | } else if (lastFill.type === 'IMAGE') { 250 | styles.backgroundImage = imageURL(lastFill.imageRef); 251 | styles.backgroundSize = backgroundSize(lastFill.scaleMode); 252 | } else if (lastFill.type === 'GRADIENT_LINEAR') { 253 | styles.background = paintToLinearGradient(lastFill); 254 | } else if (lastFill.type === 'GRADIENT_RADIAL') { 255 | styles.background = paintToRadialGradient(lastFill); 256 | } 257 | } 258 | 259 | if (node.effects) { 260 | for (let i=0; i 0) { 282 | styles.borderRadius = `${cornerRadii[0]}px ${cornerRadii[1]}px ${cornerRadii[2]}px ${cornerRadii[3]}px`; 283 | } 284 | } 285 | } else if (node.type === 'TEXT') { 286 | const lastFill = getPaint(node.fills); 287 | if (lastFill) { 288 | styles.color = colorString(lastFill.color); 289 | } 290 | 291 | const lastStroke = getPaint(node.strokes); 292 | if (lastStroke) { 293 | const weight = node.strokeWeight || 1; 294 | styles.WebkitTextStroke = `${weight}px ${colorString(lastStroke.color)}`; 295 | } 296 | 297 | const fontStyle = node.style; 298 | 299 | const applyFontStyle = (_styles, fontStyle) => { 300 | if (fontStyle) { 301 | _styles.fontSize = fontStyle.fontSize; 302 | _styles.fontWeight = fontStyle.fontWeight; 303 | _styles.fontFamily = fontStyle.fontFamily; 304 | _styles.textAlign = fontStyle.textAlignHorizontal; 305 | _styles.fontStyle = fontStyle.italic ? 'italic' : 'normal'; 306 | _styles.lineHeight = `${fontStyle.lineHeightPercent * 1.25}%`; 307 | _styles.letterSpacing = `${fontStyle.letterSpacing}px`; 308 | } 309 | } 310 | applyFontStyle(styles, fontStyle); 311 | 312 | if (node.name.substring(0, 6) === 'input:') { 313 | content = [``]; 314 | } else if (node.characterStyleOverrides) { 315 | let para = ''; 316 | const ps = []; 317 | const styleCache = {}; 318 | let currStyle = 0; 319 | 320 | const commitParagraph = (key) => { 321 | if (para !== '') { 322 | if (styleCache[currStyle] == null && currStyle !== 0) { 323 | styleCache[currStyle] = {}; 324 | applyFontStyle(styleCache[currStyle], node.styleOverrideTable[currStyle]); 325 | } 326 | 327 | const styleOverride = styleCache[currStyle] ? JSON.stringify(styleCache[currStyle]) : '{}'; 328 | 329 | ps.push(`${para}`); 330 | para = ''; 331 | } 332 | } 333 | 334 | for (const i in node.characters) { 335 | let idx = node.characterStyleOverrides[i]; 336 | 337 | if (node.characters[i] === '\n') { 338 | commitParagraph(i); 339 | ps.push(`
`); 340 | continue; 341 | } 342 | 343 | if (idx == null) idx = 0; 344 | if (idx !== currStyle) { 345 | commitParagraph(i); 346 | currStyle = idx; 347 | } 348 | 349 | para += node.characters[i]; 350 | } 351 | commitParagraph('end'); 352 | 353 | content = ps; 354 | } else { 355 | content = node.characters.split("\n").map((line, idx) => `
${line}
`); 356 | } 357 | } 358 | 359 | function printDiv(styles, outerStyle, indent) { 360 | print(`
`, indent); 361 | print(` `, indent); 366 | } 367 | if (parent != null) { 368 | printDiv(styles, outerStyle, indent); 369 | } 370 | 371 | if (node.id !== component.id && node.name.charAt(0) === '#') { 372 | print(` `, indent); 373 | createComponent(node, imgMap, componentMap); 374 | } else if (node.type === 'VECTOR') { 375 | print(`
`, indent); 376 | } else { 377 | const newNodeBounds = node.absoluteBoundingBox; 378 | const newLastVertical = newNodeBounds && newNodeBounds.y + newNodeBounds.height; 379 | print(`
`, indent); 380 | let first = true; 381 | for (const child of minChildren) { 382 | visitNode(child, node, first ? null : newLastVertical, indent + ' '); 383 | first = false; 384 | } 385 | for (const child of centerChildren) visitNode(child, node, null, indent + ' '); 386 | if (maxChildren.length > 0) { 387 | outerClass += ' maxer'; 388 | styles.width = '100%'; 389 | styles.pointerEvents = 'none'; 390 | styles.backgroundColor = null; 391 | printDiv(styles, outerStyle, indent + ' '); 392 | first = true; 393 | for (const child of maxChildren) { 394 | visitNode(child, node, first ? null : newLastVertical, indent + ' '); 395 | first = false; 396 | } 397 | print(`
`, indent); 398 | print(`
`, indent); 399 | } 400 | if (content != null) { 401 | if (node.name.charAt(0) === '$') { 402 | const varName = node.name.substring(1); 403 | print(` {this.props.${varName} && this.props.${varName}.split("\\n").map((line, idx) =>
{line}
)}`, indent); 404 | print(` {!this.props.${varName} && (
`, indent); 405 | for (const piece of content) { 406 | print(piece, indent + ' '); 407 | } 408 | print(`
)}`, indent); 409 | } else { 410 | for (const piece of content) { 411 | print(piece, indent + ' '); 412 | } 413 | } 414 | } 415 | print(`
`, indent); 416 | } 417 | 418 | if (parent != null) { 419 | print(` `, indent); 420 | print(``, indent); 421 | } 422 | } 423 | 424 | visitNode(component, null, null, ' '); 425 | print(' );', ''); 426 | print(' }', ''); 427 | print('}', ''); 428 | componentMap[component.id] = {instance, name, doc}; 429 | } 430 | 431 | module.exports = {createComponent, colorString} 432 | -------------------------------------------------------------------------------- /figma-to-react/main.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const fetch = require('node-fetch'); 3 | const fs = require('fs'); 4 | const figma = require('./lib/figma'); 5 | 6 | const headers = new fetch.Headers(); 7 | let devToken = process.env.DEV_TOKEN; 8 | 9 | if (process.argv.length < 3) { 10 | console.log('Usage: node setup.js [figma-dev-token]'); 11 | process.exit(0); 12 | } 13 | 14 | if (process.argv.length > 3) { 15 | devToken = process.argv[3]; 16 | } 17 | 18 | headers.append('X-Figma-Token', devToken); 19 | 20 | const fileKey = process.argv[2]; 21 | const baseUrl = 'https://api.figma.com'; 22 | 23 | const vectorMap = {}; 24 | const vectorList = []; 25 | const vectorTypes = ['VECTOR', 'LINE', 'REGULAR_POLYGON', 'ELLIPSE', 'STAR']; 26 | 27 | function preprocessTree(node) { 28 | let vectorsOnly = node.name.charAt(0) !== '#'; 29 | let vectorVConstraint = null; 30 | let vectorHConstraint = null; 31 | 32 | function paintsRequireRender(paints) { 33 | if (!paints) return false; 34 | 35 | let numPaints = 0; 36 | for (const paint of paints) { 37 | if (paint.visible === false) continue; 38 | 39 | numPaints++; 40 | if (paint.type === 'EMOJI') return true; 41 | } 42 | 43 | return numPaints > 1; 44 | } 45 | 46 | if (paintsRequireRender(node.fills) || 47 | paintsRequireRender(node.strokes) || 48 | (node.blendMode != null && ['PASS_THROUGH', 'NORMAL'].indexOf(node.blendMode) < 0)) { 49 | node.type = 'VECTOR'; 50 | } 51 | 52 | const children = node.children && node.children.filter((child) => child.visible !== false); 53 | if (children) { 54 | for (let j=0; j 0 && vectorsOnly) { 67 | node.type = 'VECTOR'; 68 | node.constraints = { 69 | vertical: vectorVConstraint, 70 | horizontal: vectorHConstraint, 71 | }; 72 | } 73 | 74 | if (vectorTypes.indexOf(node.type) >= 0) { 75 | node.type = 'VECTOR'; 76 | vectorMap[node.id] = node; 77 | vectorList.push(node.id); 78 | node.children = []; 79 | } 80 | 81 | if (node.children) { 82 | for (const child of node.children) { 83 | preprocessTree(child); 84 | } 85 | } 86 | } 87 | 88 | async function main() { 89 | let resp = await fetch(`${baseUrl}/v1/files/${fileKey}`, {headers}); 90 | let data = await resp.json(); 91 | 92 | const doc = data.document; 93 | const canvas = doc.children[0]; 94 | let html = ''; 95 | 96 | for (let i=0; i\n`; 142 | nextSection += ` \n`; 143 | nextSection += " \n"; 144 | nextSection += " }\n"; 145 | nextSection += "}\n\n"; 146 | } 147 | } 148 | 149 | const imported = {}; 150 | for (const key in componentMap) { 151 | const component = componentMap[key]; 152 | const name = component.name; 153 | if (!imported[name]) { 154 | contents += `import { ${name} } from './components/${name}';\n`; 155 | } 156 | imported[name] = true; 157 | } 158 | contents += "\n"; 159 | contents += nextSection; 160 | nextSection = ''; 161 | 162 | contents += `export function getComponentFromId(id) {\n`; 163 | 164 | for (const key in componentMap) { 165 | contents += ` if (id === "${key}") return ${componentMap[key].instance};\n`; 166 | nextSection += componentMap[key].doc + "\n"; 167 | } 168 | 169 | contents += " return null;\n}\n\n"; 170 | contents += nextSection; 171 | 172 | const path = "./src/figmaComponents.js"; 173 | fs.writeFile(path, contents, function(err) { 174 | if (err) console.log(err); 175 | console.log(`wrote ${path}`); 176 | }); 177 | } 178 | 179 | main().catch((err) => { 180 | console.error(err); 181 | console.error(err.stack); 182 | }); 183 | -------------------------------------------------------------------------------- /figma-to-react/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-to-react", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "dotenv": { 8 | "version": "5.0.1", 9 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", 10 | "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /figma-to-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-to-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "moment": "^2.20.1", 7 | "node-fetch": "^1.7.3", 8 | "svg-to-jsx": "0.0.21", 9 | "react": "^16.2.0", 10 | "react-dom": "^16.2.0", 11 | "react-scripts": "1.0.17" 12 | }, 13 | "scripts": { 14 | "start": "react-scripts start", 15 | "build": "react-scripts build", 16 | "test": "react-scripts test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | }, 19 | "devDependencies": { 20 | "dotenv": "^5.0.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /figma-to-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Figma to React 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /figma-to-react/src/components/CClock.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import React, { PureComponent } from 'react'; 3 | import { getComponentFromId } from '../figmaComponents'; 4 | 5 | export class CClock extends PureComponent { 6 | state = this.getTime(); 7 | 8 | ticker = null; 9 | 10 | getTime() { 11 | return { 12 | time: moment().format('hh:mm'), 13 | seconds: moment().format(':ss'), 14 | ampm: moment().format('A'), 15 | }; 16 | } 17 | 18 | componentWillMount() { 19 | this.ticker = setInterval(() => { 20 | this.setState(this.getTime()); 21 | }, 1000); 22 | } 23 | 24 | componentWillUnmount() { 25 | if (this.ticker) clearInterval(this.ticker); 26 | } 27 | 28 | render() { 29 | const Component = getComponentFromId(this.props.nodeId); 30 | return ; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /figma-to-react/src/figmaComponents.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { CClock } from './components/CClock'; 3 | 4 | export class MasterClock extends PureComponent { 5 | render() { 6 | return
7 | 8 |
9 | } 10 | } 11 | 12 | export function getComponentFromId(id) { 13 | if (id === "1:2") return CClock1D2; 14 | return null; 15 | } 16 | 17 | class CClock1D2 extends PureComponent { 18 | render() { 19 | return ( 20 |
21 |
22 |
27 | Fade 28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
56 |
57 | {this.props.time && this.props.time.split("\n").map((line, idx) =>
{line}
)} 58 | {!this.props.time && (
59 | 10:10 60 |
)} 61 |
62 |
63 |
64 |
65 |
70 |
71 | {this.props.seconds && this.props.seconds.split("\n").map((line, idx) =>
{line}
)} 72 | {!this.props.seconds && (
73 | :10 74 |
)} 75 |
76 |
77 |
78 |
79 |
84 |
85 | {this.props.ampm && this.props.ampm.split("\n").map((line, idx) =>
{line}
)} 86 | {!this.props.ampm && (
87 | PM 88 |
)} 89 |
90 |
91 |
92 |
93 | ); 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /figma-to-react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | position: absolute; 6 | width: 100%; 7 | min-height: 100%; 8 | } 9 | 10 | .master { 11 | position: absolute; 12 | overflow: hidden; 13 | width: 100%; 14 | min-height: 100%; 15 | } 16 | 17 | input { 18 | font: inherit; 19 | border: inherit; 20 | padding: inherit; 21 | background-color: inherit; 22 | color: inherit; 23 | } 24 | 25 | input:focus { 26 | outline: none; 27 | } 28 | 29 | .outerDiv { 30 | position: relative; 31 | display: flex; 32 | width: 100%; 33 | pointer-events: none; 34 | } 35 | 36 | .innerDiv { 37 | position: relative; 38 | box-sizing: border-box; 39 | pointer-events: auto; 40 | } 41 | 42 | .centerer { 43 | position: absolute; 44 | height: 100%; 45 | top: 0; 46 | left: 0; 47 | } 48 | 49 | .maxer { 50 | align-items: flex-end; 51 | position: absolute; 52 | bottom: 0; 53 | } 54 | 55 | .vector svg { 56 | width: 100%; 57 | height: 100%; 58 | position: absolute; 59 | } 60 | 61 | a { 62 | cursor: pointer; 63 | } 64 | -------------------------------------------------------------------------------- /figma-to-react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import { MasterClock } from './figmaComponents'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | ReactDOM.render(, document.body); 8 | registerServiceWorker(); 9 | -------------------------------------------------------------------------------- /figma-to-react/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | } else { 39 | // Is not local host. Just register service worker 40 | registerValidSW(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /kaleidescope/README.md: -------------------------------------------------------------------------------- 1 | # Figma Kaleidescope 2 | 3 | This demonstrates using the Figma REST API to use symbols loaded from a 4 | Figma file to create a pop-art rendition of an image 5 | 6 | ## API Usage 7 | 8 | We use 2 endpoints in this project: 9 | 10 | - `GET /v1/files/:file_key` - Get the JSON representation of a file to extract top level frames (the symbols) 11 | - `GET /v1/images/:file_key` - Render each frame as an image 12 | 13 | We render each of the top level frames in your Figma document and use those rendered images 14 | to fill in the input file provided. 15 | 16 | ## Set up 17 | 18 | 1. Update `API_TOKEN` in kaleidescope.py with your personal access token. 19 | 2. Run `python kaleidescope.py INPUT_FILE FILE_KEY` where `FILE_KEY` is the figma file where the symbols you want to use is and `INPUT_FILE` is the image you want to render. 20 | 21 | Your output image will appear as `out.png`. 22 | -------------------------------------------------------------------------------- /kaleidescope/kaleidescope.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import requests 3 | import random 4 | from PIL import Image 5 | from io import BytesIO 6 | 7 | BLOCK_W = 8 8 | BLOCK_H = 8 9 | 10 | def extract_symbols(doc, file_key, headers): 11 | canvas = doc['document']['children'][0] 12 | symbols = [] 13 | guids = [] 14 | for node in canvas['children']: 15 | if node['type'] == 'FRAME': 16 | guids.append(node['id']) 17 | url = "https://api.figma.com/v1/images/{}?ids={}&format=png&scale=0.0625".format(file_key, ','.join(guids)) 18 | print("Calling {}".format(url)) 19 | resp = requests.get(url, headers=headers) 20 | for img_id, img_url in resp.json()['images'].items(): 21 | img_resp = requests.get(img_url) 22 | im = Image.open(BytesIO(img_resp.content)) 23 | loaded = im.load() 24 | lightness = 0 25 | for n in range(BLOCK_W): 26 | for m in range(BLOCK_H): 27 | x = loaded[n, m] 28 | lightness += x[0] + x[1] + x[2] 29 | 30 | symbols.append([lightness, loaded]) 31 | symbols.sort() 32 | 33 | max_cap = 225*3*BLOCK_W*BLOCK_H 34 | min_val = symbols[0][0] 35 | max_val = symbols[-1][0] 36 | ratio = max_cap / (max_val - min_val) 37 | for symbol in symbols: 38 | symbol[0] = ratio * (symbol[0] - min_val) 39 | 40 | return symbols 41 | 42 | if __name__ == '__main__': 43 | parser = argparse.ArgumentParser(description='Draw an image with Figma symbols.') 44 | parser.add_argument('image', type=str, help='image file to render') 45 | parser.add_argument('file_key', type=str, help='file key from Figma for symbols') 46 | args = parser.parse_args() 47 | 48 | API_TOKEN = 'REPLACE_ME' 49 | 50 | url = "https://api.figma.com/v1/files/{}".format(args.file_key) 51 | headers = {'X-Figma-Token': API_TOKEN} 52 | resp = requests.get(url, headers=headers) 53 | symbols = extract_symbols(resp.json(), args.file_key, headers) 54 | 55 | im = Image.open(args.image) 56 | 57 | w, h = im.size 58 | if w > 800: 59 | h = (800 * h) // w 60 | w = 800 61 | 62 | h -= h % BLOCK_H 63 | w -= w % BLOCK_W 64 | im = im.resize((w, h)) 65 | out_img = Image.new('RGB', (w, h)) 66 | 67 | h_blocks = h // BLOCK_H 68 | w_blocks = w // BLOCK_W 69 | data = im.load() 70 | 71 | for i in range(w_blocks): 72 | for j in range(h_blocks): 73 | lightness = 0 74 | for n in range(BLOCK_W): 75 | for m in range(BLOCK_H): 76 | x = i*BLOCK_W + n 77 | y = j*BLOCK_H + m 78 | 79 | a = data[x, y] 80 | lightness += a[0] + a[1] + a[2] 81 | 82 | 83 | match = symbols[0][1] 84 | diff = abs(symbols[0][0] - lightness) 85 | for light, im in symbols: 86 | if light > lightness and light - lightness > diff: 87 | break 88 | diff = abs(light - lightness) 89 | match = im 90 | 91 | for n in range(BLOCK_W): 92 | for m in range(BLOCK_H): 93 | x = i*BLOCK_W + n 94 | y = j*BLOCK_H + m 95 | 96 | pxl = match[n, m] 97 | out_img.putpixel((x, y),(pxl[0], pxl[1], pxl[2])) 98 | 99 | out_img.save('out.png') 100 | -------------------------------------------------------------------------------- /projector/README.md: -------------------------------------------------------------------------------- 1 | # Figma Api Projector 2 | 3 | This demonstrates using the Figma REST API to render and project an 4 | image onto a mockup. 5 | 6 | ## API Usage 7 | 8 | We use 1 endpoint in this project: 9 | 10 | - `GET /v1/images/:file_key` - Render a frame from a specific Figma document and return a url to 11 | rendered image. 12 | 13 | We render an image of a specific frame and use `perspective.js` to project that rendered 14 | frame into several different mock ups. You could imagine using this to demo the design 15 | for an app you are working on in Figma. 16 | 17 | ## Set up 18 | 19 | 1. Update `PERSONAL_ACCESS_TOKEN` in `index.html` 20 | 2. Open `index.html` in a web browser. 21 | 3. Select a frame from a Figma document you want to project on to the mock ups and copy the url. 22 | - Be sure the the url you copy includes the `node-id`. 23 | 4. Input the frame url on `index.html` and click render. 24 | 25 | You should see your frame projected into each image on the page. 26 | -------------------------------------------------------------------------------- /projector/images/_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figma/figma-api-demo/3afa225e6bfe6aaa0cd8de0a1a4707287ba4a13e/projector/images/_bottom.png -------------------------------------------------------------------------------- /projector/images/_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figma/figma-api-demo/3afa225e6bfe6aaa0cd8de0a1a4707287ba4a13e/projector/images/_top.png -------------------------------------------------------------------------------- /projector/images/busstop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figma/figma-api-demo/3afa225e6bfe6aaa0cd8de0a1a4707287ba4a13e/projector/images/busstop.png -------------------------------------------------------------------------------- /projector/images/phone-in-hand.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figma/figma-api-demo/3afa225e6bfe6aaa0cd8de0a1a4707287ba4a13e/projector/images/phone-in-hand.jpeg -------------------------------------------------------------------------------- /projector/images/reflection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/figma/figma-api-demo/3afa225e6bfe6aaa0cd8de0a1a4707287ba4a13e/projector/images/reflection.png -------------------------------------------------------------------------------- /projector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Projector 6 | 7 | 8 | 9 | 98 | 99 | 132 | 133 | 195 | 196 |
197 |
Rendering
198 |
199 | 200 |
201 |
202 | 203 |
204 | 205 |
206 | 207 |
208 |
209 | 210 | 211 | 212 |
213 |
214 | 215 |
216 |
217 | 218 | 219 |
220 |
221 | 222 |
223 |
224 | 225 | 226 | 227 |
228 |
229 | 230 | 231 | -------------------------------------------------------------------------------- /projector/perspective.js: -------------------------------------------------------------------------------- 1 | // Copyright 2010 futomi http://www.html5.jp/ 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // perspective.js v0.0.2 16 | // 2010-08-28 17 | 18 | /* ------------------------------------------------------------------- 19 | * define objects (name space) for this library. 20 | * ----------------------------------------------------------------- */ 21 | if( typeof html5jp == 'undefined' ) { 22 | html5jp = new Object(); 23 | } 24 | 25 | (function () { 26 | 27 | /* ------------------------------------------------------------------- 28 | * constructor 29 | * ----------------------------------------------------------------- */ 30 | html5jp.perspective = function(ctxd, image) { 31 | // check the arguments 32 | if( ! ctxd || ! ctxd.strokeStyle ) { return; } 33 | if( ! image || ! image.width || ! image.height ) { return; } 34 | // prepare a for the image 35 | var cvso = document.createElement('canvas'); 36 | cvso.width = parseInt(image.width); 37 | cvso.height = parseInt(image.height); 38 | var ctxo = cvso.getContext('2d'); 39 | ctxo.drawImage(image, 0, 0, cvso.width, cvso.height); 40 | // prepare a for the transformed image 41 | var cvst = document.createElement('canvas'); 42 | cvst.width = ctxd.canvas.width; 43 | cvst.height = ctxd.canvas.height; 44 | var ctxt = cvst.getContext('2d'); 45 | // parameters 46 | this.p = { 47 | ctxd: ctxd, 48 | cvso: cvso, 49 | ctxo: ctxo, 50 | ctxt: ctxt 51 | } 52 | }; 53 | 54 | /* ------------------------------------------------------------------- 55 | * prototypes 56 | * ----------------------------------------------------------------- */ 57 | var proto = html5jp.perspective.prototype; 58 | 59 | /* ------------------------------------------------------------------- 60 | * public methods 61 | * ----------------------------------------------------------------- */ 62 | proto.draw = function(points) { 63 | var d0x = points[0][0]; 64 | var d0y = points[0][1]; 65 | var d1x = points[1][0]; 66 | var d1y = points[1][1]; 67 | var d2x = points[2][0]; 68 | var d2y = points[2][1]; 69 | var d3x = points[3][0]; 70 | var d3y = points[3][1]; 71 | // compute the dimension of each side 72 | var dims = [ 73 | Math.sqrt( Math.pow(d0x-d1x, 2) + Math.pow(d0y-d1y, 2) ), // top side 74 | Math.sqrt( Math.pow(d1x-d2x, 2) + Math.pow(d1y-d2y, 2) ), // right side 75 | Math.sqrt( Math.pow(d2x-d3x, 2) + Math.pow(d2y-d3y, 2) ), // bottom side 76 | Math.sqrt( Math.pow(d3x-d0x, 2) + Math.pow(d3y-d0y, 2) ) // left side 77 | ]; 78 | // 79 | var ow = this.p.cvso.width; 80 | var oh = this.p.cvso.height; 81 | // specify the index of which dimension is longest 82 | var base_index = 0; 83 | var max_scale_rate = 0; 84 | var zero_num = 0; 85 | for( var i=0; i<4; i++ ) { 86 | var rate = 0; 87 | if( i % 2 ) { 88 | rate = dims[i] / ow; 89 | } else { 90 | rate = dims[i] / oh; 91 | } 92 | if( rate > max_scale_rate ) { 93 | base_index = i; 94 | max_scale_rate = rate; 95 | } 96 | if( dims[i] == 0 ) { 97 | zero_num ++; 98 | } 99 | } 100 | if(zero_num > 1) { return; } 101 | // 102 | var step = 2; 103 | var cover_step = step * 5; 104 | // 105 | var ctxo = this.p.ctxo; 106 | var ctxt = this.p.ctxt; 107 | ctxt.clearRect(0, 0, ctxt.canvas.width, ctxt.canvas.height); 108 | if(base_index % 2 == 0) { // top or bottom side 109 | var ctxl = this.create_canvas_context(ow, cover_step); 110 | var cvsl = ctxl.canvas; 111 | for( var y=0; y` 23 | - `personal_access_token` can be found in Account Settings (you may need to generate your token) 24 | 3. Run `npm run build` to build the project. 25 | 4. Run `npm run spellcheck` to spellcheck and comment on your document. 26 | -------------------------------------------------------------------------------- /spellchecker/app.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'request'; 2 | import * as traverse from 'traverse'; 3 | import * as spellchecker from 'spellchecker'; 4 | import {ErrorRange} from "spellchecker"; 5 | 6 | // REPLACE ME: replace these values with a file you own and with your own developer token. 7 | const file_id = ''; 8 | const personal_access_token = ''; 9 | 10 | const api_endpoint = 'https://api.figma.com/v1'; 11 | 12 | function getTextNodes(figFile: any) { 13 | return traverse.nodes(figFile) 14 | .filter(node => hasKey(node, 'type') && node.type === 'TEXT') 15 | } 16 | 17 | function postComment(message: string, x: number, y: number) { 18 | request.post( 19 | `${api_endpoint}/files/${file_id}/comments`, 20 | { 21 | headers: { 22 | "Content-Type": "application/json", 23 | "x-figma-token": personal_access_token, 24 | }, 25 | body: JSON.stringify({ 26 | message, 27 | client_meta: { 28 | x, 29 | y, 30 | }, 31 | }), 32 | }, 33 | requestErrorHandler); 34 | } 35 | 36 | function spellCheckTextNodes(textNodes: Array) { 37 | textNodes.forEach(node => { 38 | const misspelledWords = spellchecker.checkSpelling(node.characters) 39 | .map((error: ErrorRange) => { 40 | return node.characters.slice(error.start, error.end) 41 | }); 42 | 43 | if (misspelledWords.length > 0) { 44 | let annotation = 'You may have several misspellings.\n\n'; 45 | 46 | misspelledWords.forEach((word: string) => { 47 | annotation += `${word} -> `; 48 | const corrections = spellchecker.getCorrectionsForMisspelling(word); 49 | 50 | if (corrections.length > 0) { 51 | annotation += corrections.slice(0, 3).join(', ') 52 | } else { 53 | annotation += '???' 54 | } 55 | 56 | annotation += '\n'; 57 | }); 58 | 59 | postComment(annotation, node.absoluteBoundingBox.x, node.absoluteBoundingBox.y); 60 | } 61 | }) 62 | } 63 | 64 | request.get(`${api_endpoint}/files/${file_id}`, { 65 | headers: { 66 | "Content-Type": "application/json", 67 | "x-figma-token": personal_access_token, 68 | }, 69 | }, function (error, response, body) { 70 | requestErrorHandler(error, response, body); 71 | 72 | const textNodes = getTextNodes(JSON.parse(body)); 73 | spellCheckTextNodes(textNodes); 74 | }); 75 | 76 | 77 | 78 | function requestErrorHandler(error: any, response: request.RequestResponse, body: any) { 79 | if (error) { 80 | console.log(error); 81 | console.log(body); 82 | process.exit(1) 83 | } 84 | } 85 | 86 | function hasKey(node: any, key: string) { 87 | return node && typeof node === 'object' && key in node; 88 | } -------------------------------------------------------------------------------- /spellchecker/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-api-spellcheck", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/caseless": { 8 | "version": "0.12.1", 9 | "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", 10 | "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==" 11 | }, 12 | "@types/commander": { 13 | "version": "2.12.2", 14 | "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", 15 | "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", 16 | "dev": true, 17 | "requires": { 18 | "commander": "*" 19 | } 20 | }, 21 | "@types/form-data": { 22 | "version": "2.2.1", 23 | "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", 24 | "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", 25 | "requires": { 26 | "@types/node": "*" 27 | } 28 | }, 29 | "@types/mocha": { 30 | "version": "2.2.46", 31 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.46.tgz", 32 | "integrity": "sha512-fwTTP5QLf4xHMkv7ovcKvmlLWX3GrxCa5DRQDOilVyYGCp+arZTAQJCy7/4GKezzYJjfWMpB/Cy4e8nrc9XioA==", 33 | "dev": true 34 | }, 35 | "@types/node": { 36 | "version": "9.4.0", 37 | "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.0.tgz", 38 | "integrity": "sha512-zkYho6/4wZyX6o9UQ8rd0ReEaiEYNNCqYFIAACe2Tf9DrYlgzWW27OigYHnnztnnZQwVRpwWmZKegFmDpinIsA==" 39 | }, 40 | "@types/request": { 41 | "version": "2.47.0", 42 | "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz", 43 | "integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==", 44 | "requires": { 45 | "@types/caseless": "*", 46 | "@types/form-data": "*", 47 | "@types/node": "*", 48 | "@types/tough-cookie": "*" 49 | } 50 | }, 51 | "@types/sinon": { 52 | "version": "4.1.3", 53 | "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.1.3.tgz", 54 | "integrity": "sha512-Xxn32Q3mAJHOMU20bxcT6HiPksUJEkZA+nyZS4NhLo8kKb8hLhkBgp5OeW/BI3+9QmdrvDRk3caYNqtYb+TEbA==", 55 | "dev": true 56 | }, 57 | "@types/strip-bom": { 58 | "version": "3.0.0", 59 | "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", 60 | "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", 61 | "dev": true 62 | }, 63 | "@types/strip-json-comments": { 64 | "version": "0.0.30", 65 | "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", 66 | "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", 67 | "dev": true 68 | }, 69 | "@types/tough-cookie": { 70 | "version": "2.3.2", 71 | "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.2.tgz", 72 | "integrity": "sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA==" 73 | }, 74 | "@types/traverse": { 75 | "version": "0.6.30", 76 | "resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.30.tgz", 77 | "integrity": "sha512-a+JGi6c9ebb1HiYEWEaAWHkISWxo7LJ0uyg4e+5p6ZNRx5vPEMtSGxw5q+p2ukhC5mScyWmd281uVyndmUoU7Q==" 78 | }, 79 | "ajv": { 80 | "version": "5.5.2", 81 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 82 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 83 | "requires": { 84 | "co": "^4.6.0", 85 | "fast-deep-equal": "^1.0.0", 86 | "fast-json-stable-stringify": "^2.0.0", 87 | "json-schema-traverse": "^0.3.0" 88 | } 89 | }, 90 | "ansi-styles": { 91 | "version": "3.2.0", 92 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", 93 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", 94 | "dev": true, 95 | "requires": { 96 | "color-convert": "^1.9.0" 97 | } 98 | }, 99 | "any-promise": { 100 | "version": "1.3.0", 101 | "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", 102 | "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" 103 | }, 104 | "arrify": { 105 | "version": "1.0.1", 106 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 107 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 108 | "dev": true 109 | }, 110 | "asn1": { 111 | "version": "0.2.4", 112 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 113 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 114 | "requires": { 115 | "safer-buffer": "~2.1.0" 116 | } 117 | }, 118 | "assert": { 119 | "version": "1.4.1", 120 | "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", 121 | "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", 122 | "dev": true, 123 | "requires": { 124 | "util": "0.10.3" 125 | } 126 | }, 127 | "assert-plus": { 128 | "version": "1.0.0", 129 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 130 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 131 | }, 132 | "asynckit": { 133 | "version": "0.4.0", 134 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 135 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 136 | }, 137 | "aws-sign2": { 138 | "version": "0.7.0", 139 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 140 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 141 | }, 142 | "aws4": { 143 | "version": "1.6.0", 144 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 145 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 146 | }, 147 | "balanced-match": { 148 | "version": "1.0.0", 149 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 150 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 151 | "dev": true 152 | }, 153 | "bcrypt-pbkdf": { 154 | "version": "1.0.2", 155 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 156 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 157 | "requires": { 158 | "tweetnacl": "^0.14.3" 159 | } 160 | }, 161 | "boom": { 162 | "version": "4.3.1", 163 | "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", 164 | "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", 165 | "requires": { 166 | "hoek": "4.x.x" 167 | }, 168 | "dependencies": { 169 | "hoek": { 170 | "version": "4.2.1", 171 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 172 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 173 | } 174 | } 175 | }, 176 | "brace-expansion": { 177 | "version": "1.1.8", 178 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 179 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 180 | "dev": true, 181 | "requires": { 182 | "balanced-match": "^1.0.0", 183 | "concat-map": "0.0.1" 184 | } 185 | }, 186 | "browser-stdout": { 187 | "version": "1.3.0", 188 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", 189 | "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", 190 | "dev": true 191 | }, 192 | "caseless": { 193 | "version": "0.12.0", 194 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 195 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 196 | }, 197 | "chalk": { 198 | "version": "2.3.0", 199 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", 200 | "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", 201 | "dev": true, 202 | "requires": { 203 | "ansi-styles": "^3.1.0", 204 | "escape-string-regexp": "^1.0.5", 205 | "supports-color": "^4.0.0" 206 | } 207 | }, 208 | "co": { 209 | "version": "4.6.0", 210 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 211 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 212 | }, 213 | "color-convert": { 214 | "version": "1.9.1", 215 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", 216 | "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", 217 | "dev": true, 218 | "requires": { 219 | "color-name": "^1.1.1" 220 | } 221 | }, 222 | "color-name": { 223 | "version": "1.1.3", 224 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 225 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 226 | "dev": true 227 | }, 228 | "combined-stream": { 229 | "version": "1.0.5", 230 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 231 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", 232 | "requires": { 233 | "delayed-stream": "~1.0.0" 234 | } 235 | }, 236 | "commander": { 237 | "version": "2.13.0", 238 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", 239 | "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" 240 | }, 241 | "concat-map": { 242 | "version": "0.0.1", 243 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 244 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 245 | "dev": true 246 | }, 247 | "core-util-is": { 248 | "version": "1.0.2", 249 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 250 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 251 | }, 252 | "cryptiles": { 253 | "version": "4.1.2", 254 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-4.1.2.tgz", 255 | "integrity": "sha512-U2ALcoAHvA1oO2xOreyHvtkQ+IELqDG2WVWRI1GH/XEmmfGIOalnM5MU5Dd2ITyWfr3m6kNqXiy8XuYyd4wKJw==", 256 | "requires": { 257 | "boom": "7.x.x" 258 | }, 259 | "dependencies": { 260 | "boom": { 261 | "version": "7.2.0", 262 | "resolved": "https://registry.npmjs.org/boom/-/boom-7.2.0.tgz", 263 | "integrity": "sha1-K/8kpVVldn/ehp7ICDF+sQxI6WY=", 264 | "requires": { 265 | "hoek": "5.x.x" 266 | } 267 | } 268 | } 269 | }, 270 | "dashdash": { 271 | "version": "1.14.1", 272 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 273 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 274 | "requires": { 275 | "assert-plus": "^1.0.0" 276 | } 277 | }, 278 | "debug": { 279 | "version": "3.1.0", 280 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 281 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 282 | "dev": true, 283 | "requires": { 284 | "ms": "2.0.0" 285 | } 286 | }, 287 | "delayed-stream": { 288 | "version": "1.0.0", 289 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 290 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 291 | }, 292 | "diff": { 293 | "version": "3.5.0", 294 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 295 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" 296 | }, 297 | "ecc-jsbn": { 298 | "version": "0.1.2", 299 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 300 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 301 | "requires": { 302 | "jsbn": "~0.1.0", 303 | "safer-buffer": "^2.1.0" 304 | } 305 | }, 306 | "escape-string-regexp": { 307 | "version": "1.0.5", 308 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 309 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 310 | "dev": true 311 | }, 312 | "extend": { 313 | "version": "3.0.2", 314 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 315 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 316 | }, 317 | "extsprintf": { 318 | "version": "1.3.0", 319 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 320 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 321 | }, 322 | "fast-deep-equal": { 323 | "version": "1.0.0", 324 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", 325 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" 326 | }, 327 | "fast-json-stable-stringify": { 328 | "version": "2.0.0", 329 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 330 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 331 | }, 332 | "forever-agent": { 333 | "version": "0.6.1", 334 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 335 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 336 | }, 337 | "form-data": { 338 | "version": "2.3.1", 339 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", 340 | "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", 341 | "requires": { 342 | "asynckit": "^0.4.0", 343 | "combined-stream": "^1.0.5", 344 | "mime-types": "^2.1.12" 345 | } 346 | }, 347 | "formatio": { 348 | "version": "1.2.0", 349 | "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", 350 | "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", 351 | "dev": true, 352 | "requires": { 353 | "samsam": "1.x" 354 | } 355 | }, 356 | "fs.realpath": { 357 | "version": "1.0.0", 358 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 359 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 360 | "dev": true 361 | }, 362 | "getpass": { 363 | "version": "0.1.7", 364 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 365 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 366 | "requires": { 367 | "assert-plus": "^1.0.0" 368 | } 369 | }, 370 | "glob": { 371 | "version": "7.1.2", 372 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 373 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 374 | "dev": true, 375 | "requires": { 376 | "fs.realpath": "^1.0.0", 377 | "inflight": "^1.0.4", 378 | "inherits": "2", 379 | "minimatch": "^3.0.4", 380 | "once": "^1.3.0", 381 | "path-is-absolute": "^1.0.0" 382 | } 383 | }, 384 | "growl": { 385 | "version": "1.10.3", 386 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", 387 | "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", 388 | "dev": true 389 | }, 390 | "har-schema": { 391 | "version": "2.0.0", 392 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 393 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 394 | }, 395 | "har-validator": { 396 | "version": "5.0.3", 397 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 398 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 399 | "requires": { 400 | "ajv": "^5.1.0", 401 | "har-schema": "^2.0.0" 402 | } 403 | }, 404 | "has-flag": { 405 | "version": "2.0.0", 406 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 407 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 408 | "dev": true 409 | }, 410 | "hawk": { 411 | "version": "6.0.2", 412 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", 413 | "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", 414 | "requires": { 415 | "boom": "4.x.x", 416 | "cryptiles": "3.x.x", 417 | "hoek": "4.x.x", 418 | "sntp": "2.x.x" 419 | }, 420 | "dependencies": { 421 | "cryptiles": { 422 | "version": "3.1.2", 423 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", 424 | "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", 425 | "requires": { 426 | "boom": "5.x.x" 427 | }, 428 | "dependencies": { 429 | "boom": { 430 | "version": "5.2.0", 431 | "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", 432 | "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", 433 | "requires": { 434 | "hoek": "4.x.x" 435 | } 436 | } 437 | } 438 | }, 439 | "hoek": { 440 | "version": "4.2.1", 441 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 442 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 443 | } 444 | } 445 | }, 446 | "he": { 447 | "version": "1.1.1", 448 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 449 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 450 | "dev": true 451 | }, 452 | "hoek": { 453 | "version": "5.0.3", 454 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz", 455 | "integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw==" 456 | }, 457 | "homedir-polyfill": { 458 | "version": "1.0.1", 459 | "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", 460 | "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", 461 | "dev": true, 462 | "requires": { 463 | "parse-passwd": "^1.0.0" 464 | } 465 | }, 466 | "http-signature": { 467 | "version": "1.2.0", 468 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 469 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 470 | "requires": { 471 | "assert-plus": "^1.0.0", 472 | "jsprim": "^1.2.2", 473 | "sshpk": "^1.7.0" 474 | } 475 | }, 476 | "inflight": { 477 | "version": "1.0.6", 478 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 479 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 480 | "dev": true, 481 | "requires": { 482 | "once": "^1.3.0", 483 | "wrappy": "1" 484 | } 485 | }, 486 | "inherits": { 487 | "version": "2.0.1", 488 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", 489 | "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", 490 | "dev": true 491 | }, 492 | "is-typedarray": { 493 | "version": "1.0.0", 494 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 495 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 496 | }, 497 | "isarray": { 498 | "version": "0.0.1", 499 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 500 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 501 | "dev": true 502 | }, 503 | "isstream": { 504 | "version": "0.1.2", 505 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 506 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 507 | }, 508 | "jsbn": { 509 | "version": "0.1.1", 510 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 511 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 512 | }, 513 | "json-schema": { 514 | "version": "0.2.3", 515 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 516 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 517 | }, 518 | "json-schema-traverse": { 519 | "version": "0.3.1", 520 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 521 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 522 | }, 523 | "json-stringify-safe": { 524 | "version": "5.0.1", 525 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 526 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 527 | }, 528 | "jsprim": { 529 | "version": "1.4.1", 530 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 531 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 532 | "requires": { 533 | "assert-plus": "1.0.0", 534 | "extsprintf": "1.3.0", 535 | "json-schema": "0.2.3", 536 | "verror": "1.10.0" 537 | } 538 | }, 539 | "just-extend": { 540 | "version": "4.0.2", 541 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.0.2.tgz", 542 | "integrity": "sha512-FrLwOgm+iXrPV+5zDU6Jqu4gCRXbWEQg2O3SKONsWE4w7AXFRkryS53bpWdaL9cNol+AmR3AEYz6kn+o0fCPnw==" 543 | }, 544 | "lodash.get": { 545 | "version": "4.4.2", 546 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 547 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", 548 | "dev": true 549 | }, 550 | "lolex": { 551 | "version": "2.3.2", 552 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", 553 | "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", 554 | "dev": true 555 | }, 556 | "make-error": { 557 | "version": "1.3.2", 558 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.2.tgz", 559 | "integrity": "sha512-l9ra35l5VWLF24y75Tg8XgfGLX0ueRhph118WKM6H5denx4bB5QF59+4UAm9oJ2qsPQZas/CQUDdtDdfvYHBdQ==", 560 | "dev": true 561 | }, 562 | "mime-db": { 563 | "version": "1.30.0", 564 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", 565 | "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" 566 | }, 567 | "mime-types": { 568 | "version": "2.1.17", 569 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", 570 | "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", 571 | "requires": { 572 | "mime-db": "~1.30.0" 573 | } 574 | }, 575 | "minimatch": { 576 | "version": "3.0.4", 577 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 578 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 579 | "dev": true, 580 | "requires": { 581 | "brace-expansion": "^1.1.7" 582 | } 583 | }, 584 | "minimist": { 585 | "version": "0.0.8", 586 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 587 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 588 | "dev": true 589 | }, 590 | "mkdirp": { 591 | "version": "0.5.1", 592 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 593 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 594 | "dev": true, 595 | "requires": { 596 | "minimist": "0.0.8" 597 | } 598 | }, 599 | "mocha": { 600 | "version": "5.0.0", 601 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", 602 | "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", 603 | "dev": true, 604 | "requires": { 605 | "browser-stdout": "1.3.0", 606 | "commander": "2.11.0", 607 | "debug": "3.1.0", 608 | "diff": "3.3.1", 609 | "escape-string-regexp": "1.0.5", 610 | "glob": "7.1.2", 611 | "growl": "1.10.3", 612 | "he": "1.1.1", 613 | "mkdirp": "0.5.1", 614 | "supports-color": "4.4.0" 615 | }, 616 | "dependencies": { 617 | "commander": { 618 | "version": "2.11.0", 619 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 620 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", 621 | "dev": true 622 | }, 623 | "diff": { 624 | "version": "3.3.1", 625 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", 626 | "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", 627 | "dev": true 628 | } 629 | } 630 | }, 631 | "ms": { 632 | "version": "2.0.0", 633 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 634 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 635 | "dev": true 636 | }, 637 | "nan": { 638 | "version": "2.8.0", 639 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz", 640 | "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" 641 | }, 642 | "nise": { 643 | "version": "1.2.2", 644 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.2.2.tgz", 645 | "integrity": "sha512-rvxf+PSZeCKtP0DgmwMmNf1G3I6X1r4WHiP2H88PlIkOkt7mGqufdokjS8caoHBgZzVx0ee/5ytGcGHbZaUw8w==", 646 | "dev": true, 647 | "requires": { 648 | "formatio": "^1.2.0", 649 | "just-extend": "^1.1.26", 650 | "lolex": "^1.6.0", 651 | "path-to-regexp": "^1.7.0", 652 | "text-encoding": "^0.6.4" 653 | }, 654 | "dependencies": { 655 | "just-extend": { 656 | "version": "1.1.27", 657 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", 658 | "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", 659 | "dev": true 660 | }, 661 | "lolex": { 662 | "version": "1.6.0", 663 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", 664 | "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", 665 | "dev": true 666 | } 667 | } 668 | }, 669 | "oauth-sign": { 670 | "version": "0.8.2", 671 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 672 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 673 | }, 674 | "once": { 675 | "version": "1.4.0", 676 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 677 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 678 | "dev": true, 679 | "requires": { 680 | "wrappy": "1" 681 | } 682 | }, 683 | "parse-passwd": { 684 | "version": "1.0.0", 685 | "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", 686 | "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", 687 | "dev": true 688 | }, 689 | "path-is-absolute": { 690 | "version": "1.0.1", 691 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 692 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 693 | "dev": true 694 | }, 695 | "path-to-regexp": { 696 | "version": "1.7.0", 697 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 698 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 699 | "dev": true, 700 | "requires": { 701 | "isarray": "0.0.1" 702 | } 703 | }, 704 | "performance-now": { 705 | "version": "2.1.0", 706 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 707 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 708 | }, 709 | "punycode": { 710 | "version": "1.4.1", 711 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 712 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 713 | }, 714 | "qs": { 715 | "version": "6.5.1", 716 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 717 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 718 | }, 719 | "request": { 720 | "version": "2.83.0", 721 | "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", 722 | "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", 723 | "requires": { 724 | "aws-sign2": "~0.7.0", 725 | "aws4": "^1.6.0", 726 | "caseless": "~0.12.0", 727 | "combined-stream": "~1.0.5", 728 | "extend": "~3.0.1", 729 | "forever-agent": "~0.6.1", 730 | "form-data": "~2.3.1", 731 | "har-validator": "~5.0.3", 732 | "hawk": "~6.0.2", 733 | "http-signature": "~1.2.0", 734 | "is-typedarray": "~1.0.0", 735 | "isstream": "~0.1.2", 736 | "json-stringify-safe": "~5.0.1", 737 | "mime-types": "~2.1.17", 738 | "oauth-sign": "~0.8.2", 739 | "performance-now": "^2.1.0", 740 | "qs": "~6.5.1", 741 | "safe-buffer": "^5.1.1", 742 | "stringstream": "~0.0.5", 743 | "tough-cookie": "~2.3.3", 744 | "tunnel-agent": "^0.6.0", 745 | "uuid": "^3.1.0" 746 | } 747 | }, 748 | "safe-buffer": { 749 | "version": "5.1.1", 750 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 751 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 752 | }, 753 | "safer-buffer": { 754 | "version": "2.1.2", 755 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 756 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 757 | }, 758 | "samsam": { 759 | "version": "1.3.0", 760 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 761 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", 762 | "dev": true 763 | }, 764 | "sinon": { 765 | "version": "4.1.6", 766 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.1.6.tgz", 767 | "integrity": "sha1-nLNGvdsYDWioBEKf/hSXjX+v1ik=", 768 | "dev": true, 769 | "requires": { 770 | "diff": "^3.1.0", 771 | "formatio": "1.2.0", 772 | "lodash.get": "^4.4.2", 773 | "lolex": "^2.2.0", 774 | "nise": "^1.2.0", 775 | "supports-color": "^5.1.0", 776 | "type-detect": "^4.0.5" 777 | }, 778 | "dependencies": { 779 | "supports-color": { 780 | "version": "5.1.0", 781 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", 782 | "integrity": "sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ==", 783 | "dev": true, 784 | "requires": { 785 | "has-flag": "^2.0.0" 786 | } 787 | } 788 | } 789 | }, 790 | "sntp": { 791 | "version": "2.1.0", 792 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", 793 | "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", 794 | "requires": { 795 | "hoek": "4.x.x" 796 | }, 797 | "dependencies": { 798 | "hoek": { 799 | "version": "4.2.1", 800 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", 801 | "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" 802 | } 803 | } 804 | }, 805 | "source-map": { 806 | "version": "0.6.1", 807 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 808 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 809 | "dev": true 810 | }, 811 | "source-map-support": { 812 | "version": "0.5.3", 813 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.3.tgz", 814 | "integrity": "sha512-eKkTgWYeBOQqFGXRfKabMFdnWepo51vWqEdoeikaEPFiJC7MCU5j2h4+6Q8npkZTeLGbSyecZvRxiSoWl3rh+w==", 815 | "dev": true, 816 | "requires": { 817 | "source-map": "^0.6.0" 818 | } 819 | }, 820 | "spellchecker": { 821 | "version": "3.4.4", 822 | "resolved": "https://registry.npmjs.org/spellchecker/-/spellchecker-3.4.4.tgz", 823 | "integrity": "sha512-l0s86YZs5+PzATeFbqD0sTSMEF7bgzqUYgxrU8+nBSw3V19tzRYKMi+hDGG6v8MskWeG2dRK0Q79sqs1eGIKwQ==", 824 | "requires": { 825 | "any-promise": "^1.3.0", 826 | "nan": "^2.0.0" 827 | } 828 | }, 829 | "sshpk": { 830 | "version": "1.15.2", 831 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", 832 | "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", 833 | "requires": { 834 | "asn1": "~0.2.3", 835 | "assert-plus": "^1.0.0", 836 | "bcrypt-pbkdf": "^1.0.0", 837 | "dashdash": "^1.12.0", 838 | "ecc-jsbn": "~0.1.1", 839 | "getpass": "^0.1.1", 840 | "jsbn": "~0.1.0", 841 | "safer-buffer": "^2.0.2", 842 | "tweetnacl": "~0.14.0" 843 | } 844 | }, 845 | "stringstream": { 846 | "version": "0.0.6", 847 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", 848 | "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" 849 | }, 850 | "strip-bom": { 851 | "version": "3.0.0", 852 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 853 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", 854 | "dev": true 855 | }, 856 | "strip-json-comments": { 857 | "version": "2.0.1", 858 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 859 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 860 | "dev": true 861 | }, 862 | "supports-color": { 863 | "version": "4.4.0", 864 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 865 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 866 | "dev": true, 867 | "requires": { 868 | "has-flag": "^2.0.0" 869 | } 870 | }, 871 | "text-encoding": { 872 | "version": "0.6.4", 873 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 874 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", 875 | "dev": true 876 | }, 877 | "tough-cookie": { 878 | "version": "2.3.3", 879 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", 880 | "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", 881 | "requires": { 882 | "punycode": "^1.4.1" 883 | } 884 | }, 885 | "traverse": { 886 | "version": "0.6.6", 887 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", 888 | "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" 889 | }, 890 | "ts-node": { 891 | "version": "4.1.0", 892 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-4.1.0.tgz", 893 | "integrity": "sha512-xcZH12oVg9PShKhy3UHyDmuDLV3y7iKwX25aMVPt1SIXSuAfWkFiGPEkg+th8R4YKW/QCxDoW7lJdb15lx6QWg==", 894 | "dev": true, 895 | "requires": { 896 | "arrify": "^1.0.0", 897 | "chalk": "^2.3.0", 898 | "diff": "^3.1.0", 899 | "make-error": "^1.1.1", 900 | "minimist": "^1.2.0", 901 | "mkdirp": "^0.5.1", 902 | "source-map-support": "^0.5.0", 903 | "tsconfig": "^7.0.0", 904 | "v8flags": "^3.0.0", 905 | "yn": "^2.0.0" 906 | }, 907 | "dependencies": { 908 | "minimist": { 909 | "version": "1.2.0", 910 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 911 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 912 | "dev": true 913 | } 914 | } 915 | }, 916 | "tsconfig": { 917 | "version": "7.0.0", 918 | "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", 919 | "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", 920 | "dev": true, 921 | "requires": { 922 | "@types/strip-bom": "^3.0.0", 923 | "@types/strip-json-comments": "0.0.30", 924 | "strip-bom": "^3.0.0", 925 | "strip-json-comments": "^2.0.0" 926 | } 927 | }, 928 | "tunnel-agent": { 929 | "version": "0.6.0", 930 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 931 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 932 | "requires": { 933 | "safe-buffer": "^5.0.1" 934 | } 935 | }, 936 | "tweetnacl": { 937 | "version": "0.14.5", 938 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 939 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 940 | }, 941 | "type-detect": { 942 | "version": "4.0.7", 943 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.7.tgz", 944 | "integrity": "sha512-4Rh17pAMVdMWzktddFhISRnUnFIStObtUMNGzDwlA6w/77bmGv3aBbRdCmQR6IjzfkTo9otnW+2K/cDRhKSxDA==", 945 | "dev": true 946 | }, 947 | "typescript": { 948 | "version": "2.6.2", 949 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", 950 | "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=" 951 | }, 952 | "util": { 953 | "version": "0.10.3", 954 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", 955 | "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", 956 | "dev": true, 957 | "requires": { 958 | "inherits": "2.0.1" 959 | } 960 | }, 961 | "uuid": { 962 | "version": "3.2.1", 963 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 964 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 965 | }, 966 | "v8flags": { 967 | "version": "3.0.1", 968 | "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.1.tgz", 969 | "integrity": "sha1-3Oj8N5wX2fLJ6e142JzgAFKxt2s=", 970 | "dev": true, 971 | "requires": { 972 | "homedir-polyfill": "^1.0.1" 973 | } 974 | }, 975 | "verror": { 976 | "version": "1.10.0", 977 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 978 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 979 | "requires": { 980 | "assert-plus": "^1.0.0", 981 | "core-util-is": "1.0.2", 982 | "extsprintf": "^1.2.0" 983 | } 984 | }, 985 | "wrappy": { 986 | "version": "1.0.2", 987 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 988 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 989 | "dev": true 990 | }, 991 | "yn": { 992 | "version": "2.0.0", 993 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 994 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 995 | "dev": true 996 | } 997 | } 998 | } 999 | -------------------------------------------------------------------------------- /spellchecker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "figma-api-spellcheck", 3 | "version": "1.0.0", 4 | "description": "A tool to spellcheck Figma documents.", 5 | "scripts": { 6 | "clean": "rm -rf build", 7 | "build": "node_modules/.bin/tsc", 8 | "spellcheck": "node build/app.js" 9 | }, 10 | "devDependencies": { 11 | "@types/commander": "2.12.2", 12 | "@types/mocha": "2.2.46", 13 | "@types/sinon": "4.1.3", 14 | "assert": "1.4.1", 15 | "mocha": "5.0.0", 16 | "sinon": "4.1.6", 17 | "ts-node": "4.1.0" 18 | }, 19 | "dependencies": { 20 | "@types/request": "^2.47.0", 21 | "@types/traverse": "^0.6.30", 22 | "commander": "2.13.0", 23 | "cryptiles": ">=4.1.2", 24 | "diff": "^3.5.0", 25 | "extend": ">=3.0.2", 26 | "hoek": "5.0.3", 27 | "just-extend": ">=4.0.0", 28 | "request": "^2.83.0", 29 | "spellchecker": "^3.4.4", 30 | "sshpk": ">=1.13.2", 31 | "traverse": "^0.6.6", 32 | "typescript": "2.6.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spellchecker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noFallthroughCasesInSwitch": true, 5 | "allowUnreachableCode": false, 6 | "noUnusedLocals": true, 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "target": "es6", 10 | "outDir": "build" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spellchecker/types/spellchecker.d.ts: -------------------------------------------------------------------------------- 1 | declare module "spellchecker" { 2 | class ErrorRange { 3 | public start : number; 4 | public end : number; 5 | } 6 | function checkSpelling(corpus : string) : Array; 7 | function getCorrectionsForMisspelling(word : string) : Array; 8 | } 9 | --------------------------------------------------------------------------------